diff --git a/bot_twitch.py b/bot_twitch.py index 3202f97..7b9054d 100644 --- a/bot_twitch.py +++ b/bot_twitch.py @@ -202,7 +202,12 @@ class TwitchBot(commands.Bot): async def run(self): """ Run the Twitch bot, refreshing tokens if needed. - """ + """ + # modules.utility.list_channels(self) + + # kami_status = "OokamiKunTV is currently LIVE" if await modules.utility.is_channel_live(self) else "OokamikunTV is currently not streaming" + # globals.log(kami_status) + retries = 0 while True: if retries > 3: diff --git a/bots.py b/bots.py index 71a4979..17a54c7 100644 --- a/bots.py +++ b/bots.py @@ -7,6 +7,7 @@ import time import traceback import globals from functools import partial +import twitchio.ext from discord.ext import commands from dotenv import load_dotenv @@ -102,4 +103,4 @@ if __name__ == "__main__": except Exception as e: error_trace = traceback.format_exc() globals.log(f"Fatal Error: {e}\n{error_trace}", "FATAL") - utility.log_bot_shutdown(db_conn) + utility.log_bot_shutdown(db_conn) \ No newline at end of file diff --git a/cmd_twitch.py b/cmd_twitch.py index 327f6a6..d1370f6 100644 --- a/cmd_twitch.py +++ b/cmd_twitch.py @@ -6,7 +6,7 @@ import globals from cmd_common import common_commands as cc from modules.permissions import has_permission -from modules.utility import handle_help_command +from modules.utility import handle_help_command, is_channel_live, command_allowed_twitch def setup(bot, db_conn=None): """ @@ -14,37 +14,46 @@ def setup(bot, db_conn=None): We also attach the db_conn and log so the commands can use them. """ - @commands.command(name='funfact', aliases=['fun-fact']) - async def funfact_command(self, ctx: commands.Context, *keywords: str): + @bot.command(name='funfact', aliases=['fun-fact']) + @command_allowed_twitch + async def cmd_funfact(ctx: commands.Context, *keywords: str): # Convert keywords tuple to list and pass to get_fun_fact. fact = cc.get_fun_fact(list(keywords)) # Reply to the invoking user by prefixing their name. await ctx.reply(f"@{ctx.author.name} {fact}") @bot.command(name="greet") + @command_allowed_twitch async def cmd_greet(ctx: commands.Context): - result = cc.greet(ctx.author.display_name, "Twitch") - await ctx.reply(result) + if not await is_channel_live(): + result = cc.greet(ctx.author.display_name, "Twitch") + await ctx.reply(result) @bot.command(name="ping") + @command_allowed_twitch async def cmd_ping(ctx: commands.Context): - result = cc.ping() - await ctx.reply(result) + if not await is_channel_live(): + result = cc.ping() + await ctx.reply(result) @bot.command(name="howl") + @command_allowed_twitch async def cmd_howl(ctx: commands.Context): - response = cc.handle_howl_command(ctx) - await ctx.reply(response) + if not await is_channel_live(): + response = cc.handle_howl_command(ctx) + await ctx.reply(response) @bot.command(name="hi") + @command_allowed_twitch async def cmd_hi(ctx: commands.Context): - user_id = str(ctx.author.id) # Twitch user ID - user_roles = [role.lower() for role in ctx.author.badges.keys()] # "roles" from Twitch badges + if not await is_channel_live(): + user_id = str(ctx.author.id) # Twitch user ID + user_roles = [role.lower() for role in ctx.author.badges.keys()] # "roles" from Twitch badges - if not has_permission("hi", user_id, user_roles, "twitch"): - return await ctx.send("You don't have permission to use this command.") + if not has_permission("hi", user_id, user_roles, "twitch"): + return await ctx.send("You don't have permission to use this command.") - await ctx.reply("Hello there!") + await ctx.reply("Hello there!") # @bot.command(name="acc_link") # @monitor_cmds(bot.log) @@ -122,6 +131,7 @@ def setup(bot, db_conn=None): # await ctx.send(result) @bot.command(name="quote") + @command_allowed_twitch async def cmd_quote(ctx: commands.Context): """ Handles the !quote command with multiple subcommands. @@ -144,37 +154,40 @@ def setup(bot, db_conn=None): - !quote last/latest/newest -> Retrieves the latest (most recent) non-removed quote. """ - if not globals.init_db_conn: - await ctx.reply("Database is unavailable, sorry.") - return + if not await is_channel_live(): + if not globals.init_db_conn: + await ctx.reply("Database is unavailable, sorry.") + return - # Parse the arguments from the message text - args = ctx.message.content.strip().split() - args = args[1:] if args else [] - globals.log(f"'quote' command initiated with arguments: {args}", "DEBUG") - globals.log(f"'quote' command message content: {ctx.message.content}", "DEBUG") + # Parse the arguments from the message text + args = ctx.message.content.strip().split() + args = args[1:] if args else [] + globals.log(f"'quote' command initiated with arguments: {args}", "DEBUG") + globals.log(f"'quote' command message content: {ctx.message.content}", "DEBUG") - def get_twitch_game_for_channel(chan_name): - # Placeholder for your actual logic to fetch the current game - return "SomeGame" + def get_twitch_game_for_channel(chan_name): + # Placeholder for your actual logic to fetch the current game + return "SomeGame" - result = await cc.handle_quote_command( - db_conn=globals.init_db_conn, - is_discord=False, - ctx=ctx, - args=args, - get_twitch_game_for_channel=get_twitch_game_for_channel - ) + result = await cc.handle_quote_command( + db_conn=globals.init_db_conn, + is_discord=False, + ctx=ctx, + args=args, + get_twitch_game_for_channel=get_twitch_game_for_channel + ) - globals.log(f"'quote' result: {result}", "DEBUG") - - await ctx.reply(result) + globals.log(f"'quote' result: {result}", "DEBUG") + + await ctx.reply(result) @bot.command(name="help") + @command_allowed_twitch async def cmd_help(ctx): - parts = ctx.message.content.strip().split() - cmd_name = parts[1] if len(parts) > 1 else None - await handle_help_command(ctx, cmd_name, bot, is_discord=False) + if not await is_channel_live(bot): + parts = ctx.message.content.strip().split() + cmd_name = parts[1] if len(parts) > 1 else None + await handle_help_command(ctx, cmd_name, bot, is_discord=False) ###################### # The following log entry must be last in the file to verify commands loading as they should diff --git a/dictionary/funfacts.json b/dictionary/funfacts.json index 9b62c9d..6efa98f 100644 --- a/dictionary/funfacts.json +++ b/dictionary/funfacts.json @@ -529,5 +529,8 @@ "Some artisanal cheeses are aged in natural caves, where unique microclimates contribute to complex flavors that are difficult to replicate in modern facilities.", "While Cheddar cheese is now a global staple, it originally came from the small English village of Cheddar, and its production methods have evolved dramatically over time.", "Modern cheese production often uses vegetarian rennet, derived from microbial or plant sources, challenging the common belief that all cheese is made with animal-derived enzymes.", - "Cows are related to whales, as they both evolved from land-dwelling, even-toed ungulates, hence why a baby whale is called a 'calf'." + "Cows are related to whales, as they both evolved from land-dwelling, even-toed ungulates, hence why a baby whale is called a 'calf'.", + "McNamee was the first to reach 999 of an item, sticks, and was awarded the sticker 'McNamee's Stick' for his efforts.", + "Jenni is 159cm (5.2 feet) tall, while Kami is 186cm (6.1 feet), making Kami almost 20% taller, or the length of a sheet of A4 paper.", + "If Jenni were to bury Kami's dead body in the back of the garden, she wouldn't be able to get out of the hole without a ladder." ] \ No newline at end of file diff --git a/globals.py b/globals.py index f349260..2a5ddd1 100644 --- a/globals.py +++ b/globals.py @@ -102,7 +102,7 @@ def log(message: str, level="INFO", exec_info=False, linebreaks=False): log_message = f"[{timestamp} - {uptime_str}] [{level}] {message}" # Include traceback for certain error levels - if exec_info or level in ["CRITICAL", "FATAL"]: + if exec_info or level in ["ERROR", "CRITICAL", "FATAL"]: log_message += f"\n{traceback.format_exc()}" # Print to terminal if enabled @@ -229,4 +229,9 @@ class Constants: return_dict = {"object": primary_guild_object, "id": primary_guild_int} return return_dict + def twitch_channels_config(self): + with open("settings/twitch_channels_config.json", "r") as f: + CHANNEL_CONFIG = json.load(f) + return CHANNEL_CONFIG + constants = Constants() \ No newline at end of file diff --git a/modules/utility.py b/modules/utility.py index ad24713..c1fefff 100644 --- a/modules/utility.py +++ b/modules/utility.py @@ -10,6 +10,7 @@ from typing import Union from modules.db import run_db_operation, lookup_user, log_message import modules.utility as utility import discord +from functools import wraps import globals @@ -992,6 +993,54 @@ async def get_guild_info(bot: discord.Client, guild_id: Union[int, str]) -> dict } return info +async def is_channel_live(bot = None) -> bool: + streams = await bot.fetch_streams(user_logins=["ookamikuntv"]) if bot else [] + return bool(streams) + +def list_channels(self): + # Command to list connected channels. + connected_channels = ", ".join(channel.name for channel in self.connected_channels) + globals.log(f"Currently connected to {connected_channels}") + +def command_allowed_twitch(func): + """ + A custom check that allows a command to run based on channel settings. + It looks up the current channel in CHANNEL_CONFIG and either allows or denies + the command based on the filter mode and list. + """ + @wraps(func) + async def wrapper(ctx, *args, **kwargs): + # Load the full configuration. + full_config = globals.constants.twitch_channels_config() + + # Get the channel name and then the channel-specific configuration. + channel_name = ctx.channel.name.lower() + channel_config = full_config.get(channel_name) + + # If there's no configuration for this channel, block the command. + if not channel_config: + globals.log(f"No configuration found for Twitch channel '{channel_name}'. Blocking command '{ctx.command.name}'.") + return + + mode = channel_config.get("commands_filter_mode") + filtered = channel_config.get("commands_filtered", []) + command_name = ctx.command.name + + # Check based on filter mode. + if mode == "exclude": + if command_name in filtered: + globals.log(f"Command '{command_name}' is excluded on Twitch channel '{channel_name}'.") + return + elif mode == "include": + if command_name not in filtered: + globals.log(f"Command '{command_name}' is not allowed on Twitch channel '{channel_name}' (include mode).") + return + + # If all checks pass, run the command. + return await func(ctx, *args, **kwargs) + + return wrapper + ############################################### diff --git a/settings/discord_guilds_config.json b/settings/discord_guilds_config.json new file mode 100644 index 0000000..e69de29 diff --git a/settings/twitch_channels_config.json b/settings/twitch_channels_config.json new file mode 100644 index 0000000..5188198 --- /dev/null +++ b/settings/twitch_channels_config.json @@ -0,0 +1,10 @@ +{ + "ookamikuntv": { + "commands_filter_mode": "exclude", + "commands_filtered": [] + }, + "ookamipup": { + "commands_filter_mode": "exclude", + "commands_filtered": [] + } +} \ No newline at end of file diff --git a/systems/adventure/items.py b/systems/adventure/items.py new file mode 100644 index 0000000..e69de29