2323"""
2424
2525import argparse
26+ import asyncio
2627import getpass
2728import os
2829import pathlib
5455parser .add_argument ("--version" , action = "store_true" , help = "Get version and debug information for TwitchIO." )
5556
5657# TODO: Only uncomment for testing, until complete...
57- # new_bot = parser.add_argument_group("Create Bot", "Create and generate bot boilerplate via an interactive walkthrough.")
58- # new_bot.add_argument("--create-new", action="store_true", help="Start an interactive walkthrough.")
58+ new_bot = parser .add_argument_group ("Create Bot" , "Create and generate bot boilerplate via an interactive walkthrough." )
59+ new_bot .add_argument ("--create-new" , action = "store_true" , help = "Start an interactive walkthrough." )
5960
6061args = parser .parse_args ()
6162
6263
63- COMPONENT = """from typing import TYPE_CHECKING
64+ COMPONENT = """from __future__ import annotations
65+
66+ from typing import TYPE_CHECKING
6467
65- import twitchio
6668from twitchio.ext import commands
6769
6870
@@ -88,7 +90,96 @@ async def teardown(bot: Bot) -> None: ...
8890
8991"""
9092
91- MAIN = """"""
93+ MAIN = """import asyncio
94+ import logging
95+ import tomllib
96+
97+ from bot import Bot
98+
99+ import twitchio
100+
101+
102+ LOGGER: logging.Logger = logging.getLogger(__name__)
103+
104+
105+ def main() -> None:
106+ twitchio.utils.setup_logging(level=logging.INFO)
107+
108+ with open("config.toml", "rb") as fp:
109+ config = tomllib.load(fp)
110+
111+ async def runner() -> None:
112+ async with Bot(**config["bot"]) as bot:
113+ await bot.start()
114+
115+ try:
116+ asyncio.run(runner())
117+ except KeyboardInterrupt:
118+ LOGGER.warning("Shutting down due to Keyboard Interrupt.")
119+
120+
121+ if __name__ == "__main__":
122+ main()
123+
124+ """
125+
126+ BOT = """from __future__ import annotations
127+
128+ import json
129+ import logging
130+ from typing import TYPE_CHECKING, Any
131+
132+ from twitchio import eventsub
133+ from twitchio.ext import commands
134+
135+
136+ if TYPE_CHECKING:
137+ from twitchio.authentication import UserTokenPayload
138+
139+
140+ LOGGER: logging.Logger = logging.getLogger("Bot")
141+
142+
143+ class Bot(commands.AutoBot):
144+ def __init__(self, *args: Any, **kwargs: Any) -> None:
145+ super().__init__(*args, **kwargs)
146+
147+ async def setup_hook(self) -> None:
148+ subs: list[eventsub.SubscriptionPayload] = []
149+
150+ with open(".tio.tokens.json", "rb") as fp:
151+ tokens = json.load(fp)
152+
153+ for user_id in tokens:
154+ subs.extend(self.generate_subs(user_id))
155+
156+ if subs:
157+ await self.multi_subscribe(subs)
158+
159+ {COMP}
160+
161+ async def event_ready(self) -> None:
162+ LOGGER.info("Logged in as: %s. Owner: %s", self.user, self.owner)
163+
164+ def generate_subs(self, user_id: str) -> tuple[eventsub.SubscriptionPayload, ...]:
165+ # Add the required eventsub subscriptions for each user...
166+ assert self.user
167+
168+ return (eventsub.ChatMessageSubscription(broadcaster_user_id=user_id, user_id=self.user.id),)
169+
170+ async def event_oauth_authorized(self, payload: UserTokenPayload) -> None:
171+ await self.add_token(payload.access_token, payload.refresh_token)
172+
173+ if not payload.user_id:
174+ return
175+
176+ if payload.user_id == self.bot_id:
177+ # Don't subscribe to events for the bot user
178+ return
179+
180+ await self.multi_subscribe(self.generate_subs(payload.user_id))
181+
182+ """
92183
93184BOOLS = {
94185 "y" : True ,
@@ -210,7 +301,7 @@ def generate_venv() -> None:
210301 install_packages (exe , starlette )
211302
212303
213- def generate_bot () -> ...:
304+ async def generate_bot () -> ...:
214305 name = validate_input ("Project name? (Leave blank to generate files in this directory): " )
215306 if name :
216307 _dir = pathlib .Path (name )
@@ -230,67 +321,83 @@ def generate_bot() -> ...:
230321 if resp :
231322 generate_venv ()
232323
233- components = bool_check (validate_input ("Would you like to setup commands.Components? (y/N): " , bool_validate ))
324+ components = bool_check (validate_input ("\n Would you like to setup commands.Components? (y/N): " , bool_validate ))
234325 if components :
235326 comp_dir = pathlib .Path ("components" )
236327 comp_dir .mkdir (exist_ok = True )
237328
238329 with open (comp_dir / "general.py" , "w" ) as fp :
239330 fp .write (COMPONENT )
240331
241- client_id = None
242- client_sec = None
243- config = bool_check ( validate_input ("Would you like to create a config? (y/N) : " , bool_validate ) )
332+ while True :
333+ client_id = validate_input ( "Please enter your Client-ID: " )
334+ cid_reenter = validate_input ("Please re-enter your Client-ID : " )
244335
245- if config :
246- while True :
247- client_id = validate_input ("Please enter your Client-ID: " )
248- cid_reenter = validate_input ("Please re-enter your Client-ID: " )
336+ if client_id != cid_reenter :
337+ print ("Client-ID does not match, please try again..." , end = "\n \n " )
338+ continue
249339
250- if client_id != cid_reenter :
251- print ("Client-ID does not match, please try again..." , end = "\n \n " )
252- continue
340+ break
253341
254- break
342+ while True :
343+ client_sec = getpass .getpass ("Please enter your Client-Secret: " )
344+ csec_reenter = getpass .getpass ("Please re-enter your Client-Secret: " )
255345
256- while True :
257- client_sec = getpass . getpass ( "Please enter your Client-Secret: " )
258- csec_reenter = getpass . getpass ( "Please re-enter your Client-Secret: " )
346+ if client_sec != csec_reenter :
347+ print ( " Client-Secret does not match, please try again..." , end = " \n \n " )
348+ continue
259349
260- if client_sec != csec_reenter :
261- print ("Client-Secret does not match, please try again..." , end = "\n \n " )
262- continue
350+ break
263351
264- break
352+ while True :
353+ owner_name = validate_input ("Please enter the Twitch username of the owner of this Bot (E.g. chillymosh): " )
354+ bot_name = validate_input ("Please enter the Twitch username of the Bot Account (E.g. chillybot): " )
355+ names = f"Owner Name: '{ owner_name } '\n Bot Name: '{ bot_name } '"
265356
266- config_data = f"""[secrets]\n client_id = \" { client_id } \" \n client_secret = \" { client_sec } \" """
267- with open ("config.toml" , "w" ) as fp :
268- fp .write (config_data )
357+ correct = bool_check (validate_input (f"Is this information correct? (y/N)\n \n { names } \n " , bool_validate ))
358+ if not correct :
359+ continue
360+
361+ break
269362
270- if client_id and client_sec :
271- while True :
272- owner_name = validate_input ("Please enter the Twitch username of the owner of this Bot (E.g. chillymosh): " )
273- bot_name = validate_input ("Please enter the Twitch username of the Bot Account (E.g. chillybot): " )
274- names = f"Owner Name: '{ owner_name } '\n Bot Name: '{ bot_name } '"
363+ import twitchio
275364
276- correct = bool_check ( validate_input ( f"Is this information correct? (y/N) \n \n { names } \n " , bool_validate ) )
277- if not correct :
278- continue
365+ client = twitchio . Client ( client_id = client_id , client_secret = client_sec )
366+ async with client :
367+ await client . login ()
279368
280- break
369+ owner = bot = None
370+ try :
371+ owner , bot = await client .fetch_users (logins = [owner_name , bot_name ])
372+ except twitchio .HTTPException :
373+ print (
374+ "Error fetching IDs of provided users. Please do this manually and enter them into the generated config.toml"
375+ )
376+
377+ prefixr = validate_input ("Please enter a command prefix for the Bot (Leave blank for '!'): " )
378+ prefix = prefixr or "!"
379+
380+ config_data = (
381+ f'[bot]\n client_id = "{ client_id } "\n client_secret = "{ client_sec } "\n '
382+ f'owner_id = "{ owner .id if owner else "" } "\n bot_id = "{ bot .id if bot else "" } "\n '
383+ f'prefix = "{ prefix } "'
384+ )
385+
386+ with open ("config.toml" , "w" ) as fp :
387+ fp .write (config_data )
388+
389+ with open ("main.py" , "w" ) as fp :
390+ fp .write (MAIN )
281391
282- # TODO: .env
283- # TODO: client details
284- # TODO: fetch owner/bot IDs
285- # with open(dir / "main.py", "w") as fp:
286- # ...
392+ with open ("bot.py" , "w" ) as fp :
393+ comp = 'await self.load_module("components.general")' if components else ""
394+ fp .write (BOT .format (COMP = comp ))
287395
288- # with open(dir / "bot.py", "w") as fp:
289- # ...
396+ print ("\n \n Successfully created Bot boilerplate with the provided details!" )
290397
291398
292399if args .version :
293400 version_info ()
294401
295402elif args .create_new :
296- generate_bot ()
403+ asyncio . run ( generate_bot () )
0 commit comments