From fc67fc39b178642ae5b27cde2fc01b8666c93c94 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 30 Dec 2021 18:36:16 -0800 Subject: [PATCH 01/48] prep new 2.0 branch --- .github/workflows/python-dev.yml | 2 +- requirements.txt | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/python-dev.yml b/.github/workflows/python-dev.yml index fe0672f..dd9212c 100644 --- a/.github/workflows/python-dev.yml +++ b/.github/workflows/python-dev.yml @@ -5,7 +5,7 @@ name: Dev branch on: push: - branches: [ dev-unstable ] + branches: [ dev-unstable dev-2.0 ] diff --git a/requirements.txt b/requirements.txt index 56b0179..2279320 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,4 @@ -discord.py >= 1.7.3 -discord.py-stubs >= 1.7.3 +git+github.com/Pycord-Development/pycord cryptography >= 36.0.1 lz4 >= 3.1.10 typing-extensions >= 3.7.4 From 588a2b5277db85a543561b58ceb52d4471ded042 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 30 Dec 2021 18:40:11 -0800 Subject: [PATCH 02/48] add planned optimizations to compat lib --- libs/lib_compatibility.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/libs/lib_compatibility.py b/libs/lib_compatibility.py index 408eefd..2dc9f81 100644 --- a/libs/lib_compatibility.py +++ b/libs/lib_compatibility.py @@ -69,7 +69,7 @@ def user_avatar_url(user: Union[discord.User, discord.Member]) -> str: # 1.7: user.avatar_url -> Asset (supports __str__()) # 2.0: user.display_avatar.url -> str - return _avatar_url_func(user) + return user.display_avatar.url def default_avatar_url(user: Union[discord.User, discord.Member]) -> str: @@ -81,7 +81,7 @@ def default_avatar_url(user: Union[discord.User, discord.Member]) -> str: :raises: AttributeError - Failed to get the avatar url (programming error) """ - return _default_avatar_url_func(user) + return user.default_avatar.url def has_default_avatar(user: Union[discord.User, discord.Member]) -> bool: @@ -92,7 +92,7 @@ def has_default_avatar(user: Union[discord.User, discord.Member]) -> bool: :returns: bool - if the user has a default avatar """ - return _default_avatar_url_func(user) == _avatar_url_func(user) + return user.avatar is None # Returns either an aware or unaware @@ -108,4 +108,4 @@ def discord_datetime_now() -> datetime.datetime: # 1.7: datetime naive # 2.0: datetime aware - return _datetime_now_func() + return datetime.datetime.now(datetime.timezone.utc) From 3c44b205a7bf3f57afbd4d6f68b066f0e87535c1 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 30 Dec 2021 18:42:34 -0800 Subject: [PATCH 03/48] patch workflows --- .github/workflows/python-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-dev.yml b/.github/workflows/python-dev.yml index dd9212c..e9dbd27 100644 --- a/.github/workflows/python-dev.yml +++ b/.github/workflows/python-dev.yml @@ -5,7 +5,7 @@ name: Dev branch on: push: - branches: [ dev-unstable dev-2.0 ] + branches: [ dev-unstable, dev-2.0 ] From cb6a7af45123682d9f3ba24c8ec96b13dc2a1128 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 30 Dec 2021 18:43:49 -0800 Subject: [PATCH 04/48] add https to github link --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 2279320..d9db679 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -git+github.com/Pycord-Development/pycord +git+https://github.com/Pycord-Development/pycord cryptography >= 36.0.1 lz4 >= 3.1.10 typing-extensions >= 3.7.4 From a2a241c93324056c9bdd9f82afb29220554d080b Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 5 Feb 2022 13:53:47 -0800 Subject: [PATCH 05/48] update dev2.0 requirements --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index d9db679..feb0993 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -git+https://github.com/Pycord-Development/pycord +py-cord==2.0.0b3 cryptography >= 36.0.1 lz4 >= 3.1.10 typing-extensions >= 3.7.4 From af8b5de9793e661f595a7837ad69b9634403a2e3 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 4 Mar 2022 18:59:57 -0800 Subject: [PATCH 06/48] swap dev2.0 lib testing to nextcord --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index feb0993..df88117 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -py-cord==2.0.0b3 +nextcord == 2.0.0a8 cryptography >= 36.0.1 lz4 >= 3.1.10 typing-extensions >= 3.7.4 From 09f37217564fe12068ac9c68579738a754902397 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sun, 27 Mar 2022 23:11:14 -0700 Subject: [PATCH 07/48] swap 2.0 to dpy2.0 pre alpha --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index df88117..0de9301 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -nextcord == 2.0.0a8 +git+https://github.com/rapptz/discord.py cryptography >= 36.0.1 lz4 >= 3.1.10 typing-extensions >= 3.7.4 From 0cbea314052bb4255d48e490bb285a7904d9188a Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 19 Aug 2022 02:08:30 -0700 Subject: [PATCH 08/48] push LeXdPyK 1.5: adds extra executions per module --- main.py | 124 +++++++++++++++++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 46 deletions(-) diff --git a/main.py b/main.py index 42db2db..b34c16c 100644 --- a/main.py +++ b/main.py @@ -289,6 +289,50 @@ def _dump_data(self, dirstr: Optional[str] = None, dirlist: Optional[List[str]] command_modules_dict: Dict[str, Any] = {} dynamiclib_modules: List[Any] = [] dynamiclib_modules_dict: Dict[str, Any] = {} +# LeXdPyK 1.5: undefined exec order feature +# on-message is now Dict[on-message: [[on-message items], [on-message-0 items], [on-message-1 items]] +# this feature means you can have multiple on-message calls that will exec in an undefined order, but on-message-0 will always +# exec after on-message and on-message-1 after that etc +# this allows flexibility with multiple modules that just "must run after command processor init" +dynamiclib_modules_exec_dict: Dict[str, List[List[Any]]] = {} + + +def add_module_to_exec_dict(module_dlibs: Dict[str, Any]) -> None: + + global dynamiclib_modules_exec_dict + + for k, v in module_dlibs.items(): + + if (maybe_num := k.split("-")[-1]).isnumeric(): + true_key = "-".join(k.split("-")[:-1]) + idx = int(maybe_num) + 1 + + else: + true_key = k + idx = 0 + + if idx >= 2048: + raise RuntimeError("Command execution order request exceeds 2048 (oom safety limit reached)") + + try: + data_list = dynamiclib_modules_exec_dict[true_key] + except KeyError: + data_list = dynamiclib_modules_exec_dict[true_key] = [] + + if len(data_list) < (idx + 1): + data_list.extend([] for _ in range((idx + 1) - len(data_list))) + + data_list[idx].append(v) + + +def compress_exec_dict() -> None: + + global dynamiclib_modules_exec_dict + + for k in dynamiclib_modules_exec_dict: + + dynamiclib_modules_exec_dict[k] = [i for i in dynamiclib_modules_exec_dict[k] if i] + # Initialize ramfs, kernel ramfs ramfs = ram_filesystem() @@ -329,11 +373,12 @@ def kernel_load_command_modules(args: List[str] = []) -> Optional[Tuple[str, Lis log_kernel_info("Loading Kernel Modules") start_load_modules = time.monotonic() # Globalize variables - global command_modules, command_modules_dict, dynamiclib_modules, dynamiclib_modules_dict + global command_modules, command_modules_dict, dynamiclib_modules, dynamiclib_modules_dict, dynamiclib_modules_exec_dict command_modules = [] command_modules_dict = {} dynamiclib_modules = [] dynamiclib_modules_dict = {} + dynamiclib_modules_exec_dict = {} importlib.invalidate_caches() # Init return state @@ -361,10 +406,13 @@ def kernel_load_command_modules(args: List[str] = []) -> Optional[Tuple[str, Lis err.append((KernelSyntaxError("Missing commands"), module.__name__), ) for module in dynamiclib_modules: try: + add_module_to_exec_dict(module.commands) dynamiclib_modules_dict.update(module.commands) except AttributeError: err.append((KernelSyntaxError("Missing commands"), module.__name__), ) + compress_exec_dict() + log_kernel_info(f"Loaded Kernel Modules in {(time.monotonic()-start_load_modules)*1000:.1f}ms") if err: return ("\n".join([f"Error importing {i[1]}: {type(i[0]).__name__}: {i[0]}" for i in err]), [i[0] for i in err]) @@ -417,6 +465,7 @@ def kernel_reload_command_modules(args: List[str] = []) -> Optional[Tuple[str, L err.append([KernelSyntaxError("Missing commands"), module.__name__]) for module in dynamiclib_modules: try: + add_module_to_exec_dict(module.commands) dynamiclib_modules_dict.update(module.commands) except AttributeError: err.append([KernelSyntaxError("Missing commands"), module.__name__]) @@ -424,6 +473,8 @@ def kernel_reload_command_modules(args: List[str] = []) -> Optional[Tuple[str, L # Regen tempramfs regenerate_ramfs() + compress_exec_dict() + log_kernel_info(f"Reloaded Kernel Modules in {(time.monotonic()-start_reload_modules)*1000:.1f}ms") if err: return ("\n".join([f"Error reimporting {i[1]}: {type(i[0]).__name__}: {i[0]}" for i in err]), [i[0] for i in err]) @@ -582,21 +633,25 @@ async def on_error(event: str, *args: Any, **kwargs: Any) -> None: raise -async def do_event(event: Any, args: Tuple[Any, ...]) -> None: - await event( - *args, - client=Client, - ramfs=ramfs, - bot_start=bot_start_time, - command_modules=[command_modules, command_modules_dict], - dynamiclib_modules=[dynamiclib_modules, dynamiclib_modules_dict], - kernel_version=version_info, - kernel_ramfs=kernel_ramfs - ) +async def do_event_return_error(event: Any, args: Tuple[Any, ...]) -> Optional[Exception]: + try: + + await event( + *args, + client=Client, + ramfs=ramfs, + bot_start=bot_start_time, + command_modules=[command_modules, command_modules_dict], + dynamiclib_modules=[dynamiclib_modules, dynamiclib_modules_dict], + kernel_version=version_info, + kernel_ramfs=kernel_ramfs + ) + return None + except Exception as e: + return e async def event_call(argtype: str, *args: Any) -> Optional[errtype]: - # TODO(ultrabear): refactor to use undefined dispatch loop (on-message-0 will not call after on-message/may call at same time etc, module dispatches automatically namespaced) # used by dev mode tstartexec = time.monotonic() @@ -604,42 +659,19 @@ async def event_call(argtype: str, *args: Any) -> Optional[errtype]: etypes = [] try: + functions = dynamiclib_modules_exec_dict[argtype] + except KeyError: + functions = [] - # Do hash lookup with KeyError - # Separate from running function so we do not catch a KeyError deeper in the stack - try: - func = dynamiclib_modules_dict[argtype] - except KeyError: - raise KernelKeyError - - await do_event(func, args) - - # Check for KernelKeyError before checking for Exception (inherits from) - except KernelKeyError: - pass - except Exception as e: - etypes.append(errtype(e, argtype)) - - call = 0 - while True: - - exname = f"{argtype}-{call}" - - # If there is no hash then break the loop - try: - func = dynamiclib_modules_dict[exname] - except KeyError: - break - - try: - await do_event(func, args) - except Exception as e: - etypes.append(errtype(e, exname)) + for ftable in functions: + tasks = [asyncio.create_task(do_event_return_error(func, args)) for func in ftable] - call += 1 + for i in tasks: + if e := (await i): + etypes.append(errtype(e, argtype)) if DEVELOPMENT_MODE: - log_kernel_info(f"EVENT {argtype} : {round((time.monotonic()-tstartexec)*100000)/100}ms CC {call+1}") + log_kernel_info(f"EVENT {argtype} : {round((time.monotonic()-tstartexec)*100000)/100}ms CC {len(functions)}") if etypes: return etypes[0] @@ -988,7 +1020,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 1.4.15" +version_info: str = "LeXdPyK 1.5" bot_start_time: float = time.time() if __name__ == "__main__": From 2796500504fa50a8b537fb394bdccdda355e8838 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 19 Aug 2022 02:13:34 -0700 Subject: [PATCH 09/48] randomize undefined ordering to not rely on file load order --- main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index b34c16c..2f78412 100644 --- a/main.py +++ b/main.py @@ -15,7 +15,7 @@ import os, importlib, sys, io, traceback # Import sub dependencies -import glob, json, hashlib, logging, getpass, datetime, argparse +import glob, json, hashlib, logging, getpass, datetime, argparse, random # Import typing support from typing import List, Optional, Any, Tuple, Dict, Union, Type, Protocol @@ -333,6 +333,10 @@ def compress_exec_dict() -> None: dynamiclib_modules_exec_dict[k] = [i for i in dynamiclib_modules_exec_dict[k] if i] + # randomize inner ordering to prevent people relying on it + for unordered in dynamiclib_modules_exec_dict[k]: + random.shuffle(unordered) + # Initialize ramfs, kernel ramfs ramfs = ram_filesystem() From f69b283ffbfd49f87d2d1afee8c534e7ba8108f7 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 19 Aug 2022 04:45:43 -0700 Subject: [PATCH 10/48] patch pyright issues --- cmds/cmd_reactionroles.py | 5 ++++- cmds/cmd_scripting.py | 8 ++++---- cmds/cmd_utils.py | 7 ++++--- dlibs/dlib_messages.py | 8 +++++--- dlibs/dlib_reactionroles.py | 16 +++++++++------- dlibs/dlib_startup.py | 4 ++-- libs/lib_parsers.py | 9 +++++---- 7 files changed, 33 insertions(+), 24 deletions(-) diff --git a/cmds/cmd_reactionroles.py b/cmds/cmd_reactionroles.py index 3910920..9a1d8e2 100644 --- a/cmds/cmd_reactionroles.py +++ b/cmds/cmd_reactionroles.py @@ -106,6 +106,9 @@ async def try_add_reaction(message: discord.Message, emoji: Union[discord.Emoji, async def try_remove_reaction(me: discord.Client, message: discord.Message, emoji: Union[discord.Emoji, str]) -> None: + if not me.user: + return + try: await message.remove_reaction(emoji, me.user) except discord.errors.Forbidden: @@ -369,4 +372,4 @@ async def rr_purge(message: discord.Message, args: List[str], client: discord.Cl }, } -version_info: str = "1.2.14" +version_info: str = "2.0.0-DEV" diff --git a/cmds/cmd_scripting.py b/cmds/cmd_scripting.py index c2d3f42..7988427 100644 --- a/cmds/cmd_scripting.py +++ b/cmds/cmd_scripting.py @@ -110,7 +110,7 @@ async def sonnet_sh(message: discord.Message, args: List[str], client: discord.C try: suc = (await cmd.execute_ctx(message, arguments, client, newctx)) or 0 except lib_sonnetcommands.CommandError as ce: - await message.channel.send(ce) + await message.channel.send(str(ce)) suc = 1 # Stop processing if error @@ -222,7 +222,7 @@ async def sonnet_map(message: discord.Message, args: List[str], client: discord. try: suc = (await cmd.execute_ctx(message, arguments, client, newctx)) or 0 except lib_sonnetcommands.CommandError as ce: - await message.channel.send(ce) + await message.channel.send(str(ce)) suc = 1 if suc != 0: @@ -247,7 +247,7 @@ async def wrapasyncerror(cmd: SonnetCommand, message: discord.Message, args: Lis await cmd.execute_ctx(message, args, client, ctx) except lib_sonnetcommands.CommandError as ce: # catch CommandError to print message try: - await message.channel.send(ce) + await message.channel.send(str(ce)) except discord.errors.Forbidden: pass @@ -430,4 +430,4 @@ async def sleep_for(message: discord.Message, args: List[str], client: discord.C }, } -version_info: str = "1.2.13" +version_info: str = "2.0.0-DEV" diff --git a/cmds/cmd_utils.py b/cmds/cmd_utils.py index c9a7f30..367c8f9 100644 --- a/cmds/cmd_utils.py +++ b/cmds/cmd_utils.py @@ -40,7 +40,7 @@ importlib.reload(lib_datetimeplus) -from typing import Any, Final, List, Optional, Tuple, Dict, cast +from typing import Any, Final, List, Optional, Tuple, Dict import lib_constants as constants import lib_lexdpyk_h as lexdpyk @@ -416,7 +416,8 @@ async def grab_guild_info(message: discord.Message, args: List[str], client: dis guild_embed.add_field(name="Creation Date:", value=parsedate(guild.created_at)) guild_embed.set_footer(text=f"gid: {guild.id}") - guild_embed.set_thumbnail(url=cast(str, guild.icon_url)) + if guild.icon: + guild_embed.set_thumbnail(url=guild.icon.url) try: await message.channel.send(embed=guild_embed) @@ -634,4 +635,4 @@ async def coinflip(message: discord.Message, args: List[str], client: discord.Cl } } -version_info: str = "1.2.14" +version_info: str = "2.0.0-DEV" diff --git a/dlibs/dlib_messages.py b/dlibs/dlib_messages.py index ec7ea51..3d3616c 100644 --- a/dlibs/dlib_messages.py +++ b/dlibs/dlib_messages.py @@ -50,7 +50,7 @@ async def catch_logging_error(channel: discord.TextChannel, contents: discord.Embed, files: Optional[List[discord.File]] = None) -> None: try: - await channel.send(embed=contents, files=files) + await channel.send(embed=contents, files=(files or [])) except discord.errors.Forbidden: pass except discord.errors.HTTPException: @@ -450,6 +450,8 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) return elif not message.guild: return + elif not client.user: + return inc_statistics_better(message.guild.id, "on-message", kernel_args.kernel_ramfs) @@ -561,7 +563,7 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) await cmd.execute_ctx(message, arguments, client, command_ctx) except lib_sonnetcommands.CommandError as ce: try: - await message.channel.send(ce) + await message.channel.send(str(ce)) except discord.errors.Forbidden: pass @@ -594,4 +596,4 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) "on-message-delete": on_message_delete, } -version_info: Final = "1.2.14" +version_info: Final = "2.0.0-DEV" diff --git a/dlibs/dlib_reactionroles.py b/dlibs/dlib_reactionroles.py index 6444b87..9212181 100644 --- a/dlibs/dlib_reactionroles.py +++ b/dlibs/dlib_reactionroles.py @@ -65,9 +65,10 @@ async def on_raw_reaction_add(payload: discord.RawReactionActionEvent, kargs: Ke client: discord.Client = kargs.client rrconf: Optional[Dict[str, Dict[str, int]]] = load_message_config(payload.guild_id, kargs.ramfs, datatypes=reactionrole_types)["reaction-role-data"] - # do not give reactionroles to self - if payload.user_id == client.user.id: - return + if client.user: + # do not give reactionroles to self + if payload.user_id == client.user.id: + return if rrconf: opt = await get_role_from_emojiname(payload, client, rrconf) @@ -89,9 +90,10 @@ async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent, kargs: client: discord.Client = kargs.client rrconf: Optional[Dict[str, Dict[str, int]]] = load_message_config(payload.guild_id, kargs.ramfs, datatypes=reactionrole_types)["reaction-role-data"] - # do not remove reactionroles from self - if payload.user_id == client.user.id: - return + if client.user: + # do not remove reactionroles from self + if payload.user_id == client.user.id: + return if rrconf: opt = await get_role_from_emojiname(payload, client, rrconf) @@ -110,4 +112,4 @@ async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent, kargs: "on-raw-reaction-remove": on_raw_reaction_remove, } -version_info = "1.2.14" +version_info = "2.0.0-DEV" diff --git a/dlibs/dlib_startup.py b/dlibs/dlib_startup.py index 8206ade..81ec92f 100644 --- a/dlibs/dlib_startup.py +++ b/dlibs/dlib_startup.py @@ -40,7 +40,7 @@ async def on_ready(**kargs: Any) -> None: print(f'{Client.user} has connected to Discord!') # Warn if user is not bot - if not Client.user.bot: + if Client.user and not Client.user.bot: print("WARNING: The connected account is not a bot, as it is against ToS we do not condone user botting") # bot start time check to not reparse timers on network disconnect @@ -84,4 +84,4 @@ async def on_guild_join(guild: discord.Guild, **kargs: Any) -> None: commands: Dict[str, Callable[..., Any]] = {"on-ready": on_ready, "on-guild-join": on_guild_join} -version_info: str = "1.2.10" +version_info: str = "2.0.0-DEV" diff --git a/libs/lib_parsers.py b/libs/lib_parsers.py index 3bc351a..88e896d 100644 --- a/libs/lib_parsers.py +++ b/libs/lib_parsers.py @@ -199,8 +199,9 @@ def parse_skip_message(Client: discord.Client, message: discord.Message, *, allo """ # Make sure we don't start a feedback loop. - if message.author.id == Client.user.id: - return True + if Client.user: + if message.author.id == Client.user.id: + return True # only check if we are not allowing bots if not allow_bots: @@ -624,7 +625,7 @@ async def parse_channel_message(message: discord.Message, args: List[str], clien try: return await parse_channel_message_noexcept(message, args, client) except lib_sonnetcommands.CommandError as ce: - await message.channel.send(ce) + await message.channel.send(str(ce)) raise errors.message_parse_failure(ce) @@ -727,7 +728,7 @@ async def parse_user_member(message: discord.Message, args: List[str], client: d try: return await parse_user_member_noexcept(message, args, client, argindex=argindex, default_self=default_self) except lib_sonnetcommands.CommandError as ce: - await message.channel.send(ce) + await message.channel.send(str(ce)) raise errors.user_parse_error(ce) From 3bbd6fa6e8f62dd026948c903f2eb96d4680dbab Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 19 Aug 2022 04:46:36 -0700 Subject: [PATCH 11/48] patch error that only mypy found, pyright did not catch this error, and sonnet would have had a error on production if mypy was not being run over this codebase --- dlibs/dlib_userupdate.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/dlibs/dlib_userupdate.py b/dlibs/dlib_userupdate.py index 08cb37b..a004703 100644 --- a/dlibs/dlib_userupdate.py +++ b/dlibs/dlib_userupdate.py @@ -116,8 +116,11 @@ async def try_mute_on_rejoin(member: discord.Member, db: db_hlapi, client: disco if log and (channel := client.get_channel(int(log))): if isinstance(channel, discord.TextChannel): - muted_embed = discord.Embed(title=f"Notify on muted member join: {member}", description=f"This user has an entry in the mute database and {stringcases[success]}.") - muted_embed.color = load_embed_color(member.guild, embed_colors.primary, ramfs) + muted_embed = discord.Embed( + title=f"Notify on muted member join: {member}", + description=f"This user has an entry in the mute database and {stringcases[success]}.", + color=load_embed_color(member.guild, embed_colors.primary, ramfs) + ) muted_embed.set_footer(text=f"uid: {member.id}") await catch_logging_error(channel, muted_embed) @@ -202,4 +205,4 @@ async def on_member_remove(member: discord.Member, **kargs: Any) -> None: "on-member-remove": on_member_remove, } -version_info: str = "1.2.14" +version_info: str = "2.0.0-DEV" From 7ffef7fc4bd4df91991d5a869e527c0cb2f0458c Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 19 Aug 2022 04:47:08 -0700 Subject: [PATCH 12/48] LeXdPyK 2: Support dpy 2.0 --- main.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/main.py b/main.py index 2f78412..fc02bd8 100644 --- a/main.py +++ b/main.py @@ -41,6 +41,7 @@ intents.guilds = True intents.members = True intents.reactions = True +intents.message_content = True # Initialize Discord Client. Client = discord.Client(status=discord.Status.online, intents=intents) @@ -773,7 +774,7 @@ async def on_message(message: discord.Message) -> None: # If bot owner run a debug command if len(args) >= 2 and args[0] in debug_commands: - if message.author.id in BOT_OWNER and args[1].strip("<@!>") == str(Client.user.id): + if Client.user and message.author.id in BOT_OWNER and args[1].strip("<@!>") == str(Client.user.id): if e := debug_commands[args[0]](args[2:]): await message.channel.send(e[0]) for i in e[1]: @@ -1000,7 +1001,7 @@ def main(args: List[str]) -> int: # Start bot if TOKEN: try: - Client.run(TOKEN, reconnect=True) + Client.run(TOKEN, reconnect=True, log_handler=None) except discord.errors.LoginFailure: print("Invalid token passed") return 1 @@ -1024,7 +1025,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 1.5" +version_info: str = "LeXdPyK 2" bot_start_time: float = time.time() if __name__ == "__main__": From 696f2e8a42f9f816798236a9b1f052a1fe5526ed Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 20 Aug 2022 05:51:16 -0700 Subject: [PATCH 13/48] add patch for mypy bug --- libs/lib_compatibility.py | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/libs/lib_compatibility.py b/libs/lib_compatibility.py index 2dc9f81..c40747d 100644 --- a/libs/lib_compatibility.py +++ b/libs/lib_compatibility.py @@ -5,7 +5,7 @@ import discord import datetime -from typing import Union, Dict, Callable, cast +from typing import Union, Dict, Callable, Protocol, cast _releaselevel: int = discord.version_info[0] @@ -15,6 +15,7 @@ "default_avatar_url", "has_default_avatar", "discord_datetime_now", + "to_snowflake", ] @@ -109,3 +110,17 @@ def discord_datetime_now() -> datetime.datetime: # 2.0: datetime aware return datetime.datetime.now(datetime.timezone.utc) + + +class _WeakSnowflake(Protocol): + id: int + + +def to_snowflake(v: _WeakSnowflake) -> discord.abc.Snowflake: + """ + Casts any true compatible type into a discord.py Showflake interface, bypassing a interface bug with mypy + """ + # FIXME(ultrabear): + # we ignore interface errors here because dpy/mypy 2.0 has a bug where snowflakes interface includes slots + # when this bug is patched mypy should raise an error for unused type ignores and this should be patched + return v # type: ignore[return-value] From ce111f2519171ebcc92629613682882b85bcfddf Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 20 Aug 2022 05:52:28 -0700 Subject: [PATCH 14/48] apply lib_compatibility patches for mypy bug --- cmds/cmd_moderation.py | 20 ++++++++++---------- cmds/cmd_reactionroles.py | 6 +++++- dlibs/dlib_reactionroles.py | 8 ++++++-- dlibs/dlib_startup.py | 6 +++++- dlibs/dlib_userupdate.py | 4 ++-- 5 files changed, 28 insertions(+), 16 deletions(-) diff --git a/cmds/cmd_moderation.py b/cmds/cmd_moderation.py index 981ccd6..ff7f685 100644 --- a/cmds/cmd_moderation.py +++ b/cmds/cmd_moderation.py @@ -35,7 +35,7 @@ from lib_loaders import generate_infractionid, load_embed_color, load_message_config, embed_colors, datetime_now from lib_db_obfuscator import db_hlapi from lib_parsers import parse_user_member, format_duration, parse_core_permissions, parse_boolean_strict -from lib_compatibility import user_avatar_url +from lib_compatibility import user_avatar_url, to_snowflake from lib_sonnetconfig import BOT_NAME from lib_sonnetcommands import CommandCtx import lib_constants as constants @@ -315,7 +315,7 @@ async def kick_user(message: discord.Message, args: List[str], client: discord.C try: if dm_sent: await dm_sent # Wait for dm to be sent before kicking - await message.guild.kick((member), reason=reason[:512]) + await message.guild.kick(to_snowflake(member), reason=reason[:512]) if warn_text is not None: await message.channel.send(warn_text) @@ -365,7 +365,7 @@ async def ban_user(message: discord.Message, args: List[str], client: discord.Cl try: if member and dm_sent: await dm_sent # Wait for dm to be sent before banning - await message.guild.ban(user, delete_message_days=delete_days, reason=reason[:512]) + await message.guild.ban(to_snowflake(user), delete_message_days=delete_days, reason=reason[:512]) except discord.errors.Forbidden: raise lib_sonnetcommands.CommandError(f"{BOT_NAME} does not have permission to ban this user.") @@ -405,7 +405,7 @@ async def unban_user(message: discord.Message, args: List[str], client: discord. # Attempt to unban user try: - await message.guild.unban(user, reason=reason[:512]) + await message.guild.unban(to_snowflake(user), reason=reason[:512]) except discord.errors.Forbidden: await message.channel.send(f"{BOT_NAME} does not have permission to unban this user.") return 1 @@ -446,13 +446,13 @@ async def softban_user(message: discord.Message, args: List[str], client: discor try: if member and dm_sent: await dm_sent # Wait for dm to be sent before banning - await message.guild.ban(user, delete_message_days=delete_days, reason=reason[:512]) + await message.guild.ban(to_snowflake(user), delete_message_days=delete_days, reason=reason[:512]) except discord.errors.Forbidden: raise lib_sonnetcommands.CommandError(f"{BOT_NAME} does not have permission to ban this user.") try: - await message.guild.unban(user, reason=reason[:512]) + await message.guild.unban(to_snowflake(user), reason=reason[:513]) except discord.errors.Forbidden: raise lib_sonnetcommands.CommandError(f"{BOT_NAME} does not have permission to unban this user.") except discord.errors.NotFound: @@ -498,7 +498,7 @@ async def sleep_and_unmute(guild: discord.Guild, member: discord.Member, infract db.unmute_user(infractionid=infractionID) try: - await member.remove_roles(mute_role) + await member.remove_roles(to_snowflake(mute_role)) except discord.errors.HTTPException: pass @@ -554,7 +554,7 @@ async def mute_user(message: discord.Message, args: List[str], client: discord.C # Attempt to mute user try: - await member.add_roles(mute_role) + await member.add_roles(to_snowflake(mute_role)) except discord.errors.Forbidden: await message.channel.send(f"{BOT_NAME} does not have permission to mute this user.") return 1 @@ -617,7 +617,7 @@ async def unmute_user(message: discord.Message, args: List[str], client: discord # Attempt to unmute user try: - await member.remove_roles(mute_role) + await member.remove_roles(to_snowflake(mute_role)) except discord.errors.Forbidden: await message.channel.send(f"{BOT_NAME} does not have permission to unmute this user.") return 1 @@ -744,4 +744,4 @@ async def purge_cli(message: discord.Message, args: List[str], client: discord.C } } -version_info: str = "1.2.14" +version_info: str = "2.0.0-DEV" diff --git a/cmds/cmd_reactionroles.py b/cmds/cmd_reactionroles.py index 9a1d8e2..2ede0d0 100644 --- a/cmds/cmd_reactionroles.py +++ b/cmds/cmd_reactionroles.py @@ -15,10 +15,14 @@ import lib_loaders importlib.reload(lib_loaders) +import lib_compatibility + +importlib.reload(lib_compatibility) from lib_db_obfuscator import db_hlapi from lib_parsers import parse_channel_message_noexcept from lib_loaders import load_embed_color, embed_colors +from lib_compatibility import to_snowflake from typing import List, Any, Final, Dict, Union @@ -110,7 +114,7 @@ async def try_remove_reaction(me: discord.Client, message: discord.Message, emoj return try: - await message.remove_reaction(emoji, me.user) + await message.remove_reaction(emoji, to_snowflake(me.user)) except discord.errors.Forbidden: # raise non permission errors pass diff --git a/dlibs/dlib_reactionroles.py b/dlibs/dlib_reactionroles.py index 9212181..59ad366 100644 --- a/dlibs/dlib_reactionroles.py +++ b/dlibs/dlib_reactionroles.py @@ -11,9 +11,13 @@ import lib_lexdpyk_h importlib.reload(lib_lexdpyk_h) +import lib_compatibility + +importlib.reload(lib_compatibility) from lib_loaders import load_message_config, inc_statistics_better from lib_lexdpyk_h import ToKernelArgs, KernelArgs +from lib_compatibility import to_snowflake from typing import Dict, Any, Union, Optional, Tuple @@ -75,7 +79,7 @@ async def on_raw_reaction_add(payload: discord.RawReactionActionEvent, kargs: Ke if opt is not None: try: member, role = opt - await member.add_roles(role) + await member.add_roles(to_snowflake(role)) except discord.errors.Forbidden: pass @@ -100,7 +104,7 @@ async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent, kargs: if opt is not None: try: member, role = opt - await member.remove_roles(role) + await member.remove_roles(to_snowflake(role)) except discord.errors.Forbidden: pass diff --git a/dlibs/dlib_startup.py b/dlibs/dlib_startup.py index 81ec92f..cfd4c8f 100644 --- a/dlibs/dlib_startup.py +++ b/dlibs/dlib_startup.py @@ -11,9 +11,13 @@ import lib_loaders importlib.reload(lib_loaders) +import lib_compatibility + +importlib.reload(lib_compatibility) from lib_db_obfuscator import db_hlapi from lib_loaders import inc_statistics_better, datetime_now +from lib_compatibility import to_snowflake from typing import Dict, Callable, Any, List, Tuple @@ -27,7 +31,7 @@ async def attempt_unmute(Client: discord.Client, mute_entry: Tuple[str, str, str if (guild := Client.get_guild(int(mute_entry[0]))) and mute_role_id: if (user := guild.get_member(int(mute_entry[2]))) and (mute_role := guild.get_role(int(mute_role_id))): try: - await user.remove_roles(mute_role) + await user.remove_roles(to_snowflake(mute_role)) except discord.errors.Forbidden: pass diff --git a/dlibs/dlib_userupdate.py b/dlibs/dlib_userupdate.py index a004703..5bd71b6 100644 --- a/dlibs/dlib_userupdate.py +++ b/dlibs/dlib_userupdate.py @@ -25,7 +25,7 @@ from typing import Any, Dict, List, Optional, Union import lib_lexdpyk_h as lexdpyk -from lib_compatibility import (discord_datetime_now, has_default_avatar, user_avatar_url) +from lib_compatibility import (discord_datetime_now, has_default_avatar, user_avatar_url, to_snowflake) from lib_db_obfuscator import db_hlapi from lib_loaders import (datetime_now, embed_colors, inc_statistics_better, load_embed_color, load_message_config) from lib_parsers import parse_boolean_strict @@ -103,7 +103,7 @@ async def try_mute_on_rejoin(member: discord.Member, db: db_hlapi, client: disco success: bool try: - await member.add_roles(mute_role) + await member.add_roles(to_snowflake(mute_role)) success = True except discord.errors.Forbidden: success = False From ff8eda65246ac2dea5327bd16c5d03df19fd7672 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 20 Aug 2022 05:53:17 -0700 Subject: [PATCH 15/48] add patches to lexdpyk for mypy bug --- main.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index fc02bd8..f573e81 100644 --- a/main.py +++ b/main.py @@ -684,6 +684,10 @@ async def event_call(argtype: str, *args: Any) -> Optional[errtype]: return None +def lexdpyk_to_snowflake(v: Union[discord.User, discord.Member]) -> discord.abc.Snowflake: + return v # type: ignore[return-value] + + async def safety_check(guild: Optional[discord.Guild] = None, guild_id: Optional[int] = None, user: Optional[Union[discord.User, discord.Member]] = None, user_id: Optional[int] = None) -> bool: if guild: guild_id = guild.id @@ -703,7 +707,7 @@ async def safety_check(guild: Optional[discord.Guild] = None, guild_id: Optional return False try: - await non_null_guild.ban(user, reason="LeXdPyK: SYSTEM LEVEL BLACKLIST", delete_message_days=0) + await non_null_guild.ban(lexdpyk_to_snowflake(user), reason="LeXdPyK: SYSTEM LEVEL BLACKLIST", delete_message_days=0) except discord.errors.Forbidden: # call kernel_blacklist_guild to add to json db, blacklist guild From a7e59c565bbb1115b1f1fad9b17ad307f6e099db Mon Sep 17 00:00:00 2001 From: ultrabear Date: Wed, 24 Aug 2022 23:13:16 -0700 Subject: [PATCH 16/48] apply usage of is_guild_messageable to better express intent --- cmds/cmd_moderation.py | 4 ++-- cmds/cmd_utils.py | 4 ++-- libs/lib_compatibility.py | 20 +++++++++++++++++++- libs/lib_parsers.py | 38 ++++++++++++++++++++++---------------- 4 files changed, 45 insertions(+), 21 deletions(-) diff --git a/cmds/cmd_moderation.py b/cmds/cmd_moderation.py index ff7f685..38f1159 100644 --- a/cmds/cmd_moderation.py +++ b/cmds/cmd_moderation.py @@ -35,7 +35,7 @@ from lib_loaders import generate_infractionid, load_embed_color, load_message_config, embed_colors, datetime_now from lib_db_obfuscator import db_hlapi from lib_parsers import parse_user_member, format_duration, parse_core_permissions, parse_boolean_strict -from lib_compatibility import user_avatar_url, to_snowflake +from lib_compatibility import user_avatar_url, to_snowflake, GuildMessageable from lib_sonnetconfig import BOT_NAME from lib_sonnetcommands import CommandCtx import lib_constants as constants @@ -674,7 +674,7 @@ async def purge_cli(message: discord.Message, args: List[str], client: discord.C raise lib_sonnetcommands.CommandError("User does not exist") try: - purged = await cast(discord.TextChannel, message.channel).purge(limit=limit, check=ucheck) + purged = await cast(GuildMessageable, message.channel).purge(limit=limit, check=ucheck) await message.channel.send(f"Purged {len(purged)} message{'s' * (len(purged)!=1)}, initiated by {message.author.mention}", allowed_mentions=discord.AllowedMentions.none()) except discord.errors.Forbidden: raise lib_sonnetcommands.CommandError("ERROR: Bot lacks perms to purge") diff --git a/cmds/cmd_utils.py b/cmds/cmd_utils.py index 367c8f9..dfd9c1e 100644 --- a/cmds/cmd_utils.py +++ b/cmds/cmd_utils.py @@ -44,7 +44,7 @@ import lib_constants as constants import lib_lexdpyk_h as lexdpyk -from lib_compatibility import discord_datetime_now, user_avatar_url +from lib_compatibility import discord_datetime_now, user_avatar_url, is_guild_messageable from lib_db_obfuscator import db_hlapi from lib_loaders import embed_colors, load_embed_color from lib_parsers import (parse_boolean_strict, parse_permissions, parse_core_permissions, parse_user_member_noexcept, parse_channel_message_noexcept, generate_reply_field, grab_files) @@ -95,7 +95,7 @@ def parsedate(indata: Optional[datetime]) -> str: def _get_highest_perm(message: discord.Message, member: discord.Member, conf_cache: Dict[str, Any]) -> str: - if not isinstance(message.channel, discord.TextChannel): + if not is_guild_messageable(message.channel): return "everyone" highest = "everyone" diff --git a/libs/lib_compatibility.py b/libs/lib_compatibility.py index c40747d..11e0f5e 100644 --- a/libs/lib_compatibility.py +++ b/libs/lib_compatibility.py @@ -6,6 +6,7 @@ import datetime from typing import Union, Dict, Callable, Protocol, cast +from typing_extensions import TypeGuard _releaselevel: int = discord.version_info[0] @@ -16,6 +17,8 @@ "has_default_avatar", "discord_datetime_now", "to_snowflake", + "GuildMessageable", + "is_guild_messageable", ] @@ -116,7 +119,7 @@ class _WeakSnowflake(Protocol): id: int -def to_snowflake(v: _WeakSnowflake) -> discord.abc.Snowflake: +def to_snowflake(v: _WeakSnowflake, /) -> discord.abc.Snowflake: """ Casts any true compatible type into a discord.py Showflake interface, bypassing a interface bug with mypy """ @@ -124,3 +127,18 @@ def to_snowflake(v: _WeakSnowflake) -> discord.abc.Snowflake: # we ignore interface errors here because dpy/mypy 2.0 has a bug where snowflakes interface includes slots # when this bug is patched mypy should raise an error for unused type ignores and this should be patched return v # type: ignore[return-value] + + +GuildMessageable = Union[discord.TextChannel, discord.Thread] + +_concrete_channels = Union[discord.TextChannel, discord.VoiceChannel, discord.CategoryChannel, discord.StageChannel, discord.ForumChannel, discord.Thread, discord.DMChannel, discord.GroupChannel, + discord.PartialMessageable] +_abstract_base_class_channels = Union[discord.abc.PrivateChannel, discord.abc.GuildChannel] + + +def is_guild_messageable(v: Union[_concrete_channels, _abstract_base_class_channels], /) -> TypeGuard[GuildMessageable]: + """ + Returns True if the channel type passed is within a guild and messageable + """ + + return isinstance(v, (discord.TextChannel, discord.Thread)) diff --git a/libs/lib_parsers.py b/libs/lib_parsers.py index 88e896d..2567ef1 100644 --- a/libs/lib_parsers.py +++ b/libs/lib_parsers.py @@ -22,11 +22,15 @@ import lib_sonnetcommands importlib.reload(lib_sonnetcommands) +import lib_compatibility + +importlib.reload(lib_compatibility) from lib_sonnetconfig import REGEX_VERSION from lib_db_obfuscator import db_hlapi from lib_encryption_wrapper import encrypted_reader import lib_constants as constants +from lib_compatibility import is_guild_messageable, GuildMessageable from typing import Callable, Iterable, Optional, Any, Tuple, Dict, Union, List, TypeVar, Literal, overload, cast import lib_lexdpyk_h as lexdpyk @@ -263,7 +267,7 @@ async def update_log_channel(message: discord.Message, args: list[str], client: if not message.guild: raise errors.log_channel_update_error("ERROR: No guild") - if not isinstance(message.channel, discord.TextChannel): + if not is_guild_messageable(message.channel): raise errors.log_channel_update_error("ERROR: Wrong channel context") if args: @@ -295,9 +299,10 @@ async def update_log_channel(message: discord.Message, args: list[str], client: await message.channel.send(constants.sonnet.error_channel.invalid) raise errors.log_channel_update_error("Channel is not a valid channel") + # we may only log to textchannels, not threads as they may be deleted automatically if not isinstance(discord_channel, discord.TextChannel): await message.channel.send(constants.sonnet.error_channel.wrongType) - raise errors.log_channel_update_error("Channel is not a valid channel") + raise errors.log_channel_update_error("Channel is not a TextChannel") if discord_channel.guild.id != message.channel.guild.id: await message.channel.send(constants.sonnet.error_channel.scope) @@ -318,21 +323,21 @@ def _parse_role_perms(author: discord.Member, permrole: str) -> bool: @overload -def parse_core_permissions(channel: discord.TextChannel, member: discord.Member, mconf: Dict[str, str], perms: Literal["everyone"]) -> Literal[True]: +def parse_core_permissions(channel: GuildMessageable, member: discord.Member, mconf: Dict[str, str], perms: Literal["everyone"]) -> Literal[True]: ... @overload -def parse_core_permissions(channel: discord.TextChannel, member: discord.Member, mconf: Dict[str, str], perms: Literal["moderator", "administrator", "owner"]) -> bool: +def parse_core_permissions(channel: GuildMessageable, member: discord.Member, mconf: Dict[str, str], perms: Literal["moderator", "administrator", "owner"]) -> bool: ... @overload -def parse_core_permissions(channel: discord.TextChannel, member: discord.Member, mconf: Dict[str, str], perms: str) -> Optional[bool]: +def parse_core_permissions(channel: GuildMessageable, member: discord.Member, mconf: Dict[str, str], perms: str) -> Optional[bool]: ... -def parse_core_permissions(channel: discord.TextChannel, member: discord.Member, mconf: Dict[str, str], perms: str) -> Optional[bool]: +def parse_core_permissions(channel: GuildMessageable, member: discord.Member, mconf: Dict[str, str], perms: str) -> Optional[bool]: """ Parse permissions of a given TextChannel and Member, only parses core permissions (everyone,moderator,administrator,owner) and does not have verbosity This is a lightweight alternative to parse_permissions for parsing simple permissions, while not sufficient for full command permission parsing. @@ -367,7 +372,7 @@ async def parse_permissions(message: discord.Message, mconf: Dict[str, str], per :returns: bool """ - if not isinstance(message.channel, discord.TextChannel): + if not is_guild_messageable(message.channel): # Perm check called outside a guild return False @@ -598,7 +603,7 @@ async def parse_channel_message_noexcept(message: discord.Message, args: list[st if not discord_channel: raise lib_sonnetcommands.CommandError(constants.sonnet.error_channel.invalid) - if not isinstance(discord_channel, discord.TextChannel): + if not is_guild_messageable(discord_channel): raise lib_sonnetcommands.CommandError(constants.sonnet.error_channel.scope) if discord_channel.guild.id != message.guild.id: @@ -633,10 +638,10 @@ async def parse_channel_message(message: discord.Message, args: List[str], clien # should return a union of many types but for now only handle discord.Message -async def _guess_id_type(message: discord.Message, mystery_id: int) -> Optional[Union[discord.Message, discord.Role, discord.TextChannel]]: +async def _guess_id_type(message: discord.Message, mystery_id: int) -> Optional[Union[discord.Message, discord.Role, GuildMessageable]]: # hot path current channel id - if message.channel.id == mystery_id and isinstance(message.channel, discord.TextChannel): + if message.channel.id == mystery_id and is_guild_messageable(message.channel): return message.channel # asserts guild @@ -648,11 +653,11 @@ async def _guess_id_type(message: discord.Message, mystery_id: int) -> Optional[ return role if (chan := message.guild.get_channel(mystery_id)) is not None: - if isinstance(chan, discord.TextChannel): + if is_guild_messageable(chan): return chan # asserts channel - if not isinstance(message.channel, discord.TextChannel): + if not is_guild_messageable(message.channel): return None # requires channel @@ -702,12 +707,13 @@ async def parse_user_member_noexcept(message: discord.Message, except (discord.errors.NotFound, discord.errors.HTTPException): if (pot := await _guess_id_type(message, uid)) is not None: errappend = "Note: While this ID is not a valid user ID, it is " - if isinstance(pot, discord.TextChannel): - errappend += f"a valid channel ID: <#{pot.id}>" + if isinstance(pot, discord.Role): + errappend += "a valid role" elif isinstance(pot, discord.Message): errappend += f"a valid message by a user with ID {pot.author.id}\n(did you mean to select this user?)" - elif isinstance(pot, discord.Role): - errappend += "a valid role" + else: + chan_name = "channel" if isinstance(pot, discord.TextChannel) else "thread" + errappend += f"a valid {chan_name} ID: <#{pot.id}>" raise lib_sonnetcommands.CommandError(f"User does not exist\n{errappend}") From db42bf2f225b04e91b96179175580aab5f6a1881 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Wed, 24 Aug 2022 23:14:11 -0700 Subject: [PATCH 17/48] add new lexdpyk events, clarify dev status --- main.py | 52 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 51 insertions(+), 1 deletion(-) diff --git a/main.py b/main.py index f573e81..d8b2a46 100644 --- a/main.py +++ b/main.py @@ -936,6 +936,56 @@ async def on_member_unban(guild: discord.Guild, user: discord.User) -> None: await event_call("on-member-unban", guild, user) +# new in 2.0: + + +@Client.event +async def on_raw_app_command_permissions_update(payload: discord.RawAppCommandPermissionsUpdateEvent) -> None: + if await safety_check(guild=payload.guild): + await event_call("on-raw-app-command-permissions-update", payload) + + +@Client.event +async def on_app_command_completion(interaction: discord.Interaction, command: Union[discord.app_commands.Command[Any, Any, Any], discord.app_commands.ContextMenu]) -> None: + if await safety_check(guild=interaction.guild, user=interaction.user): + await event_call("on-app-command-completion", interaction, command) + + +@Client.event +async def on_automod_rule_create(rule: discord.AutoModRule) -> None: + if await safety_check(guild=rule.guild): + await event_call("on-automod-rule-create", rule) + + +@Client.event +async def on_automod_rule_update(rule: discord.AutoModRule) -> None: + if await safety_check(guild=rule.guild): + await event_call("on-automod-rule-update", rule) + + +@Client.event +async def on_automod_rule_delete(rule: discord.AutoModRule) -> None: + if await safety_check(guild=rule.guild): + await event_call("on-automod-rule-delete", rule) + + +@Client.event +async def on_automod_action(execution: discord.AutoModAction) -> None: + if await safety_check(guild_id=execution.guild_id): + await event_call("on-automod-action", execution) + + +@Client.event +async def on_raw_member_remove(payload: discord.RawMemberRemoveEvent) -> None: + if await safety_check(guild_id=payload.guild_id): + await event_call("on-raw-member-remove", payload) + + +async def on_presence_update(before: discord.Member, after: discord.Member) -> None: + if await safety_check(guild=before.guild, user=before): + await event_call("on-presence-update") + + def gentoken() -> str: TOKEN = getpass.getpass("Enter TOKEN: ") @@ -1029,7 +1079,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 2" +version_info: str = "LeXdPyK 2-DEV" bot_start_time: float = time.time() if __name__ == "__main__": From 838838bdccbebba21bbd282601a34b5c185a8641 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 00:18:58 -0700 Subject: [PATCH 18/48] (#97) add guild specific avatars to profile and avatar functions --- cmds/cmd_utils.py | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/cmds/cmd_utils.py b/cmds/cmd_utils.py index dfd9c1e..fa80987 100644 --- a/cmds/cmd_utils.py +++ b/cmds/cmd_utils.py @@ -124,8 +124,10 @@ async def profile_function(message: discord.Message, args: List[str], client: di "invisible": "\U000026AB (offline)" } + avatar_asset = user.display_avatar if member is None else member.display_avatar + embed: Final = discord.Embed(title="User Information", description=f"User information for {user.mention}:", color=load_embed_color(message.guild, embed_colors.primary, ctx.ramfs)) - embed.set_thumbnail(url=user_avatar_url(user)) + embed.set_thumbnail(url=avatar_asset.url) embed.add_field(name="Username", value=str(user), inline=True) embed.add_field(name="User ID", value=str(user.id), inline=True) if member: @@ -154,10 +156,24 @@ async def avatar_function(message: discord.Message, args: List[str], client: dis if not message.guild: return 1 - user, _ = await parse_user_member_noexcept(message, args, client, default_self=True) + p = Parser("avatar") + global_flag: lib_tparse.Promise[bool] = p.add_arg(["-g", "--global"], lib_tparse.store_true, flag=True, helpstr="whether or not to grab global avatar") + + try: + p.parse(args, stderr=io.StringIO(), exit_on_fail=False, lazy=True, consume=True) + except lib_tparse.ParseFailureError: + # this is a programming error because lazy is set and no value parsing happens + raise + + user, member = await parse_user_member_noexcept(message, args, client, default_self=True) + + global_avatar = user.avatar if user.avatar is not None else user.default_avatar + guild_avatar = member.display_avatar if member is not None else user.display_avatar + + avatar_asset = global_avatar if global_flag.get(False) else guild_avatar embed = discord.Embed(description=f"{user.mention}'s Avatar", color=load_embed_color(message.guild, embed_colors.primary, kwargs["ramfs"])) - embed.set_image(url=user_avatar_url(user)) + embed.set_image(url=avatar_asset.url) embed.timestamp = Time.now().as_datetime() try: await message.channel.send(embed=embed) @@ -595,10 +611,8 @@ async def coinflip(message: discord.Message, args: List[str], client: discord.Cl 'alias': 'avatar' }, 'avatar': { - 'pretty_name': 'avatar [user]', - 'description': 'Get avatar of a user', - 'permission': 'everyone', - 'cache': 'keep', + 'pretty_name': 'avatar [user] [--global]', + 'description': 'Get avatar of a user, returns guild avatar if it exists unless --global is specified', 'execute': avatar_function }, 'server-info': { From d04e2fe710ea629e607412f78f9ec5004621539d Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 00:31:05 -0700 Subject: [PATCH 19/48] make kick/mute/unmute deny creation of infractions on non members --- cmds/cmd_moderation.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/cmds/cmd_moderation.py b/cmds/cmd_moderation.py index 38f1159..0a7f6b5 100644 --- a/cmds/cmd_moderation.py +++ b/cmds/cmd_moderation.py @@ -182,7 +182,8 @@ async def process_infraction( ramfs: lexdpyk.ram_filesystem, infraction: bool = True, automod: bool = False, - modifiers: Optional[List[InfractionModifier]] = None + modifiers: Optional[List[InfractionModifier]] = None, + require_in_guild: bool = False ) -> InfractionInfo: if not message.guild or not isinstance(message.author, discord.Member): raise InfractionGenerationError("User is not member, or no guild") @@ -199,6 +200,10 @@ async def process_infraction( except lib_parsers.errors.user_parse_error: raise InfractionGenerationError("Could not parse user") + if require_in_guild and member is None: + await message.channel.send(f"User is not in guild (required to {i_type} user)") + raise InfractionGenerationError(f"Attempted to {i_type} a user but they were not a member") + local_conf_cache = load_message_config(message.guild.id, ramfs) # Test if user is a moderator @@ -212,7 +217,7 @@ async def process_infraction( if bool(int(local_conf_cache["moderator-protect"])): await message.channel.send(f"Cannot {i_type} specified user, user is a moderator+\n" f"(to disable this behavior see {get_help})") - raise InfractionGenerationError("Attempted to warn a moderator+ but mprotect was on") + raise InfractionGenerationError(f"Attempted to {i_type} a moderator+ but mprotect was on") # Test if user is self if member and moderator.id == member.id: @@ -306,7 +311,7 @@ async def kick_user(message: discord.Message, args: List[str], client: discord.C modifiers = parse_infraction_modifiers(message.guild, args) try: - member, _, reason, _, dm_sent, warn_text = await process_infraction(message, args, client, "kick", ramfs, automod=automod, modifiers=modifiers) + member, _, reason, _, dm_sent, warn_text = await process_infraction(message, args, client, "kick", ramfs, automod=automod, modifiers=modifiers, require_in_guild=True) except InfractionGenerationError: return 1 @@ -543,7 +548,7 @@ async def mute_user(message: discord.Message, args: List[str], client: discord.C try: mute_role = await grab_mute_role(message, ramfs) - member, _, reason, infractionID, _, warn_text = await process_infraction(message, args, client, "mute", ramfs, automod=automod, modifiers=modifiers) + member, _, reason, infractionID, _, warn_text = await process_infraction(message, args, client, "mute", ramfs, automod=automod, modifiers=modifiers, require_in_guild=True) except (NoMuteRole, InfractionGenerationError): return 1 @@ -607,7 +612,7 @@ async def unmute_user(message: discord.Message, args: List[str], client: discord try: mute_role = await grab_mute_role(message, ramfs) - member, _, reason, _, _, _ = await process_infraction(message, args, client, "unmute", ramfs, infraction=False, automod=automod) + member, _, reason, _, _, _ = await process_infraction(message, args, client, "unmute", ramfs, infraction=False, automod=automod, require_in_guild=True) except (InfractionGenerationError, NoMuteRole): return 1 From 5d349204d21f3fc7cd397b56ab09dd7ff5b9e905 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 03:09:39 -0700 Subject: [PATCH 20/48] (#96) add timeout/untimeout moderation functions --- cmds/cmd_moderation.py | 115 ++++++++++++++++++++++++++++++++++++----- 1 file changed, 102 insertions(+), 13 deletions(-) diff --git a/cmds/cmd_moderation.py b/cmds/cmd_moderation.py index 0a7f6b5..2ebb231 100644 --- a/cmds/cmd_moderation.py +++ b/cmds/cmd_moderation.py @@ -3,6 +3,7 @@ import importlib +from datetime import timedelta import discord, asyncio, json from dataclasses import dataclass @@ -508,15 +509,11 @@ async def sleep_and_unmute(guild: discord.Guild, member: discord.Member, infract pass -async def mute_user(message: discord.Message, args: List[str], client: discord.Client, ctx: CommandCtx) -> int: - if not message.guild: - return 1 - - ramfs = ctx.ramfs - automod = ctx.automod - verbose = ctx.verbose - - modifiers = parse_infraction_modifiers(message.guild, args) +def parse_duration_for_mutes(args: List[str], inf_name: str, /) -> Tuple[int, Optional[str]]: + """ + Parses a duration from args to get mutetime/timeouttime and returns string if a duration was not passed in the correct place but is valid + abstracts logic for mutes/timeouts + """ # Grab mute time if len(args) >= 2: @@ -536,6 +533,23 @@ async def mute_user(message: discord.Message, args: List[str], client: discord.C if mutetime is None: mutetime = 0 + duration_str = f"\n(No {inf_name} length was specified, but one of the reason items `{misplaced_duration}` is a valid duration, did you mean to {inf_name} for this length?)" if misplaced_duration is not None else None + + return mutetime, duration_str + + +async def mute_user(message: discord.Message, args: List[str], client: discord.Client, ctx: CommandCtx) -> int: + if not message.guild: + return 1 + + ramfs = ctx.ramfs + automod = ctx.automod + verbose = ctx.verbose + + mutetime, duration_str = parse_duration_for_mutes(args, "mute") + + modifiers = parse_infraction_modifiers(message.guild, args) + # This ones for you, curl if not 0 <= mutetime < 60 * 60 * 256: mutetime = 0 @@ -565,10 +579,9 @@ async def mute_user(message: discord.Message, args: List[str], client: discord.C return 1 mod_str = f" with {','.join(m.title for m in modifiers)}" if modifiers else "" - duration_str = f"\n(No mute length was specified, but one of the reason items `{misplaced_duration}` is a valid duration, did you mean to mute for this length?)" if misplaced_duration is not None else "" if verbose and not mutetime: - await message.channel.send(f"Muted {member.mention} with ID {member.id}{mod_str} for {reason}{duration_str}", allowed_mentions=discord.AllowedMentions.none()) + await message.channel.send(f"Muted {member.mention} with ID {member.id}{mod_str} for {reason}{duration_str or ''}", allowed_mentions=discord.AllowedMentions.none()) if warn_text is not None: await message.channel.send(warn_text) @@ -635,6 +648,66 @@ async def unmute_user(message: discord.Message, args: List[str], client: discord return 0 +async def timeout_user(message: discord.Message, args: List[str], client: discord.Client, ctx: CommandCtx) -> int: + if not message.guild: + return 1 + + mutetime, duration_str = parse_duration_for_mutes(args, "timeout") + + if mutetime >= ((60 * 60) * 24) * 28 or mutetime == 0: + mutetime = ((60 * 60) * 24) * 28 + + modifiers = parse_infraction_modifiers(message.guild, args) + + try: + member, _, reason, _, _, warn_text = await process_infraction(message, args, client, "timeout", ctx.ramfs, automod=ctx.automod, modifiers=modifiers, require_in_guild=True) + except InfractionGenerationError: + return 1 + + if not member: + raise lib_sonnetcommands.CommandError("ERROR: User not in guild") + + try: + await member.timeout(timedelta(seconds=mutetime), reason=reason[:512]) + except discord.errors.Forbidden: + raise lib_sonnetcommands.CommandError(f"{BOT_NAME} does not have permission to timeout this user.") + + mod_str = f" with {','.join(m.title for m in modifiers)}" if modifiers else "" + + if ctx.verbose: + await message.channel.send( + f"Timed out {member.mention} with ID {member.id}{mod_str} for {format_duration(mutetime)} for {reason}{duration_str or ''}", allowed_mentions=discord.AllowedMentions.none() + ) + + if warn_text is not None: + await message.channel.send(warn_text) + + return 0 + + +async def un_timeout_user(message: discord.Message, args: List[str], client: discord.Client, ctx: CommandCtx) -> int: + if not message.guild: + return 1 + + try: + member, _, reason, _, _, _ = await process_infraction(message, args, client, "untimeout", ctx.ramfs, infraction=False, automod=ctx.automod, require_in_guild=True) + except InfractionGenerationError: + return 1 + + if not member: + raise lib_sonnetcommands.CommandError("ERROR: User is not a member") + + # Attempt to untimeout user + try: + await member.timeout(None, reason=reason[:512]) + except discord.errors.Forbidden: + raise lib_sonnetcommands.CommandError(f"{BOT_NAME} does not have permission to untimeout this user.") + + if ctx.verbose: + await message.channel.send(f"Removed timeout for {member.mention} with ID {member.id} for {reason}", allowed_mentions=discord.AllowedMentions.none()) + return 0 + + class purger: __slots__ = "user_id", "message_id" @@ -713,7 +786,7 @@ async def purge_cli(message: discord.Message, args: List[str], client: discord.C 'execute': ban_user }, 'unban': { - 'pretty_name': 'unban ', + 'pretty_name': 'unban [reason]', 'description': 'Unban a user, does not dm user', 'permission': 'moderator', 'execute': unban_user @@ -732,11 +805,27 @@ async def purge_cli(message: discord.Message, args: List[str], client: discord.C 'execute': mute_user }, 'unmute': { - 'pretty_name': 'unmute ', + 'pretty_name': 'unmute [reason]', 'description': 'Unmute a user, does not dm user', 'permission': 'moderator', 'execute': unmute_user }, + "timeout": + { + 'pretty_name': 'timeout [+modifiers] [time[h|m|S]] [reason]', + 'description': 'Timeout a member, defaults to longest timeout possible (28 days)', + 'permission': "moderator", + "execute": timeout_user, + }, + "untimeout": { + 'alias': "remove-timeout", + }, + "remove-timeout": { + 'pretty_name': 'remove-timeout [reason]', + 'description': 'Remove a timeout on a member', + 'permission': "moderator", + "execute": un_timeout_user, + }, 'purge': { 'pretty_name': 'purge [user]', From 7f9c2502ec3b50d977dcdd9f78ced2a8427c9d21 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 03:28:11 -0700 Subject: [PATCH 21/48] (#96) add set-antispam-action and change set-mutetime to set-antispam-timeout --- cmds/cmd_automod.py | 54 ++++++++++++++++++++++++++++++++++-------- dlibs/dlib_messages.py | 2 +- libs/lib_loaders.py | 4 ++-- 3 files changed, 47 insertions(+), 13 deletions(-) diff --git a/cmds/cmd_automod.py b/cmds/cmd_automod.py index bfa88ab..9da7115 100644 --- a/cmds/cmd_automod.py +++ b/cmds/cmd_automod.py @@ -284,6 +284,29 @@ async def set_blacklist_infraction_level(message: discord.Message, args: List[st if ctx.verbose: await message.channel.send(f"Updated blacklist action to `{action}`") +async def set_antispam_command(message: discord.Message, args: List[str], client: discord.Client, ctx: CommandCtx) -> int: + if not message.guild: + return 1 + + if args: + action = args[0].lower() + else: + await message.channel.send(f"antispam action is `{ctx.conf_cache['antispam-action']}`") + return 0 + + if action not in ["timeout", "mute"]: + await message.channel.send("ERROR: Antispam action is not valid\nValid Actions: `timeout` and `mute`") + return 1 + + with db_hlapi(message.guild.id) as database: + database.add_config("antispam-action", action) + + if ctx.verbose: + await message.channel.send(f"Updated antispam action to `{action}`") + + return 0 + + async def change_rolewhitelist(message: discord.Message, args: List[str], client: discord.Client, ctx: CommandCtx) -> Any: return await parse_role(message, args, "blacklist-whitelist", verbose=ctx.verbose) @@ -376,21 +399,21 @@ async def antispam_time_set(message: discord.Message, args: List[str], client: d return 1 else: mutetime = int(ctx.conf_cache["antispam-time"]) - await message.channel.send(f"Antispam mute time is {mutetime} seconds") + await message.channel.send(f"Antispam timeout is {format_duration(mutetime)}") return 0 if mutetime < 0: - await message.channel.send("ERROR: Mutetime cannot be negative") + await message.channel.send("ERROR: timeout cannot be negative") return 1 elif mutetime >= 60 * 60 * 256: - await message.channel.send("ERROR: Mutetime cannot be greater than 256 hours") + await message.channel.send("ERROR: timeout cannot be greater than 256 hours") return 1 with db_hlapi(message.guild.id) as db: db.add_config("antispam-time", str(mutetime)) - if ctx.verbose: await message.channel.send(f"Set antispam mute time to {format_duration(mutetime)}") + if ctx.verbose: await message.channel.send(f"Set antispam timeout to {format_duration(mutetime)}") class NoGuildError(Exception): @@ -669,15 +692,26 @@ async def add_joinrule(message: discord.Message, args: List[str], client: discor 'execute': antispam_set }, 'mutetime-set': { - 'alias': 'set-mutetime' + 'alias': 'set-antispam-timeout' }, - 'set-mutetime': + 'set-mutetime': { + 'alias': 'set-antispam-timeout' + }, + 'set-antispam-timeout': + { + 'pretty_name': 'set-antispam-timeout ', + 'description': 'Set how many seconds a person should be out for with antispam auto mute/timeout', + 'permission': 'administrator', + 'cache': 'regenerate', + 'execute': antispam_time_set, + }, + 'set-antispam-action': { - 'pretty_name': 'set-mutetime ', - 'description': 'Set how many seconds a person should be muted for with antispam automute', + 'pretty_name': 'set-antispam-action [timeout|mute]', + 'description': 'set whether to use mute or timeout for antispam triggers', 'permission': 'administrator', 'cache': 'regenerate', - 'execute': antispam_time_set + 'execute': set_antispam_command, }, 'add-regexnotifier': { @@ -697,4 +731,4 @@ async def add_joinrule(message: discord.Message, args: List[str], client: discor }, } -version_info: str = "1.2.14" +version_info: str = "2.0.0-DEV" diff --git a/dlibs/dlib_messages.py b/dlibs/dlib_messages.py index 3d3616c..fb484a5 100644 --- a/dlibs/dlib_messages.py +++ b/dlibs/dlib_messages.py @@ -499,7 +499,7 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) with db_hlapi(message.guild.id) as db: if not db.is_muted(userid=message.author.id): execargs = [str(message.author.id), mconf["antispam-time"], "[AUTOMOD]", spamstr] - asyncio.create_task(warn_missing(command_modules_dict, "mute")(message, execargs, client, automod_ctx)) + asyncio.create_task(warn_missing(command_modules_dict, mconf["antispam-action"])(message, execargs, client, automod_ctx)) if notify: asyncio.create_task(grab_an_adult(message, message.guild, client, mconf, ramfs)) diff --git a/libs/lib_loaders.py b/libs/lib_loaders.py index b048024..f27f96b 100644 --- a/libs/lib_loaders.py +++ b/libs/lib_loaders.py @@ -82,8 +82,8 @@ def directBinNumber(inData: int, length: int) -> Tuple[int, ...]: "csv": [["word-blacklist", ""], ["filetype-blacklist", ""], ["word-in-word-blacklist", ""], ["url-blacklist", ""], ["antispam", "3,2"], ["char-antispam", "2,2,1000"]], "text": [ - ["prefix", GLOBAL_PREFIX], ["blacklist-action", BLACKLIST_ACTION], ["blacklist-whitelist", ""], ["regex-notifier-log", ""], ["admin-role", ""], ["moderator-role", ""], - ["antispam-time", "20"], ["moderator-protect", "0"] + ["prefix", GLOBAL_PREFIX], ["blacklist-action", BLACKLIST_ACTION], ["antispam-action", "mute"], ["blacklist-whitelist", ""], ["regex-notifier-log", ""], ["admin-role", ""], + ["moderator-role", ""], ["antispam-time", "20"], ["moderator-protect", "0"] ], 0: "sonnet_default" } From eb449ee4b3b2cc8748b69a2c43d4e7bb125900e3 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 03:28:41 -0700 Subject: [PATCH 22/48] make cmds to html also test that documentation is partially well formed --- build_tools/cmds_to_html.py | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/build_tools/cmds_to_html.py b/build_tools/cmds_to_html.py index 1db5e50..1c21337 100644 --- a/build_tools/cmds_to_html.py +++ b/build_tools/cmds_to_html.py @@ -73,6 +73,19 @@ raise SyntaxError(f"ERROR IN [{execmodule} : {command}] PERMISSION TYPE({cmd.permission}) IS NOT VALID") +# Test for pretty_name starting with the keyname +for command in command_modules_dict: + if "alias" in command_modules_dict[command]: + continue + + # cmd.execute might point to lib_sonnetcommands if it builds a closure for backwards compat, so get the raw value + execmodule = command_modules_dict[command]['execute'].__module__ + + if command_modules_dict[command]["pretty_name"].startswith(command): + continue + + raise SyntaxError(f"ERROR IN [{execmodule} : {command}] pretty_name does not start with command name (malformed helptext)") + # Test for aliases pointing to existing commands that are not aliases for command in command_modules_dict: if "alias" not in command_modules_dict[command]: From 4c47b50d3f033b3918ab6b39b4e5be1a7afd41c5 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 03:35:01 -0700 Subject: [PATCH 23/48] patch unresolved commanderrors in automod invocation --- dlibs/dlib_messages.py | 19 +++++++++++++++---- 1 file changed, 15 insertions(+), 4 deletions(-) diff --git a/dlibs/dlib_messages.py b/dlibs/dlib_messages.py index fb484a5..3d99781 100644 --- a/dlibs/dlib_messages.py +++ b/dlibs/dlib_messages.py @@ -41,7 +41,7 @@ from lib_compatibility import user_avatar_url from lib_sonnetcommands import SonnetCommand, CommandCtx, CallCtx, ExecutableCtxT -from typing import List, Any, Dict, Optional, Callable, Tuple, Final, Literal, TypedDict, NewType, Union, cast +from typing import List, Any, Dict, Optional, Callable, Tuple, Final, Literal, TypedDict, NewType, Union, Awaitable, cast import lib_lexdpyk_h as lexdpyk import lib_constants as constants @@ -273,7 +273,7 @@ async def on_message_edit(old_message: discord.Message, message: discord.Message asyncio.create_task(attempt_message_delete(message)) execargs: Final = [str(message.author.id), "[AUTOMOD]", ", ".join(infraction_type), "Blacklist"] - await warn_missing(kctx.command_modules[1], mconf["blacklist-action"])(message, execargs, client, command_ctx) + await catch_ce(message, warn_missing(kctx.command_modules[1], mconf["blacklist-action"])(message, execargs, client, command_ctx)) if notify: asyncio.create_task(grab_an_adult(message, message.guild, client, mconf, kctx.ramfs)) @@ -435,6 +435,17 @@ async def dummy(message: discord.Message, args: List[str], client: discord.Clien return dummy +async def catch_ce(err_rsp: discord.Message, promise: Awaitable[Any]) -> None: + + try: + await promise + except lib_sonnetcommands.CommandError as ce: + try: + await err_rsp.channel.send(str(ce)) + except discord.errors.HTTPException: + pass + + @lexdpyk.ToKernelArgs async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) -> None: @@ -491,7 +502,7 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) message_deleted = True asyncio.create_task(attempt_message_delete(message)) execargs = [str(message.author.id), "[AUTOMOD]", ", ".join(infraction_type), "Blacklist"] - asyncio.create_task(warn_missing(command_modules_dict, mconf["blacklist-action"])(message, execargs, client, automod_ctx)) + asyncio.create_task(catch_ce(message, warn_missing(command_modules_dict, mconf["blacklist-action"])(message, execargs, client, automod_ctx))) if spammer: message_deleted = True @@ -499,7 +510,7 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) with db_hlapi(message.guild.id) as db: if not db.is_muted(userid=message.author.id): execargs = [str(message.author.id), mconf["antispam-time"], "[AUTOMOD]", spamstr] - asyncio.create_task(warn_missing(command_modules_dict, mconf["antispam-action"])(message, execargs, client, automod_ctx)) + asyncio.create_task(catch_ce(message, warn_missing(command_modules_dict, mconf["antispam-action"])(message, execargs, client, automod_ctx))) if notify: asyncio.create_task(grab_an_adult(message, message.guild, client, mconf, ramfs)) From 72a837287459ef61cc38b6a1d7257b61a6a1f883 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 15:52:04 -0700 Subject: [PATCH 24/48] update docs for to_snowflke --- libs/lib_compatibility.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/libs/lib_compatibility.py b/libs/lib_compatibility.py index 11e0f5e..d11b92c 100644 --- a/libs/lib_compatibility.py +++ b/libs/lib_compatibility.py @@ -121,7 +121,8 @@ class _WeakSnowflake(Protocol): def to_snowflake(v: _WeakSnowflake, /) -> discord.abc.Snowflake: """ - Casts any true compatible type into a discord.py Showflake interface, bypassing a interface bug with mypy + Casts any snowflake compatible type into something satisfying the discord.py Showflake interface, bypassing a interface bug with mypy + When discord.py/mypy is updated this method will be changed to a bounded identity function """ # FIXME(ultrabear): # we ignore interface errors here because dpy/mypy 2.0 has a bug where snowflakes interface includes slots From 3404d1adf2997c03d45e9e5f7cffa06aac3b2c3e Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 18:31:43 -0700 Subject: [PATCH 25/48] LeXdPyK 2.0: make library reloads optional --- main.py | 43 +++++++++++++++++++++++++++++++++++++++---- 1 file changed, 39 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index d8b2a46..0886506 100644 --- a/main.py +++ b/main.py @@ -297,6 +297,12 @@ def _dump_data(self, dirstr: Optional[str] = None, dirlist: Optional[List[str]] # this allows flexibility with multiple modules that just "must run after command processor init" dynamiclib_modules_exec_dict: Dict[str, List[List[Any]]] = {} +# LeXdPyK 2.0: optional lib reloads +# lexdpyk 2.0 ships with the new feature of not needing to reload library modules in sonnet, +# as the kernel handles reloading them at module load and reload time +# this comes with the side effect of all library modules being init by the kernel +loaded_libraries: List[Any] = [] + def add_module_to_exec_dict(module_dlibs: Dict[str, Any]) -> None: @@ -374,6 +380,31 @@ def log_kernel_info(s: object) -> None: logger.info(f"{version_info}: {s}") +def reload_libraries() -> List[Tuple[Exception, str]]: + """ + Reloads all lib_ libraries + """ + global loaded_libraries + loaded_libraries = [] + + err = [] + + # import libraries + for f in filter(lambda f: f.startswith("lib_") and f.endswith(".py"), os.listdir('./libs')): + try: + loaded_libraries.append(importlib.import_module(f[:-3])) + except Exception as e: + err.append((e, f[:-3]), ) + + for i in range(len(loaded_libraries)): + try: + loaded_libraries[i] = importlib.reload(loaded_libraries[i]) + except Exception as e: + err.append((e, loaded_libraries[i].__name__), ) + + return err + + def kernel_load_command_modules(args: List[str] = []) -> Optional[Tuple[str, List[Exception]]]: log_kernel_info("Loading Kernel Modules") start_load_modules = time.monotonic() @@ -389,6 +420,8 @@ def kernel_load_command_modules(args: List[str] = []) -> Optional[Tuple[str, Lis # Init return state err: List[Tuple[Exception, str]] = [] + err.extend(reload_libraries()) + # Init imports for f in filter(lambda f: f.startswith("cmd_") and f.endswith(".py"), os.listdir('./cmds')): print(f) @@ -450,30 +483,32 @@ def kernel_reload_command_modules(args: List[str] = []) -> Optional[Tuple[str, L # Init ret state err = [] + err.extend(reload_libraries()) + # Update set for i in range(len(command_modules)): try: command_modules[i] = (importlib.reload(command_modules[i])) except Exception as e: - err.append([e, command_modules[i].__name__]) + err.append((e, command_modules[i].__name__)) for i in range(len(dynamiclib_modules)): try: dynamiclib_modules[i] = (importlib.reload(dynamiclib_modules[i])) except Exception as e: - err.append([e, dynamiclib_modules[i].__name__]) + err.append((e, dynamiclib_modules[i].__name__)) # Update hashmaps for module in command_modules: try: command_modules_dict.update(module.commands) except AttributeError: - err.append([KernelSyntaxError("Missing commands"), module.__name__]) + err.append((KernelSyntaxError("Missing commands"), module.__name__)) for module in dynamiclib_modules: try: add_module_to_exec_dict(module.commands) dynamiclib_modules_dict.update(module.commands) except AttributeError: - err.append([KernelSyntaxError("Missing commands"), module.__name__]) + err.append((KernelSyntaxError("Missing commands"), module.__name__)) # Regen tempramfs regenerate_ramfs() From ab4575bb11e224f4004d71b68ab04c369a138f15 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 18:40:53 -0700 Subject: [PATCH 26/48] make all libraries in sonnet not reload libraries internally, speeding up load time by ~150ms --- libs/lib_db_obfuscator.py | 3 --- libs/lib_goparsers.py | 5 ----- libs/lib_loaders.py | 9 --------- libs/lib_parsers.py | 12 ------------ libs/lib_sonnetdb.py | 2 -- libs/lib_starboard.py | 6 ------ 6 files changed, 37 deletions(-) diff --git a/libs/lib_db_obfuscator.py b/libs/lib_db_obfuscator.py index 80eb9ac..2b36ebc 100644 --- a/libs/lib_db_obfuscator.py +++ b/libs/lib_db_obfuscator.py @@ -4,9 +4,6 @@ # Explicitly export db_hlapi __all__ = ["db_hlapi"] -import importlib - import lib_sonnetdb -importlib.reload(lib_sonnetdb) from lib_sonnetdb import db_hlapi diff --git a/libs/lib_goparsers.py b/libs/lib_goparsers.py index 15325d3..5c351f6 100644 --- a/libs/lib_goparsers.py +++ b/libs/lib_goparsers.py @@ -11,8 +11,6 @@ "GetVersion", ] -import importlib - import ctypes as _ctypes import subprocess as _subprocess from functools import lru_cache @@ -20,11 +18,8 @@ from typing import Optional import lib_sonnetconfig - -importlib.reload(lib_sonnetconfig) import lib_datetimeplus -importlib.reload(lib_datetimeplus) from lib_sonnetconfig import GOLIB_LOAD, GOLIB_VERSION from lib_datetimeplus import Duration as _Duration diff --git a/libs/lib_loaders.py b/libs/lib_loaders.py index f27f96b..33732cf 100644 --- a/libs/lib_loaders.py +++ b/libs/lib_loaders.py @@ -3,8 +3,6 @@ from __future__ import annotations -import importlib - import discord import random, ctypes, time, io, json, pickle, threading, warnings @@ -12,17 +10,10 @@ import subprocess import lib_db_obfuscator - -importlib.reload(lib_db_obfuscator) import lib_sonnetconfig - -importlib.reload(lib_sonnetconfig) import lib_goparsers - -importlib.reload(lib_goparsers) import lib_datetimeplus -importlib.reload(lib_datetimeplus) from lib_goparsers import GenerateCacheFile from lib_db_obfuscator import db_hlapi diff --git a/libs/lib_parsers.py b/libs/lib_parsers.py index 2567ef1..3d34e0f 100644 --- a/libs/lib_parsers.py +++ b/libs/lib_parsers.py @@ -8,24 +8,12 @@ import lz4.frame, discord, os, json, hashlib, io, warnings, math import lib_db_obfuscator - -importlib.reload(lib_db_obfuscator) import lib_encryption_wrapper - -importlib.reload(lib_encryption_wrapper) import lib_sonnetconfig - -importlib.reload(lib_sonnetconfig) import lib_constants - -importlib.reload(lib_constants) import lib_sonnetcommands - -importlib.reload(lib_sonnetcommands) import lib_compatibility -importlib.reload(lib_compatibility) - from lib_sonnetconfig import REGEX_VERSION from lib_db_obfuscator import db_hlapi from lib_encryption_wrapper import encrypted_reader diff --git a/libs/lib_sonnetdb.py b/libs/lib_sonnetdb.py index c8ab83b..9a66504 100644 --- a/libs/lib_sonnetdb.py +++ b/libs/lib_sonnetdb.py @@ -9,8 +9,6 @@ import lib_sonnetconfig -importlib.reload(lib_sonnetconfig) - from lib_sonnetconfig import DB_TYPE, SQLITE3_LOCATION from typing import Union, Dict, List, Tuple, Optional, Any, Type, cast diff --git a/libs/lib_starboard.py b/libs/lib_starboard.py index e74f207..f1b7059 100644 --- a/libs/lib_starboard.py +++ b/libs/lib_starboard.py @@ -8,15 +8,9 @@ import discord import lib_sonnetconfig - -importlib.reload(lib_sonnetconfig) import lib_parsers - -importlib.reload(lib_parsers) import lib_compatibility -importlib.reload(lib_compatibility) - from lib_parsers import generate_reply_field from lib_sonnetconfig import STARBOARD_EMOJI, STARBOARD_COUNT, REGEX_VERSION from lib_compatibility import user_avatar_url From c70e7179fabc4d021d5d96a9fdb05415c003f9d8 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 18:43:03 -0700 Subject: [PATCH 27/48] patch pyflakes unused import lint --- libs/lib_db_obfuscator.py | 2 -- libs/lib_goparsers.py | 4 ---- libs/lib_loaders.py | 6 ------ libs/lib_parsers.py | 6 +----- libs/lib_sonnetdb.py | 2 -- libs/lib_starboard.py | 4 ---- 6 files changed, 1 insertion(+), 23 deletions(-) diff --git a/libs/lib_db_obfuscator.py b/libs/lib_db_obfuscator.py index 2b36ebc..002ed3d 100644 --- a/libs/lib_db_obfuscator.py +++ b/libs/lib_db_obfuscator.py @@ -4,6 +4,4 @@ # Explicitly export db_hlapi __all__ = ["db_hlapi"] -import lib_sonnetdb - from lib_sonnetdb import db_hlapi diff --git a/libs/lib_goparsers.py b/libs/lib_goparsers.py index 5c351f6..a829563 100644 --- a/libs/lib_goparsers.py +++ b/libs/lib_goparsers.py @@ -17,10 +17,6 @@ from typing import Optional -import lib_sonnetconfig -import lib_datetimeplus - - from lib_sonnetconfig import GOLIB_LOAD, GOLIB_VERSION from lib_datetimeplus import Duration as _Duration diff --git a/libs/lib_loaders.py b/libs/lib_loaders.py index 33732cf..dc02388 100644 --- a/libs/lib_loaders.py +++ b/libs/lib_loaders.py @@ -9,12 +9,6 @@ import datetime import subprocess -import lib_db_obfuscator -import lib_sonnetconfig -import lib_goparsers -import lib_datetimeplus - - from lib_goparsers import GenerateCacheFile from lib_db_obfuscator import db_hlapi from lib_sonnetconfig import CLIB_LOAD, GLOBAL_PREFIX, BLACKLIST_ACTION diff --git a/libs/lib_parsers.py b/libs/lib_parsers.py index 3d34e0f..a0e60a7 100644 --- a/libs/lib_parsers.py +++ b/libs/lib_parsers.py @@ -7,12 +7,8 @@ import lz4.frame, discord, os, json, hashlib, io, warnings, math -import lib_db_obfuscator -import lib_encryption_wrapper -import lib_sonnetconfig -import lib_constants import lib_sonnetcommands -import lib_compatibility +import lib_encryption_wrapper from lib_sonnetconfig import REGEX_VERSION from lib_db_obfuscator import db_hlapi diff --git a/libs/lib_sonnetdb.py b/libs/lib_sonnetdb.py index 9a66504..60de71f 100644 --- a/libs/lib_sonnetdb.py +++ b/libs/lib_sonnetdb.py @@ -7,8 +7,6 @@ import warnings import io -import lib_sonnetconfig - from lib_sonnetconfig import DB_TYPE, SQLITE3_LOCATION from typing import Union, Dict, List, Tuple, Optional, Any, Type, cast diff --git a/libs/lib_starboard.py b/libs/lib_starboard.py index f1b7059..5cff650 100644 --- a/libs/lib_starboard.py +++ b/libs/lib_starboard.py @@ -7,10 +7,6 @@ import discord -import lib_sonnetconfig -import lib_parsers -import lib_compatibility - from lib_parsers import generate_reply_field from lib_sonnetconfig import STARBOARD_EMOJI, STARBOARD_COUNT, REGEX_VERSION from lib_compatibility import user_avatar_url From eb8744fa9b2c5225b6df12bc5ab95db3ee7e26b8 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 18:53:24 -0700 Subject: [PATCH 28/48] make dlib_messages not use importlib anymore, slight speedup --- dlibs/dlib_messages.py | 25 ------------------------- 1 file changed, 25 deletions(-) diff --git a/dlibs/dlib_messages.py b/dlibs/dlib_messages.py index 3d99781..f66b513 100644 --- a/dlibs/dlib_messages.py +++ b/dlibs/dlib_messages.py @@ -1,39 +1,14 @@ # Dynamic libraries (editable at runtime) for message handling # Ultrabear 2020 -import importlib - import time, asyncio, os, hashlib, string, io, gzip import warnings import copy as pycopy import discord, lz4.frame -import lib_db_obfuscator - -importlib.reload(lib_db_obfuscator) -import lib_parsers - -importlib.reload(lib_parsers) -import lib_loaders - -importlib.reload(lib_loaders) -import lib_encryption_wrapper - -importlib.reload(lib_encryption_wrapper) -import lib_lexdpyk_h - -importlib.reload(lib_lexdpyk_h) -import lib_constants - -importlib.reload(lib_constants) -import lib_compatibility - -importlib.reload(lib_compatibility) import lib_sonnetcommands -importlib.reload(lib_sonnetcommands) - from lib_db_obfuscator import db_hlapi from lib_loaders import load_message_config, inc_statistics_better, load_embed_color, embed_colors, datetime_now from lib_parsers import parse_blacklist, parse_skip_message, parse_permissions, grab_files, generate_reply_field, parse_boolean_strict From 65c6e090b7f72efc4a064cac3f31f02a569c8538 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 19:35:54 -0700 Subject: [PATCH 29/48] mark LeXdPyK 2 as final, further changes are nonbreaking/bugfixes --- main.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/main.py b/main.py index 0886506..b5b4ac9 100644 --- a/main.py +++ b/main.py @@ -1114,7 +1114,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 2-DEV" +version_info: str = "LeXdPyK 2" bot_start_time: float = time.time() if __name__ == "__main__": From a4778f0066e9ac8835c0abe5fce9acfce903ff87 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 19:44:29 -0700 Subject: [PATCH 30/48] add flush to encrypted_writer --- libs/lib_encryption_wrapper.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/libs/lib_encryption_wrapper.py b/libs/lib_encryption_wrapper.py index 02804a0..cb3e767 100644 --- a/libs/lib_encryption_wrapper.py +++ b/libs/lib_encryption_wrapper.py @@ -115,6 +115,9 @@ def finalize(self) -> None: self.rawfile.close() self.encryptor_module.finalize() + def flush(self) -> None: + return + def close(self) -> None: self.finalize() From ea8e76a221df5ed1e54e621df6306154c188072f Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 19:48:35 -0700 Subject: [PATCH 31/48] remove reraising errors --- libs/lib_mdb_handler.py | 2 -- libs/lib_sonnetdb.py | 5 +---- libs/lib_sql_handler.py | 2 -- 3 files changed, 1 insertion(+), 8 deletions(-) diff --git a/libs/lib_mdb_handler.py b/libs/lib_mdb_handler.py index d51dd21..fd2eb22 100644 --- a/libs/lib_mdb_handler.py +++ b/libs/lib_mdb_handler.py @@ -199,5 +199,3 @@ def __exit__(self, err_type: Any, err_value: Any, err_traceback: Any) -> None: self.con.commit() self.con.close() self.closed = True - if err_type: - raise err_type(err_value) diff --git a/libs/lib_sonnetdb.py b/libs/lib_sonnetdb.py index 60de71f..8a68f79 100644 --- a/libs/lib_sonnetdb.py +++ b/libs/lib_sonnetdb.py @@ -555,8 +555,6 @@ def close(self) -> None: def __exit__(self, err_type: Optional[Type[Exception]], err_value: Optional[str], err_traceback: Any) -> None: self._db.commit() - if err_type: - raise err_type(err_value) class _enum_context: @@ -571,8 +569,7 @@ def __enter__(self) -> "_enum_context": return self def __exit__(self, err_type: Optional[Type[Exception]], err_value: Optional[str], err_traceback: Any) -> None: - if err_type: - raise err_type(err_value) + return def grab(self, name: Union[str, int]) -> Optional[List[Union[str, int]]]: self._hlapi.grab_enum.__doc__ diff --git a/libs/lib_sql_handler.py b/libs/lib_sql_handler.py index 3ee690e..24c776f 100644 --- a/libs/lib_sql_handler.py +++ b/libs/lib_sql_handler.py @@ -225,5 +225,3 @@ def __exit__(self, err_type: Any, err_value: Any, err_traceback: Any) -> None: self.con.commit() self.con.close() self.closed = True - if err_type: - raise err_type(err_value) From 62f6d9fc6075c263ab18e94221314f5aa7b2a81f Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 19:52:01 -0700 Subject: [PATCH 32/48] ignore perm errors on catch_ce --- dlibs/dlib_messages.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/dlibs/dlib_messages.py b/dlibs/dlib_messages.py index f66b513..e9ac23a 100644 --- a/dlibs/dlib_messages.py +++ b/dlibs/dlib_messages.py @@ -419,6 +419,9 @@ async def catch_ce(err_rsp: discord.Message, promise: Awaitable[Any]) -> None: await err_rsp.channel.send(str(ce)) except discord.errors.HTTPException: pass + except discord.errors.Forbidden: + # ignore permission errors + pass @lexdpyk.ToKernelArgs From bf80bc87553c7213cc132715109bc47eb8605f91 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 19:55:21 -0700 Subject: [PATCH 33/48] LeXdPyK 2.0.1: patch reloads not dropping exec dict --- main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index b5b4ac9..dc26039 100644 --- a/main.py +++ b/main.py @@ -474,9 +474,10 @@ def regenerate_kernel_ramfs(args: List[str] = []) -> Optional[Tuple[str, List[Ex def kernel_reload_command_modules(args: List[str] = []) -> Optional[Tuple[str, List[Exception]]]: log_kernel_info("Reloading Kernel Modules") # Init vars - global command_modules, command_modules_dict, dynamiclib_modules, dynamiclib_modules_dict + global command_modules, command_modules_dict, dynamiclib_modules, dynamiclib_modules_dict, dynamiclib_modules_exec_dict command_modules_dict = {} dynamiclib_modules_dict = {} + dynamiclib_modules_exec_dict = {} start_reload_modules = time.monotonic() @@ -1114,7 +1115,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 2" +version_info: str = "LeXdPyK 2.0.1" bot_start_time: float = time.time() if __name__ == "__main__": From bfe9a3601a576e449294aa0f1527df72b7ad7970 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 19:56:44 -0700 Subject: [PATCH 34/48] remove dev-2.0 from workflows --- .github/workflows/python-dev.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-dev.yml b/.github/workflows/python-dev.yml index a95b0da..072dbfc 100644 --- a/.github/workflows/python-dev.yml +++ b/.github/workflows/python-dev.yml @@ -5,7 +5,7 @@ name: Dev branch on: push: - branches: [ dev-unstable, dev-2.0 ] + branches: [ dev-unstable ] From 6f660b36507c858991c4e41d20b247285f71a20b Mon Sep 17 00:00:00 2001 From: ultrabear Date: Thu, 25 Aug 2022 20:02:16 -0700 Subject: [PATCH 35/48] mark VoiceChannel as guild messageable --- libs/lib_compatibility.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/libs/lib_compatibility.py b/libs/lib_compatibility.py index d11b92c..f041e69 100644 --- a/libs/lib_compatibility.py +++ b/libs/lib_compatibility.py @@ -130,7 +130,7 @@ def to_snowflake(v: _WeakSnowflake, /) -> discord.abc.Snowflake: return v # type: ignore[return-value] -GuildMessageable = Union[discord.TextChannel, discord.Thread] +GuildMessageable = Union[discord.TextChannel, discord.Thread, discord.VoiceChannel] _concrete_channels = Union[discord.TextChannel, discord.VoiceChannel, discord.CategoryChannel, discord.StageChannel, discord.ForumChannel, discord.Thread, discord.DMChannel, discord.GroupChannel, discord.PartialMessageable] @@ -142,4 +142,4 @@ def is_guild_messageable(v: Union[_concrete_channels, _abstract_base_class_chann Returns True if the channel type passed is within a guild and messageable """ - return isinstance(v, (discord.TextChannel, discord.Thread)) + return isinstance(v, (discord.TextChannel, discord.Thread, discord.VoiceChannel)) From a3c714a3f7ceb9e6e7f6bddebc1e6900aa544075 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 26 Aug 2022 00:20:24 -0700 Subject: [PATCH 36/48] remove unneeded type ignore --- libs/lib_sonnetconfig.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/libs/lib_sonnetconfig.py b/libs/lib_sonnetconfig.py index 911a995..072dbbe 100644 --- a/libs/lib_sonnetconfig.py +++ b/libs/lib_sonnetconfig.py @@ -40,8 +40,7 @@ def _load_cfg(attr: str, default: Typ, typ: Type[Typ], testfunc: Optional[Callab if testfunc is not None and not testfunc(conf): raise TypeError(f"Sonnet Config {attr}: {errmsg}") - # pyright thinks that it can still be Any despite isinstance check - return conf # pyright: ignore[reportGeneralTypeIssues] + return conf # Prints a warning if not using re2 From de61d3b093125578f8cf5d6514fd70205b535d7c Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 26 Aug 2022 00:20:37 -0700 Subject: [PATCH 37/48] formalize contract for db_hander api --- libs/lib_sonnetdb.py | 63 ++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 61 insertions(+), 2 deletions(-) diff --git a/libs/lib_sonnetdb.py b/libs/lib_sonnetdb.py index 8a68f79..5aacaa9 100644 --- a/libs/lib_sonnetdb.py +++ b/libs/lib_sonnetdb.py @@ -9,7 +9,9 @@ from lib_sonnetconfig import DB_TYPE, SQLITE3_LOCATION -from typing import Union, Dict, List, Tuple, Optional, Any, Type, cast +from typing import Union, Dict, List, Tuple, Optional, Any, Type, Protocol, cast + +db_handler: Type["_DataBaseHandler"] # Get db handling library if DB_TYPE == "mariadb": @@ -30,6 +32,63 @@ raise RuntimeError("Could not load database backend (non valid specifier)") +class _DataBaseHandler(Protocol): + @property + def TEXT_KEY(self) -> bool: + ... + + def __init__(self, db_connection: Any, /) -> None: + ... + + def __enter__(self, /) -> "_DataBaseHandler": + ... + + def make_new_index(self, tablename: str, indexname: str, columns: List[str], /) -> None: + ... + + def make_new_table(self, tablename: str, data: Union[List[Any], Tuple[Any, ...]], /) -> None: + ... + + def add_to_table(self, table: str, data: Union[List[Any], Tuple[Any, ...]], /) -> None: + ... + + def multicount_rows_from_table(self, table: str, searchparms: List[List[Any]], /) -> int: + ... + + def fetch_rows_from_table(self, table: str, search: List[Any], /) -> Tuple[Any, ...]: + ... + + def multifetch_rows_from_table(self, table: str, searchparms: List[List[Any]], /) -> Tuple[Any, ...]: + ... + + def delete_rows_from_table(self, table: str, column_search: List[Any], /) -> None: + ... + + def delete_table(self, table: str, /) -> None: + ... + + def fetch_table(self, table: str, /) -> Tuple[Any, ...]: + ... + + def list_tables(self, searchterm: str, /) -> Tuple[Tuple[str], ...]: + ... + + def ping(self, /) -> None: + ... + + def commit(self, /) -> None: + ... + + def close(self, /) -> None: + ... + + def __del__(self, /) -> None: + ... + + def __exit__(self, err_type: Any, err_value: Any, err_traceback: Any, /) -> None: + ... + + class DATABASE_FATAL_CONNECTION_LOSS(Exception): __slots__ = () @@ -41,7 +100,7 @@ class DATABASE_FATAL_CONNECTION_LOSS(Exception): raise DATABASE_FATAL_CONNECTION_LOSS("Database failure") -def db_grab_connection() -> db_handler: # pytype: disable=invalid-annotation +def db_grab_connection() -> _DataBaseHandler: # pytype: disable=invalid-annotation global db_connection try: db_connection.ping() From eaebad40bc607e5caa4b244e78e39608c07081fe Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 26 Aug 2022 00:50:13 -0700 Subject: [PATCH 38/48] apply patches for pyright strict --- libs/lib_encryption_wrapper.py | 4 ++-- libs/lib_goparsers.py | 4 ++-- libs/lib_lexdpyk_h.py | 6 ++---- libs/lib_tparse.py | 11 ++++++++--- 4 files changed, 14 insertions(+), 11 deletions(-) diff --git a/libs/lib_encryption_wrapper.py b/libs/lib_encryption_wrapper.py index cb3e767..701f720 100644 --- a/libs/lib_encryption_wrapper.py +++ b/libs/lib_encryption_wrapper.py @@ -6,7 +6,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import hashes, hmac -from typing import Generator, Union, Protocol +from typing import Generator, Union, List, Protocol class errors: @@ -204,7 +204,7 @@ def read(self, size: int = -1) -> bytes: if size == -1: if self.pointer == 0: # Return entire file if pointer is at 0 - datamap = [] + datamap: List[bytes] = [] while a := self.rawfile.read(2): datamap.append(self._grab_amount(int.from_bytes(a, "little"))) return b"".join(datamap) diff --git a/libs/lib_goparsers.py b/libs/lib_goparsers.py index a829563..14233c6 100644 --- a/libs/lib_goparsers.py +++ b/libs/lib_goparsers.py @@ -15,7 +15,7 @@ import subprocess as _subprocess from functools import lru_cache -from typing import Optional +from typing import Optional, List from lib_sonnetconfig import GOLIB_LOAD, GOLIB_VERSION from lib_datetimeplus import Duration as _Duration @@ -118,7 +118,7 @@ def GenerateCacheFile(fin: str, fout: str) -> None: with open(fin, "rb") as words: maxval = 0 - structured_data = [] + structured_data: List[bytes] = [] for byte in words.read().split(b"\n"): if byte and not len(byte) > 85 and not b"\xc3" in byte: diff --git a/libs/lib_lexdpyk_h.py b/libs/lib_lexdpyk_h.py index 2e748b2..49db3d6 100644 --- a/libs/lib_lexdpyk_h.py +++ b/libs/lib_lexdpyk_h.py @@ -100,16 +100,14 @@ def newfunc(*args: Any, **kwargs: Any) -> Coroutine[Any, Any, Any]: class _BotOwners: __slots__ = "owners", - def __init__(self, unknown: object) -> None: + def __init__(self, unknown: Union[List[Union[str, int]], Tuple[Union[str, int], ...], str, int]) -> None: self.owners: Set[int] if isinstance(unknown, (int, str)): self.owners = set([int(unknown)]) if unknown else set() - elif isinstance(unknown, (list, tuple)): - self.owners = set(map(int, unknown)) else: - self.owners = set() + self.owners = set(map(int, unknown)) def is_owner(self, user: Union[discord.User, discord.Member]) -> bool: """ diff --git a/libs/lib_tparse.py b/libs/lib_tparse.py index d44f8e4..84d915d 100644 --- a/libs/lib_tparse.py +++ b/libs/lib_tparse.py @@ -61,6 +61,8 @@ class ParseFailureError(TParseError): _ParserT = TypeVar("_ParserT") # C Iterator Type _CIT = TypeVar("_CIT") +# Generic T +_T = TypeVar("_T") class _IteratorCtx: @@ -194,13 +196,16 @@ def parse(self, args: List[str] = sys.argv[1:], stderr: StringWriter = sys.stder garbage: List[int] = [] + def store_private(promise: Promise[_T], v: _T) -> None: + promise._data = v # pyright: ignore[reportPrivateUsage] + for idx, val in _CIterator(args): if val in self._arghash: garbage.append(idx.i) arg = self._arghash[val] if arg.flag is True: - arg.store._data = arg.func("") + store_private(arg.store, arg.func("")) else: idx.i += 1 garbage.append(idx.i) @@ -208,7 +213,7 @@ def parse(self, args: List[str] = sys.argv[1:], stderr: StringWriter = sys.stder self._error(f"Failure to parse argument {val}, expected parameter, reached EOL", exit_on_fail, stderr) try: - arg.store._data = arg.func(args[idx.i]) + store_private(arg.store, arg.func(args[idx.i])) except ValueError as ve: self._error(f"Failed to parse argument {val}; ValueError: {ve}", exit_on_fail, stderr) @@ -221,7 +226,7 @@ def parse(self, args: List[str] = sys.argv[1:], stderr: StringWriter = sys.stder del args[di] for i in self._arguments: - i.store._parsed = True + i.store._parsed = True # pyright: ignore[reportPrivateUsage] def store_true(s: str) -> Literal[True]: From be167fc0ffd7d5372719e9fbd4b2e7aca5967c49 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 26 Aug 2022 00:50:25 -0700 Subject: [PATCH 39/48] make certain files fall under strict --- pyrightconfig.json | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/pyrightconfig.json b/pyrightconfig.json index b4ab2ff..bbe787a 100644 --- a/pyrightconfig.json +++ b/pyrightconfig.json @@ -2,5 +2,17 @@ "extraPaths": ["libs/", "common/"], "pythonVersion": "3.8", "pythonPlatform": "Linux", - "reportMissingImports": false + "reportMissingImports": false, + "strict": [ + "libs/lib_compatibility.py", + "libs/lib_datetimeplus.py", + "libs/lib_tparse.py", + "libs/lib_constants.py", + "libs/lib_db_obfuscator.py", + "libs/lib_encryption_wrapper.py", + "libs/lib_goparsers.py", + "libs/lib_lexdpyk_h.py", + "libs/lib_sonnetconfig.py", + "libs/lib_starboard.py" + ] } From 323fe353fe6272c5708012a0ab08cc150e014ddc Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 26 Aug 2022 06:08:54 -0700 Subject: [PATCH 40/48] remove reimports from cmd_moderation --- cmds/cmd_moderation.py | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/cmds/cmd_moderation.py b/cmds/cmd_moderation.py index 2ebb231..0a76401 100644 --- a/cmds/cmd_moderation.py +++ b/cmds/cmd_moderation.py @@ -1,37 +1,13 @@ # Moderation commands # bredo, 2020 -import importlib - from datetime import timedelta import discord, asyncio, json from dataclasses import dataclass -import lib_db_obfuscator - -importlib.reload(lib_db_obfuscator) -import lib_loaders - -importlib.reload(lib_loaders) import lib_parsers - -importlib.reload(lib_parsers) -import lib_constants - -importlib.reload(lib_constants) -import lib_goparsers - -importlib.reload(lib_goparsers) -import lib_compatibility - -importlib.reload(lib_compatibility) -import lib_sonnetconfig - -importlib.reload(lib_sonnetconfig) import lib_sonnetcommands -importlib.reload(lib_sonnetcommands) - from lib_goparsers import ParseDurationSuper from lib_loaders import generate_infractionid, load_embed_color, load_message_config, embed_colors, datetime_now from lib_db_obfuscator import db_hlapi From 7f6323d211fe99bed45cd3f24eb7331910eb01ff Mon Sep 17 00:00:00 2001 From: ultrabear Date: Fri, 26 Aug 2022 06:31:10 -0700 Subject: [PATCH 41/48] make cached_yapf diff mode print results as they arrive --- build_tools/cached_yapf.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/build_tools/cached_yapf.py b/build_tools/cached_yapf.py index 142db91..30ee6a6 100644 --- a/build_tools/cached_yapf.py +++ b/build_tools/cached_yapf.py @@ -10,7 +10,7 @@ import subprocess from dataclasses import dataclass -from typing import List, Literal, Final, Dict +from typing import List, Literal, Final, Dict, AsyncIterator @dataclass @@ -153,12 +153,12 @@ async def run_single_yapf(mode: Literal["diff", "inplace"], filename: str) -> Pr return ProcessedData(filename, stdout, stderr, proc.returncode or 0) -async def run_yapf_async(mode: Literal["diff", "inplace"], files: List[str]) -> List[ProcessedData]: +async def run_yapf_async(mode: Literal["diff", "inplace"], files: List[str]) -> AsyncIterator[ProcessedData]: """ Runs multiple yapf instances in async subprocesses and provides returncode and info for each """ tasks = [asyncio.create_task(run_single_yapf(mode, i)) for i in files] - return [await task for task in tasks] + return (await task for task in tasks) def run_yapf_once(mode: Literal["diff", "inplace"], files: List[str]) -> "subprocess.CompletedProcess[bytes]": @@ -184,12 +184,12 @@ def process_inplace(cache: Dict[str, CacheEntry], files: List[str]) -> int: return proc.returncode -def process_diff(cache: Dict[str, CacheEntry], files: List[str]) -> int: +async def process_diff(cache: Dict[str, CacheEntry], files: List[str]) -> int: returncode = 0 safe_cached = [] - for proc in asyncio.run(run_yapf_async("diff", files)): + async for proc in await run_yapf_async("diff", files): if proc.stdout: print(proc.stdout.decode("utf8")) @@ -231,7 +231,7 @@ def main(args: List[str]) -> int: if parsed.mode == "inplace": return process_inplace(cache, process) else: - return process_diff(cache, process) + return asyncio.run(process_diff(cache, process)) if __name__ == "__main__": From 9114311171e8e64c13777fd727b909ede209b6ed Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 27 Aug 2022 22:22:10 -0700 Subject: [PATCH 42/48] patch bug in debug-drop-modules not dropping exec dict --- main.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/main.py b/main.py index dc26039..a29ff61 100644 --- a/main.py +++ b/main.py @@ -592,9 +592,10 @@ def kernel_logout(args: List[str] = []) -> Optional[Tuple[str, List[Exception]]] def kernel_drop_dlibs(args: List[str] = []) -> Optional[Tuple[str, List[Exception]]]: log_kernel_info("Dropping dynamiclib modules") - global dynamiclib_modules, dynamiclib_modules_dict + global dynamiclib_modules, dynamiclib_modules_dict, dynamiclib_modules_exec_dict dynamiclib_modules = [] dynamiclib_modules_dict = {} + dynamiclib_modules_exec_dict = {} return None @@ -1115,7 +1116,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 2.0.1" +version_info: str = "LeXdPyK 2.0.2" bot_start_time: float = time.time() if __name__ == "__main__": From d3be1c271f4b39f51c36e01f02d6adf602c09bb8 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 27 Aug 2022 22:30:21 -0700 Subject: [PATCH 43/48] LeXdPyK 2.0.3: support dpy2.0.1 --- main.py | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/main.py b/main.py index a29ff61..d7c597a 100644 --- a/main.py +++ b/main.py @@ -18,7 +18,7 @@ import glob, json, hashlib, logging, getpass, datetime, argparse, random # Import typing support -from typing import List, Optional, Any, Tuple, Dict, Union, Type, Protocol +from typing import List, Optional, Any, Tuple, Dict, Union, Type, Protocol, TypeVar # Start Discord.py import discord, asyncio @@ -721,8 +721,11 @@ async def event_call(argtype: str, *args: Any) -> Optional[errtype]: return None -def lexdpyk_to_snowflake(v: Union[discord.User, discord.Member]) -> discord.abc.Snowflake: - return v # type: ignore[return-value] +UT = TypeVar("UT", bound=Union[discord.User, discord.Member]) + + +def lexdpyk_to_snowflake(v: UT) -> UT: + return v async def safety_check(guild: Optional[discord.Guild] = None, guild_id: Optional[int] = None, user: Optional[Union[discord.User, discord.Member]] = None, user_id: Optional[int] = None) -> bool: @@ -1116,7 +1119,7 @@ def main(args: List[str]) -> int: # Define version info and start time -version_info: str = "LeXdPyK 2.0.2" +version_info: str = "LeXdPyK 2.0.3" bot_start_time: float = time.time() if __name__ == "__main__": From 84833dd3157cc27cf4da86d2384f76a7b05ef99a Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 27 Aug 2022 22:30:56 -0700 Subject: [PATCH 44/48] remove dpy2.0.0 type ignores --- libs/lib_compatibility.py | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/libs/lib_compatibility.py b/libs/lib_compatibility.py index f041e69..0df5417 100644 --- a/libs/lib_compatibility.py +++ b/libs/lib_compatibility.py @@ -5,7 +5,7 @@ import discord import datetime -from typing import Union, Dict, Callable, Protocol, cast +from typing import Union, Dict, Callable, Protocol, TypeVar, cast from typing_extensions import TypeGuard _releaselevel: int = discord.version_info[0] @@ -119,15 +119,15 @@ class _WeakSnowflake(Protocol): id: int -def to_snowflake(v: _WeakSnowflake, /) -> discord.abc.Snowflake: +SF = TypeVar("SF", bound=_WeakSnowflake) + + +def to_snowflake(v: SF, /) -> SF: """ - Casts any snowflake compatible type into something satisfying the discord.py Showflake interface, bypassing a interface bug with mypy - When discord.py/mypy is updated this method will be changed to a bounded identity function + ~~Casts any snowflake compatible type into something satisfying the discord.py Showflake interface, bypassing a interface bug with mypy~~ + This is patched as of dpy2.0.1, it now returns the type passed in as long as it satisfies the bound of a snowflake """ - # FIXME(ultrabear): - # we ignore interface errors here because dpy/mypy 2.0 has a bug where snowflakes interface includes slots - # when this bug is patched mypy should raise an error for unused type ignores and this should be patched - return v # type: ignore[return-value] + return v GuildMessageable = Union[discord.TextChannel, discord.Thread, discord.VoiceChannel] From be33d1b7f2e4c452786e2a20b738fcde241d5f0b Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sat, 27 Aug 2022 22:31:07 -0700 Subject: [PATCH 45/48] change version deps --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index cb116ea..1a5f747 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -discord.py >=2, <3 +discord.py >=2.0.1, <3 cryptography >=37.0.0, <38.0.0 lz4 >=3.1.10, <5.0.0 typing-extensions >=3.10.0.0, <5 From a7284541304c13cb5b7712cf5968b675aaa02a70 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sun, 28 Aug 2022 14:08:13 -0700 Subject: [PATCH 46/48] make dpy use speed and bump typing extensions now that dpy-stubs is no longer needed --- requirements.txt | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/requirements.txt b/requirements.txt index 1a5f747..334e2de 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,4 @@ -discord.py >=2.0.1, <3 +discord.py[speed] >=2.0.1, <3 cryptography >=37.0.0, <38.0.0 lz4 >=3.1.10, <5.0.0 -typing-extensions >=3.10.0.0, <5 +typing-extensions >=4.3, <5 From 2e2d25f5e55c070f04b8c9ba1b10e8d6493f713d Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sun, 28 Aug 2022 14:13:13 -0700 Subject: [PATCH 47/48] move versions to RC status --- cmds/cmd_automod.py | 2 +- cmds/cmd_moderation.py | 2 +- cmds/cmd_reactionroles.py | 2 +- cmds/cmd_scripting.py | 2 +- cmds/cmd_utils.py | 2 +- dlibs/dlib_messages.py | 2 +- dlibs/dlib_reactionroles.py | 2 +- dlibs/dlib_startup.py | 2 +- dlibs/dlib_userupdate.py | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/cmds/cmd_automod.py b/cmds/cmd_automod.py index 9da7115..6c0b7f1 100644 --- a/cmds/cmd_automod.py +++ b/cmds/cmd_automod.py @@ -731,4 +731,4 @@ async def add_joinrule(message: discord.Message, args: List[str], client: discor }, } -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" diff --git a/cmds/cmd_moderation.py b/cmds/cmd_moderation.py index 0a76401..2fdffcb 100644 --- a/cmds/cmd_moderation.py +++ b/cmds/cmd_moderation.py @@ -814,4 +814,4 @@ async def purge_cli(message: discord.Message, args: List[str], client: discord.C } } -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" diff --git a/cmds/cmd_reactionroles.py b/cmds/cmd_reactionroles.py index 2ede0d0..b3d96ca 100644 --- a/cmds/cmd_reactionroles.py +++ b/cmds/cmd_reactionroles.py @@ -376,4 +376,4 @@ async def rr_purge(message: discord.Message, args: List[str], client: discord.Cl }, } -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" diff --git a/cmds/cmd_scripting.py b/cmds/cmd_scripting.py index 7988427..e719589 100644 --- a/cmds/cmd_scripting.py +++ b/cmds/cmd_scripting.py @@ -430,4 +430,4 @@ async def sleep_for(message: discord.Message, args: List[str], client: discord.C }, } -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" diff --git a/cmds/cmd_utils.py b/cmds/cmd_utils.py index fa80987..fa20ade 100644 --- a/cmds/cmd_utils.py +++ b/cmds/cmd_utils.py @@ -649,4 +649,4 @@ async def coinflip(message: discord.Message, args: List[str], client: discord.Cl } } -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" diff --git a/dlibs/dlib_messages.py b/dlibs/dlib_messages.py index e9ac23a..79a24cb 100644 --- a/dlibs/dlib_messages.py +++ b/dlibs/dlib_messages.py @@ -585,4 +585,4 @@ async def on_message(message: discord.Message, kernel_args: lexdpyk.KernelArgs) "on-message-delete": on_message_delete, } -version_info: Final = "2.0.0-DEV" +version_info: Final = "2.0.0" diff --git a/dlibs/dlib_reactionroles.py b/dlibs/dlib_reactionroles.py index 59ad366..9b1adf9 100644 --- a/dlibs/dlib_reactionroles.py +++ b/dlibs/dlib_reactionroles.py @@ -116,4 +116,4 @@ async def on_raw_reaction_remove(payload: discord.RawReactionActionEvent, kargs: "on-raw-reaction-remove": on_raw_reaction_remove, } -version_info = "2.0.0-DEV" +version_info = "2.0.0" diff --git a/dlibs/dlib_startup.py b/dlibs/dlib_startup.py index cfd4c8f..56d8e41 100644 --- a/dlibs/dlib_startup.py +++ b/dlibs/dlib_startup.py @@ -88,4 +88,4 @@ async def on_guild_join(guild: discord.Guild, **kargs: Any) -> None: commands: Dict[str, Callable[..., Any]] = {"on-ready": on_ready, "on-guild-join": on_guild_join} -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" diff --git a/dlibs/dlib_userupdate.py b/dlibs/dlib_userupdate.py index 5bd71b6..dd9ae91 100644 --- a/dlibs/dlib_userupdate.py +++ b/dlibs/dlib_userupdate.py @@ -205,4 +205,4 @@ async def on_member_remove(member: discord.Member, **kargs: Any) -> None: "on-member-remove": on_member_remove, } -version_info: str = "2.0.0-DEV" +version_info: str = "2.0.0" From 75fc13fa9068a016c0517df8640ecd053473fdd2 Mon Sep 17 00:00:00 2001 From: ultrabear Date: Sun, 28 Aug 2022 23:26:20 -0700 Subject: [PATCH 48/48] patch encrypted_writer to use flush if possible --- libs/lib_encryption_wrapper.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/libs/lib_encryption_wrapper.py b/libs/lib_encryption_wrapper.py index 701f720..a06a219 100644 --- a/libs/lib_encryption_wrapper.py +++ b/libs/lib_encryption_wrapper.py @@ -6,7 +6,7 @@ from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.primitives import hashes, hmac -from typing import Generator, Union, List, Protocol +from typing import Generator, Union, List, Protocol, runtime_checkable class errors: @@ -47,6 +47,12 @@ def close(self) -> None: ... +@runtime_checkable +class _Flushable(Protocol): + def flush(self) -> None: + ... + + class encrypted_writer: __slots__ = "cipher", "encryptor_module", "HMACencrypt", "rawfile", "buf" @@ -116,7 +122,8 @@ def finalize(self) -> None: self.encryptor_module.finalize() def flush(self) -> None: - return + if isinstance(self.rawfile, _Flushable): + self.rawfile.flush() def close(self) -> None: