Skip to content

Latest commit

 

History

History
523 lines (358 loc) · 16.8 KB

File metadata and controls

523 lines (358 loc) · 16.8 KB

Smartbot Feature Expansion Plan

Context

Smartbot is a working Discord voice bot (wake word, Gemini AI, ElevenLabs TTS). The core pipeline is functional. This plan adds a massive set of fun features to make it a fully-featured group companion. The user's friend group plays League of Legends, Counter-Strike, Rust, and Monster Hunter. Whisper currently struggles with game-specific names, which we'll fix with the hotwords parameter.

Pre-Requisite: Architectural Changes ✅ COMPLETE

Before adding 20+ new tools, two foundational changes prevent brain.py from becoming unmaintainable.

P1. Tool Registry Pattern in brain.py

Replace the if/elif chain in _dispatch_tool() with a registry dict. Each tool module registers a handler at import time.

Files: bot/ai/brain.py

# New: registry dict populated by tool modules
_TOOL_REGISTRY: dict[str, Callable] = {}

def register_tool(name: str, handler: Callable):
    _TOOL_REGISTRY[name] = handler

# _dispatch_tool becomes:
async def _dispatch_tool(name, inputs, guild_id, session_id, display_name, user_discord_id, bot_client):
    handler = _TOOL_REGISTRY.get(name)
    if not handler:
        return f"Unknown tool: {name}"
    return await handler(
        inputs, guild_id=guild_id, session_id=session_id,
        display_name=display_name, user_discord_id=user_discord_id,
        bot_client=bot_client,
    )

Each existing tool module (web_search, weather, etc.) gets a register() call at the bottom. New tools self-register on import. brain.py imports all tool modules in _dispatch_tool's first call (lazy) or at module level.

P2. Text Channel Embed Helper

Many features (leaderboards, trivia scores, stats) produce output too long for TTS. Add a helper to post embeds to the text channel.

New file: bot/utils/text_channel.py

async def post_embed(bot_client, guild_id: int, embed: discord.Embed) -> None:
    """Post an embed to the configured text channel."""

P3. Gaming Vocabulary for Whisper (Hotwords)

Fix Whisper's poor recognition of game character names by using the hotwords parameter.

File: bot/audio/transcriber.py_transcribe_sync() File: bot/config.py — add WHISPER_HOTWORDS optional config

Add a hotwords string with League champions, CS:GO maps/weapons, Rust items, Monster Hunter monsters. Pass to model.transcribe(hotwords=...). Also expand initial_prompt to include common gaming terms.

New file: bot/audio/gaming_vocab.py — Contains the hotwords string and can be expanded over time.

Hotwords string includes: Ahri, Akali, Anivia, Ashe, Blitzcrank, Caitlyn, Darius, Draven, Ezreal, Garen, Jinx, Kai'Sa, Lee Sin, Lux, Miss Fortune, Morgana, Nasus, Pyke, Thresh, Vayne, Yasuo, Yone, Zed, Dust 2, Mirage, Inferno, AWP, AK-47, Fatalis, Rathalos, Zinogre, etc.


Phase 1: Simple Gemini Tools (No DB Changes) ✅ COMPLETE

These are pure functions — no state, no DB. Fast to implement.

1A. Dice Roller + Coin Flip + Magic 8-Ball

New file: bot/tools/random_fun.py

Functions:

  • roll_dice(expression: str) -> str — Parses "2d20", "4d6+3", "d100"
  • flip_coin(count: int = 1) -> str — Heads/tails results
  • magic_8ball(question: str) -> str — Classic 20 responses

3 tool definitions in bot/ai/tools.py: roll_dice, flip_coin, magic_8ball

1B. Dad Jokes

New file: bot/tools/jokes.py

Function: get_dad_joke() -> str — Calls icanhazdadjoke.com API (free, no key, uses httpx)

1 tool definition: tell_joke

1C. Calculator / Unit Conversion

New file: bot/tools/calculator.py

Function: calculate(expression: str) -> str — Safe math eval + unit conversions (F/C, miles/km, lbs/kg)

1 tool definition: calculate

1D. Gaming Lookup Tool

New file: bot/tools/gaming.py

Functions:

  • game_lookup(game: str, query: str) -> str — Uses web_search internally but with game-specific query formatting
    • LoL: "league of legends {champion} build season 14 mobafire"
    • CS: "counter strike {query}"
    • Monster Hunter: "monster hunter wilds {query} guide"
    • Rust: "rust game {query}"

1 tool definition: game_lookup — Gemini uses this when someone asks about builds, guides, or game info

