3939 from models .eventsub_ import ChatMessage
4040 from types_ .options import Prefix_T
4141
42+ from twitchio .eventsub .subscriptions import SubscriptionPayload
43+ from twitchio .types_ .eventsub import SubscriptionResponse
4244 from twitchio .types_ .options import ClientOptions
45+ from twitchio .user import PartialUser
4346
4447 from .components import Component
4548
4851
4952
5053class Bot (Mixin [None ], Client ):
54+ """The TwitchIO ``commands.Bot`` class.
55+
56+ The Bot is an extension of and inherits from :class:`twitchio.Client` and comes with additonal powerful features for
57+ creating and managing bots on Twitch.
58+
59+ Unlike :class:`twitchio.Client`, the :class:`~.Bot` class allows you to easily make use of built-in the commands ext.
60+
61+ The easiest way of creating and using a bot is via subclassing, some examples are provided below.
62+
63+ .. note::
64+
65+ Any examples contained in this class which use ``twitchio.Client`` can be changed to ``commands.Bot``.
66+
67+
68+ Parameters
69+ ----------
70+ client_id: str
71+ The client ID of the application you registered on the Twitch Developer Portal.
72+ client_secret: str
73+ The client secret of the application you registered on the Twitch Developer Portal.
74+ This must be associated with the same ``client_id``.
75+ bot_id: str
76+ The User ID associated with the Bot Account.
77+ Unlike on :class:`~twitchio.Client` this is a required argument on :class:`~.Bot`.
78+ owner_id: str | None
79+ An optional ``str`` which is the User ID associated with the owner of this bot. This should be set to your own user
80+ accounts ID, but is not required. Defaults to ``None``.
81+ prefix: str | Iterabale[str] | Coroutine[Any, Any, str | Iterable[str]]
82+ The prefix(es) to listen to, to determine whether a message should be treated as a possible command.
83+
84+ This can be a ``str``, an iterable of ``str`` or a coroutine which returns either.
85+
86+ This is a required argument, common prefixes include: ``"!"`` or ``"?"``.
87+
88+ Example
89+ -------
90+
91+ .. code:: python3
92+
93+ import asyncio
94+ import logging
95+
96+ import twitchio
97+ from twitchio import eventsub
98+ from twitchio.ext import commands
99+
100+ LOGGER: logging.Logger = logging.getLogger("Bot")
101+
102+ class Bot(commands.Bot):
103+
104+ def __init__(self) -> None:
105+ super().__init__(client_id="...", client_secret="...", bot_id="...", owner_id="...", prefix="!")
106+
107+ # Do some async setup, as an example we will load a component and subscribe to some events...
108+ # Passing the bot to the component is completely optional...
109+ async def setup_hook(self) -> None:
110+
111+ # Listen for messages on our channel...
112+ # You need appropriate scopes, see the docs on authenticating for more info...
113+ payload = eventsub.ChatMessageSubscription(broadcaster_user_id=self.owner_id, user_id=self.bot_id)
114+ await self.subscribe_websocket(payload=payload)
115+
116+ await self.add_component(SimpleCommands(self))
117+ LOGGER.info("Finished setup hook!")
118+
119+ class SimpleCommands(commands.Component):
120+
121+ def __init__(self, bot: Bot) -> None:
122+ self.bot = bot
123+
124+ @commands.command()
125+ async def hi(self, ctx: commands.Context) -> None:
126+ '''Command which sends you a hello.'''
127+ await ctx.reply(f"Hello {ctx.chatter}!")
128+
129+ @commands.command()
130+ async def say(self, ctx: commands.Context, *, message: str) -> None:
131+ '''Command which repeats what you say: !say I am an apple...'''
132+ await ctx.send(message)
133+
134+ def main() -> None:
135+ # Setup logging, this is optional, however a nice to have...
136+ twitchio.utils.setup_logging(level=logging.INFO)
137+
138+ async def runner() -> None:
139+ async with Bot() as bot:
140+ await bot.start()
141+
142+ try:
143+ asyncio.run(runner())
144+ except KeyboardInterrupt:
145+ LOGGER.warning("Shutting down due to Keyboard Interrupt...")
146+
147+ main()
148+ """
149+
51150 def __init__ (
52151 self ,
53152 * ,
@@ -74,7 +173,7 @@ def __init__(
74173 def bot_id (self ) -> str :
75174 """Property returning the ID of the bot.
76175
77- This **MUST** be set via the keyword argument ``bot_id="..."`` in the constructor of this class.
176+ You must ensure you set this via the keyword argument ``bot_id="..."`` in the constructor of this class.
78177
79178 Returns
80179 -------
@@ -199,7 +298,7 @@ async def invoke(self, ctx: Context) -> None:
199298 payload = CommandErrorPayload (context = ctx , exception = e )
200299 self .dispatch ("command_error" , payload = payload )
201300
202- async def event_channel_chat_message (self , payload : ChatMessage ) -> None :
301+ async def event_message (self , payload : ChatMessage ) -> None :
203302 if payload .chatter .id == self .bot_id :
204303 return
205304
@@ -286,4 +385,67 @@ async def after_invoke(self, ctx: Context) -> None:
286385 The context associated with command invocation, after being passed through the command.
287386 """
288387
289- async def guard (self , ctx : Context ) -> None : ...
388+ async def global_guard (self , ctx : Context , / ) -> bool :
389+ """|coro|
390+
391+ A global guard applied to all commmands added to the bot.
392+
393+ This coroutine function should take in one parameter :class:`~.commands.Context` the context surrounding
394+ command invocation, and return a bool indicating whether a command should be allowed to run.
395+
396+ If this function returns ``False``, the chatter will not be able to invoke the command and an error will be
397+ raised. If this function returns ``True`` the chatter will be able to invoke the command,
398+ assuming all the other guards also pass their predicate checks.
399+
400+ See: :func:`~.commands.guard` for more information on guards, what they do and how to use them.
401+
402+ .. note::
403+
404+ This is the first guard to run, and is applied to every command.
405+
406+ .. important::
407+
408+ Unlike command specific guards or :meth:`.commands.Component.guard`, this function must
409+ be always be a coroutine.
410+
411+
412+ This coroutine is intended to be overriden when needed and by default always returns ``True``.
413+
414+ Parameters
415+ ----------
416+ ctx: commands.Context
417+ The context associated with command invocation.
418+
419+ Raises
420+ ------
421+ GuardFailure
422+ The guard predicate returned ``False`` and prevented the chatter from using the command.
423+ """
424+ return True
425+
426+ async def subscribe_webhook (
427+ self ,
428+ * ,
429+ payload : SubscriptionPayload ,
430+ as_bot : bool = True ,
431+ token_for : str | PartialUser | None ,
432+ callback_url : str | None = None ,
433+ eventsub_secret : str | None = None ,
434+ ) -> SubscriptionResponse | None :
435+ return await super ().subscribe_webhook (
436+ payload = payload ,
437+ as_bot = as_bot ,
438+ token_for = token_for ,
439+ callback_url = callback_url ,
440+ eventsub_secret = eventsub_secret ,
441+ )
442+
443+ async def subscribe_websocket (
444+ self ,
445+ * ,
446+ payload : SubscriptionPayload ,
447+ as_bot : bool = True ,
448+ token_for : str | PartialUser | None = None ,
449+ socket_id : str | None = None ,
450+ ) -> SubscriptionResponse | None :
451+ return await super ().subscribe_websocket (payload = payload , as_bot = as_bot , token_for = token_for , socket_id = socket_id )
0 commit comments