[APPROVED] Vrt-cogs

Discord Name: Vertyco#0117

GitHub Repository (Must be V3): GitHub - vertyco/vrt-cogs: Utility cogs for Red by Vertyco

Description: Assorted utility cogs for game servers that I have created over the past couple months while learning python.

Bumping since its been a full year now, all my cogs will be ready for dpy2 when red hits 3.5 :slightly_smiling_face:

With the release of 3.5, we are returning to reviewing Cog Creator applications.

Since 3.5 will break every cog that was not specifically coded with it in mind, you need to ensure your repo is updated to be compatible with 3.5 prior to a review being possible. The relevant documentation you will need to update your repo is the following:
-discord.py 2.0 migration: https://discordpy.readthedocs.io/en/v2.2.3/migrating.html
-Red 3.5 breakages: https://docs.discord.red/en/stable/incompatible_changes/3.5.html#incompatible-changes-3-5

Once your repo is updated to support 3.5 on the default branch, please make a comment in this thread to confirm you are once again ready for review. We will only be reviewing repos which comment to confirm 3.5 support, but we will prioritize repos that have been in the queue for a longer period of time first.

If you wish to revoke your application, please comment accordingly so we can clean up the thread. If you need time to make changes, but are actively working on making updates, please also comment accordingly. Applications which do not see any activity within 1 month from today will be closed.

Hi Flame, can confirm my repo already supports 3.5

Hi Vertyco, thank you for your patience while I review your repo.

Commit hash at time of review - 6da8916343af1034f69a56b13f9b5f7a16ae94f8

Some general comments regarding the whole repo:
There’s a lot of missing permissions checks for both the bot and the user. These are the main gotcha’s cog creators can run into especially regarding role permissions when assigning roles to users. Some other general things I noticed is using @commands.admin() for everything which isn’t the best tracker for permissions checks. This decorator only checks that the admin role has been setup by the server owner (or bot owner). This may not reliably translate trust to the user and can be a deterrant for some bot owners who aren’t intimately aware of the bot working like this.

I imagine many people might install your cogs and not have setup the admin role and suddenly none of your commands work. While that’s totally up to you to decide there’s at least one instance I found where this can lead to potential hierarchy escalation which is not okay. If you intended to check that the user has administrator permission then you should use @commands.admin_or_permissions(administrator=True). Then the server owner can set an explicit admin role or anyone who has the administrator permission can also use this. No two people have the same interpretation of a servers Admin and Mod definition and blanket assumptions tend to be bad. If you want to opt out of Red’s mod and admin role checks entirely look at using @commands.has_permissions() then only people with that explicit role/channel permission can run the command. Instances where this isn’t a problem are labeled as optional.

For simplicity I tend to use manage_messages as a base trust permission as generally servers treat manage messages permission as the most basic moderator signal. If you can’t delete someone elses message you probably shouldn’t be modifying the servers bot settings. Again this is my opinion on the matter and you’re free to set things up as you wish.

I understand that these cogs have been in flux since prior to 3.5’s release and you have attempted some backwards comapatibility but the way you went about that backwards compatibility has some potential issues and have been noted below.


assistant

api.py

  • Lines 177-246: If the bot does not have embed_links permission and there is a response larger than 2000 characters all of this will fail.

  • Line 369: You should not allow user defined regex to be unregulated like this. A server admin could define a regex pattern that is catastrophically backtracking. This would mean any time someone calls the API in that server the bot could indefinitely hang for everyone. There are ways to avoid this issue namely the re2 module from facebook is designed to prevent this but that is complicated to install. Another approach might be to spin up the regex searching in a process pool and let a whole new python interpreter run the regex and if it takes too long spit it out. An example of this failing would be ^(?:a+)+$/ and someone sending a message with about 100 a’s. This pattern will compile correctly according to your original checks but would cause the bot to hang attempting to replace the a’s on this pattern. For an example of the process pool in action you can see my ReTrigger cog. I have had marginal success using the regex library as well specifically not allowing this particular pattern to run at all and returning nothing.

  • Lines 122-131: Some bot owners might not appreciate the error message being so open even when they run it. It would be better to make the upload/traceback error optional for the owner asking them if they want it there specifically. The bot already has [p]traceback command which the owner can use to pull this information publicly if wanted.