1E. Sports Scores

New file: bot/tools/sports.py

Function: get_sports_scores(query: str) -> str — Uses web_search internally with sports-optimized queries

1 tool definition: get_sports_scores

1F. Morning Briefing

New file: bot/tools/briefing.py

Function: morning_briefing(guild_id: int, user_discord_id: str) -> str — Compiles weather + birthdays + news headlines using existing tools

1 tool definition: morning_briefing

Phase 1 total: 8 new tools, 3 new files, 0 DB changes


Phase 2: Discord Soundboard Integration ✅ COMPLETE

Use Discord's built-in soundboard (server already has sounds uploaded).

2A. Play Soundboard Sounds via Voice

New file: bot/tools/soundboard.py

Functions:

  • list_sounds(guild) -> strguild.fetch_soundboard_sounds() → return names
  • play_sound(sound_name: str, guild, voice_channel) -> str — Fuzzy-match name → voice_channel.send_sound(sound)

2 tool definitions: play_sound, list_sounds

The dispatch needs guild + voice_channel access. Resolved via bot_client.get_guild(guild_id) and guild.voice_client.channel.

2B. Soundboard Management Slash Commands

New file: bot/cogs/soundboard_cog.py

Commands:

  • /soundboard list — Embed with all server sounds
  • /soundboard play <name> — Play a sound from text

File: bot/main.py — load bot.cogs.soundboard_cog

Phase 2 total: 2 new tools, 2 new files, 0 DB changes


Phase 3: Database-Backed Social Features ✅ COMPLETE

3A. Birthday Tracker

New DB table in bot/db/database.py:

