diff --git a/bot_discord.py b/bot_discord.py new file mode 100644 index 0000000..0bdb601 --- /dev/null +++ b/bot_discord.py @@ -0,0 +1,38 @@ +# bot_discord.py +import discord +from discord.ext import commands +import importlib +import cmd_discord + +class DiscordBot(commands.Bot): + def __init__(self, config, logger): + super().__init__(command_prefix="!", intents=discord.Intents.all()) + self.config = config + self.logger = logger + self.load_commands() + + def load_commands(self): + """ + Load all commands dynamically from cmd_discord.py. + """ + try: + importlib.reload(cmd_discord) + cmd_discord.setup(self) + self.logger.info("Discord commands loaded successfully.") + except Exception as e: + self.logger.error(f"Error loading Discord commands: {e}") + + async def on_ready(self): + self.logger.info(f"Discord bot is online as {self.user}") + +# @commands.command() +# async def reload(self, ctx): +# """ Reload all Discord commands dynamically. """ +# self.load_commands() +# await ctx.send("Commands reloaded!") + + async def run(self, token): + try: + await super().start(token) + except Exception as e: + self.logger.error(f"Discord bot error: {e}") diff --git a/bot_twitch.py b/bot_twitch.py new file mode 100644 index 0000000..8b06a51 --- /dev/null +++ b/bot_twitch.py @@ -0,0 +1,115 @@ +import os +import requests +import logging +import asyncio +from twitchio.ext import commands +import importlib +import cmd_twitch + +class TwitchBot(commands.Bot): + def __init__(self, config, logger): + self.client_id = os.getenv("TWITCH_CLIENT_ID") + self.client_secret = os.getenv("TWITCH_CLIENT_SECRET") + self.token = os.getenv("TWITCH_BOT_TOKEN") + self.refresh_token = os.getenv("TWITCH_REFRESH_TOKEN") + self.logger = logger + self.config = config + + # 1) Initialize the parent Bot FIRST + super().__init__( + token=self.token, + prefix="!", + initial_channels=config["twitch_channels"] + ) + + # 2) Then load commands + self.load_commands() + + async def refresh_access_token(self): + """ + Refreshes the Twitch access token using the stored refresh token. + Retries up to 2 times before logging a fatal error. + """ + self.logger.info("Attempting to refresh Twitch token...") + + url = "https://id.twitch.tv/oauth2/token" + params = { + "client_id": self.client_id, + "client_secret": self.client_secret, + "refresh_token": self.refresh_token, + "grant_type": "refresh_token" + } + + for attempt in range(3): # Attempt up to 3 times + try: + response = requests.post(url, params=params) + data = response.json() + + if "access_token" in data: + self.token = data["access_token"] + self.refresh_token = data.get("refresh_token", self.refresh_token) + + os.environ["TWITCH_BOT_TOKEN"] = self.token + os.environ["TWITCH_REFRESH_TOKEN"] = self.refresh_token + self.update_env_file() + + self.logger.info("Twitch token refreshed successfully.") + return # Success, exit function + else: + self.logger.warning(f"Twitch token refresh failed (Attempt {attempt+1}/3): {data}") + + except Exception as e: + self.logger.error(f"Twitch token refresh error (Attempt {attempt+1}/3): {e}") + + await asyncio.sleep(10) # Wait before retrying + + # If all attempts fail, log error + from bots import log_error + log_error("Twitch token refresh failed after 3 attempts.") + + def update_env_file(self): + """ + Updates the .env file with the new Twitch token. + """ + try: + with open(".env", "r") as file: + lines = file.readlines() + + with open(".env", "w") as file: + for line in lines: + if line.startswith("TWITCH_BOT_TOKEN="): + file.write(f"TWITCH_BOT_TOKEN={self.token}\n") + elif line.startswith("TWITCH_REFRESH_TOKEN="): + file.write(f"TWITCH_REFRESH_TOKEN={self.refresh_token}\n") + else: + file.write(line) + + self.logger.info("Updated .env file with new Twitch token.") + + except Exception as e: + self.logger.error(f"Failed to update .env file: {e}") + + def load_commands(self): + """ + Load all commands dynamically from cmd_twitch.py. + """ + try: + importlib.reload(cmd_twitch) + cmd_twitch.setup(self) + self.logger.info("Twitch commands loaded successfully.") + except Exception as e: + self.logger.error(f"Error loading Twitch commands: {e}") + + async def run(self): + """ + Run the Twitch bot, refreshing tokens if needed. + """ + try: + await self.connect() # Connect to Twitch + await self.start() # Start the bot event loop + while True: + await self.refresh_access_token() + await asyncio.sleep(10800) # 3 hours + except Exception as e: + from bots import log_error + log_error(f"Twitch bot failed to start: {e}") diff --git a/cmd_discord.py b/cmd_discord.py new file mode 100644 index 0000000..5560362 --- /dev/null +++ b/cmd_discord.py @@ -0,0 +1,34 @@ +# cmd_discord.py +from discord.ext import commands + +def setup(bot): + @bot.command() + async def greet(ctx): + from cmd_common.common_commands import greet + result = greet(ctx.author.display_name, "Discord") + await ctx.send(result) + + @bot.command() + async def ping(ctx): + from cmd_common.common_commands import ping + result = ping() + await ctx.send(result) + + @bot.command() + async def howl(ctx): + """Calls the shared !howl logic.""" + from cmd_common.common_commands import generate_howl_message + result = generate_howl_message(ctx.author.display_name) + await ctx.send(result) + + @bot.command() + async def reload(ctx): + """ Dynamically reloads Discord commands. """ + try: + import cmd_discord + import importlib + importlib.reload(cmd_discord) + cmd_discord.setup(bot) + await ctx.send("Commands reloaded!") + except Exception as e: + await ctx.send(f"Error reloading commands: {e}") diff --git a/cmd_twitch.py b/cmd_twitch.py new file mode 100644 index 0000000..e0daaf9 --- /dev/null +++ b/cmd_twitch.py @@ -0,0 +1,21 @@ +# cmd_twitch.py +from twitchio.ext import commands + +def setup(bot): + @bot.command(name="greet") + async def greet(ctx): + from cmd_common.common_commands import greet + result = greet(ctx.author.display_name, "Twitch") + await ctx.send(result) + + @bot.command(name="ping") + async def ping(ctx): + from cmd_common.common_commands import ping + result = ping() + await ctx.send(result) + + @bot.command(name="howl") + async def howl_command(ctx): + from cmd_common.common_commands import generate_howl_message + result = generate_howl_message(ctx.author.display_name) + await ctx.send(result) diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..688004a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,4 @@ +discord.py +twitchio +python-dotenv +PyMySQL