commands

admin.py

  • Lines 199-258: There should be some sort of opt-in setting for this. Not all the users in the channels being searched necessarily want their messages being fed to ChatGPT and could be considered in violation of discord developer TOS and the expected privacy. Users should explicitly opt into sending their data to a third party API. I know this seems a little prohibitive but the nature of the AI space is still relatively new and a lot of people don’t understand its rammifications just yet. There are definitely going to be people who don’t want their likeness used to train AI and allowing a bot owner to blanket send this data to a third party without their consent is problematic. If this was a locally hosted AI language model handling it it could be fine but as per the discord developer agreement bots should not perform actions for users without their consent. That especially includes passing their data to a third party without their consent.

  • Line 55, 155, 199, 711: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Line 298 and 371: It is not advised to respond with the full exception when a command errors, you should instead provide a nicer error message to the user especially for a blanket exception catch like this and you don’t know what exceptions can be thrown. It might be better to raise your own Errors from the API responses which then you can define the error response. If you want you can add an owner check and provide the full response to the owner then although some owners might prefer any exceptions appear in their logs privately instead of being sent in discord public fo anyone who can see the channel to witness and give anyone else a generic response that it failed and maybe why it failed without providing useless traceback information to the user.

  • Lines 906, 926, 944, 964: This is inaccurate, the bot could not have permission to attach_files in the channel requested. You should check that prior to spending a bunch of time compressing the data and trying to send it again.

base.py

  • Line 41: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

autodocs

autodocs.py

  • Line 230: It seems unlikely but there should be a size check on this before sending or a try/except incase the generated file is larger than discord allows. You should also be checking that the bot actually has permission to attach_files here.

economytrack

__init__.py

  • Line 12: If discord.py backports a security fix and releases 1.7.4 this would fail. Consider changing it to discord.__version__ < "2.0.0". Though I would personally recommend checking Red’s version using
from redbot.core import version_info, VersionInfo

async def setup(bot):
    cog = ...
    if version_info >= VersionInfo.from_str("3.5.0"):
        await bot.add_cog(cog)
    else:
        bot.add_cog(cog)

Though you can also use the max_bot_version key in info.json to prevent users from upgrading to a version requiring the newer features as well.

commands.py

  • Line 89, 129, 157, 254: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Lines 157 and 254: You should also be checking that the bot has attach_files permission.

graph.py

  • Line 31: Realistically speaking .close() is only required on actual files read from disk so that other files can open and read them. Closing a buffer like this is not necessary since the garbage collector will automatically clean it up later. (Optional)

emojitracker

__init__.py

  • Line 14: See __init__.py for economytrack.

emojitracker.py

  • Line 63-67: I don’t feel like fetching the message for every single reaction event is necessary here since you’re only using the message object to detect whether or not the message has existed somewhere if you’re receiving this event it clearly does or did. This consumes unnecessary rate limits in the channel for the bot especially on larger guilds with lots of potential reactions on messages. Since this is just tracking I don’t think these checks are worthwhile and can be eliminated entirely since you have the message ID already. The same can be considered for channel above but it does not affect the bots potential performance. (Optional)

  • Line 116, 139, 180: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Line 125: Typo? \b is not a newline character. You might consider using from redbot.core.utils.chat_formatting import box and then box(blacklist, lang="py") instead. (Optional)


events

__init__.py

  • Line 12: See __init__.py for economytrack.

events.py

  • Line 32: See __init__.py for economytrack.

  • Line 141, 358, 799: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Line 339: You should be checking the bot has attach_files permission in the channel.

  • Line 353: I recommend using @commands.admin_or_permissions() here, not everyone who installs the bot knows to setup the admin role which would render every command unusable. At the very least I recommend setting a base required permission for the settings such as manage_messages to signify someone who is most likely a mod or admin and then further customize requirements on additional commands. (Optional)

utils.py

  • Line 13: See __init__.py for economytrack.

  • Line 70-72: Your typehint should be discord.Guild but also if you intend these to be used in embeds a simpler way is to return the actual discord.Asset instead of the url if it’s None. (Optional)


fluent

api.py

  • Line 27: Why is the bot even required to be passed into this? (Optional)

fluent.py

  • Line 61, 111, 148: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Lines 226 and 128: You should be checking that the bot has permission to embed_links in the destination channel when being setup and when actually executing.


guildlog

__init__.py

  • Line 12: See __init__.py for economytrack.

guildlog.py

  • Lines 76 and 125: You should also be checking that the bot has permission to embed links in the channel not just assume it always will be from the settings.

  • Lines 175-184: You should probably check if the destination channel is already setup and whether or not the bot has permission to emebd links in there. (Optional as long as the correct permission checks are applied elsewhere.)

  • Line 138, 209, 249: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().


hunting

__init__.py

  • Line 11: See __init__.py for economytrack.

hunting.py

  • Line 116: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Line 121: You should be using config.all_users() instead of this private method.


levelup

__init__.py

  • Line 22: See __init__.py for economytrack.

base.py

  • Line 38-46: See __init__.py for economytrack.

  • Line 258, 518, 562, 607, 778, 932, 1007, 1038, 1114, 1145: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Lines 225, 257, 716:, 912-920 You should be checking that the bot has attach_files permission.

  • Lines 460, 488: Or the bot could be missing attach_files permission.

levelup.py

  • Line 87: You might consider doing for gid in self.data.copy().keys() since data is unused. (Optional)

  • Line 216: See __init__.py for economytrack.

  • Line 494: Typehint should be discord.Guild. (Optional)

  • Line 677, 688, 699: These should have either prior checks for or try/except catches incase a role set is higher than the bots highest role or has been changed since originally setup.

  • Line 998: You can just set em.set_thumbnail(url=guild.icon) and d.py will handle the conversion to string for you. (Optional)

  • Line 1066: Again Admin perm is okay but there should be some more granularity here. Not everyone sets the bots admin and furthermore that requires the server owner to setup. Consider adding a base level permission here. (Optional)

  • Line 2382: This one I cannot leave like this. Role changes of any sort need to check that the user in question actually has permission to manage roles lest they become open to role hierarchy issues. These group commands inherit the @commands.admin() permission from lvl_group but that is not enough of a check. On add_level_role (line 2476) an Admin can use this to assign themselves or anyone a higher role than their own assuming the bot has a higher role than them which is possible. You should also be checking that the role in question is not higher than or equal to the bots highest role and the users highest role on top of the permissions checks as well as checking these when actually asigning the roles elsewhere. While these checks need to be in place for add_level_role, on init_roles, toggle_autoremove, and del_level_roles they’re not required as extensively but I highly recommend having at least @admin_or_permissions(manage_roles=True).

  • Lines 2411-2451: You might consider calculating the role changes first before adding/removing roles in this list to help reduce potential bugs where the settings might be configured incorrectly and this attempts to add roles that should be removed. (Optional)

  • Line 1073, 1335, 1625, 2186, 2385, 2669, 2712: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Lines 1372, 1382, and 2187: You should be checking that the bot has attach_files permission.


meow

__init__.py

  • Line 14: See __init__.py for economytrack.

nobot

__init__.py

  • Line 14: See __init__.py for economytrack.

  • Line 90, 110: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().


nonuke

__init__.py

  • Line 15: See __init__.py for economytrack.

listen.py

  • Line 72: See __init__.py for economytrack.

  • Lines 170, 180, and 199: These should be guarded against the bot not having permission to embed links and send messages to the log channel.

nonuke.py

  • Line 128: You should be checking here that the bot has permission to embed links and send messages in the log channel when being setup.
  • Line 212: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

pixl

pixl.py

  • Line 74, 153, 310, 436, 438, 446, 490, 584: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Line 153: You should be checking that the bot has attach_files permission.

utils.py

  • Line 136: You should probably save a reference to this task inside the class so that it can be reliably canceled when the current game is done. This way you’re not setting up an infinite listener that doesn’t do anything and are not relying on the garbage collector to clean this up which it may not do.

pupper

pupper.py

  • Line 56: This should be Optional[discord.Guild] typehint. (Optional)

  • Lines56-69: You should consider not having this iterate over all of the bots guild and instead load the cache differently. (Optional)


tickets

utils.py

  • Line 175: You should be confirming that the bot still has permission to attach_files and embed_links in the log channel.

views.py

  • Lines 514-527: Consider using kwarg formatting here so that translators know not to remove a {} because it doesn’t make sense to them and they know that that’s a formatted variable. (Optional)

commands

admin.py

  • Line 124: This should say the bot requires manage permissions enabled in the category. Manage roles is a guild permission not a channel permission.

  • Line 158: Consider using the discord.Message converter here it can simplify some logic as it will allow message links and channel_id-message_id pairs getting the exact message you require and removing the necessity of running the command in the channel required. (Optional)

  • Line 328: This is missing attach_files permission check on the channel since you can sometimes attempt to upload a file.


upgradechat

__init__.py

  • Line 14: See __init__.py for economytrack.

api.py

  • You had some good premise here but are missing the mark just a bit. This class can be better suited for actually building out the API methods and handling everything including a dedicated session that the class manages which can help with keepalive. Instead you’re using this class as a module of staticmethods. I am not going to tell you how to code but this could be so much more by instantiating a class that contains the tokens required for auth and then saving a session until the cog is unloaded and always ready to return data. You can even have the session setup to always contain the correct headers for all requests and generally make your life easier requesting things from the API. (Optional)

vrtutils

vrtutils.py

  • Line 149: Setting ctx.assume_yes = True here isn’t strictly necessary, it’s already set in [p]cog update when you pass reload=True to the command. It could also be problematic as you will want to avoid a user setting up a scheduling cog to automatically update and reload commands. I see that this isn’t currently prevented in core so I will be leaving this as optional. (Optional)

  • Line 261: You are aware that [p]pipinstall is already part of core, right? I can’t think of many other pip commands one would want for their bot. (Optional)

  • Lines 522-525: redbot.core.utils.chat_formatting.text_to_file exists for exactly this. (Optional)

  • Lines 522-525: You should be checking that the bot has permission to attach_files.

  • Line 569: you should consider using discord.utils.utcnow() to ensure you get a timezone aware datetime object or utilize datetime.datetime.now(datetime.timezone.utc). (Optional)


xtools

xtools.py

  • Line 232, 250, 403, 469, 531, 708, 856, 959: You should be checking that the bot has embed_links permission with @commands.bot_has_permissions(embed_links=True) to prevent errors running this command or alternatively provide a text only option in the body checking await ctx.embed_requested().

  • Lines 299-300: You should consider using red’s shared API token system for these particular credentials, though I understand not using them since this is a full OAuth requirement cog and you’ll want to store users credentials separately. (Optional)

  • Line 310: lol. This can potentially happen if another bot was scanning for tokens, or something and deleted the message before this bot had time to fully process it. (Optional)

  • Lines 922-926: While this will only happen if you run the command if you do run it on someone elses bot and the bot does not have attach_files permission it will error.


Thank you for the review Trusty! All of the changes you have listed have been handled including about 95% of the optional ones. Commit hash for these changes is 3e7faf26fb22463e15a815daac7e5524db5ab623

Thanks again for taking the time to dig through my repo :slight_smile:

Round 2. There’s a lot here that wasn’t present in my first review and some things that need to be addressed. Generally speaking we suggest avoiding use of globals() and global variables when we see them and that’s mainly what caught my eye here. Reading globals() is typically fine but writing to them we suggest avoiding. While investigating your usage of globals() in assistant I found some other problems.

While there’s no rules against making a cog accessible to other cogs there’s some things to be wary of. There’s plenty of tools at your disposal for getting references to other cogs via listeners, bot.get_cog, etc. Managing those should be done with care though. Storing a reference to a cog in your cog can cause some odd behavior such as, but not limited to below:

  • Your reference may not be up to date and return incorrect data or no data.
  • The original might not be loaded anymore also returning no data or erroring unexpectedly.

While you don’t use this reference for anything other than as a key in a dictionary, this also means that the garbage collector can’t clean it up properly when the cog is unloaded since it’s now referenced by your cog which now requires me to reload two cogs instead of just my cog.

There’s more discussion about this below. I am not particularly fond of the way you added third party support for assistant and I think it can be greatly improved in simplicity for both you and other cog creators.

Commit hash at time of review - 74f68cff613e0a12032e27b29b491bb1127199f5


assistant

info.json

  • install_msg should explain that other cogs installed may register additional functionality on this cog.

assistant.py

  • Line 296: You should not be storing a reference to the cog itself here. This would prevent garbage collection from cleaning up the cog after the user reloads it until this cog is unloaded. I recommend asking third party users to provide the cogname in the json schema instead which will then be used to get the current cog object instead. Especially since the cog is not a unique object. A cog may load and register itself with new commands and you will find that two instances of the cog in your registry. Thankfully everything in cog_unload is still called on the old instance when unloaded but everything it had saved in memory is still active and available through assistant.

commands

common

utils.py

  • Line 118: What is the purpose of storing an uninstanced class of Red in utils’ globals()? That object won’t have anything usable in it since it hasn’t been instantiated with the name of which bots config to load or anything usable from the actual bot instance.
  • Line 120: This methodology doesn’t respect the namespace of the packages. i.e. cogs can overwrite other cogs function here when this is entirely more complex than it needs to be. A better way might be to let a cog register a schema containing the cog name and the methods available to them rather than pointing to their function. This way when you want to call an external function you’re not messing with your module’s globals(), which is not recommended, and you can handle things more cleanly. For example:
    @commands.Cog.listener()
    async def on_assistant_cog_add(self, cog: commands.Cog):
        # This function could also be a string, and can be async OR sync
        schema = {
            "cogname": "Fluent",
            "name": "get_translation",
            "description": "Use this function to translate text",
            "parameters": {
                "type": "object",
                "properties": {
                    "message": {"type": "string", "description": "the text to translate"},
                    "to_language": {
                        "type": "string",
                        "description": "the target language to translate to",
                    },
                },
                "required": ["message", "to_language"],
            },
        }
        await cog.register_function(schema)

Then when assistant needs to find the result of the third party cog:

if conf.use_function_calls and extend_function_calls:
    function_calls.extend(self.db.get_function_calls(conf))
    function_map.update(self.db.get_function_map())
    for cog_functions in self.registry.values():
        for function_name, function in cog_functions.items():
            cog = self.bot.get_cog(function.jsonschema.cogname)
            if cog is None:
                continue
            func = getattr(cog, function_name, None)
            if func is None:
                continue
            function_calls.append(function.jsonschema)
            function_map[function_name] = func

This avoids overwriting functions in utils.py’s globals() and will get the currently listed method from the cog. This way that cog’s method isn’t still stored in memory when that cog is unloaded.

This way of handling registration also reduces code duplication. For example let’s say I already have a method that returns data in a way chatGPT might expect, I don’t want to have to duplicate the code when I can just tell assistant to call my existing function. If I want to provide a cleaner response it would be better for me to do something like:

async def get_translation_for_assistant(self, message: str, to_language: str):
    try:
        return await self.get_translation(message, to_language)
    except SomeException:
        return "There was an error getting a translation"

Letting me define a cleaner error message and not have to worry about anything like checking if my own cog is loaded when this function is present.

If it was done this way to pass attributes more easily that can still be done. You setup the ability to define the parameters, and self is automatically when called on an instance of the cog. If the method requires ctx you can pass that still too just have it defined in the schemas parameters and maybe special case it in assistant.

The only thing your method supports that my suggestion doesn’t is module level methods, so the method has to be part of the cog class (e.g. staticmethods). I don’t see any reason you should have to support module methods for this.


tickets

tickets.py

  • Lines 231-238: You might consider sending the CloseView with a custom ID based on the owner ID and some saved information about that particular button. That way you don’t need to mass edit messages every time you reload the cog and can simply add the view object to the bot when loading. This is the preferred way to handle views that are intended to persiste between restarts on the bot. It’s also worth mentioning that this persistent view should not continue to function if the cog is unloaded. The way this is setup the button will still do something if you unloaded the cog. (Optional)

Assistant

So for the Assistant cog storing other cog functions, the cog does not store the instance of the cog as a key, but rather the name of the cog itself as a string. cog = cog.qualified_name this appears to already be in the hash that you reviewed so i may not be understanding what you meant, but Ive ensured that unloaded cog functions don’t remain registered with the cog_remove listener. Additionally i have added a bit in the cleanup method to remove any cogs that have been unloaded that somehow weren’t caught in the cog_remove listener.

Info.json

  • install_msg has been updated

assistant.py

  • cog name is used instead of reference as mentioned above

utils.py

  • Line 118: removed instance of Red from globals, it was put there to allow using as a type hint in string functions but that’s not needed
  • Line 120: logic for checking if a function name exists is elsewhere in the code. additionally, this method is now only used for bot-owner added custom string functions, cog registered functions don’t touch this method anymore. String functions added by the bot owner however will still need to use this unless you know of a better way.
  • Schema: the schema provided is used by the API and not the cog, and there are checks to make sure a cog hasn’t already added a function with that name.
  • I assume you saw Fluent’s registration of the function, i have fixed that since the cog doesnt need to check if itself is loaded. that bit was accidentally still there for when it was a custom string function

Schema change proposal for custom functions

I’ve added more robust checking to make sure the cog is loaded before mapping the function for the api call, however I don’t believe passing the function object itself is “bad” if the cleanup process is sufficient, and implementing the change you proposed would be a breaking change, so i’ve opted to keep the ability to pass functions in the code for now.

Tickets

  • The CloseView is now being added as a persistent view instead of re-editing the message each time

Let me know if there’s anything I missed

I’ve split the terminology a bit to better describe

  • Custom Functions added by the bot owner via the [p]customfunc menu with a string
  • Registry Functions added by cogs, bot owner cant edit but can toggle/view
    Both types are viewable in the [p]customfunc menu but it will say if its managed by a 3rd party cog

The register function now looks like this

async def register_function(self, cog_name: str, schema: dict) -> bool:
    ...

likewise with the rest of them.

The self.registry is now just Dict[str, Dict[str, dict]]
or
{cog_name: {function_name: {function_json_schema}}}

Example implementation

@commands.Cog.listener()
async def on_assistant_cog_add(self, cog: commands.Cog):
    """Registers a command with Assistant enabling it to access translations"""
    schema = {
        "name": "get_translation",
        "description": "Translate text to another language",
        "parameters": {
            "type": "object",
            "properties": {
                "message": {"type": "string", "description": "the text to translate"},
                "to_language": {
                    "type": "string",
                    "description": "the target language to translate to",
                },
            },
            "required": ["message", "to_language"],
        },
    }
    await cog.register_function(cog_name="Fluent", schema=schema)

async def get_translation(self, message: str, to_language: str, *args, **kwargs) -> str:
    lang = await self.converter(to_language)
    if not lang:
        return "Invalid target language"
    try:
        translation = await self.translate(message, lang)
        return f"{translation.text}\n({translation.src} -> {lang})"
    except Exception as e:
        return f"Error: {e}"

The cog instance and function are fetched each time now to ensure nothing is stale.

Documentation has also been updated: vrt-cogs/assistant/example-funcs at main · vertyco/vrt-cogs · GitHub

Hi Vertyco, thank you for making those changes. I feel a lot better knowing that there’s no risk of assistant holding another cog’s methods hostage because of some race condition. I hope at the very least you’ve learned a lot through this process. Marking this as approved!

1 Like