Skip to content

Revamp bot with setup wizard and tests#1

Open
UN7X wants to merge 8 commits intomasterfrom
codex/revamp-bot,-add-unit-tests,-and-integrate-setup-wizard
Open

Revamp bot with setup wizard and tests#1
UN7X wants to merge 8 commits intomasterfrom
codex/revamp-bot,-add-unit-tests,-and-integrate-setup-wizard

Conversation

@UN7X
Copy link
Copy Markdown
Owner

@UN7X UN7X commented Jun 7, 2025

Summary

  • add psutil dependency
  • expose bot info with ping, shard and cpu load
  • introduce setup wizard for guild configuration
  • notify admins on guild join about setup
  • wrap bot run in __main__ guard
  • add unit tests for helpers

Testing

  • pytest -q

https://chatgpt.com/codex/tasks/task_e_684373bacd28832baf456a77e6db9559

Summary by Sourcery

Integrate psutil to report bot performance metrics, implement an interactive setup wizard with guild join notifications, wrap execution in a main guard, and add comprehensive unit tests for helper utilities.

New Features:

  • Expose bot info command with ping, shard ID, and CPU load
  • Add an interactive setup wizard for guild configuration
  • Notify guild admins upon bot join with setup instructions

Enhancements:

  • Wrap bot execution in a main guard
  • Enhance beta info command to include new bot statistics

Build:

  • Add psutil to project dependencies

Documentation:

  • Add help and man page entry for the setup command

Tests:

  • Introduce unit tests for helper functions including date calculation, uptime, database persistence, and get_bot_info

Summary by CodeRabbit

  • New Features

    • Introduced a modular Discord bot with advanced features including music playback, economy, leveling, moderation, games, fun commands, utility tools, auto-moderation, and server setup wizard.
    • Added interactive help menus, server and user info commands, and bot statistics.
    • Enabled dynamic server-specific configuration, including command prefix and economy type.
    • Implemented a full-featured music system with queue, radio, and volume controls.
    • Added paginated leaderboards for economy and leveling systems.
  • Bug Fixes

    • Improved error handling and user feedback for command usage and permission issues.
  • Chores

    • Added deployment and configuration files for streamlined setup.
    • Added automated tests to verify core bot functionalities.
    • Updated dependencies to support new features and integrations.

@sourcery-ai
Copy link
Copy Markdown

sourcery-ai bot commented Jun 7, 2025

Reviewer's Guide

This PR enhances the bot by exposing CPU load and shard info via a new helper, introducing an interactive setup wizard (with built-in help and guild-join notifications), wrapping execution in a main guard, and adding unit tests for core helpers.

Sequence Diagram for Bot Info Request (7/beta info)

sequenceDiagram
    actor User
    participant Bot
    participant Helper as get_bot_info()
    User->>Bot: Executes 7/beta info
    Bot->>Helper: Request bot information (ping, shard, CPU)
    Helper-->>Bot: Returns ping_ms, shard_id, cpu_load
    Bot-->>User: Sends bot info (Build ID, Uptime, Ping, Shard, CPU)
Loading

Sequence Diagram for New Guild Join Notification

sequenceDiagram
    participant Bot
    participant DiscordGuild as Discord Guild
    Bot->>DiscordGuild: Bot joins guild
    activate Bot
    Note over Bot: on_guild_join event triggered
    Bot->>DiscordGuild: Identifies system_channel or suitable text_channel
    Bot->>DiscordGuild: Sends setup instruction message to channel
    deactivate Bot
Loading

Entity Relationship Diagram for Guild Configuration Data

erDiagram
    GuildConfiguration {
        string guild_id PK "Guild's unique ID"
        string strike_roles "List of role IDs (stored as list of IDs)"
        boolean ai_enabled "AI features enabled (true/false)"
        string economy "Economy type (e.g., regular, prankful, none)"
    }
    note "Represents the configuration stored per guild in the database."
Loading

Class Diagram: Bot Command Additions and New Helper Functions

classDiagram
  class BotInstance {
    + <<command>> setup_wizard(Context, Optional[str])
    + <<event>> on_guild_join(Guild)
    + <<command>> beta(Context, Optional[str], Optional[str])
  }
  class HelperFunctions {
    + get_bot_info(Bot, Optional[Context]): dict
  }
  BotInstance --|> discord.ext.commands.Bot
  BotInstance ..> HelperFunctions : uses get_bot_info()
  note for BotInstance "Represents the bot instance with new/modified commands and events."
  note for HelperFunctions "New helper functions."
Loading

File-Level Changes

Change Details Files
Expose detailed bot info including ping, shard ID, and CPU load
  • Add psutil to requirements and import it
  • Implement get_bot_info() to return ping_ms, shard_id, cpu_load
  • Update beta/info command to include new metrics
7xbot.py
requirements.txt
Implement interactive guild setup wizard
  • Define setup_explanation and register it in man_pages
  • Add @bot.command 'setup' with manage_guild guard and help subcommand
  • Prompt for strike roles, AI enablement, and economy type, handling 'cancel'
  • Persist collected config to db and confirm completion
7xbot.py
Notify guild admins on join to run setup
  • Add on_guild_join event listener
  • Select system or first writable text channel
  • Send onboarding message with setup instructions
7xbot.py
Wrap bot startup in a main guard
  • Replace direct bot.run() call with if name == 'main': bot.run(my_secret)
7xbot.py
Add unit tests for helper functions and new features
  • Test days_until_christmas() returns 0–366
  • Verify get_uptime() yields non-empty result
  • Validate save_db/load_db roundtrip
  • Test get_bot_info() returns expected keys
tests/test_basic.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

Copy link
Copy Markdown

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey @UN7X - I've reviewed your changes - here's some feedback:

  • Add timeouts (and corresponding timeout handlers) to each bot.wait_for in the setup wizard to avoid hanging indefinitely if a user never responds.
  • Refactor the helper functions (e.g. days_until_christmas, get_bot_info, DB helpers) into a separate util module so tests can import them without loading the full bot and side effects.
  • Wrap your save_db/load_db calls in try/except around file I/O during setup to handle and report errors rather than letting the bot crash on corrupt or inaccessible files.
Here's what I looked at during the review
  • 🟡 General issues: 5 issues found
  • 🟢 Security: all looks good
  • 🟡 Testing: 3 issues found
  • 🟡 Complexity: 1 issue found
  • 🟢 Documentation: all looks good

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

"""Return basic bot info such as ping, shard ID and CPU load."""
ping_ms = round(bot.latency * 1000)
shard_id = ctx.guild.shard_id if ctx and ctx.guild else 0
load = psutil.cpu_percent(interval=None)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: Consider measuring bot process CPU instead of system-wide.

psutil.cpu_percent() gives system-wide CPU usage. For the bot's own usage, use psutil.Process().cpu_percent() or set a short interval for per-process data.

7xbot.py Outdated
config = {}

await ctx.send("Enter strike roles separated by spaces or 'none':")
msg = await bot.wait_for('message', check=check)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (bug_risk): Add a timeout to wait_for to avoid hanging indefinitely.

Use the timeout parameter with wait_for and handle asyncio.TimeoutError to prevent the bot from getting stuck if the user doesn't respond.

Suggested implementation:

    config = {}

    await ctx.send("Enter strike roles separated by spaces or 'none':")
    try:
        msg = await bot.wait_for('message', check=check, timeout=60)
    except asyncio.TimeoutError:
        await ctx.send("Timed out waiting for a response. Setup cancelled.")
        return
    if msg.content.lower() == 'cancel':
        await ctx.send("Setup cancelled.")
        return
    config['strike_roles'] = [r.id for r in msg.role_mentions]

    await ctx.send("Enable AI features? (yes/no):")
    try:
        msg = await bot.wait_for('message', check=check, timeout=60)
    except asyncio.TimeoutError:
        await ctx.send("Timed out waiting for a response. Setup cancelled.")
        return
    if msg.content.lower() == 'cancel':
        await ctx.send("Setup cancelled.")
        return
    config['ai_enabled'] = msg.content.lower().startswith('y')
        return

    await ctx.send("Starting setup wizard. Reply with 'cancel' at any time to stop.")

    import asyncio

    def check(m: discord.Message):
        return m.author == ctx.author and m.channel == ctx.channel

7xbot.py Outdated

config = {}

await ctx.send("Enter strike roles separated by spaces or 'none':")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: Prompt and parsing for roles don’t align.

Update the prompt to match the expected input, or add logic to resolve role names from the message content.

7xbot.py Outdated
if msg.content.lower() == 'cancel':
await ctx.send("Setup cancelled.")
return
config['economy'] = msg.content.lower()
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (bug_risk): Economy input isn’t validated.

Validate that the input is one of the allowed options and handle invalid responses appropriately.

assert 0 <= days <= 366


def test_get_uptime_non_empty():
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): This test might fail due to bot.start_time being None and the assertion could be more specific.

Since bot.start_time is not set during tests, get_uptime() may raise a TypeError. Set botmod.bot.start_time to a valid timestamp at the start of the test. Also, make the assertion more specific by checking the format or value of the returned uptime string, rather than just its truthiness.

Comment on lines +24 to +29
def test_db_save_load(tmp_path: Path):
data = {"a": 1}
file = tmp_path / "db.json"
botmod.save_db(data, filename=str(file))
loaded = botmod.load_db(filename=str(file))
assert loaded == data
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (testing): Consider adding test cases for load_db edge conditions.

Please add tests for when the database file is missing (should return {}) and for saving/loading an empty dictionary.

Suggested change
def test_db_save_load(tmp_path: Path):
data = {"a": 1}
file = tmp_path / "db.json"
botmod.save_db(data, filename=str(file))
loaded = botmod.load_db(filename=str(file))
assert loaded == data
def test_db_save_load(tmp_path: Path):
data = {"a": 1}
file = tmp_path / "db.json"
botmod.save_db(data, filename=str(file))
loaded = botmod.load_db(filename=str(file))
assert loaded == data
def test_load_db_missing_file(tmp_path: Path):
file = tmp_path / "missing.json"
loaded = botmod.load_db(filename=str(file))
assert loaded == {}
def test_db_save_load_empty_dict(tmp_path: Path):
data = {}
file = tmp_path / "empty.json"
botmod.save_db(data, filename=str(file))
loaded = botmod.load_db(filename=str(file))
assert loaded == data

@@ -0,0 +1,38 @@
import types
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (testing): The new setup_wizard command, a key feature, lacks automated tests.

Please add unit tests for setup_wizard, including: (1) a full successful flow with mocked user input, (2) handling of 'cancel' at various steps, (3) the help scenario, and (4) verification of correct data persistence. Use mocks for Discord objects and database interactions as needed. Tests can go in this or a new test file.

else:
await ctx.send(f"""Error: Expected boolean value (true/false) or 'help', received: "{state}" """)

@bot.command(name="setup")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (complexity): Consider refactoring the setup wizard by extracting repeated prompt/wait/cancel logic into a helper function and driving the flow from a list of questions.

It’s working—but the `setup_wizard` is very boilerplate-heavy. You can collapse each “send prompt → wait → cancel check → parse” into a helper and drive it from a small list of questions. For example:

```python
class SetupCancelled(Exception):
    pass

async def ask(ctx, bot, prompt: str, parser: Callable[[discord.Message], Any]):
    await ctx.send(prompt)
    msg = await bot.wait_for("message", check=lambda m: m.author == ctx.author and m.channel == ctx.channel)
    if msg.content.lower() == "cancel":
        await ctx.send("Setup cancelled.")
        raise SetupCancelled
    return parser(msg)

Then rewrite setup_wizard like this:

@bot.command(name="setup")
@commands.has_permissions(manage_guild=True)
async def setup_wizard(ctx, *, args: Optional[str] = None):
    if args:
        return await ctx.send(embed=discord.Embed(
            title="Setup Command Help",
            description=setup_explanation,
            color=0x00ff00
        ))

    await ctx.send("Starting setup wizard. Reply with 'cancel' at any time to stop.")
    questions = [
        ("strike_roles",
         "Enter strike roles separated by spaces or 'none':",
         lambda m: [r.id for r in m.role_mentions]
        ),
        ("ai_enabled",
         "Enable AI features? (yes/no):",
         lambda m: m.content.lower().startswith("y")
        ),
        ("economy",
         "Economy type (regular/prankful/none):",
         lambda m: m.content.lower()
        ),
    ]

    config = {}
    try:
        for key, prompt, parser in questions:
            config[key] = await ask(ctx, bot, prompt, parser)
    except SetupCancelled:
        return

    db.setdefault("config", {})[str(ctx.guild.id)] = config
    save_db(db)
    await ctx.send("Setup complete.")

This:

  • Eliminates repeated wait_for/cancel blocks
  • Keeps logic linear and focused
  • Remains fully compatible with your current flow

Comment on lines +422 to +425
# Notify guild admins about setup
channel = guild.system_channel or next((c for c in guild.text_channels if c.permissions_for(guild.me).send_messages), None)
if channel:
await channel.send("Thanks for adding me! Server admins can run `7/setup` to configure the bot.")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (code-quality): Use named expression to simplify assignment and conditional (use-named-expression)

Suggested change
# Notify guild admins about setup
channel = guild.system_channel or next((c for c in guild.text_channels if c.permissions_for(guild.me).send_messages), None)
if channel:
await channel.send("Thanks for adding me! Server admins can run `7/setup` to configure the bot.")
if channel := guild.system_channel or next(
(
c
for c in guild.text_channels
if c.permissions_for(guild.me).send_messages
),
None,
):
await channel.send("Thanks for adding me! Server admins can run `7/setup` to configure the bot.")

UN7X and others added 4 commits June 6, 2025 20:05
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jun 8, 2025

Walkthrough

This update introduces a comprehensive, modular Discord bot with advanced features. It adds a dynamic configuration system, asynchronous PostgreSQL database integration, and a main entry point for bot startup and error handling. Multiple cogs are implemented for moderation, automoderation, leveling, economy, music playback, fun, games, admin controls, and utility commands. Deployment and dependency files are added, alongside a suite of automated tests.

Changes

File(s) Change Summary
.python-version
Procfile
app.json
Added project metadata/configuration files specifying Python version, deployment process, and environment variables for Heroku deployment.
requirements.txt Updated to specify minimum versions and added dependencies for Discord, database, audio, and utility libraries.
cookies.txt Added Netscape-format cookie file with YouTube cookies for media features.
config.py Introduced a Config dataclass to centralize bot configuration using environment variables.
database.py Added Database class for async PostgreSQL operations, including schema setup and methods for guild/user data.
main.py Implemented AdvancedBot class, bot initialization, cog loading, message handling, error handling, and clean shutdown.
7xbot.py Refactored and expanded: added interactive setup wizard, improved shop system, info commands, error handling, and CPU load monitoring.
cogs/admin.py New admin cog with owner-only commands for cog management, shutdown, eval, SQL, and guild listing.
cogs/automod.py New automoderation cog for spam, bad word, caps, and mention checks, plus configuration commands.
cogs/economy.py New economy cog with commands for balance, daily, work, rob, gamble, deposit, withdraw, pay, and leaderboard.
cogs/fun.py New fun cog with commands for 8ball, flip, roll, choose, joke, meme, fact, quote, reverse, ascii art, and poll.
cogs/games.py New games cog with Rock Paper Scissors, number guessing, trivia, and Tic Tac Toe commands.
cogs/leveling.py New leveling cog for XP, levels, leaderboard, and rank, with message-based XP gain and cooldowns.
cogs/moderation.py New moderation cog for kick, ban, unban, mute, unmute, warn, warnings, and clear commands, with role/permission checks.
cogs/music.py New music cog with queue, radio, playback, volume, and voice channel management using FFmpeg and async queueing.
cogs/utility.py New utility cog with info commands (ping, serverinfo, userinfo, avatar, botinfo, invite, prefix) and an interactive help system.
tests/test_basic.py Added test suite for core bot logic, setup wizard, shop, manual command, and database save/load operations.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant Discord
    participant AdvancedBot
    participant Database
    participant Cog

    User->>Discord: Sends command message
    Discord->>AdvancedBot: Receives message
    AdvancedBot->>Database: (If needed) Fetch prefix/guild/user data
    AdvancedBot->>Cog: Dispatches command to appropriate cog
    Cog->>Database: (If needed) Read/write data (e.g., economy, leveling)
    Cog->>Discord: Sends embed/response
Loading
sequenceDiagram
    participant Admin
    participant AdvancedBot
    participant AdminCog

    Admin->>AdvancedBot: Uses admin command (e.g., reload, eval)
    AdvancedBot->>AdminCog: Invokes command
    AdminCog->>AdvancedBot: Loads/unloads/reloads cogs, evaluates code, executes SQL
    AdminCog->>Admin: Sends result embed
Loading
sequenceDiagram
    participant User
    participant AdvancedBot
    participant MusicCog
    participant VoiceChannel

    User->>AdvancedBot: play <url>/radio:<station>
    AdvancedBot->>MusicCog: Invokes play command
    MusicCog->>VoiceChannel: Joins/plays audio
    MusicCog->>User: Sends now playing embed
Loading

Poem

In burrows deep, this bot now grows,
With cogs and gears, its wisdom shows.
From games to tunes, from mods to fun,
A rabbit’s work is never done!
With tests and docs, deployment’s near—
Hop along, the bot is here!

((\
( -.-)
o_(")(")

✨ Finishing Touches
  • 📝 Generate Docstrings

🪧 Tips

Chat

There are 3 ways to chat with CodeRabbit:

  • Review comments: Directly reply to a review comment made by CodeRabbit. Example:
    • I pushed a fix in commit <commit_id>, please review it.
    • Explain this complex logic.
    • Open a follow-up GitHub issue for this discussion.
  • Files and specific lines of code (under the "Files changed" tab): Tag @coderabbitai in a new review comment at the desired location with your query. Examples:
    • @coderabbitai explain this code block.
    • @coderabbitai modularize this function.
  • PR comments: Tag @coderabbitai in a new PR comment to ask questions about the PR branch. For the best results, please provide a very specific query, as very limited context is provided in this mode. Examples:
    • @coderabbitai gather interesting stats about this repository and render them as a table. Additionally, render a pie chart showing the language distribution in the codebase.
    • @coderabbitai read src/utils.ts and explain its main purpose.
    • @coderabbitai read the files in the src/scheduler package and generate a class diagram using mermaid and a README in the markdown format.
    • @coderabbitai help me debug CodeRabbit configuration file.

Support

Need help? Create a ticket on our support page for assistance with any issues or questions.

Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments.

CodeRabbit Commands (Invoked using PR comments)

  • @coderabbitai pause to pause the reviews on a PR.
  • @coderabbitai resume to resume the paused reviews.
  • @coderabbitai review to trigger an incremental review. This is useful when automatic reviews are disabled for the repository.
  • @coderabbitai full review to do a full review from scratch and review all the files again.
  • @coderabbitai summary to regenerate the summary of the PR.
  • @coderabbitai generate docstrings to generate docstrings for this PR.
  • @coderabbitai generate sequence diagram to generate a sequence diagram of the changes in this PR.
  • @coderabbitai resolve resolve all the CodeRabbit review comments.
  • @coderabbitai configuration to show the current CodeRabbit configuration for the repository.
  • @coderabbitai help to get help.

Other keywords and placeholders

  • Add @coderabbitai ignore anywhere in the PR description to prevent this PR from being reviewed.
  • Add @coderabbitai summary to generate the high-level summary at a specific location in the PR description.
  • Add @coderabbitai anywhere in the PR title to generate the title automatically.

CodeRabbit Configuration File (.coderabbit.yaml)

  • You can programmatically configure CodeRabbit by adding a .coderabbit.yaml file to the root of your repository.
  • Please see the configuration documentation for more information.
  • If your editor has YAML language server enabled, you can add the path at the top of this file to enable auto-completion and validation: # yaml-language-server: $schema=https://coderabbit.ai/integrations/schema.v2.json

Documentation and Community

  • Visit our Documentation for detailed information on how to use CodeRabbit.
  • Join our Discord Community to get help, request features, and share feedback.
  • Follow us on X/Twitter for updates and announcements.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 33

🔭 Outside diff range comments (1)
cookies.txt (1)

1-11: ⚠️ Potential issue

Critical: Remove sensitive cookies from version control

This file contains real authentication cookies that could be used to impersonate the account owner. Committing cookies to a repository is a severe security risk, especially for public repositories.

Apply this diff to remove the sensitive data:

-# Netscape HTTP Cookie File
-# http://curl.haxx.se/rfc/cookie_spec.html
-# This is a generated file!  Do not edit.
-
-.youtube.com	TRUE	/	TRUE	1749307322	GPS	1
-.youtube.com	TRUE	/	TRUE	1783865541	PREF	f4=4000000&f6=40000000&tz=Europe.Istanbul&f7=100
-.youtube.com	TRUE	/	TRUE	0	YSC	DlJrprbNfnA
-.youtube.com	TRUE	/	TRUE	1764857524	VISITOR_INFO1_LIVE	XscOorzO6Cs
-.youtube.com	TRUE	/	TRUE	1764857524	VISITOR_PRIVACY_METADATA	CgJUUhIEGgAgVw%3D%3D
-.youtube.com	TRUE	/	TRUE	1764857524	__Secure-ROLLOUT_TOKEN	CIanydmQofiWvwEQwoaf9L7fjQMYxpWc9b7fjQM%3D
+# This file should not contain actual cookies
+# Use environment variables or a secure vault for authentication

Additionally:

  1. Add cookies.txt to .gitignore to prevent accidental commits
  2. Rotate/invalidate these exposed cookies immediately
  3. Use environment variables or a secure secrets management system for authentication tokens

Would you like me to help set up a secure authentication system using environment variables or create documentation for managing cookies securely?

♻️ Duplicate comments (3)
cogs/economy.py (1)

238-244: Same missing-row issue as above for withdraw

Mirror the fix applied to deposit.

cogs/moderation.py (2)

62-67: Same suppression issue as above in ban – apply the same pattern.

🧰 Tools
🪛 Ruff (0.11.9)

62-65: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


64-64: Do not use bare except

(E722)


185-188: Repeat of bare except in warn – convert to suppress or catch concrete exceptions.

🧰 Tools
🪛 Ruff (0.11.9)

185-188: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


187-187: Do not use bare except

(E722)

🧹 Nitpick comments (20)
config.py (1)

8-9: Initialize OWNER_IDS to avoid attribute errors

The OWNER_IDS field is initialized as None but will always be overwritten to a list in __post_init__. Initialize it directly as an empty list for clarity.

Apply this diff:

-    OWNER_IDS: List[int] = None
+    OWNER_IDS: List[int] = field(default_factory=list)

Also add the import:

from dataclasses import dataclass, field
database.py (2)

1-6: Remove unused imports

Several imports are not used in this file and should be removed to keep the code clean.

Apply this diff:

 import asyncpg
-import asyncio
-from typing import Optional, List, Dict, Any
-import json
+from typing import Optional, List, Dict
 import os
-from urllib.parse import urlparse
🧰 Tools
🪛 Ruff (0.11.9)

2-2: asyncio imported but unused

Remove unused import: asyncio

(F401)


3-3: typing.Any imported but unused

Remove unused import: typing.Any

(F401)


4-4: json imported but unused

Remove unused import: json

(F401)


6-6: urllib.parse.urlparse imported but unused

Remove unused import: urllib.parse.urlparse

(F401)


141-154: Extract level calculation formula

The level calculation formula int(new_xp ** (1/4)) is hardcoded and could be difficult to maintain or adjust.

Consider extracting it to a method or constant:

def calculate_level(xp: int) -> int:
    """Calculate level from XP using the fourth root formula"""
    return int(xp ** 0.25)

Then use it in the add_xp method:

-        new_level = int(new_xp ** (1/4))  # Level formula
+        new_level = calculate_level(new_xp)
tests/test_basic.py (2)

6-6: Remove unused pytest import.

The static analysis correctly identifies that pytest is imported but not used in the code.

-import pytest
🧰 Tools
🪛 Ruff (0.11.9)

6-6: pytest imported but unused

Remove unused import: pytest

(F401)


8-12: Consider using a more robust module import approach.

The dynamic module loading works but could be fragile if file paths change. Consider using a more standard import approach or adding error handling.

-spec = importlib.util.spec_from_file_location(
-    "botmod", pathlib.Path(__file__).resolve().parents[1] / "7xbot.py"
-)
-botmod = importlib.util.module_from_spec(spec)
-spec.loader.exec_module(botmod)
+try:
+    spec = importlib.util.spec_from_file_location(
+        "botmod", pathlib.Path(__file__).resolve().parents[1] / "7xbot.py"
+    )
+    botmod = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(botmod)
+except (FileNotFoundError, AttributeError) as e:
+    pytest.skip(f"Could not load 7xbot.py: {e}")
cogs/leveling.py (3)

24-26: Flatten nested condition for readability

The double-nested if can be collapsed without changing behaviour:

-        if user_key in self.xp_cooldowns:
-            if now < self.xp_cooldowns[user_key]:
-                return
+        if user_key in self.xp_cooldowns and now < self.xp_cooldowns[user_key]:
+            return
🧰 Tools
🪛 Ruff (0.11.9)

24-25: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)


31-33: XP random range can break if XP_PER_MESSAGE < 10

random.randint(10, self.bot.config.XP_PER_MESSAGE) raises ValueError when the upper bound is < 10. Consider exposing both min / max values in config:

-        xp_gained = random.randint(10, self.bot.config.XP_PER_MESSAGE)
+        low = self.bot.config.get("XP_MIN_PER_MESSAGE", 5)
+        high = self.bot.config.XP_PER_MESSAGE
+        xp_gained = random.randint(low, high)

12-30: Potential unbounded growth of xp_cooldowns

self.xp_cooldowns never evicts old keys, so long-running bots will leak memory.
Use collections.OrderedDict with size cap, or periodically purge entries whose
expiry < now.

Would you like a compact helper to auto-expire keys?

🧰 Tools
🪛 Ruff (0.11.9)

24-25: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)

cogs/automod.py (1)

18-39: Ensure commands still run after moderation checks

Depending on dispatch order, on_message here may run after the bot’s global
on_message that calls process_commands, preventing moderated messages from
ever becoming commands. To be safe:

await self.bot.process_commands(message)

at the very end of the listener (guarded by the same early returns).

cogs/utility.py (1)

385-395: Avoid one-liners with two statements; switch to a mapping

The chained if … : emoji = … statements violate E701 and are brittle. A small mapping is clearer and extendable.

-emoji = "🔧"
-if "🛡️" in cog.description: emoji = "🛡️"
-elif "🎵" in cog.description: emoji = "🎵"
-elif "🎮" in cog.description: emoji = "🎮"
-elif "💰" in cog.description: emoji = "💰"
-elif "📊" in cog.description: emoji = "📊"
-elif "⚙️" in cog.description: emoji = "⚙️"
-elif "🎯" in cog.description: emoji = "🎯"
-elif "🤖" in cog.description: emoji = "🤖"
+emoji_lookup = {
+    "🛡️": "🛡️", "🎵": "🎵", "🎮": "🎮", "💰": "💰",
+    "📊": "📊", "⚙️": "⚙️", "🎯": "🎯", "🤖": "🤖",
+}
+emoji = next((e for e in emoji_lookup if e in (cog.description or "")), "🔧")
🧰 Tools
🪛 Ruff (0.11.9)

387-387: Multiple statements on one line (colon)

(E701)


388-388: Multiple statements on one line (colon)

(E701)


389-389: Multiple statements on one line (colon)

(E701)


390-390: Multiple statements on one line (colon)

(E701)


391-391: Multiple statements on one line (colon)

(E701)


392-392: Multiple statements on one line (colon)

(E701)


393-393: Multiple statements on one line (colon)

(E701)


394-394: Multiple statements on one line (colon)

(E701)

main.py (2)

3-9: Prune unused imports

asyncio and json are never referenced here. Drop them to keep startup light and silence lint.

-import asyncio
 ...
-import json
🧰 Tools
🪛 Ruff (0.11.9)

3-3: asyncio imported but unused

Remove unused import: asyncio

(F401)


8-8: json imported but unused

Remove unused import: json

(F401)


141-145: Use sys.exit for clarity

Minor, but sys.exit(1) conveys intent better than naked exit(1) and plays nicer with tooling.

-        exit(1)
+        import sys
+        sys.exit(1)
🧰 Tools
🪛 Pylint (3.3.7)

[refactor] 144-144: Consider using 'sys.exit' instead

(R1722)

cogs/fun.py (1)

159-180: Remove unused exception variables.

The exception handlers capture exceptions in variable e but don't use them.

Apply this diff to remove the unused variables:

-    except Exception as e:
+    except Exception:
         await ctx.send("❌ Error fetching fact!")
-    except Exception as e:
+    except Exception:
         await ctx.send("❌ Error fetching quote!")

Also applies to: 182-204

🧰 Tools
🪛 Ruff (0.11.9)

179-179: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

cogs/music.py (1)

201-209: Use contextlib.suppress for cleaner exception handling.

The empty exception handlers can be simplified using contextlib.suppress.

Apply this diff to improve readability:

+from contextlib import suppress
+
 async def cleanup(self, guild):
     """Cleanup the guild player."""
-    try:
+    with suppress(AttributeError):
         await guild.voice_client.disconnect()
-    except AttributeError:
-        pass
         
-    try:
+    with suppress(KeyError):
         del self.players[guild.id]
-    except KeyError:
-        pass
🧰 Tools
🪛 Ruff (0.11.9)

201-204: Use contextlib.suppress(AttributeError) instead of try-except-pass

Replace with contextlib.suppress(AttributeError)

(SIM105)


206-209: Use contextlib.suppress(KeyError) instead of try-except-pass

Replace with contextlib.suppress(KeyError)

(SIM105)

7xbot.py (4)

423-428: Duplicate on_guild_join handler may shadow the one in main.py

A second handler means the earlier one (or this one) will never fire, depending on load order.
Confirm you really need two and consolidate if possible.


860-897: Minor efficiency / style tweaks for shop commands

  • Use a dict-lookup once (details = items.get(item) is 👍).
  • Consider wrapping the balance check & update in a single helper to avoid race-conditions
    if you ever persist points asynchronously.

1162-1167: Second on_command_error – clashes with earlier global handler

Only the last definition registered wins. Merge the missing-argument message into the
existing handler instead of creating a new event.


1290-1299: get_shop_items_for_guild can return the same dict for all guilds – beware mutation

If a caller mutates the returned dict, it will affect every guild using the same economy.
Return a shallow copy:

return shop_items_regular.copy()
cogs/moderation.py (2)

28-33: Replace bare try/except/pass with contextlib.suppress

Cleaner and avoids swallowing unexpected exceptions:

-import contextlib
-
-try:
-    await member.send(...)
-except:
-    pass
+from contextlib import suppress
+
+with suppress(discord.HTTPException):
+    await member.send(...)
🧰 Tools
🪛 Ruff (0.11.9)

28-31: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


30-30: Do not use bare except

(E722)


220-228: Unused loop index & long line in warnings embed

i isn’t used – rename to _ to silence linters.
Long f-string also crosses 120 chars; consider breaking for readability.

🧰 Tools
🪛 Ruff (0.11.9)

220-220: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

📜 Review details

Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 1d53ae1 and a8a7d81.

📒 Files selected for processing (19)
  • .python-version (1 hunks)
  • 7xbot.py (11 hunks)
  • Procfile (1 hunks)
  • app.json (1 hunks)
  • cogs/admin.py (1 hunks)
  • cogs/automod.py (1 hunks)
  • cogs/economy.py (1 hunks)
  • cogs/fun.py (1 hunks)
  • cogs/games.py (1 hunks)
  • cogs/leveling.py (1 hunks)
  • cogs/moderation.py (1 hunks)
  • cogs/music.py (1 hunks)
  • cogs/utility.py (1 hunks)
  • config.py (1 hunks)
  • cookies.txt (1 hunks)
  • database.py (1 hunks)
  • main.py (1 hunks)
  • requirements.txt (1 hunks)
  • tests/test_basic.py (1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (6)
cogs/automod.py (3)
cogs/leveling.py (1)
  • on_message (15-46)
main.py (1)
  • on_message (130-133)
database.py (1)
  • add_warning (156-161)
cogs/leveling.py (3)
cogs/automod.py (2)
  • on_message (19-38)
  • setup (220-221)
main.py (1)
  • on_message (130-133)
database.py (3)
  • add_xp (141-154)
  • get_user_level (131-139)
  • setup (17-81)
main.py (11)
config.py (1)
  • Config (6-42)
database.py (5)
  • Database (8-184)
  • get_guild_prefix (83-88)
  • setup (17-81)
  • setup_guild (101-110)
  • close (181-184)
cogs/leveling.py (3)
  • level (54-85)
  • setup (155-156)
  • on_message (15-46)
cogs/utility.py (2)
  • prefix (285-309)
  • setup (409-410)
cogs/admin.py (1)
  • setup (204-205)
cogs/automod.py (2)
  • setup (220-221)
  • on_message (19-38)
cogs/fun.py (1)
  • setup (298-299)
cogs/economy.py (1)
  • setup (318-319)
cogs/games.py (1)
  • setup (323-324)
cogs/moderation.py (1)
  • setup (257-258)
cogs/music.py (1)
  • setup (580-581)
cogs/admin.py (10)
cogs/games.py (1)
  • setup (323-324)
database.py (2)
  • close (181-184)
  • setup (17-81)
main.py (1)
  • close (122-128)
cogs/music.py (1)
  • setup (580-581)
cogs/automod.py (1)
  • setup (220-221)
cogs/fun.py (1)
  • setup (298-299)
cogs/economy.py (1)
  • setup (318-319)
cogs/leveling.py (1)
  • setup (155-156)
cogs/moderation.py (1)
  • setup (257-258)
cogs/utility.py (1)
  • setup (409-410)
database.py (2)
cogs/utility.py (1)
  • prefix (285-309)
main.py (1)
  • close (122-128)
7xbot.py (2)
tests/test_basic.py (6)
  • guild (67-68)
  • guild (109-110)
  • guild (144-145)
  • send (63-64)
  • send (105-106)
  • send (140-141)
main.py (2)
  • on_guild_join (84-103)
  • on_command_error (105-120)
🪛 Pylint (3.3.7)
config.py

[refactor] 6-6: Too many instance attributes (13/7)

(R0902)

cogs/utility.py

[refactor] 316-316: Too many local variables (16/15)

(R0914)


[refactor] 316-316: Too many branches (24/12)

(R0912)


[refactor] 316-316: Too many statements (51/50)

(R0915)

main.py

[error] 133-133: Possibly using variable 'bot' before assignment

(E0606)


[refactor] 144-144: Consider using 'sys.exit' instead

(R1722)

cogs/games.py

[refactor] 34-36: Too many boolean expressions in if statement (6/5)

(R0916)


[refactor] 127-127: Too many local variables (16/15)

(R0914)


[refactor] 214-214: Too many local variables (20/15)

(R0914)

cogs/admin.py

[error] 13-13: Method 'is_owner' has no argument

(E0211)


[error] 24-24: Too many positional arguments for method call

(E1121)


[error] 48-48: Too many positional arguments for method call

(E1121)


[error] 72-72: Too many positional arguments for method call

(E1121)


[error] 96-96: Too many positional arguments for method call

(E1121)


[error] 112-112: Too many positional arguments for method call

(E1121)


[error] 144-144: Too many positional arguments for method call

(E1121)


[error] 184-184: Too many positional arguments for method call

(E1121)

cogs/music.py

[refactor] 15-15: Too many instance attributes (8/7)

(R0902)


[refactor] 18-18: Too many arguments (8/5)

(R0913)


[refactor] 18-18: Too many positional arguments (8/5)

(R0917)


[refactor] 30-30: Too many arguments (7/5)

(R0913)


[refactor] 15-15: Too few public methods (1/2)

(R0903)


[refactor] 96-96: Too many instance attributes (9/7)

(R0902)


[refactor] 215-230: Too many nested blocks (6/5)

(R1702)

7xbot.py

[error] 70-70: Undefined variable 'ctx'

(E0602)


[error] 70-70: Undefined variable 'ctx'

(E0602)


[error] 70-70: Undefined variable 'ctx'

(E0602)


[error] 438-438: Undefined variable 'get_bot_info'

(E0602)

🪛 Ruff (0.11.9)
cogs/economy.py

4-4: datetime.datetime imported but unused

Remove unused import

(F401)


4-4: datetime.timedelta imported but unused

Remove unused import

(F401)


5-5: asyncio imported but unused

Remove unused import: asyncio

(F401)

tests/test_basic.py

6-6: pytest imported but unused

Remove unused import: pytest

(F401)

cogs/automod.py

3-3: re imported but unused

Remove unused import: re

(F401)


4-4: asyncio imported but unused

Remove unused import: asyncio

(F401)

cogs/utility.py

3-3: asyncio imported but unused

Remove unused import: asyncio

(F401)


4-4: aiohttp imported but unused

Remove unused import: aiohttp

(F401)


7-7: psutil imported but unused

Remove unused import: psutil

(F401)


8-8: os imported but unused

Remove unused import: os

(F401)


9-9: math imported but unused

Remove unused import: math

(F401)


387-387: Multiple statements on one line (colon)

(E701)


388-388: Multiple statements on one line (colon)

(E701)


389-389: Multiple statements on one line (colon)

(E701)


390-390: Multiple statements on one line (colon)

(E701)


391-391: Multiple statements on one line (colon)

(E701)


392-392: Multiple statements on one line (colon)

(E701)


393-393: Multiple statements on one line (colon)

(E701)


394-394: Multiple statements on one line (colon)

(E701)

cogs/leveling.py

3-3: asyncio imported but unused

Remove unused import: asyncio

(F401)


24-25: Use a single if statement instead of nested if statements

Combine if statements using and

(SIM102)

main.py

3-3: asyncio imported but unused

Remove unused import: asyncio

(F401)


8-8: json imported but unused

Remove unused import: json

(F401)

cogs/games.py

274-274: Function definition does not bind loop variable current_player

(B023)

cogs/fun.py

4-4: aiohttp imported but unused

Remove unused import: aiohttp

(F401)


5-5: asyncio imported but unused

Remove unused import: asyncio

(F401)


156-156: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


179-179: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)


203-203: Local variable e is assigned to but never used

Remove assignment to unused variable e

(F841)

cogs/admin.py

4-4: sys imported but unused

Remove unused import: sys

(F401)


5-5: traceback imported but unused

Remove unused import: traceback

(F401)

database.py

2-2: asyncio imported but unused

Remove unused import: asyncio

(F401)


3-3: typing.Any imported but unused

Remove unused import: typing.Any

(F401)


4-4: json imported but unused

Remove unused import: json

(F401)


6-6: urllib.parse.urlparse imported but unused

Remove unused import: urllib.parse.urlparse

(F401)

cogs/music.py

5-5: re imported but unused

Remove unused import: re

(F401)


6-6: json imported but unused

Remove unused import: json

(F401)


7-7: random imported but unused

Remove unused import: random

(F401)


10-10: functools imported but unused

Remove unused import: functools

(F401)


11-11: collections.deque imported but unused

Remove unused import: collections.deque

(F401)


12-12: urllib.parse.urlparse imported but unused

Remove unused import: urllib.parse.urlparse

(F401)


13-13: mimetypes imported but unused

Remove unused import: mimetypes

(F401)


201-204: Use contextlib.suppress(AttributeError) instead of try-except-pass

Replace with contextlib.suppress(AttributeError)

(SIM105)


206-209: Use contextlib.suppress(KeyError) instead of try-except-pass

Replace with contextlib.suppress(KeyError)

(SIM105)


218-220: Use a single if statement instead of nested if statements

(SIM102)


224-227: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


226-226: Do not use bare except

(E722)


248-248: Do not use bare except

(E722)


264-264: Do not use bare except

(E722)

cogs/moderation.py

28-31: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


30-30: Do not use bare except

(E722)


62-65: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


64-64: Do not use bare except

(E722)


185-188: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


187-187: Do not use bare except

(E722)


220-220: Loop control variable i not used within loop body

Rename unused i to _i

(B007)

7xbot.py

70-70: Undefined name ctx

(F821)


70-70: Undefined name ctx

(F821)


70-70: Undefined name ctx

(F821)


438-438: Undefined name get_bot_info

(F821)


562-562: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)


1153-1153: Use key in dict instead of key in dict.keys()

Remove .keys()

(SIM118)

🔇 Additional comments (12)
database.py (1)

183-184: Ensure connection pool exists before closing

Good defensive programming by checking if the pool exists before closing.

.python-version (1)

1-1: LGTM! Appropriate Python version choice.

Python 3.11 is an excellent choice for a modern Discord bot, providing good performance and compatibility with the dependencies listed in requirements.txt.

Procfile (1)

1-1: LGTM! Standard Heroku deployment configuration.

The Procfile correctly defines a worker process for continuous bot operation. Ensure that main.py exists and properly initializes the bot.

app.json (1)

6-35: LGTM! Well-structured Heroku configuration.

The environment variables, addons, and buildpacks are appropriately configured for a Discord bot with database and music features. The FFmpeg buildpack aligns with the music cog requirements.

tests/test_basic.py (3)

20-22: Test implementation addresses previous review concerns.

The test now properly sets bot_start_time before calling get_uptime(), addressing the previous review comment about potential TypeError when bot.start_time is None.


25-44: Excellent database test coverage.

The database tests now include all the edge cases requested in previous reviews: normal save/load, missing files, and empty dictionaries. Good implementation of the feedback.


54-94: Comprehensive setup wizard test coverage.

The setup wizard tests effectively address the previous review comment by covering the success flow, cancellation, and help scenarios with proper mocking of Discord objects and user interactions.

cogs/admin.py (1)

204-206: LGTM!

The cog setup follows the standard pattern used across all other cogs in the project.

cogs/fun.py (1)

298-300: LGTM!

The cog setup follows the standard pattern consistent with other cogs.

cogs/music.py (1)

580-582: LGTM!

The cog setup follows the standard pattern used throughout the project.

7xbot.py (1)

438-441: Call site now safe once get_bot_info is fixed – nothing further.

🧰 Tools
🪛 Ruff (0.11.9)

438-438: Undefined name get_bot_info

(F821)

🪛 Pylint (3.3.7)

[error] 438-438: Undefined variable 'get_bot_info'

(E0602)

cogs/moderation.py (1)

36-44: Color constants rely on bot.config

Ensure config is attached to the bot before this cog loads; otherwise AttributeError will occur.
Consider defaulting:

color = getattr(self.bot.config, "WARNING_COLOR", 0xffcc00)

Comment on lines +18 to +28
DAILY_REWARD: int = int(os.getenv('DAILY_REWARD', 100))
WORK_REWARD_MIN: int = int(os.getenv('WORK_REWARD_MIN', 50))
WORK_REWARD_MAX: int = int(os.getenv('WORK_REWARD_MAX', 200))

# Leveling
XP_PER_MESSAGE: int = int(os.getenv('XP_PER_MESSAGE', 15))
XP_COOLDOWN: int = int(os.getenv('XP_COOLDOWN', 60)) # seconds

# Music
MAX_QUEUE_SIZE: int = int(os.getenv('MAX_QUEUE_SIZE', 100))
MAX_SONG_LENGTH: int = int(os.getenv('MAX_SONG_LENGTH', 600)) # seconds
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for environment variable parsing

The int() conversions could raise ValueError if environment variables contain non-numeric values. This would crash the bot during initialization.

Apply this diff to add error handling:

-    DAILY_REWARD: int = int(os.getenv('DAILY_REWARD', 100))
-    WORK_REWARD_MIN: int = int(os.getenv('WORK_REWARD_MIN', 50))
-    WORK_REWARD_MAX: int = int(os.getenv('WORK_REWARD_MAX', 200))
+    DAILY_REWARD: int = int(os.getenv('DAILY_REWARD', '100'))
+    WORK_REWARD_MIN: int = int(os.getenv('WORK_REWARD_MIN', '50'))
+    WORK_REWARD_MAX: int = int(os.getenv('WORK_REWARD_MAX', '200'))
     
     # Leveling
-    XP_PER_MESSAGE: int = int(os.getenv('XP_PER_MESSAGE', 15))
-    XP_COOLDOWN: int = int(os.getenv('XP_COOLDOWN', 60))  # seconds
+    XP_PER_MESSAGE: int = int(os.getenv('XP_PER_MESSAGE', '15'))
+    XP_COOLDOWN: int = int(os.getenv('XP_COOLDOWN', '60'))  # seconds
     
     # Music
-    MAX_QUEUE_SIZE: int = int(os.getenv('MAX_QUEUE_SIZE', 100))
-    MAX_SONG_LENGTH: int = int(os.getenv('MAX_SONG_LENGTH', 600))  # seconds
+    MAX_QUEUE_SIZE: int = int(os.getenv('MAX_QUEUE_SIZE', '100'))
+    MAX_SONG_LENGTH: int = int(os.getenv('MAX_SONG_LENGTH', '600'))  # seconds

Additionally, consider wrapping these in a try-except block in __post_init__ to provide better error messages:

def __post_init__(self):
    # Validate integer environment variables
    int_configs = {
        'DAILY_REWARD': self.DAILY_REWARD,
        'WORK_REWARD_MIN': self.WORK_REWARD_MIN,
        'WORK_REWARD_MAX': self.WORK_REWARD_MAX,
        'XP_PER_MESSAGE': self.XP_PER_MESSAGE,
        'XP_COOLDOWN': self.XP_COOLDOWN,
        'MAX_QUEUE_SIZE': self.MAX_QUEUE_SIZE,
        'MAX_SONG_LENGTH': self.MAX_SONG_LENGTH
    }
    
    for name, value in int_configs.items():
        try:
            int(str(value))
        except ValueError:
            raise ValueError(f"Invalid integer value for {name}: {value}")
🤖 Prompt for AI Agents
In config.py around lines 18 to 28, the int() conversions of environment
variables can raise ValueError if the values are non-numeric, causing a crash.
To fix this, wrap the parsing of these environment variables in a try-except
block inside a __post_init__ method that validates each integer config value and
raises a clear ValueError with the variable name and invalid value if parsing
fails. This will provide better error messages and prevent silent crashes during
initialization.

Comment on lines +30 to +42
def __post_init__(self):
owner_id = os.getenv('OWNER_ID')
if owner_id:
self.OWNER_IDS = [int(owner_id)]
else:
self.OWNER_IDS = []

# Add additional owners from comma-separated env var
additional_owners = os.getenv('ADDITIONAL_OWNER_IDS', '')
if additional_owners:
for owner in additional_owners.split(','):
if owner.strip():
self.OWNER_IDS.append(int(owner.strip()))
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add validation for owner ID parsing

The owner ID parsing could fail with ValueError if the environment variables contain non-numeric values.

Apply this diff to add error handling:

 def __post_init__(self):
     owner_id = os.getenv('OWNER_ID')
     if owner_id:
-        self.OWNER_IDS = [int(owner_id)]
+        try:
+            self.OWNER_IDS = [int(owner_id)]
+        except ValueError:
+            print(f"Warning: Invalid OWNER_ID '{owner_id}', must be numeric")
+            self.OWNER_IDS = []
     else:
         self.OWNER_IDS = []
     
     # Add additional owners from comma-separated env var
     additional_owners = os.getenv('ADDITIONAL_OWNER_IDS', '')
     if additional_owners:
         for owner in additional_owners.split(','):
             if owner.strip():
-                self.OWNER_IDS.append(int(owner.strip()))
+                try:
+                    self.OWNER_IDS.append(int(owner.strip()))
+                except ValueError:
+                    print(f"Warning: Skipping invalid owner ID '{owner.strip()}'")
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __post_init__(self):
owner_id = os.getenv('OWNER_ID')
if owner_id:
self.OWNER_IDS = [int(owner_id)]
else:
self.OWNER_IDS = []
# Add additional owners from comma-separated env var
additional_owners = os.getenv('ADDITIONAL_OWNER_IDS', '')
if additional_owners:
for owner in additional_owners.split(','):
if owner.strip():
self.OWNER_IDS.append(int(owner.strip()))
def __post_init__(self):
owner_id = os.getenv('OWNER_ID')
if owner_id:
try:
self.OWNER_IDS = [int(owner_id)]
except ValueError:
print(f"Warning: Invalid OWNER_ID '{owner_id}', must be numeric")
self.OWNER_IDS = []
else:
self.OWNER_IDS = []
# Add additional owners from comma-separated env var
additional_owners = os.getenv('ADDITIONAL_OWNER_IDS', '')
if additional_owners:
for owner in additional_owners.split(','):
if owner.strip():
try:
self.OWNER_IDS.append(int(owner.strip()))
except ValueError:
print(f"Warning: Skipping invalid owner ID '{owner.strip()}'")
🤖 Prompt for AI Agents
In config.py around lines 30 to 42, the code parses OWNER_ID and
ADDITIONAL_OWNER_IDS from environment variables but does not handle non-numeric
values, which can cause ValueError. Add try-except blocks around the int
conversions to catch ValueError and handle it gracefully, such as by logging a
warning or ignoring invalid entries, to prevent the application from crashing
due to malformed environment variables.

Comment on lines +17 to +81
async def setup(self):
"""Initialize database and create tables"""
self.pool = await asyncpg.create_pool(self.connection_url)

# Guild settings
await self.pool.execute("""
CREATE TABLE IF NOT EXISTS guilds (
id BIGINT PRIMARY KEY,
prefix TEXT DEFAULT '!',
welcome_channel BIGINT,
log_channel BIGINT,
automod_enabled BOOLEAN DEFAULT FALSE,
settings JSONB DEFAULT '{}'::jsonb
)
""")

# User economy
await self.pool.execute("""
CREATE TABLE IF NOT EXISTS economy (
user_id BIGINT,
guild_id BIGINT,
balance BIGINT DEFAULT 0,
bank BIGINT DEFAULT 0,
daily_last TIMESTAMP,
work_last TIMESTAMP,
PRIMARY KEY (user_id, guild_id)
)
""")

# User levels
await self.pool.execute("""
CREATE TABLE IF NOT EXISTS levels (
user_id BIGINT,
guild_id BIGINT,
xp BIGINT DEFAULT 0,
level INTEGER DEFAULT 0,
last_message TIMESTAMP,
PRIMARY KEY (user_id, guild_id)
)
""")

# Moderation logs
await self.pool.execute("""
CREATE TABLE IF NOT EXISTS mod_logs (
id SERIAL PRIMARY KEY,
guild_id BIGINT,
user_id BIGINT,
moderator_id BIGINT,
action TEXT,
reason TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")

# Warnings
await self.pool.execute("""
CREATE TABLE IF NOT EXISTS warnings (
id SERIAL PRIMARY KEY,
guild_id BIGINT,
user_id BIGINT,
moderator_id BIGINT,
reason TEXT,
timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
""")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Add error handling for database setup

The setup method could fail due to connection issues or SQL errors, but there's no error handling. This could cause the bot to crash during startup.

Consider wrapping the setup operations in try-except blocks:

async def setup(self):
    """Initialize database and create tables"""
    try:
        self.pool = await asyncpg.create_pool(self.connection_url)
    except Exception as e:
        raise RuntimeError(f"Failed to create database pool: {e}")
    
    tables = [
        ("guilds", """CREATE TABLE IF NOT EXISTS guilds ..."""),
        ("economy", """CREATE TABLE IF NOT EXISTS economy ..."""),
        ("levels", """CREATE TABLE IF NOT EXISTS levels ..."""),
        ("mod_logs", """CREATE TABLE IF NOT EXISTS mod_logs ..."""),
        ("warnings", """CREATE TABLE IF NOT EXISTS warnings ...""")
    ]
    
    for table_name, query in tables:
        try:
            await self.pool.execute(query)
        except Exception as e:
            raise RuntimeError(f"Failed to create table {table_name}: {e}")
🤖 Prompt for AI Agents
In database.py around lines 17 to 81, the setup method lacks error handling for
database connection and table creation, which can cause crashes on failure. Wrap
the asyncpg.create_pool call in a try-except block to catch connection errors
and raise a RuntimeError with a descriptive message. Similarly, organize the
table creation queries into a list of tuples with table names and SQL strings,
then iterate over them, executing each inside a try-except block to catch SQL
execution errors and raise RuntimeErrors specifying which table creation failed.

Comment on lines +122 to +129
async def update_balance(self, user_id: int, guild_id: int, amount: int):
"""Update user's balance"""
await self.pool.execute("""
INSERT INTO economy (user_id, guild_id, balance, bank)
VALUES ($1, $2, $3, 0)
ON CONFLICT (user_id, guild_id) DO UPDATE
SET balance = economy.balance + $3
""", user_id, guild_id, amount)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential data loss from overflow

The update_balance method adds to the existing balance without checking for integer overflow. PostgreSQL BIGINT can overflow if the sum exceeds its limits.

Consider adding a check or using a safer update approach:

 async def update_balance(self, user_id: int, guild_id: int, amount: int):
     """Update user's balance"""
     await self.pool.execute("""
         INSERT INTO economy (user_id, guild_id, balance, bank)
         VALUES ($1, $2, $3, 0)
         ON CONFLICT (user_id, guild_id) DO UPDATE 
-        SET balance = economy.balance + $3
+        SET balance = LEAST(economy.balance + $3, 9223372036854775807)
     """, user_id, guild_id, amount)

This ensures the balance doesn't exceed PostgreSQL's BIGINT maximum value.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
async def update_balance(self, user_id: int, guild_id: int, amount: int):
"""Update user's balance"""
await self.pool.execute("""
INSERT INTO economy (user_id, guild_id, balance, bank)
VALUES ($1, $2, $3, 0)
ON CONFLICT (user_id, guild_id) DO UPDATE
SET balance = economy.balance + $3
""", user_id, guild_id, amount)
async def update_balance(self, user_id: int, guild_id: int, amount: int):
"""Update user's balance"""
await self.pool.execute("""
INSERT INTO economy (user_id, guild_id, balance, bank)
VALUES ($1, $2, $3, 0)
ON CONFLICT (user_id, guild_id) DO UPDATE
SET balance = LEAST(economy.balance + $3, 9223372036854775807)
""", user_id, guild_id, amount)
🤖 Prompt for AI Agents
In database.py around lines 122 to 129, the update_balance method adds the
amount to the existing balance without checking for integer overflow, risking
data corruption if the sum exceeds PostgreSQL BIGINT limits. Modify the SQL
query to include a check that caps the balance at the maximum BIGINT value or
prevents the update if it would overflow. This can be done by using a CASE
statement or a LEAST function to ensure the new balance does not exceed the
BIGINT maximum allowed value.

requirements.txt Outdated
Comment on lines +3 to +14
g4f
psutil>=5.9.0
asyncpg>=0.28.0
yt-dlp>=2023.3.4
PyNaCl>=1.5.0
wavelink>=2.0.0
aiohttp>=3.8.0
async-timeout
psycopg2-binary
ffmpeg-python
mutagen
pydub
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Add version constraints to prevent dependency conflicts.

Several packages lack version constraints, which can lead to compatibility issues and security vulnerabilities. Consider pinning versions for production stability.

 g4f
-psutil>=5.9.0
-asyncpg>=0.28.0
-yt-dlp>=2023.3.4
-PyNaCl>=1.5.0
-wavelink>=2.0.0
-aiohttp>=3.8.0
-async-timeout
-psycopg2-binary
-ffmpeg-python
-mutagen
-pydub
+psutil>=5.9.0,<6.0.0
+asyncpg>=0.28.0,<1.0.0
+yt-dlp>=2023.3.4
+PyNaCl>=1.5.0,<2.0.0
+wavelink>=2.0.0,<3.0.0
+aiohttp>=3.8.0,<4.0.0
+async-timeout>=4.0.0,<5.0.0
+psycopg2-binary>=2.9.0,<3.0.0
+ffmpeg-python>=0.2.0,<1.0.0
+mutagen>=1.45.0,<2.0.0
+pydub>=0.25.0,<1.0.0

Also consider whether both asyncpg and psycopg2-binary are needed, as they serve similar PostgreSQL connectivity purposes.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
g4f
psutil>=5.9.0
asyncpg>=0.28.0
yt-dlp>=2023.3.4
PyNaCl>=1.5.0
wavelink>=2.0.0
aiohttp>=3.8.0
async-timeout
psycopg2-binary
ffmpeg-python
mutagen
pydub
g4f
psutil>=5.9.0,<6.0.0
asyncpg>=0.28.0,<1.0.0
yt-dlp>=2023.3.4
PyNaCl>=1.5.0,<2.0.0
wavelink>=2.0.0,<3.0.0
aiohttp>=3.8.0,<4.0.0
async-timeout>=4.0.0,<5.0.0
psycopg2-binary>=2.9.0,<3.0.0
ffmpeg-python>=0.2.0,<1.0.0
mutagen>=1.45.0,<2.0.0
pydub>=0.25.0,<1.0.0
🤖 Prompt for AI Agents
In requirements.txt lines 3 to 14, several packages lack version constraints,
which can cause dependency conflicts and security risks. Add specific version
numbers or version ranges for all packages to ensure stability and
compatibility. Additionally, review the need for both asyncpg and
psycopg2-binary since they both provide PostgreSQL connectivity; remove one if
redundant to avoid unnecessary dependencies.

Comment on lines +218 to +227
if voice_client and voice_client.is_connected():
# Check if there are any non-bot members in the voice channel
if not any(member for member in voice_client.channel.members if not member.bot):
await voice_client.disconnect()
if guild.id in self.players:
await self.cleanup(guild)
try:
await self.players[guild.id].channel.send("👋 Left the voice channel because everyone left.")
except:
pass
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Improve exception handling specificity.

Using bare except: can hide unexpected errors and make debugging difficult.

Apply this diff to catch specific exceptions:

 if not any(member for member in voice_client.channel.members if not member.bot):
     await voice_client.disconnect()
     if guild.id in self.players:
         await self.cleanup(guild)
-        try:
+        try:
             await self.players[guild.id].channel.send("👋 Left the voice channel because everyone left.")
-        except:
-            pass
+        except (discord.HTTPException, AttributeError):
+            # Channel might be deleted or bot lacks permissions
+            pass
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
if voice_client and voice_client.is_connected():
# Check if there are any non-bot members in the voice channel
if not any(member for member in voice_client.channel.members if not member.bot):
await voice_client.disconnect()
if guild.id in self.players:
await self.cleanup(guild)
try:
await self.players[guild.id].channel.send("👋 Left the voice channel because everyone left.")
except:
pass
if voice_client and voice_client.is_connected():
# Check if there are any non-bot members in the voice channel
if not any(member for member in voice_client.channel.members if not member.bot):
await voice_client.disconnect()
if guild.id in self.players:
await self.cleanup(guild)
try:
await self.players[guild.id].channel.send("👋 Left the voice channel because everyone left.")
except (discord.HTTPException, AttributeError):
# Channel might be deleted or bot lacks permissions
pass
🧰 Tools
🪛 Ruff (0.11.9)

218-220: Use a single if statement instead of nested if statements

(SIM102)


224-227: Use contextlib.suppress(Exception) instead of try-except-pass

Replace with contextlib.suppress(Exception)

(SIM105)


226-226: Do not use bare except

(E722)

🤖 Prompt for AI Agents
In cogs/music.py between lines 218 and 227, replace the bare except clause with
a specific exception handler to avoid hiding unexpected errors. Identify the
likely exceptions that can occur when sending a message (such as
discord.Forbidden or discord.HTTPException) and catch only those. This will
improve error handling clarity and make debugging easier.

Comment on lines +332 to +339
# Check if URL is accessible
if not await self.is_valid_url(url):
return await ctx.send("❌ Could not access the provided URL.")

# Extract filename from URL for title
filename = url.split('/')[-1].split('?')[0]
title = filename
uploader = "Direct Link"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🛠️ Refactor suggestion

Potential security risk with direct URL playback.

While basic URL validation is performed, playing audio from arbitrary URLs could pose security risks:

  • Malicious audio files could exploit FFmpeg vulnerabilities
  • Large files could consume excessive bandwidth/resources
  • URLs could redirect to unexpected content

Consider adding additional safeguards:

 # Check if URL is accessible
 if not await self.is_valid_url(url):
     return await ctx.send("❌ Could not access the provided URL.")
 
+# Check if it's actually an audio file
+if not await self.is_audio_url(url):
+    return await ctx.send("❌ The URL does not appear to point to an audio file.")
+
 # Extract filename from URL for title
 filename = url.split('/')[-1].split('?')[0]
 title = filename
 uploader = "Direct Link"

Committable suggestion skipped: line range outside the PR's diff.

🤖 Prompt for AI Agents
In cogs/music.py around lines 332 to 339, the current code plays audio directly
from user-provided URLs after basic validation, which poses security risks such
as malicious files, large resource consumption, or redirects. To fix this,
implement additional safeguards like checking the file size before playback,
validating the content type to ensure it is an audio format, restricting allowed
domains or URL patterns, and handling redirects safely. These measures will help
mitigate potential security vulnerabilities when playing audio from arbitrary
URLs.

Comment on lines +40 to +94
class RadioStation:
"""Represents a radio station."""

STATIONS = {
'lofi': {
'name': 'Lo-Fi Hip Hop Radio',
'url': 'http://hyades.shoutca.st:8043/stream',
'genre': 'Lo-Fi Hip Hop'
},
'jazz': {
'name': 'Smooth Jazz',
'url': 'http://strm112.1.fm/smoothjazz_mobile_mp3',
'genre': 'Jazz'
},
'classical': {
'name': 'Classical Radio',
'url': 'http://strm112.1.fm/classical_mobile_mp3',
'genre': 'Classical'
},
'rock': {
'name': 'Rock Classics',
'url': 'http://strm112.1.fm/rockclassics_mobile_mp3',
'genre': 'Rock'
},
'ambient': {
'name': 'Ambient Dreams',
'url': 'http://strm112.1.fm/ambientdreams_mobile_mp3',
'genre': 'Ambient'
},
'top40': {
'name': 'Top 40',
'url': 'http://strm112.1.fm/top40_mobile_mp3',
'genre': 'Pop'
},
'country': {
'name': 'Country Radio',
'url': 'http://strm112.1.fm/country_mobile_mp3',
'genre': 'Country'
},
'dance': {
'name': 'Dance Hits',
'url': 'http://strm112.1.fm/club_mobile_mp3',
'genre': 'Dance'
}
}

@classmethod
def get_station(cls, name):
"""Get a radio station by name."""
return cls.STATIONS.get(name.lower())

@classmethod
def list_stations(cls):
"""List all available radio stations."""
return cls.STATIONS
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Verify radio station URLs are still active.

The hardcoded radio station URLs may become outdated or unavailable over time.

Let me verify if these radio URLs are still accessible:


🏁 Script executed:

#!/bin/bash
# Check if radio station URLs are accessible
echo "Checking radio station URLs..."

stations=(
    "http://hyades.shoutca.st:8043/stream"
    "http://strm112.1.fm/smoothjazz_mobile_mp3"
    "http://strm112.1.fm/classical_mobile_mp3"
    "http://strm112.1.fm/rockclassics_mobile_mp3"
    "http://strm112.1.fm/ambientdreams_mobile_mp3"
    "http://strm112.1.fm/top40_mobile_mp3"
    "http://strm112.1.fm/country_mobile_mp3"
    "http://strm112.1.fm/club_mobile_mp3"
)

for url in "${stations[@]}"; do
    response=$(curl -s -o /dev/null -w "%{http_code}" --max-time 5 "$url" || echo "timeout")
    echo "$url: $response"
done

Length of output: 2467


Update broken radio station URLs

The health check shows two stations are currently unreachable and need updating:

  • cogs/music.py:
    • Lo-Fi Hip Hop Radio (lofi) – timed out (000)
    • Ambient Dreams (ambient) – HTTP 404

All other streams returned HTTP 200 before the 5 s cutoff, indicating they’re active. Please verify these URLs and replace or remove the failing entries.

🤖 Prompt for AI Agents
In cogs/music.py between lines 40 and 94, the URLs for the 'lofi' and 'ambient'
radio stations are no longer accessible, causing timeouts and 404 errors. To fix
this, verify and replace these two URLs with currently active streaming URLs for
Lo-Fi Hip Hop Radio and Ambient Dreams, or remove these entries if no valid URLs
are found. Ensure the updated URLs respond with HTTP 200 within a reasonable
timeout.

Comment on lines +64 to +72
try:
load = psutil.Process().cpu_percent(interval=0.1)
except Exception:
load = psutil.cpu_percent(interval=0.1)
"""Return basic bot info such as ping, shard ID and CPU load."""
ping_ms = round(bot.latency * 1000)
shard_id = ctx.guild.shard_id if ctx and ctx.guild else 0
load = psutil.cpu_percent(interval=None)
return {"ping_ms": ping_ms, "shard_id": shard_id, "cpu_load": load}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

get_bot_info is not defined and the snippet at module level will crash on import

The block references bot and ctx before they exist and executes at import time, raising NameError.
Move the logic into a proper function and delete these top-level statements.

-try:
-    load = psutil.Process().cpu_percent(interval=0.1)
-except Exception:
-    load = psutil.cpu_percent(interval=0.1)
-"""Return basic bot info such as ping, shard ID and CPU load."""
-ping_ms = round(bot.latency * 1000)
-shard_id = ctx.guild.shard_id if ctx and ctx.guild else 0
-load = psutil.cpu_percent(interval=None)
-return {"ping_ms": ping_ms, "shard_id": shard_id, "cpu_load": load}
+
+# ────────────────────────────────────────────────────────────────
+# Utilities
+# ────────────────────────────────────────────────────────────────
+def get_bot_info(bot: commands.Bot, ctx: Optional[commands.Context] = None) -> dict[str, int]:
+    """Return latency (ms), shard-ID and CPU load for this process."""
+    try:
+        cpu = psutil.Process().cpu_percent(interval=None)        # per-process metric
+    except Exception:
+        cpu = psutil.cpu_percent(interval=None)                  # system-wide fallback
+    ping_ms = round(bot.latency * 1000)
+    shard_id = ctx.guild.shard_id if ctx and ctx.guild else 0
+    return {"ping_ms": ping_ms, "shard_id": shard_id, "cpu_load": cpu}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
try:
load = psutil.Process().cpu_percent(interval=0.1)
except Exception:
load = psutil.cpu_percent(interval=0.1)
"""Return basic bot info such as ping, shard ID and CPU load."""
ping_ms = round(bot.latency * 1000)
shard_id = ctx.guild.shard_id if ctx and ctx.guild else 0
load = psutil.cpu_percent(interval=None)
return {"ping_ms": ping_ms, "shard_id": shard_id, "cpu_load": load}
# ────────────────────────────────────────────────────────────────
# Utilities
# ────────────────────────────────────────────────────────────────
def get_bot_info(bot: commands.Bot, ctx: Optional[commands.Context] = None) -> dict[str, int]:
"""Return latency (ms), shard-ID and CPU load for this process."""
try:
cpu = psutil.Process().cpu_percent(interval=None) # per-process metric
except Exception:
cpu = psutil.cpu_percent(interval=None) # system-wide fallback
ping_ms = round(bot.latency * 1000)
shard_id = ctx.guild.shard_id if ctx and ctx.guild else 0
return {"ping_ms": ping_ms, "shard_id": shard_id, "cpu_load": cpu}
🧰 Tools
🪛 Ruff (0.11.9)

70-70: Undefined name ctx

(F821)


70-70: Undefined name ctx

(F821)


70-70: Undefined name ctx

(F821)

🪛 Pylint (3.3.7)

[error] 70-70: Undefined variable 'ctx'

(E0602)


[error] 70-70: Undefined variable 'ctx'

(E0602)


[error] 70-70: Undefined variable 'ctx'

(E0602)

🤖 Prompt for AI Agents
In 7xbot.py around lines 64 to 72, the code referencing bot and ctx variables
runs at the module level, causing a NameError since these variables are
undefined at import time. To fix this, encapsulate the entire block inside a
properly defined function (e.g., get_bot_info) that accepts bot and ctx as
parameters, then remove the top-level execution of this code so it only runs
when the function is called with valid arguments.

Comment on lines +535 to +602
@bot.command(name="setup")
@commands.has_permissions(manage_guild=True)
async def setup_wizard(ctx, *, args: Optional[str] = None):
if args and args.lower() == "help":
embed = discord.Embed(title="Setup Command Help", description=setup_explanation, color=0x00ff00)
await ctx.send(embed=embed)
return
if args:
embed = discord.Embed(title="Setup Command Help", description=setup_explanation, color=0x00ff00)
await ctx.send(embed=embed)
return

await ctx.send("Starting setup wizard. Reply with 'cancel' at any time to stop.")

class SetupCancelled(Exception):
pass

async def ask(prompt: str, parser):
await ctx.send(prompt)
try:
msg = await bot.wait_for(
"message",
check=lambda m: m.author == ctx.author and m.channel == ctx.channel,
timeout=60,
)
except asyncio.TimeoutError:
await ctx.send("Timed out waiting for a response. Setup cancelled.")
raise SetupCancelled
if msg.content.lower() == "cancel":
await ctx.send("Setup cancelled.")
raise SetupCancelled
return parser(msg)

questions = [
(
"strike_roles",
"Enter strike roles by mentioning them separated by spaces or 'none':",
lambda m: [r.id for r in m.role_mentions],
),
(
"ai_enabled",
"Enable AI features? (yes/no):",
lambda m: m.content.lower().startswith("y"),
),
(
"economy",
"Economy type (regular/prankful/none):",
lambda m: m.content.lower(),
),
]

config = {}
try:
for key, prompt, parser in questions:
value = await ask(prompt, parser)
if key == "economy" and value not in {"regular", "prankful", "none"}:
await ctx.send(
"Invalid economy type. Please choose 'regular', 'prankful', or 'none'."
)
raise SetupCancelled
config[key] = value
except SetupCancelled:
return

db.setdefault("config", {})[str(ctx.guild.id)] = config
save_db(db)
await ctx.send("Setup complete.")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Persisted strike-roles are never read back by the warn command

setup_wizard stores strike_roles in the DB but later code still references a global
strike_roles list (defined nowhere).
Load the list on-demand, e.g.:

strike_roles = get_server_config(ctx.guild.id).get("strike_roles", [])

before it’s used in warn / pardon.


🏁 Script executed:

#!/bin/bash
# Search for references to strike_roles and warn/pardon commands in 7xbot.py
echo "== strike_roles references =="
rg -n "strike_roles" -C2 7xbot.py || echo "No strike_roles references found"

echo -e "\n== warn command definition =="
rg -n "@bot.command.*warn" -C5 7xbot.py || echo "No warn command decorator found"
rg -n "def warn" -C5 7xbot.py || echo "No def warn found"

echo -e "\n== pardon command definition =="
rg -n "@bot.command.*pardon" -C5 7xbot.py || echo "No pardon command decorator found"
rg -n "def pardon" -C5 7xbot.py || echo "No def pardon found"

Length of output: 5227


🏁 Script executed:

#!/bin/bash
# Search for load_db usage
echo "== load_db references =="
rg -n "load_db" -C3 7xbot.py || echo "No load_db references found"

echo -e "\n== save_db references =="
rg -n "save_db" -C3 7xbot.py || echo "No save_db references found"

echo -e "\n== db declaration =="
rg -n "^db\s*=" -C3 7xbot.py || echo "No db declaration found"

Length of output: 2002


Load persisted strike roles from the database in warn and pardon

The warn and pardon commands are currently referencing a global strike_roles that doesn’t exist, so any persisted configuration is never read back. You should pull the server’s saved list at runtime using get_server_config. For example:

@bot.command(name="warn", help="Warn a user and escalate their strike.")
@commands.has_permissions(manage_messages=True)
async def warn(ctx, member: Optional[discord.Member] = None, *, reason: Optional[str] = None):
    # load strike roles from DB
    strike_roles = get_server_config(ctx.guild.id).get("strike_roles", [])
    # match by role ID (since you stored IDs), not name
    current_role = next((r for r in member.roles if r.id in strike_roles), None)
    …

• In warn (around line 927), insert

strike_roles = get_server_config(ctx.guild.id).get("strike_roles", [])

before any use of strike_roles.
• In pardon (around line 988), do the same.
• Since you store role IDs in setup, compare r.id in strike_roles rather than r.name.

🧰 Tools
🪛 Ruff (0.11.9)

562-562: Within an except clause, raise exceptions with raise ... from err or raise ... from None to distinguish them from errors in exception handling

(B904)

🤖 Prompt for AI Agents
In 7xbot.py around lines 927 and 988, the warn and pardon commands reference a
global strike_roles list that does not exist, causing the persisted strike roles
saved in the database to never be read back. To fix this, load the strike_roles
list from the database at runtime by calling
get_server_config(ctx.guild.id).get("strike_roles", []) at the start of both
warn and pardon commands. Also, since you store role IDs in setup, update the
role matching logic to compare role IDs (r.id) against the loaded strike_roles
list instead of role names.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant