Fix trivia, update agents/champions, rename /status, fix OLLAMA env var

- Fix trivia: 25 questions with 4 answer buttons (A/B/C/D), 30s timeout,
  per-user answer tracking, gaming + internet culture categories
- Update Valorant agents: add Clove, Tejo, Waylay, Vyse, Veto (28 total)
- Update LoL champions: expand from ~70 to ~166 champions across all lanes
- Rename /status to /health to avoid Wordle conflict
- Fix OLLAMA_HOST env var mismatch (code read OLLAMA_URL, .env has OLLAMA_HOST)
- Add /trivia to help command

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-10 20:41:16 -05:00
parent 838198648e
commit ea91a40053

147
bot.py
View File

@@ -68,7 +68,7 @@ HYTALE_SERVER_UUID = os.getenv('HYTALE_SERVER_UUID', '7a656836-c3f3-491e-ac55-66
STATUS_UPDATE_INTERVAL = int(os.getenv('STATUS_UPDATE_INTERVAL', '300'))
# --- Issue #7: Hardcoded URLs - Use env vars for internal services ---
OLLAMA_URL = os.getenv('OLLAMA_URL', 'http://10.10.10.157:11434')
OLLAMA_URL = os.getenv('OLLAMA_HOST', 'http://10.10.10.157:11434')
# --- Issue #11: Magic Numbers - Extract constants ---
MIN_USERNAME_LENGTH = 3
@@ -114,7 +114,7 @@ class ConfigValidator:
OPTIONAL = {
'MINECRAFT_RCON_PASSWORD': 'Minecraft commands',
'PELICAN_API_KEY': 'Pelican integration',
'OLLAMA_URL': '/ask command'
'OLLAMA_HOST': '/ask command'
}
@classmethod
@@ -404,7 +404,7 @@ class CustomBot(commands.Bot):
if not PELICAN_API_KEY:
logger.warning("PELICAN_API_KEY not set - Pelican features disabled")
if not OLLAMA_URL:
logger.warning("OLLAMA_URL not set - /ask command will fail")
logger.warning("OLLAMA_HOST not set - /ask command will fail")
load_whitelisted_usernames()
@@ -741,6 +741,7 @@ async def help_command(interaction: discord.Interaction):
embed.add_field(name="/random", value="Generate a random number between specified range", inline=False)
embed.add_field(name="/rps", value="Play Rock Paper Scissors against the bot", inline=False)
embed.add_field(name="/poll", value="Create a simple yes/no poll", inline=False)
embed.add_field(name="/trivia", value="Play a trivia game with random questions", inline=False)
# Game Commands
embed.add_field(name="/agent", value="Get a random Valorant agent with their role", inline=False)
@@ -761,7 +762,7 @@ async def help_command(interaction: discord.Interaction):
embed.add_field(name="/clear", value="Clears specified number of messages from the channel", inline=False)
embed.add_field(name="/lockdown", value="Lock a channel to prevent messages", inline=False)
embed.add_field(name="/unlock", value="Unlock a previously locked channel", inline=False)
embed.add_field(name="/status", value="Check bot system health and service status", inline=False)
embed.add_field(name="/health", value="Check bot system health and service status", inline=False)
embed.set_footer(text="Made by https://lotusguild.org")
await interaction.response.send_message(embed=embed)
@@ -1198,10 +1199,10 @@ async def revive(interaction: discord.Interaction, member: discord.Member):
@handle_command_errors
async def agent(interaction: discord.Interaction):
agents = {
"Duelists": ["Jett", "Phoenix", "Raze", "Reyna", "Yoru", "Neon", "Iso"],
"Controllers": ["Brimstone", "Viper", "Omen", "Astra", "Harbor"],
"Initiators": ["Sova", "Breach", "Skye", "KAY/O", "Fade", "Gekko"],
"Sentinels": ["Killjoy", "Cypher", "Sage", "Chamber", "Deadlock"]
"Duelists": ["Jett", "Phoenix", "Raze", "Reyna", "Yoru", "Neon", "Iso", "Waylay"],
"Controllers": ["Brimstone", "Viper", "Omen", "Astra", "Harbor", "Clove"],
"Initiators": ["Sova", "Breach", "Skye", "KAY/O", "Fade", "Gekko", "Tejo"],
"Sentinels": ["Killjoy", "Cypher", "Sage", "Chamber", "Deadlock", "Vyse", "Veto"]
}
category = random.choice(list(agents.keys()))
@@ -1222,11 +1223,45 @@ async def agent(interaction: discord.Interaction):
@handle_command_errors
async def champion(interaction: discord.Interaction):
champions = {
"Top": ["Aatrox", "Camille", "Darius", "Fiora", "Garen", "Irelia", "Jax", "K'Sante", "Malphite", "Mordekaiser", "Nasus", "Ornn", "Riven", "Sett", "Teemo"],
"Jungle": ["Bel'Veth", "Diana", "Elise", "Evelynn", "Graves", "Hecarim", "Kayn", "Kindred", "Lee Sin", "Master Yi", "Nidalee", "Rammus", "Viego", "Warwick"],
"Mid": ["Ahri", "Akali", "Annie", "Cassiopeia", "Fizz", "Katarina", "LeBlanc", "Lux", "Orianna", "Syndra", "Twisted Fate", "Veigar", "Yasuo", "Zed"],
"Bot": ["Aphelios", "Ashe", "Caitlyn", "Draven", "Ezreal", "Jhin", "Jinx", "Kai'Sa", "Lucian", "Miss Fortune", "Samira", "Tristana", "Vayne", "Xayah"],
"Support": ["Blitzcrank", "Brand", "Janna", "Leona", "Lulu", "Morgana", "Nautilus", "Pyke", "Rakan", "Senna", "Seraphine", "Soraka", "Thresh", "Yuumi"]
"Top": [
"Aatrox", "Ambessa", "Aurora", "Camille", "Cho'Gath", "Darius",
"Dr. Mundo", "Fiora", "Gangplank", "Garen", "Gnar", "Gragas",
"Gwen", "Illaoi", "Irelia", "Jax", "Jayce", "K'Sante", "Kennen",
"Kled", "Malphite", "Mordekaiser", "Nasus", "Olaf", "Ornn",
"Poppy", "Quinn", "Renekton", "Riven", "Rumble", "Sett", "Shen",
"Singed", "Sion", "Teemo", "Trundle", "Tryndamere", "Urgot",
"Vladimir", "Volibear", "Wukong", "Yone", "Yorick"
],
"Jungle": [
"Amumu", "Bel'Veth", "Briar", "Diana", "Ekko", "Elise",
"Evelynn", "Fiddlesticks", "Graves", "Hecarim", "Ivern",
"Jarvan IV", "Kayn", "Kha'Zix", "Kindred", "Lee Sin", "Lillia",
"Maokai", "Master Yi", "Nidalee", "Nocturne", "Nunu", "Olaf",
"Rek'Sai", "Rengar", "Sejuani", "Shaco", "Skarner", "Taliyah",
"Udyr", "Vi", "Viego", "Warwick", "Xin Zhao", "Zac"
],
"Mid": [
"Ahri", "Akali", "Akshan", "Annie", "Aurelion Sol", "Azir",
"Cassiopeia", "Corki", "Ekko", "Fizz", "Galio", "Heimerdinger",
"Hwei", "Irelia", "Katarina", "LeBlanc", "Lissandra", "Lux",
"Malzahar", "Mel", "Naafiri", "Neeko", "Orianna", "Qiyana",
"Ryze", "Sylas", "Syndra", "Talon", "Twisted Fate", "Veigar",
"Vex", "Viktor", "Vladimir", "Xerath", "Yasuo", "Yone", "Zed",
"Zoe"
],
"Bot": [
"Aphelios", "Ashe", "Caitlyn", "Draven", "Ezreal", "Jhin",
"Jinx", "Kai'Sa", "Kalista", "Kog'Maw", "Lucian",
"Miss Fortune", "Nilah", "Samira", "Sivir", "Smolder",
"Tristana", "Twitch", "Varus", "Vayne", "Xayah", "Zeri"
],
"Support": [
"Alistar", "Bard", "Blitzcrank", "Brand", "Braum", "Janna",
"Karma", "Leona", "Lulu", "Lux", "Milio", "Morgana", "Nami",
"Nautilus", "Pyke", "Rakan", "Rell", "Renata Glasc", "Senna",
"Seraphine", "Sona", "Soraka", "Swain", "Taric", "Thresh",
"Yuumi", "Zilean", "Zyra"
]
}
lane = random.choice(list(champions.keys()))
@@ -1246,22 +1281,82 @@ async def champion(interaction: discord.Interaction):
@client.tree.command(name="trivia", description="Play a trivia game")
@handle_command_errors
async def trivia(interaction: discord.Interaction):
questions = [
# Gaming
{"q": "What year was the original Super Mario Bros. released?", "options": ["1983", "1985", "1987", "1990"], "answer": 1},
{"q": "Which game features the quote 'The cake is a lie'?", "options": ["Half-Life 2", "Portal", "BioShock", "Minecraft"], "answer": 1},
{"q": "What is the max level in League of Legends?", "options": ["16", "18", "20", "25"], "answer": 1},
{"q": "Which Valorant agent has the codename 'Deadeye'?", "options": ["Jett", "Sova", "Chamber", "Cypher"], "answer": 2},
{"q": "How many Ender Dragon eggs can exist in a vanilla Minecraft world?", "options": ["1", "2", "Unlimited", "0"], "answer": 0},
{"q": "What was the first battle royale game to hit mainstream popularity?", "options": ["Fortnite", "PUBG", "H1Z1", "Apex Legends"], "answer": 2},
{"q": "In Minecraft, what is the rarest ore?", "options": ["Diamond", "Emerald", "Ancient Debris", "Lapis Lazuli"], "answer": 1},
{"q": "What is the name of the main character in The Legend of Zelda?", "options": ["Zelda", "Link", "Ganondorf", "Epona"], "answer": 1},
{"q": "Which game has the most registered players of all time?", "options": ["Fortnite", "Minecraft", "League of Legends", "Roblox"], "answer": 1},
{"q": "What type of animal is Sonic?", "options": ["Fox", "Hedgehog", "Rabbit", "Echidna"], "answer": 1},
{"q": "In Among Us, what is the maximum number of impostors?", "options": ["1", "2", "3", "4"], "answer": 2},
{"q": "What does GG stand for in gaming?", "options": ["Get Good", "Good Game", "Go Go", "Great Going"], "answer": 1},
{"q": "Which company developed Valorant?", "options": ["Blizzard", "Valve", "Riot Games", "Epic Games"], "answer": 2},
{"q": "What is the highest rank in Valorant?", "options": ["Immortal", "Diamond", "Radiant", "Challenger"], "answer": 2},
{"q": "In League of Legends, what is Baron Nashor an anagram of?", "options": ["Baron Roshan", "Roshan", "Nashor Baron", "Nash Robot"], "answer": 1},
# General/Internet culture
{"q": "What does HTTP stand for?", "options": ["HyperText Transfer Protocol", "High Tech Transfer Program", "HyperText Transmission Process", "Home Tool Transfer Protocol"], "answer": 0},
{"q": "What year was Discord founded?", "options": ["2013", "2015", "2017", "2019"], "answer": 1},
{"q": "What programming language has a logo that is a snake?", "options": ["Java", "Ruby", "Python", "Go"], "answer": 2},
{"q": "How many bits are in a byte?", "options": ["4", "8", "16", "32"], "answer": 1},
{"q": "What does 'RGB' stand for?", "options": ["Really Good Build", "Red Green Blue", "Red Gold Black", "Rapid Gaming Boost"], "answer": 1},
{"q": "What is the most subscribed YouTube channel?", "options": ["PewDiePie", "MrBeast", "T-Series", "Cocomelon"], "answer": 1},
{"q": "What does 'AFK' stand for?", "options": ["A Free Kill", "Away From Keyboard", "Always Fun Killing", "Another Fake Knockdown"], "answer": 1},
{"q": "What animal is the Linux mascot?", "options": ["Fox", "Penguin", "Cat", "Dog"], "answer": 1},
{"q": "What does 'NPC' stand for?", "options": ["Non-Player Character", "New Player Content", "Normal Playing Conditions", "Never Played Competitively"], "answer": 0},
{"q": "In what year was the first iPhone released?", "options": ["2005", "2006", "2007", "2008"], "answer": 2},
]
question = random.choice(questions)
labels = ["A", "B", "C", "D"]
class TriviaView(discord.ui.View):
def __init__(self, correct_answer):
super().__init__()
self.correct_answer = correct_answer
def __init__(self):
super().__init__(timeout=30)
self.answered_users = set()
async def handle_answer(self, interaction: discord.Interaction, selected: int):
if interaction.user.id in self.answered_users:
await interaction.response.send_message("You already answered!", ephemeral=True)
return
self.answered_users.add(interaction.user.id)
if selected == question["answer"]:
await interaction.response.send_message(
f"Correct! The answer was **{labels[question['answer']]}. {question['options'][question['answer']]}**",
ephemeral=True
)
else:
await interaction.response.send_message(
f"Wrong! The correct answer was **{labels[question['answer']]}. {question['options'][question['answer']]}**",
ephemeral=True
)
@discord.ui.button(label="A", style=discord.ButtonStyle.primary)
async def answer_a(self, interaction: discord.Interaction, button: discord.ui.Button):
if "A" == self.correct_answer:
await interaction.response.send_message("Correct!", ephemeral=True)
else:
await interaction.response.send_message("Wrong!", ephemeral=True)
await self.handle_answer(interaction, 0)
embed = discord.Embed(title="Trivia Question", description="What is 2+2?")
embed.add_field(name="A", value="4")
embed.add_field(name="B", value="5")
await interaction.response.send_message(embed=embed, view=TriviaView("A"))
@discord.ui.button(label="B", style=discord.ButtonStyle.primary)
async def answer_b(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.handle_answer(interaction, 1)
@discord.ui.button(label="C", style=discord.ButtonStyle.primary)
async def answer_c(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.handle_answer(interaction, 2)
@discord.ui.button(label="D", style=discord.ButtonStyle.primary)
async def answer_d(self, interaction: discord.Interaction, button: discord.ui.Button):
await self.handle_answer(interaction, 3)
embed = discord.Embed(title="Trivia Time!", color=discord.Color.from_rgb(152, 0, 0))
embed.description = question["q"]
for i, option in enumerate(question["options"]):
embed.add_field(name=f"{labels[i]}", value=option, inline=True)
embed.set_footer(text="You have 30 seconds to answer!")
await interaction.response.send_message(embed=embed, view=TriviaView())
@client.tree.command(name="userinfo", description="Get information about a user")
@@ -1276,10 +1371,10 @@ async def userinfo(interaction: discord.Interaction, member: discord.Member):
# --- Issue #16: Health Check Command ---
@client.tree.command(name="status", description="Check bot system status")
@client.tree.command(name="health", description="Check bot system status")
@has_role_check(ADMIN_ROLE_ID)
@handle_command_errors
async def status(interaction: discord.Interaction):
async def health(interaction: discord.Interaction):
"""Show bot health metrics"""
stats = metrics.get_stats()
uptime_hours = stats["uptime_seconds"] / 3600