CREATE TABLE IF NOT EXISTS birthdays (
    discord_id TEXT PRIMARY KEY,
    display_name TEXT NOT NULL,
    month INTEGER NOT NULL,
    day INTEGER NOT NULL,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

New file: bot/tools/birthday_tracker.py

Functions:

  • set_birthday(discord_id, display_name, month, day) -> str
  • get_upcoming(guild_id) -> str

CRUD in bot/db/models.py: set_birthday(), get_birthday(), get_todays_birthdays(), get_upcoming_birthdays()

2 tool definitions: set_birthday, get_upcoming_birthdays

Birthday daily check: Add discord.ext.tasks.loop in fun_cog.py that checks daily and posts/speaks birthday announcements.

3B. Custom Voice Greetings

New DB table in bot/db/database.py:

CREATE TABLE IF NOT EXISTS greetings (
    discord_id TEXT PRIMARY KEY,
    greeting_text TEXT NOT NULL,
    greeting_sound TEXT,
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

New file: bot/tools/greetings.py

Functions:

  • set_greeting(discord_id, text, sound_name=None) -> str
  • get_greeting(discord_id) -> dict | None
  • clear_greeting(discord_id) -> str

1 tool definition: set_greeting

Voice cog change: Add on_voice_state_update listener in voice_cog.py — when a non-bot member joins the bot's channel, look up their greeting and TTS it (with a short delay).

3C. Horoscope / Zodiac

New file: bot/tools/horoscope.py

Function: get_horoscope(discord_id=None, sign=None) -> str — Looks up birthday for zodiac sign, returns sign info so Gemini generates creative horoscope

1 tool definition: get_horoscope

3D. Nickname Persistence Fix

File: bot/cogs/voice_cog.py — In _handle_wake_event, load preferred_callout for ALL channel members (not just speaker) when building channel_members roster:

channel_members = []
for m in vc.channel.members:
    if not m.bot:
        preferred = await models.get_display_name(str(m.id), m.display_name)
        channel_members.append({"name": preferred, "id": str(m.id)})

3E. Voice Stats / Leaderboard

New DB table:

CREATE TABLE IF NOT EXISTS voice_stats (
    discord_id TEXT NOT NULL,
    guild_id TEXT NOT NULL,
    display_name TEXT NOT NULL,
    wake_count INTEGER DEFAULT 0,
    games_played INTEGER DEFAULT 0,
    trivia_correct INTEGER DEFAULT 0,
    updated_at TEXT,
    PRIMARY KEY (discord_id, guild_id)
);

New file: bot/tools/stats_tracker.py

Functions:

  • increment_stat(discord_id, guild_id, stat_name) -> None
  • get_leaderboard(guild_id, metric) -> str

1 tool definition: get_leaderboard

Slash command in fun_cog.py: /stats — shows embed leaderboard

Phase 3 total: 5 new tools, 4 new files, 3 new DB tables


Phase 4: Game Engine + Games ✅ COMPLETE

4A. Shared Game State Manager

New directory: bot/games/

New file: bot/games/__init__.py New file: bot/games/manager.py

Core design:

@dataclass
class GameState:
    game_type: str
    guild_id: int
    started_by: str
    round_number: int = 0
    max_rounds: int = 5
    scores: dict[str, int] = field(default_factory=dict)
    current_data: dict = field(default_factory=dict)
    participants: list[str] = field(default_factory=list)

_active_games: dict[int, GameState] = {}  # guild_id → GameState

async def start_game(guild_id, game_type, started_by, **kwargs) -> str
async def get_active_game(guild_id) -> GameState | None
async def end_game(guild_id) -> str

4B. Trivia

New file: bot/games/trivia.py

Uses Open Trivia DB API (free, no key): https://opentdb.com/api.php?amount=1&type=multiple

Functions:

  • start_trivia(guild_id, started_by, rounds=5, category=None) -> str
  • answer_trivia(guild_id, player_id, answer) -> str — Fuzzy match answers
  • end_trivia(guild_id) -> str

1 tool definition (single tool with action param): trivia

Slash command: /trivia leaderboard in fun_cog.py

4C. Would You Rather

New file: bot/games/would_you_rather.py New file: bot/games/wyr_questions.py — Bank of ~100 questions

Functions:

  • start_wyr(guild_id, started_by, rounds=5) -> str
  • vote_wyr(guild_id, player_id, choice) -> str
  • reveal_wyr(guild_id) -> str

1 tool definition: would_you_rather

4D. Who's Most Likely To

New file: bot/games/most_likely.py

Gemini generates prompts based on channel members. Everyone votes.

Functions:

  • start_most_likely(guild_id, members) -> str
  • vote_most_likely(guild_id, voter_id, target_name) -> str
  • reveal_most_likely(guild_id) -> str

1 tool definition: most_likely_to

4E. Rock Paper Scissors

New file: bot/games/rps.py

Functions:

  • play_rps(guild_id, player_id, choice, opponent_id=None) -> str

1 tool definition: rock_paper_scissors

4F. Hot Takes

New file: bot/games/hot_takes.py

Gemini poses a statement, everyone rates 1-10.

Functions:

  • start_hot_takes(guild_id) -> str
  • rate_take(guild_id, player_id, rating) -> str
  • reveal_take(guild_id) -> str

1 tool definition: hot_takes

Phase 4 total: 5 tool definitions, 7 new files, game state infrastructure


Phase 5: Advanced Voice Features ✅ COMPLETE

5A. Personality Modes (Hype Man, Debate Mode)

New file: bot/tools/modes.py

_active_modes: dict[int, set[str]] = {}  # guild_id → set of mode names

async def toggle_mode(guild_id, mode) -> str
async def get_active_modes(guild_id) -> set[str]

Modes: hype_man, debate_mode

File: bot/ai/prompts.pybuild_system_prompt() gains active_modes param, appends mode-specific personality instructions.

File: bot/cogs/voice_cog.py — Pass active modes to build_system_prompt via brain.handle_request.

1 tool definition: toggle_mode

5B. Polls (Voice-Based Voting)

New file: bot/games/polls.py

Functions:

  • create_poll(guild_id, question, options) -> str
  • cast_vote(guild_id, voter_id, option) -> str
  • close_poll(guild_id) -> str

1 tool definition: poll

5C. Story Mode (Collaborative Storytelling)

New file: bot/games/story_mode.py

Functions:

  • start_story(guild_id, genre=None) -> str
  • add_to_story(guild_id, player_id, contribution) -> str
  • end_story(guild_id) -> str

1 tool definition: story_mode

5D. Playlist Management

New DB table:

CREATE TABLE IF NOT EXISTS playlists (
    id INTEGER PRIMARY KEY AUTOINCREMENT,
    name TEXT NOT NULL,
    guild_id TEXT NOT NULL,
    created_by TEXT NOT NULL,
    songs TEXT DEFAULT '[]',
    created_at TEXT NOT NULL DEFAULT (datetime('now'))
);

New file: bot/tools/playlist.py

Functions:

  • create_playlist(name, guild_id, created_by) -> str
  • add_song(playlist_name, guild_id, song) -> str
  • play_playlist(playlist_name, guild_id, bot_client) -> str — Sends songs to music bot via call_discord_bot
  • list_playlists(guild_id) -> str

1 tool definition: playlist

Phase 5 total: 4 tool definitions, 4 new files, 1 DB table


System Prompt Update ✅ COMPLETE

File: bot/ai/prompts.py

Update the tools section to group by category:

Tools available:
- Utility: web_search, get_weather, calculate, tell_joke, roll_dice, flip_coin, magic_8ball
- Gaming: game_lookup (builds/guides for LoL, CS, Rust, Monster Hunter), get_sports_scores
- Soundboard: play_sound, list_sounds (plays from this server's Discord soundboard)
- Social: set_birthday, get_upcoming_birthdays, set_greeting, get_horoscope, morning_briefing
- Games: trivia, would_you_rather, most_likely_to, rock_paper_scissors, hot_takes, poll, story_mode
- Music: call_discord_bot, playlist
- Memory: remember_fact, recall_facts, set_user_preference
- Other: log_quote, set_timer, toggle_mode, get_leaderboard

Add gaming context:

This friend group plays League of Legends, Counter-Strike, Rust, and Monster Hunter.
When they ask about builds, guides, or game info, use game_lookup with the right game name.

Files Summary

New files (22):

  • bot/audio/gaming_vocab.py — Whisper hotwords for gaming terms
  • bot/utils/text_channel.py — Embed posting helper
  • bot/tools/random_fun.py — Dice, coin, 8-ball
  • bot/tools/jokes.py — Dad jokes API
  • bot/tools/calculator.py — Math + unit conversion
  • bot/tools/gaming.py — Game-specific lookups
  • bot/tools/sports.py — Sports scores
  • bot/tools/briefing.py — Morning briefing
  • bot/tools/soundboard.py — Discord soundboard integration
  • bot/tools/birthday_tracker.py — Birthday CRUD
  • bot/tools/greetings.py — Custom voice greetings
  • bot/tools/horoscope.py — Zodiac/horoscope
  • bot/tools/stats_tracker.py — Voice stats/leaderboard
  • bot/tools/modes.py — Personality mode toggles
  • bot/tools/playlist.py — Playlist management
  • bot/cogs/soundboard_cog.py — Soundboard slash commands
  • bot/games/__init__.py
  • bot/games/manager.py — Shared game state
  • bot/games/trivia.py
  • bot/games/would_you_rather.py + bot/games/wyr_questions.py
  • bot/games/most_likely.py
  • bot/games/rps.py
  • bot/games/hot_takes.py
  • bot/games/polls.py
  • bot/games/story_mode.py

Modified files (8):

  • bot/ai/brain.py — Tool registry pattern
  • bot/ai/tools.py — ~24 new tool definitions
  • bot/ai/prompts.py — Updated system prompt with all features + modes + gaming context
  • bot/audio/transcriber.py — Add hotwords parameter
  • bot/db/database.py — 4 new tables (birthdays, greetings, voice_stats, playlists)
  • bot/db/models.py — CRUD for new tables
  • bot/cogs/voice_cog.py — Greeting announcements, nickname fix, mode passing
  • bot/cogs/fun_cog.py — Birthday daily check, /trivia leaderboard, /stats
  • bot/main.py — Load soundboard_cog
  • bot/config.py — WHISPER_HOTWORDS optional config

New DB tables (4):

  • birthdays (discord_id PK, display_name, month, day)
  • greetings (discord_id PK, greeting_text, greeting_sound)
  • voice_stats (discord_id + guild_id composite PK, stats counters)
  • playlists (id PK, name, guild_id, created_by, songs JSON)

New tool count: ~24 new tools (total ~32 including existing 8)


Implementation Order

  1. Pre-req: Tool registry in brain.py + text_channel helper + gaming vocab hotwords
  2. Phase 1: Simple tools (random_fun, jokes, calculator, gaming, sports, briefing)
  3. Phase 2: Soundboard (Discord API integration)
  4. Phase 3: DB tables + social features (birthdays, greetings, horoscope, nickname fix, stats)
  5. Phase 4: Game engine + all games (trivia, WYR, most likely, RPS, hot takes)
  6. Phase 5: Advanced features (modes, polls, story mode, playlists)
  7. Final: System prompt overhaul

Verification

After each phase:

  1. docker compose up --build — confirm clean build
  2. docker compose logs -f bot — check for import errors, missing config
  3. Join voice channel, test features by voice:
    • "Hey smartbot, roll 2d20"
    • "Hey smartbot, tell me a joke"
    • "Hey smartbot, play the airhorn"
    • "Hey smartbot, what should I build on Jinx?"
    • "Hey smartbot, let's play trivia"
  4. Test slash commands in Discord: /soundboard list, /quotes, /stats