EmperorFred/twitch_bot/bot.py
2025-12-05 20:21:47 -06:00

108 lines
4.6 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import importlib
import os
from typing import Optional, Type, List
from data.models.TwitchComponent import TwitchComponent
from twitchio import eventsub
from twitchio.ext import commands as twitch_commands
import twitch_bot.config as cfg
from libs.Db import Db
class TwitchBot(twitch_commands.Bot):
# def __init__(self, restart_controller=None, tokens: Optional[Dict[str, Dict[str, str]]] = None, db: Optional[Db] = None):
def __init__(self, restart_controller=None, db: Optional[Db] = None):
super().__init__(
client_id=cfg.TWITCH_CLIENT_ID,
client_secret=cfg.TWITCH_CLIENT_SECRET,
bot_id=cfg.TWITCH_BOT_ID,
owner_id=cfg.TWITCH_OWNER_ID,
prefix=cfg.TWITCH_COMMAND_PREFIX
)
# Store channel from config for reference/logging. TwitchIO v3 does not expose join_channels.
self._initial_channel: Optional[str] = cfg.TWITCH_CHANNEL
# Expose restart controller for commands (e.g., !restart)
self.restart_controller = restart_controller
self.db = db or Db(cfg.DB_CONN_STR, False)
async def setup_hook(self) -> None:
# Prefer explicit IDs from config; these should be numeric strings from Twitch.
broadcaster_id = cfg.TWITCH_OWNER_ID
bot_id = cfg.TWITCH_BOT_ID
# If broadcaster_id is missing, we cannot subscribe. Ask the user to provide it.
if not broadcaster_id:
print("⚠️ TWITCH_OWNER_ID is not set. Please set it to your broadcaster (channel) numeric user ID.")
if cfg.TWITCH_CHANNEL:
print(
" TWITCH_CHANNEL is set to '", cfg.TWITCH_CHANNEL,
"'. Provide TWITCH_OWNER_ID for that channel to enable chat subscription.",
)
return
if not bot_id:
print("⚠️ TWITCH_BOT_ID is not set. Please set it to the numeric user ID of the bot account.")
return
try:
payload = eventsub.ChatMessageSubscription(
broadcaster_user_id=broadcaster_id,
user_id=bot_id,
)
await self.subscribe_websocket(payload=payload)
channel_hint = cfg.TWITCH_CHANNEL or "<unknown>"
print(f"✅ Subscribed to EventSub ChatMessage for broadcaster {broadcaster_id} (channel hint: {channel_hint})")
except Exception as e:
print(f"❌ Failed to subscribe to EventSub ChatMessage: {e}")
async def load_commands(self) -> None:
components: List[str] = []
package_name = "twitch_bot.commands"
for filename in os.listdir(f"./{package_name.replace('.', '/')}"):
if filename.endswith(".py") and not filename.startswith("_"):
components.append(f"{package_name}.{filename[:-3]}")
for cmp in components:
try:
module = importlib.import_module(cmp)
except Exception as e:
print(f"⚠️ Failed to import {cmp}: {e}")
continue
# Find a component class to load
component_cls: Optional[Type[twitch_commands.Component]] = None
for attr in module.__dict__.values():
if isinstance(attr, type) and issubclass(attr, twitch_commands.Component):
component_cls = attr
break
if component_cls is None:
print(f"⚠️ No Component found in {cmp}; skipping.")
continue
component_record = self.db.session.query(TwitchComponent).filter_by(name=cmp).first()
if not component_record:
self.db.session.add(TwitchComponent(name=cmp))
self.db.session.commit()
component_record = self.db.session.query(TwitchComponent).filter_by(name=cmp).first()
if component_record.active:
instance = None
try:
instance = component_cls(self) # type: ignore[call-arg]
except Exception as e:
print(f"⚠️ Failed to instantiate {component_cls.__name__} in {cmp}: {e}")
continue
try:
await self.add_component(instance) # type: ignore[arg-type]
print(f"✅ Loaded Twitch component from {cmp}: {component_cls.__name__}")
except Exception as e:
print(f"⚠️ Failed to add component from {cmp}: {e}")
else:
print(f"⏭️ Component inactive in DB, skipping: {cmp}")
print(f"bot commands:", self.commands)