Added Twitch multi-channel support
- OokamiPup v2 can now enter sevaral community channels - Individual channel settings. By default, all commands are disabled - Channel-specific settings allow enabling/disabling individual commands per channel - Added a few more fun facts - Minor tweaks and bugfixeskami_dev
parent
6da1744990
commit
17fbb20cdc
|
@ -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:
|
||||
|
|
1
bots.py
1
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
|
||||
|
|
|
@ -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")
|
||||
globals.log(f"'quote' result: {result}", "DEBUG")
|
||||
|
||||
await ctx.reply(result)
|
||||
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
|
||||
|
|
|
@ -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."
|
||||
]
|
|
@ -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()
|
|
@ -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
|
||||
|
||||
|
||||
|
||||
###############################################
|
||||
|
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"ookamikuntv": {
|
||||
"commands_filter_mode": "exclude",
|
||||
"commands_filtered": []
|
||||
},
|
||||
"ookamipup": {
|
||||
"commands_filter_mode": "exclude",
|
||||
"commands_filtered": []
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue