PythonistaGuild / TwitchIO

An Async Bot/API wrapper for Twitch made in Python.
https://twitchio.dev
MIT License
785 stars 163 forks source link

Make twitch command methods not requiring a manual user_id and token input #397

Closed Gouvernathor closed 1 year ago

Gouvernathor commented 1 year ago

It would be handy if there were methods for native twitch commands (such as /announce) at the same location as send (so, in Contexts for example), and which would not require being passed the token and user_id a second time. Announcement for example (however you want to call the method, like Helix says or rather like the command) would take message and the optional color, and that would be it.

github-actions[bot] commented 1 year ago

Hello! Thanks for the issue. If this is a general help question, for a faster response consider joining the official Discord Server

Else if you have an issue with the library please wait for someone to help you here.

IAmTomahawkx commented 1 year ago

Making API calls requires a PartialUser object. This is due to the underlying state being different for api calls vs irc calls. As such, we include a .user function on PartialChatter which will fetch the User. If you wish to save on API calls, use Client.create_user to get a PartialUser. On Context, send is a special case because Context is a Messagable. We do not provide API state on the context object.

Gouvernathor commented 1 year ago

So, if I'm following correctly : the Context and Bot(/Client) classes provide IRC features, but not API ? And you need to create a User to target it with an API call ?

It would seem more easy to use, to me, that the class you use the method on is the one who sends the API thing, rather than the one receiving it. To send a mail, I use a letter and provide it with the address of the target house, I don't use the house and sign my identity at the bottom, if that makes sense. I would see it more user-friendly if the self, the Bot, had a send and a announcement methods (having whatever names), which you would pass a target (a channel, a user... a messageable I guess, sort of) and things specific to the method : a message, the optional color for announcements, a user for a ban... And it would use the credentials passed to the Bot in the first place, even though as I understand it the credentials management would not be the same in the two cases under the hood.

chillymosh commented 1 year ago

Context is used solely for chat and command usage, as per similar (huge) async OOP and stateful libraries. From your comments I assume you are new to these style of libraries and design.

The Client and therefore Bot class, by inheritance, to do have API access. There are Client level API calls and then there are User specific API calls which are made via a Partial / User object. Ultimately keeping all functions that involve a User specifically makes more sense being a method of said User object, as this is how OOP is intended. I don't see how doing Bot.chat_announcement(target, token, mod_id, colour, msg) is any better than User.chat_announcement(token, mod_id, colour) The User object already contains the target ID and the announcement method is being sent to said User. It also allows you to follow up with other methods much easier and responses.

It sounds like you would prefer to use as raw to the spec lib as possible.

Gouvernathor commented 1 year ago

It wouldn't be Bot.chat_announcement(target, token, mod_id, colour, msg) though, since token and mod_id would be managed by the Bot internally. That's the point, since they can't be in the User class anyway.

It would be Bot.chat_announcement(target, msg [, color]), which is simpler than User.chat_announcement(token, mod_id, msg [, color]) (you forgot the msg). The Bot is already there as self, as opposed to the token and mod_id you have to seek somewhere else in your data, and the User is still required and only changes place.

It seems to me that the Bot can manage token, since we pass it when creating the Bot and it takes a single one. As per the mod_id, I tried using self.user_id and it worked 🤷

Also, you're saying that there are two kinds of API calls, Client ones and User ones. That may be true under the hood, but I don't think it should necessarily be visible to the user. If I'm using a Client anyway (and I don't think you can use twitchio without using a Bot object, can you ?), then I don't care what category the API call I'm using belongs to.

chillymosh commented 1 year ago

That solution would not work for instances where the bot is VIP instead of mod and you pass the command invoker's ID as the mod id instead. Not everyone wants a bot to be a moderator in their channel.

Also rate limiting is on a per token basis, so if you are in a huge amount of channels then allowing users / devs to use the invoker, broadcaster token to not hit API rate limits.

If you feel this strongly then you could always start your own lib with your own design or use an alternative solution.

Gouvernathor commented 1 year ago

I think I can simply do it as a Bot subclass on my end, but the reason it could work badly interests me. If my Bot is VIP and not mod, why would the User method work and the Bot method would not ? The bot would receive the command/trigger, and order the human broadcaster's user account (or another account) to send the announcement ?

Proof of concept :

class Bot(commands.Bot):
    def __init__(self, token, *args, **kwargs):
        super().__init__(token, *args, **kwargs)
        self._token = token

    async def chat_announcement(self, target, message, color="primary"):
        return await target.chat_announcement(self._token, self.user_id, message, color)

Also I'm not saying it should replace the User methods, or that they should be removed.

IAmTomahawkx commented 1 year ago

You seem to have the concept that the api side revolves around a single token. That is an IRC only thing, there is no singular token for the API. As such, this design is not possible.