From 437adf87e4787a9d23b1c2be4dcb4cfc41217ce7 Mon Sep 17 00:00:00 2001 From: Harsha Raghu Date: Fri, 14 Jan 2022 18:52:51 +0530 Subject: [PATCH] [Context] Implement LionContext Need to append a Text to all bot replies to ask people to !vote - Implement LionContext - Implement Callback handler to enable Modules intercept cmdClient.Context.Utils() (attr calls) --- bot/LionContext.py | 48 +++++++++++++++++++++++++++++++++++ bot/meta/client.py | 5 ++-- bot/utils/interactive.py | 54 ++++++++++++++++++++++++++++++++++------ 3 files changed, 98 insertions(+), 9 deletions(-) create mode 100644 bot/LionContext.py diff --git a/bot/LionContext.py b/bot/LionContext.py new file mode 100644 index 00000000..cf068d9d --- /dev/null +++ b/bot/LionContext.py @@ -0,0 +1,48 @@ +import datetime + +import discord +from cmdClient import Context +from cmdClient.logger import log + +reply_callbacks: list = [] # TODO Extend to all cmdClient.Context.Utils to give flexibility to modules + +class LionContext(Context): + """ + Subclass to allow easy attachment of custom hooks and structure to contexts. + """ + + def __init__(self, client, **kwargs): + super().__init__(client, **kwargs) + + @classmethod + def util(self, util_func): + """ + Decorator to make a utility function available as a Context instance method + """ + log('added util_function: ' + util_func.__name__) + + def util_fun_wrapper(*args, **kwargs): + [args, kwargs] = self.util_pre(util_func, *args, **kwargs) + return util_func(*args, **kwargs) + + util_fun_wrapper.__name__ = util_func.__name__ # Hack + + super().util(util_fun_wrapper) + + @classmethod + def util_pre(self, util_func, *args, **kwargs): + + if util_func.__name__ == 'reply': + for cb in reply_callbacks: + [args, kwargs] = cb(util_func, *args, **kwargs) # Nesting handlers. Note: args and kwargs are mutable + + return [args, kwargs] + + +def register_reply_callback(func): + reply_callbacks.append(func) + +def unregister_reply_callback(func): + reply_callbacks.remove(func) + + diff --git a/bot/meta/client.py b/bot/meta/client.py index 50414aa8..a3f02877 100644 --- a/bot/meta/client.py +++ b/bot/meta/client.py @@ -3,7 +3,7 @@ from cmdClient.cmdClient import cmdClient from .config import conf from .sharding import shard_number, shard_count - +from LionContext import LionContext # Initialise client owners = [int(owner) for owner in conf.bot.getlist('owners')] @@ -14,6 +14,7 @@ client = cmdClient( owners=owners, intents=intents, shard_id=shard_number, - shard_count=shard_count + shard_count=shard_count, + baseContext=LionContext ) client.conf = conf diff --git a/bot/utils/interactive.py b/bot/utils/interactive.py index 986361d5..a232604e 100644 --- a/bot/utils/interactive.py +++ b/bot/utils/interactive.py @@ -1,8 +1,10 @@ import asyncio import discord -from cmdClient import Context +from LionContext import LionContext from cmdClient.lib import UserCancelled, ResponseTimedOut +import datetime +from cmdClient import lib from .lib import paginate_list # TODO: Interactive locks @@ -19,7 +21,7 @@ async def discord_shield(coro): pass -@Context.util +@LionContext.util async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout=300): """ Add a cancellation reaction to the given message. @@ -62,7 +64,7 @@ async def cancellable(ctx, msg, add_reaction=True, cancel_message=None, timeout= return task -@Context.util +@LionContext.util async def listen_for(ctx, allowed_input=None, timeout=120, lower=True, check=None): """ Listen for a one of a particular set of input strings, @@ -114,7 +116,7 @@ async def listen_for(ctx, allowed_input=None, timeout=120, lower=True, check=Non return message -@Context.util +@LionContext.util async def selector(ctx, header, select_from, timeout=120, max_len=20): """ Interactive routine to prompt the `ctx.author` to select an item from a list. @@ -214,7 +216,7 @@ async def selector(ctx, header, select_from, timeout=120, max_len=20): return result -@Context.util +@LionContext.util async def pager(ctx, pages, locked=True, start_at=0, add_cancel=False, **kwargs): """ Shows the user each page from the provided list `pages` one at a time, @@ -371,7 +373,7 @@ async def _pager(ctx, out_msg, pages, locked, start_at, add_cancel, **kwargs): pass -@Context.util +@LionContext.util async def input(ctx, msg="", timeout=120): """ Listen for a response in the current channel, from ctx.author. @@ -413,7 +415,7 @@ async def input(ctx, msg="", timeout=120): return result -@Context.util +@LionContext.util async def ask(ctx, msg, timeout=30, use_msg=None, del_on_timeout=False): """ Ask ctx.author a yes/no question. @@ -459,3 +461,41 @@ async def ask(ctx, msg, timeout=30, use_msg=None, del_on_timeout=False): if result in ["n", "no"]: return 0 return 1 + +# this reply() will be overide baseContext's reply with LionContext's, whcih can +# hook pre_execution of any util. +# Using this system, Module now have much power to change Context's utils +@LionContext.util +async def reply(ctx, content=None, allow_everyone=False, **kwargs): + """ + Helper function to reply in the current channel. + """ + if not allow_everyone: + if content: + content = lib.sterilise_content(content) + + message = await ctx.ch.send(content=content, **kwargs) + ctx.sent_messages.append(message) + return message + + +# this reply() will be overide baseContext's reply +@LionContext.util +async def error_reply(ctx, error_str): + """ + Notify the user of a user level error. + Typically, this will occur in a red embed, posted in the command channel. + """ + embed = discord.Embed( + colour=discord.Colour.red(), + description=error_str, + timestamp=datetime.datetime.utcnow() + ) + try: + message = await ctx.ch.send(embed=embed) + ctx.sent_messages.append(message) + return message + except discord.Forbidden: + message = await ctx.reply(error_str) + ctx.sent_messages.append(message) + return message \ No newline at end of file