Niko is a Discord bot with a cozy café personality, bilingual EN/DE support, and a full feature set including economy, leveling, music, moderation, roleplay, info, and AI utility commands. All cogs reside under src/cogs/.
Preferred communication style: Simple, everyday language.
- Discord.py with Cogs pattern: Modular cog system under
src/cogs/. All 37+ cogs load on startup. - All cogs live in
src/cogs/(NOT a separatecogs/root directory). - Logging via
utils/logging.pycustom logger (from utils import logging as log).
- Components v2 (cv2) LayoutView: All user-facing responses use
discord.ui.LayoutViewinstead ofdiscord.Embed.- Pattern:
view = discord.ui.LayoutView()→view.add_item(discord.ui.Container(discord.ui.TextDisplay(...)))→await ctx.send(view=view) - Thumbnail accessory: wrap
TextDisplayindiscord.ui.Section(..., accessory=discord.ui.Thumbnail(url)) - Images/GIFs:
view.add_item(discord.ui.MediaGallery(discord.ui.MediaGalleryItem(url=...))) - Link buttons:
view.add_item(discord.ui.ActionRow(discord.ui.Button(style=discord.ButtonStyle.link, url=...))) - Interactive buttons/selects CAN be added to a LayoutView via
ActionRow— subclassdiscord.ui.Buttonand useself.viewin callback. - Exception:
automodcommand usesdiscord.ui.View+discord.Embedbecause it predates the cv2 interactive pattern.
- Pattern:
- Pagination:
from utils.paginator import PaginatedView, paginate— shared cv2 paginator used by leaderboards and dev inspection commands.paginate(lines, per_page)chunks a list of strings;PaginatedView(title, pages, icon_url)renders with ◀ / ▶ nav buttons. - Bilingual EN/DE: Every cog has a
MESSAGESdict withnormal/cafepersonalities anden/delanguages.get_lang(ctx)checksguild.preferred_locale. 4-level fallback inmsg(). - Personality:
PERSONALITY = "cafe"across all cogs for cozy café-themed text.
- Do NOT use
discord.TextChannel | Noneunion type hints — use= Nonedefault instead. - The
edittool can fail with "did not appear verbatim" for files with special chars (é, ü, ☕). Use thewritetool to fully rewrite such files. discord.ui.LayoutViewanddiscord.ui.Viewcannot be mixed in the same message send.discord.ui.Sectionrequires an accessory — if no image, use a plainTextDisplayas fallback.
- All bot emojis are registered as Application Emojis (owned by the bot itself, not any guild). No emoji server needed.
src/utils/emoji_sync.pyhandles the full lifecycle:- Parses
src/config/emojis.pyto find all<:name:id>/<a:name:id>references - Downloads each image to
src/assets/emojis/(cached — skips if already on disk) - Fetches the bot's existing application emojis via
bot.fetch_application_emojis() - Uploads any missing ones via
bot.create_application_emoji() - Rewrites
src/config/emojis.pywith the live application-emoji IDs
- Parses
- Runs automatically in the background on every
on_ready(non-blocking) - Dev commands (developer-only, accessible in any guild):
.syncemojis— manual re-sync with a stats report.appemojis— paginated list of all registered application emojis.emojistatus— diff between config emojis and registered application emojis
- OpenAI API via Replit integration (
python_openai_ai_integrations) - Memory stored in
memory.jsonwithusers,favorability,conversationskeys.
src/utils/ai_debugging.py— automatic error reporting + AI-assisted fixes- On unexpected command errors, posts to
AI_DEBUG_CHANNEL(set as env var) with full traceback - Buttons: AI Debug (explains root cause) → Fix with AI (rewrites cog, reloads it, saves backup) → Revert Fix (restores backup)
- Backups stored in
data/ai_debug_backups/
- Per-user JSON files:
economy_data/{user_id}.json - Starting balance: 100 coins
- Shop items: Coffee (50), Cake (120), Café Badge (300)
- Commands: balance, deposit, withdraw (supports "all"), daily, work, crime, rob, shop, buy, inventory, leaderboard, give, net_worth
- XP stored in
data/levels.jsonper guild/user - Random 15–25 XP per message (with optional per-guild multiplier and cooldown)
- XP formula:
5 * level² + 50 * level + 100 - Per-guild config in
data/level_config.json:xp_enabled,xp_multiplier,xp_cooldown,level_up_channel,level_roles - Automatic role assignment on level-up (
level_rolesdict:{level: role_id}) - Commands:
!level/!rank,!level-leaderboard/!lvl-lb !levelpanel(aliases:!lvlpanel,!lp) — interactive cv2 management panel with 4 sections:- Overview — all leveling settings at a glance
- XP Settings — toggle XP, edit multiplier + cooldown via modal
- Announcements — set level-up channel (ephemeral ChannelSelect) + custom level-up message with
{mention}{level}{name}{guild}placeholders (modal); reset to default - Level Roles — add (two-step: modal → ephemeral RoleSelect) / remove (ephemeral Select of current assignments)
- Custom level-up messages: stored as
level_up_messageindata/level_config.json; falls back to the café MESSAGES table if not set - Config group:
!levelconfig→ subcommands:toggle,multiplier,cooldown,levelupchannel,levelrole,resetuser
- Mod log channel stored via
moderation_utils.py - AutoMod config (antispam, antilink, badwords, massmention, thresholds) via
!automodinteractive dashboard- Dashboard sections: Overview, Message Filter, Anti-Nuke, Anti-Raid, Ext. App Raid, Whitelist
- All sections use cv2 LayoutView; toggle buttons + modals for all thresholds
- AutoMod Whitelist: Users and roles exempt from all automod checks
- Dashboard "Whitelist" section has ➕/➖ buttons that open ephemeral
UserSelect/RoleSelectdropdowns whitelist_users/whitelist_rolesstored indata/modconfig.jsonper guild- Also manageable via
!whitelist add/remove user/roletext commands is_whitelisted()check runs at the top of everyon_messagehandler
- Dashboard "Whitelist" section has ➕/➖ buttons that open ephemeral
- Full bilingual EN/DE
MESSAGEStable covering all commands inmoderation.py - Commands: kick, ban, unban, warn, warnings, clearwarnings, mute, tempmute, unmute, clear, purge, slowmode, lock, unlock, nick, setmodlog, automod, badwords, whitelist
- Detects raids driven by external tools (selfbots, raid apps) by tracking
on_interactionevents from recently-joined members - Config:
antiraid_ext—interaction_threshold,interaction_window,join_age_limit,raider_action,operator_action - Operator identification: snapshots invite use counts on every
on_member_join, then diffs against the current counts at detection time to find which invite saw the biggest spike and who created it - Invite cache populated on
on_ready,on_invite_create,on_invite_delete, and every join event - Response: punishes raiding accounts (kick/ban), then takes separate action on the identified operator (notify/kick/ban)
- Dashboard: "Ext. App Raid" section with toggle + threshold modal accessible from
!automod
- Tables:
giveaways(message_id, channel_id, guild_id, prize, winners_count, end_time, ended, host_id) andparticipants(message_id, user_id) indata/database.dbviabot.cxn - Persistent across restarts:
GiveawayPersistentViewregistered viabot.add_view()incog_load— only the two main-message buttons have fixedcustom_ids (giveaway_system_join,giveaway_system_manage) - Active giveaway messages show Join + Manage buttons. Manage opens an ephemeral management panel (host or server admin only) containing: End Giveaway, Select Random, and Participants buttons
- Management panel buttons are NOT persistent (ephemeral, created fresh per click); they use
interaction.response.edit_messageto update the panel in-place - Participants button edits the ephemeral panel to a
PaginatedView(15 per page, ◀/▶ nav) - Ended messages use
_build_ended_view()(LayoutView, no buttons);end_giveaway()edits the original public message - Full bilingual EN/DE + normal/cafe personality MESSAGES table;
msg(ctx_or_interaction, key)and_guild_msg(guild, key)helpers for task callbacks - Commands:
.giveaway start <duration> <winners> <prize>,.giveaway reroll <message_id> - Background task (
check_giveaways) fires every 15 s
- All persistent interactive components have
custom_idset so Discord can re-dispatch interactions after restartsOpenTicketBtn:custom_id=f"open_ticket_{guild_id}"CloseTicketBtn:custom_id="ticket_close"DeleteTicketBtn:custom_id="ticket_delete"AgreeButton(onboarding):custom_id=f"rules_agree_{guild_id}"RoleMenuSelect(onboarding):custom_id=f"role_menu_select_{guild_id}"
- View registration moved to
setup()functions (noton_ready) since cogs load inside bot'son_ready - Onboarding:
load_all_configs()inonboarding_config.pyiterates all guild JSON files to re-register views
- Lavalink-based music via wavelink
- Nodes loaded from AjieBlogs API on startup
- discord.py: Primary bot framework
- Requires:
DISCORD_BOT_TOKENenvironment secret
- OpenAI via Replit's built-in OpenAI integration (
AI_INTEGRATIONS_OPENAI_API_KEY,AI_INTEGRATIONS_OPENAI_BASE_URL) - Local TinyLlama model is lazy-loaded only if
USE_OPENAI = Falseinbot.py
- psutil: System resource monitoring
- requests: HTTP client
- beautifulsoup4: HTML parsing for notifier cog (Twitter/TikTok scraping)
- deep-translator: Google Translate API wrapper for translate_context cog
- langdetect: Language detection for translator utility
- Local filesystem: JSON files only, no external database
memory.json: AI memory and favorabilityeconomy_data/*.json: Per-user economy statedata/levels.json: Per-guild leveling datadata/mod_config.json: Per-guild moderation configdata/warnings.json: Per-guild warning records
- Removed top-level model loading from
bot.py(was downloading 600MB TinyLlama on every start) utils/ai_local.pynow lazy-loads the local model only when actually called- Replaced incompatible
googletranswithdeep-translator+langdetectto avoid httpx version conflicts - All 33 cogs load successfully on startup