BotMeriendo is structured around discord.py’s Cog system — each functional area (music, general, events) is a separate Python module loaded dynamically at startup. This separation keeps concerns isolated: music playback logic never touches event handling, and shared state is managed in one place. The entry point (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/Melendo/BotMeriendo/llms.txt
Use this file to discover all available pages before exploring further.
src/main.py) wires everything together, while src/utils/ provides logging and queue state that every cog imports.
Entry Point
src/main.py is the single file you run to start the bot. On launch it performs these steps in order:
Load and validate configuration
Imports
TOKEN, PREFIX, and validate_config from src/config.py. validate_config() raises a ValueError immediately if TOKEN is missing, preventing a silent startup failure.Create the bot instance
Builds a
commands.Bot with discord.Intents.all() and explicitly sets intents.message_content = True so prefix commands can read message bodies.Load extensions
load_extensions() iterates over a hardcoded list and calls bot.load_extension() for each cog. Any extension that fails to load is logged at ERROR level, but the bot continues starting up with the remaining extensions.Register signal handlers and run
Before calling This is what makes
asyncio.run(main()), the __main__ block registers OS-level signal handlers for SIGTERM and SIGINT. When either signal arrives, all running asyncio tasks are cancelled and a shutdown message is logged before the process exits.docker compose down (which sends SIGTERM) produce a clean shutdown log instead of a hard kill.Module Map
src/cogs/general.py
Basic utility commands:
!ping, !hola, and !comandos (a custom help embed that groups commands by category).src/cogs/music.py
Full YouTube music streaming via
yt_dlp, queue management, playlist support, and interactive UI buttons (pause/resume, skip) rendered as Discord message components.src/cogs/events.py
Discord gateway event listeners:
on_ready, on_member_join, on_command_error, and on_voice_state_update (auto-disconnect and queue cleanup).src/utils/logger.py
Logging setup that writes to both
bot.log (file) and stdout simultaneously. Configures log levels per-namespace to reduce discord.py noise.src/utils/state.py
Defines the shared
music_queues dict (keyed by guild ID). src/cogs/music.py imports it directly; src/cogs/events.py imports it via src.cogs.music. Single source of truth for per-guild queue state.src/config.py
Loads
TOKEN and TRGGKEY from the .env file via python-dotenv. Provides validate_config() and derives PREFIX (TRGGKEY value, or ! if unset).Project Directory Tree
Shared State
Queue state lives entirely insrc/utils/state.py:
music_queues is a plain Python dict keyed by guild ID (int). Each value is a list of (video_url, title) tuples where video_url is the canonical YouTube watch URL and title is the human-readable video name. src/cogs/music.py imports music_queues directly from src.utils.state. src/cogs/events.py imports it from src.cogs.music (which re-exports the same object), so mutations from either cog are immediately visible to the other.
Because it is a plain in-memory dict:
- No persistence — the queue is completely discarded when the bot process exits or restarts.
- No concurrency control — Python’s GIL and asyncio’s single-threaded event loop make concurrent mutations safe within one process, but there is no cross-process or cross-shard safety.
- Per-guild isolation — each guild gets its own list, so one server’s queue never interferes with another’s.
Audio Pipeline
BotMeriendo uses a JIT (Just-In-Time) audio resolution pattern to avoid stale stream URLs when managing playlists or multi-song queues.Queue fast metadata only
When
!play is called, yt_dlp runs with extract_flat: 'in_playlist' for URL inputs. This retrieves only the video’s watch URL and title — it does not resolve the audio stream. The lightweight (video_url, title) tuple is appended to music_queues[guild_id] immediately.Resolve the stream URL just before playback
When
play_next_song() pops the next item from the queue, it calls ytdl.extract_info() at that moment — not during enqueueing. This runs in a thread via asyncio.to_thread so it doesn’t block the event loop while yt-dlp fetches the audio manifest.Open the FFmpeg audio source
The resolved stream URL is passed to Where
discord.FFmpegOpusAudio.from_probe() along with FFmpeg reconnect options. If the stream drops mid-play, FFmpeg automatically attempts to reconnect for up to 5 seconds.ffmpeg_options is:The JIT pattern means a playlist of 50 songs can be enqueued in under a second, but each song incurs a small resolution delay (usually under 2 seconds) just before it starts playing. This is the intended trade-off: fast queue-add at the cost of a brief per-song startup pause.