-
Notifications
You must be signed in to change notification settings - Fork 0
Added Database support for moderation commands #40
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
|
Caution Review failedThe pull request is closed. 📝 WalkthroughWalkthroughAdded a PostgreSQL-backed moderation data layer and connection pooling to Moderationbot.py, new CRUD functions for cases/warnings/notes, a Changes
Sequence DiagramsequenceDiagram
participant User
participant Bot as Moderation Bot
participant DB as PostgreSQL
participant DiscordAPI as Discord API
User->>Bot: Invoke /warn or /case
Bot->>DB: get_db_connection()
DB-->>Bot: connection
Bot->>DB: create_mod_case(...) / get_mod_case(...)
DB-->>Bot: case_id / case_data
Bot->>DB: add_warning(...) or add_mod_note(...)
DB-->>Bot: warning_id / note_id
Bot->>DiscordAPI: Send DM or guild response (embed)
DiscordAPI-->>Bot: ack
Bot->>DB: return_db_connection(connection)
DB-->>Bot: ack
Bot-->>User: Respond with embed (case details / confirmation)
Estimated code review effort🎯 4 (Complex) | ⏱️ ~60 minutes Possibly related PRs
Suggested labels
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
📜 Recent review detailsConfiguration used: defaults Review profile: CHILL Plan: Pro 📒 Files selected for processing (2)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 10
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (5)
Moderationbot.py (4)
1662-1680: Duplicateon_command_errorhandler definition (second one overrides the first).
You have two@bot.event async def on_command_error(...)blocks back-to-back; only the latter will be active.
1795-1797: Bug:datetime.timedeltawill raise (you importedtimedeltadirectly).
datetimehere is thedatetimeclass (fromfrom datetime import datetime, timedelta), sodatetime.timedelta(...)is invalid. Usetimedelta(...).
2533-2537: Bug:datetime.datetime.now()will raise (same import issue).
Usedatetime.now()(or import the module asimport datetime).
1610-1637: Database initialization and cleanup not wired into startup/shutdown.The
init_moderation_database()andclose_database()functions are defined (lines 92 and 545 respectively), and comments at lines 557–561 explicitly document that they should be called inon_ready()and the__main__finally block. However, neither is currently invoked, preventing the moderation database from initializing on startup or properly closing connections on shutdown. This will cause moderation tracking to fail.Add
init_moderation_database()toon_ready()with a guard to handle multiple reconnections, and add a finally block in the__main__section to callclose_database()on exit.README.md (1)
103-110: Owner command name mismatch vs code: README says/updatemode, code registersname="update-mode".
If you intend the user-facing command to be/updatemode, the decorator should use that exact name (or update README).
🤖 Fix all issues with AI agents
In @Moderationbot.py:
- Around line 3172-3176: The code uses f-strings where no interpolation occurs;
remove the unnecessary leading "f" on those string literals (e.g., the title and
description in the dm_embed creation around the dm_embed variable and the
similar embed created at the block around lines 3307-3311) so they are plain
strings (use string literals without f-prefix) while leaving any true
interpolations (like {interaction.guild.name}) as f-strings if present; locate
the embed constructions (variable dm_embed and the other embed instance) and
strip the f from string literals that contain no placeholders.
- Around line 3064-3074: Replace bare except blocks around the bot.fetch_user
calls (the blocks that set target_user/target_name and moderator/mod_name) with
handlers for specific Discord errors (e.g., discord.NotFound,
discord.HTTPException) and log the caught exception details (type/message)
before setting the fallback "Unknown ..." strings; ensure you still catch and
log other unexpected exceptions explicitly (e.g., except Exception as e:
logger.exception(...)) rather than using a bare except. Apply the same change
pattern to the similar try/except sites referenced (the blocks around lines
handling user/moderator lookup at the other occurrences).
- Around line 92-254: The init_moderation_database function can leak DB
resources on errors because cur and conn are not cleaned up; ensure you close
the cursor and return the connection to the pool on all paths (use a finally
block that checks for cur and conn and calls cur.close() and
return_db_connection(conn)) or change to a context manager for the cursor (with
conn.cursor() as cur:) so that cur is auto-closed, and still ensure conn is
returned via return_db_connection(conn) in a finally block; reference symbols:
init_moderation_database, cur, conn, return_db_connection.
- Around line 62-66: Add psycopg2 to the project's Python dependencies and
document the SUPABASE_URL env var and SSL requirements in the README; update
requirements.txt to include a pinned psycopg2 (or psycopg2-binary) version. In
the code that creates the database pool (the SimpleConnectionPool instantiation
in Moderationbot.py), ensure the connection string or connection params
explicitly enable TLS for Supabase (e.g., include sslmode='require' in the DSN
or pass equivalent SSL parameters) so production connections use SSL. Also add a
short README note describing the required SUPABASE_URL format and that SSL must
be enforced for Supabase connections.
- Around line 82-90: Several DB helper functions (create_mod_case,
update_mod_case_reason, add_warning, clear_user_warnings, add_mod_note) risk
UnboundLocalError and leaking connections; initialize conn = None before the try
in each of these functions, replace checks like "if conn:" with "if conn is not
None:" in except handlers, and add a finally block that returns the connection
via return_db_connection(conn) (or closes cursor) to guarantee cleanup even on
errors; follow the same pattern for any other helper that calls
get_db_connection()/db_pool.getconn() to ensure the pool is not exhausted.
- Around line 62-76: SUPABASE_URL is being read at import time; move the
os.getenv('SUPABASE_URL') call into init_moderation_database() so it runs after
load_env_file() (follow the existing TOKEN pattern), remove the module-level
SUPABASE_URL assignment, and have init_moderation_database() set a module-level
variable (or return the value) used by the database check that currently
references SUPABASE_URL (and by db_pool initialization) so the check no longer
fails due to None.
- Around line 3035-3435: The slash command handlers (slash_case, slash_warn,
slash_warnings, slash_clearwarnings, slash_modnote, slash_reason) call
synchronous DB helpers (get_mod_case, add_warning, create_mod_case,
get_user_warnings, clear_user_warnings, add_mod_note, update_mod_case_reason)
which block the async event loop; change those calls to run in a thread pool
(e.g., wrap each blocking helper call with asyncio.to_thread(...)) or replace
the helpers with async equivalents (e.g., using asyncpg) so database operations
do not run directly on the event loop; ensure every place that currently calls
those helpers (including places that check/return None on results) uses the
awaited to_thread-wrapped call and preserve existing return/exception handling.
In @README.md:
- Around line 121-130: README lists the command as `/clearwarns` but the bot
code registers it as name="clearwarnings" in Moderationbot.py; make them
consistent by either renaming the command in Moderationbot.py to
name="clearwarns" or updating the README to `/clearwarnings` (also update the
duplicate occurrence at lines 184-191) so the displayed command matches the
actual command name used in Moderationbot.py.
- Around line 290-297: Update the README’s Moderation Tracking System docs to
match the actual DB schema: rename tables to moderation_cases,
moderation_warnings, and moderation_notes (instead of cases/modnotes), document
the warnings storage (moderation_warnings), and include the created_at and
updated_at timestamp columns plus any relevant indexes present in the code;
ensure column names and index purposes mirror the migrations/schema definitions
(e.g., primary keys, foreign keys to user IDs, and indexes used for lookups) so
the README accurately reflects the implementation.
- Around line 131-139: Update the Voice Moderation Commands table so the command
names and permissions match the implementation: replace `/sdeaf`, `/smute`,
`/smuteno`, `/sdeafno` with the actual command names used in code (e.g.,
name="server-deafen", name="server-mute", name="server-unmute",
name="server-undeafen") and change the `/dc` permission entry to indicate it
uses the `send_polls` permission (not "Move Members") to reflect the intentional
design that lets everyone use `/dc`. Ensure the Description column stays
accurate for each command.
🧹 Nitpick comments (2)
Moderationbot.py (1)
3226-3240: Minor polish: unused loop variable + potentially large output safeguards.
for i, warning in enumerate(...doesn’t usei; rename to_ito reduce noise.rolememberschunking is good; consider also handling the 2000-char embed field limit defensively for very mention-heavy roles.Also applies to: 3010-3024
README.md (1)
122-139: Small markdown hygiene: add blank lines around tables for markdownlint.
Not functionally blocking, but it will keep CI/docs lint clean.
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
Moderationbot.pyREADME.md
🧰 Additional context used
🧠 Learnings (3)
📓 Common learnings
Learnt from: SorynTech
Repo: SorynTech/Discord-Moderation-Bot- PR: 0
File: :0-0
Timestamp: 2025-12-30T01:12:13.273Z
Learning: Remind SorynTech to add database support in the next PR they submit to the Discord-Moderation-Bot repository.
📚 Learning: 2025-12-23T21:50:35.230Z
Learnt from: SorynTech
Repo: SorynTech/Discord-Moderation-Bot- PR: 0
File: :0-0
Timestamp: 2025-12-23T21:50:35.230Z
Learning: In the Discord-Moderation-Bot repository, the slash_disconnect command intentionally uses send_polls permission instead of move_members permission to allow all server members to use the command without requiring admin privileges. This is a deliberate security design choice.
Applied to files:
README.md
📚 Learning: 2025-12-30T01:12:13.273Z
Learnt from: SorynTech
Repo: SorynTech/Discord-Moderation-Bot- PR: 0
File: :0-0
Timestamp: 2025-12-30T01:12:13.273Z
Learning: Remind SorynTech to add database support in the next PR they submit to the Discord-Moderation-Bot repository.
Applied to files:
README.mdModerationbot.py
🪛 markdownlint-cli2 (0.18.1)
README.md
88-88: Heading style
Expected: atx; Actual: setext
(MD003, heading-style)
122-122: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
132-132: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🪛 Ruff (0.14.10)
Moderationbot.py
248-248: Consider moving this statement to an else block
(TRY300)
250-250: Do not catch blind exception: Exception
(BLE001)
261-261: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
293-293: Consider moving this statement to an else block
(TRY300)
295-295: Do not catch blind exception: Exception
(BLE001)
324-324: Do not catch blind exception: Exception
(BLE001)
350-350: Consider moving this statement to an else block
(TRY300)
352-352: Do not catch blind exception: Exception
(BLE001)
383-383: Do not catch blind exception: Exception
(BLE001)
412-412: Consider moving this statement to an else block
(TRY300)
414-414: Do not catch blind exception: Exception
(BLE001)
444-444: Do not catch blind exception: Exception
(BLE001)
470-470: Consider moving this statement to an else block
(TRY300)
472-472: Do not catch blind exception: Exception
(BLE001)
504-504: Consider moving this statement to an else block
(TRY300)
506-506: Do not catch blind exception: Exception
(BLE001)
536-536: Do not catch blind exception: Exception
(BLE001)
2858-2858: Use explicit conversion flag
Replace with conversion flag
(RUF010)
3067-3067: Do not use bare except
(E722)
3073-3073: Do not use bare except
(E722)
3174-3174: f-string without any placeholders
Remove extraneous f prefix
(F541)
3226-3226: Loop control variable i not used within loop body
Rename unused i to _i
(B007)
3230-3230: Do not use bare except
(E722)
3273-3273: String contains ambiguous ℹ (INFORMATION SOURCE). Did you mean i (LATIN SMALL LETTER I)?
(RUF001)
3309-3309: f-string without any placeholders
Remove extraneous f prefix
(F541)
3413-3413: Do not use bare except
(E722)
🔇 Additional comments (1)
Moderationbot.py (1)
2849-2865: Role creation embed additions look good.
Clear, user-friendly confirmation with the right role metadata.
| import psycopg2 | ||
| from psycopg2.extras import RealDictCursor | ||
| from psycopg2.pool import SimpleConnectionPool | ||
| from typing import Optional, List, Dict | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
git ls-files | head -30Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 188
🏁 Script executed:
fd -type f -name "requirements*.txt" -o -name "setup.py" -o -name "pyproject.toml" -o -name "Pipfile" -o -name "poetry.lock" -o -name "*.cfg"Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 247
🏁 Script executed:
cd /repo && find . -maxdepth 2 -type f \( -name "requirements*.txt" -o -name "setup.py" -o -name "pyproject.toml" -o -name "Pipfile" -o -name "poetry.lock" \)Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 132
🏁 Script executed:
cat requirements.txtRepository: SorynTech/Discord-Moderation-Bot-
Length of output: 104
🏁 Script executed:
head -100 Moderationbot.pyRepository: SorynTech/Discord-Moderation-Bot-
Length of output: 3211
🏁 Script executed:
grep -n "psycopg2\|ssl\|SSL\|supabase\|Supabase" Moderationbot.pyRepository: SorynTech/Discord-Moderation-Bot-
Length of output: 245
🏁 Script executed:
grep -n "SimpleConnectionPool\|getconn\|connect\|sslmode\|SSL" Moderationbot.py | head -30Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 1360
🏁 Script executed:
sed -n '70,150p' Moderationbot.pyRepository: SorynTech/Discord-Moderation-Bot-
Length of output: 2684
🏁 Script executed:
cat README.mdRepository: SorynTech/Discord-Moderation-Bot-
Length of output: 17750
Add psycopg2 to requirements.txt and configure SSL for Supabase connections.
This file imports psycopg2 (line 62) but the dependency is missing from requirements.txt, which will cause deployment to fail. Additionally, the database connection at line 109 creates a SimpleConnectionPool without SSL configuration. For production Supabase connections, configure SSL explicitly (e.g., sslmode='require' in the connection string or pass SSL parameters to SimpleConnectionPool). Update requirements.txt to include the dependency and version, document the SUPABASE_URL environment variable requirement, and clarify SSL setup in the README.
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 62 - 66, Add psycopg2 to the project's Python
dependencies and document the SUPABASE_URL env var and SSL requirements in the
README; update requirements.txt to include a pinned psycopg2 (or
psycopg2-binary) version. In the code that creates the database pool (the
SimpleConnectionPool instantiation in Moderationbot.py), ensure the connection
string or connection params explicitly enable TLS for Supabase (e.g., include
sslmode='require' in the DSN or pass equivalent SSL parameters) so production
connections use SSL. Also add a short README note describing the required
SUPABASE_URL format and that SSL must be enforced for Supabase connections.
| import psycopg2 | ||
| from psycopg2.extras import RealDictCursor | ||
| from psycopg2.pool import SimpleConnectionPool | ||
| from typing import Optional, List, Dict | ||
|
|
||
| # ============================================================================ | ||
| # DATABASE CONFIGURATION | ||
| # ============================================================================ | ||
|
|
||
| # Supabase Database URL (will be loaded from .env) | ||
| SUPABASE_URL = os.getenv('SUPABASE_URL') | ||
|
|
||
| # Connection pool for database | ||
| db_pool = None | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find the main entry point and load_env_file() calls
rg "load_env_file|def load_env_file" -A 2 -B 2Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 782
🏁 Script executed:
# Check the structure of Moderationbot.py to understand initialization order
git ls-files | grep -i "moderationbot\|\.py$" | head -20Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 106
🏁 Script executed:
# Look for the main entry point or script initialization
rg "if __name__.*==.*__main__|def main" -A 5Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 410
🏁 Script executed:
# Get the exact line numbers and context around load_env_file call and SUPABASE_URL
cat -n Moderationbot.py | head -100Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 3911
🏁 Script executed:
# Also check lines 587-588 mentioned in the review
cat -n Moderationbot.py | sed -n '580,595p'Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 695
🏁 Script executed:
# Check when init_moderation_database() is actually called to see if it's before or after load_env_file
rg "init_moderation_database\(\)" -B 5 -A 2Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 1005
🏁 Script executed:
# Search for where init_moderation_database is actually invoked
rg "init_moderation_database\(\)" --type py -A 1 -B 1 | grep -v "def init_moderation"Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 386
🏁 Script executed:
# Check the on_ready event to see if it's called there
rg "on_ready|async def on_ready" -A 15Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 2881
Move SUPABASE_URL read to init_moderation_database() to ensure .env is loaded first.
SUPABASE_URL = os.getenv('SUPABASE_URL') at line 72 executes at module load time, before load_env_file() is called at line 587. This means the module-level SUPABASE_URL is always None, and the database check at line 100 will incorrectly fail. Follow the pattern used for TOKEN and other env vars: read environment variables only after load_env_file() has been called.
Proposed fix
-# Supabase Database URL (will be loaded from .env)
-SUPABASE_URL = os.getenv('SUPABASE_URL')
+# Supabase Database URL is read at init-time (after .env load)
+SUPABASE_URL = None def init_moderation_database():
"""Initialize database with moderation tracking tables"""
global db_pool
+ supabase_url = os.getenv("SUPABASE_URL")
print("=" * 60)
print("🗄️ INITIALIZING MODERATION DATABASE")
print("=" * 60)
- if not SUPABASE_URL:
+ if not supabase_url:
print("❌ SUPABASE_URL not found in environment variables!")
return
- db_pool = SimpleConnectionPool(1, 20, SUPABASE_URL)
+ db_pool = SimpleConnectionPool(1, 20, supabase_url)📝 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.
| import psycopg2 | |
| from psycopg2.extras import RealDictCursor | |
| from psycopg2.pool import SimpleConnectionPool | |
| from typing import Optional, List, Dict | |
| # ============================================================================ | |
| # DATABASE CONFIGURATION | |
| # ============================================================================ | |
| # Supabase Database URL (will be loaded from .env) | |
| SUPABASE_URL = os.getenv('SUPABASE_URL') | |
| # Connection pool for database | |
| db_pool = None | |
| import psycopg2 | |
| from psycopg2.extras import RealDictCursor | |
| from psycopg2.pool import SimpleConnectionPool | |
| from typing import Optional, List, Dict | |
| # ============================================================================ | |
| # DATABASE CONFIGURATION | |
| # ============================================================================ | |
| # Supabase Database URL is read at init-time (after .env load) | |
| SUPABASE_URL = None | |
| # Connection pool for database | |
| db_pool = None | |
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 62 - 76, SUPABASE_URL is being read at import
time; move the os.getenv('SUPABASE_URL') call into init_moderation_database() so
it runs after load_env_file() (follow the existing TOKEN pattern), remove the
module-level SUPABASE_URL assignment, and have init_moderation_database() set a
module-level variable (or return the value) used by the database check that
currently references SUPABASE_URL (and by db_pool initialization) so the check
no longer fails due to None.
| def get_db_connection(): | ||
| """Get a database connection from the pool""" | ||
| return db_pool.getconn() | ||
|
|
||
|
|
||
| def return_db_connection(conn): | ||
| """Return a connection to the pool""" | ||
| db_pool.putconn(conn) | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
find . -name "Moderationbot.py" -o -name "moderationbot.py"Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 95
🏁 Script executed:
cat -n Moderationbot.py | head -100Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 3911
🏁 Script executed:
cat -n Moderationbot.py | sed -n '260,350p'Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 3375
🏁 Script executed:
cat -n Moderationbot.py | sed -n '350,450p'Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 3764
🏁 Script executed:
cat -n Moderationbot.py | sed -n '450,540p'Repository: SorynTech/Discord-Moderation-Bot-
Length of output: 3368
Connection pool leak and UnboundLocalError risk across multiple DB helper functions.
Multiple DB helper functions lack exception-safe patterns:
- Uninitialized variables in except blocks: Functions like
create_mod_case(line 297),update_mod_case_reason(line 354),add_warning(line 416),clear_user_warnings(line 474), andadd_mod_note(line 508) checkif conn:in except handlers without initializingconnbeforehand. If an exception occurs atget_db_connection()or cursor creation, this raisesUnboundLocalError. - Missing finally blocks: All functions return connections to the pool in the happy path only, with no cleanup on exceptions. This exhausts the connection pool.
Apply the proposed pattern to all DB helpers: initialize conn = None before try, check if conn is not None: in except blocks, and use a finally block to ensure cleanup.
Proposed pattern (apply to all DB helpers)
def create_mod_case(guild_id: int, user_id: int, moderator_id: int,
action_type: str, reason: str = None) -> Optional[int]:
@@
- try:
- conn = get_db_connection()
- cur = conn.cursor()
+ conn = None
+ cur = None
+ try:
+ conn = get_db_connection()
+ cur = conn.cursor()
@@
- conn.commit()
- cur.close()
- return_db_connection(conn)
+ conn.commit()
print(f"✅ Created moderation case #{case_id} for user {user_id}")
return case_id
-
- except Exception as e:
+ except Exception as e:
print(f"❌ Error creating mod case: {e}")
- if conn:
- conn.rollback()
- return_db_connection(conn)
+ if conn is not None:
+ conn.rollback()
return None
+ finally:
+ if cur is not None:
+ cur.close()
+ if conn is not None:
+ return_db_connection(conn)Committable suggestion skipped: line range outside the PR's diff.
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 82 - 90, Several DB helper functions
(create_mod_case, update_mod_case_reason, add_warning, clear_user_warnings,
add_mod_note) risk UnboundLocalError and leaking connections; initialize conn =
None before the try in each of these functions, replace checks like "if conn:"
with "if conn is not None:" in except handlers, and add a finally block that
returns the connection via return_db_connection(conn) (or closes cursor) to
guarantee cleanup even on errors; follow the same pattern for any other helper
that calls get_db_connection()/db_pool.getconn() to ensure the pool is not
exhausted.
| try: | ||
| target_user = await bot.fetch_user(case['user_id']) | ||
| target_name = f"{target_user.name} ({target_user.mention})" | ||
| except: | ||
| target_name = f"Unknown User (ID: {case['user_id']})" | ||
|
|
||
| try: | ||
| moderator = await bot.fetch_user(case['moderator_id']) | ||
| mod_name = f"{moderator.name} ({moderator.mention})" | ||
| except: | ||
| mod_name = f"Unknown Moderator (ID: {case['moderator_id']})" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid bare except: in command handlers (it hides real failures).
Bare except will swallow unexpected bugs (including programming errors) and make diagnosing production issues harder. Narrow these (e.g., discord.NotFound, discord.HTTPException) and log the exception type/message at least.
Also applies to: 3226-3232, 3410-3415
🧰 Tools
🪛 Ruff (0.14.10)
3067-3067: Do not use bare except
(E722)
3073-3073: Do not use bare except
(E722)
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 3064 - 3074, Replace bare except blocks around
the bot.fetch_user calls (the blocks that set target_user/target_name and
moderator/mod_name) with handlers for specific Discord errors (e.g.,
discord.NotFound, discord.HTTPException) and log the caught exception details
(type/message) before setting the fallback "Unknown ..." strings; ensure you
still catch and log other unexpected exceptions explicitly (e.g., except
Exception as e: logger.exception(...)) rather than using a bare except. Apply
the same change pattern to the similar try/except sites referenced (the blocks
around lines handling user/moderator lookup at the other occurrences).
Moderationbot.py
Outdated
| dm_embed = discord.Embed( | ||
| title=f"⚠️ Warning in {interaction.guild.name}", | ||
| description=f"You have been warned by a moderator.", | ||
| color=discord.Color.orange() | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Remove f prefix on f-strings without placeholders.
Minor cleanup: a couple strings are marked as f-strings but don’t interpolate anything.
Also applies to: 3307-3311
🧰 Tools
🪛 Ruff (0.14.10)
3174-3174: f-string without any placeholders
Remove extraneous f prefix
(F541)
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 3172 - 3176, The code uses f-strings where no
interpolation occurs; remove the unnecessary leading "f" on those string
literals (e.g., the title and description in the dm_embed creation around the
dm_embed variable and the similar embed created at the block around lines
3307-3311) so they are plain strings (use string literals without f-prefix)
while leaving any true interpolations (like {interaction.guild.name}) as
f-strings if present; locate the embed constructions (variable dm_embed and the
other embed instance) and strip the f from string literals that contain no
placeholders.
| ### 📋 Moderation Tracking Commands | ||
| | Command | Description | Required Permission | | ||
| |---------|-------------|---------------------| | ||
| | `/modnote` | Add a note about a user (visible only to mods) | Moderate Members | | ||
| | `/warn` | Issue a warning to a user | Moderate Members | | ||
| | `/warnings` | View warnings for a specific user | Moderate Members | | ||
| | `/clearwarns` | Clear all warnings for a user | Administrator | | ||
| | `/case` | View details of a specific moderation case | Moderate Members | | ||
| | `/reason` | Add/edit reason for a moderation action | Moderate Members | | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Command name mismatch vs code: /clearwarns vs /clearwarnings.
In Moderationbot.py the command is name="clearwarnings", not /clearwarns. Either rename the command or update the README to match.
Also applies to: 184-191
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
122-122: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
In @README.md around lines 121 - 130, README lists the command as `/clearwarns`
but the bot code registers it as name="clearwarnings" in Moderationbot.py; make
them consistent by either renaming the command in Moderationbot.py to
name="clearwarns" or updating the README to `/clearwarnings` (also update the
duplicate occurrence at lines 184-191) so the displayed command matches the
actual command name used in Moderationbot.py.
| ### 🔊 Voice Moderation Commands | ||
| | Command | Description | Required Permission | | ||
| |---------|-------------|---------------------| | ||
| | `/dc` | Disconnect a user from voice | Move Members | | ||
| | `/server-deafen` | Server deafen a user in voice | Deafen Members | | ||
| | `/server-mute` | Server mute a user in voice | Mute Members | | ||
| | `/server-unmute` | Server unmute a user in voice | Mute Members | | ||
| | `/server-undeafen` | Server undeafen a user in voice | Deafen Members | | ||
| | `/sdeaf` | Server deafen a user in voice | Deafen Members | | ||
| | `/smute` | Server mute a user in voice | Mute Members | | ||
| | `/smuteno` | Server unmute a user in voice | Mute Members | | ||
| | `/sdeafno` | Server undeafen a user in voice | Deafen Members | | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Voice moderation command names/permissions don’t match code (and /dc permission is intentionally different).
- Code uses
name="server-deafen"/name="server-mute"/name="server-unmute"/name="server-undeafen"(not/sdeaf,/smute, etc.). - README says
/dcrequires “Move Members”, but this repo intentionally usessend_pollsto allow everyone to use it. Based on learnings, please update the README to reflect the intentional design.
🧰 Tools
🪛 markdownlint-cli2 (0.18.1)
132-132: Tables should be surrounded by blank lines
(MD058, blanks-around-tables)
🤖 Prompt for AI Agents
In @README.md around lines 131 - 139, Update the Voice Moderation Commands table
so the command names and permissions match the implementation: replace `/sdeaf`,
`/smute`, `/smuteno`, `/sdeafno` with the actual command names used in code
(e.g., name="server-deafen", name="server-mute", name="server-unmute",
name="server-undeafen") and change the `/dc` permission entry to indicate it
uses the `send_polls` permission (not "Move Members") to reflect the intentional
design that lets everyone use `/dc`. Ensure the Description column stays
accurate for each command.
| ### Moderation Tracking System | ||
| - **Persistent Storage**: All warnings, notes, and cases stored in database | ||
| - **Case Management**: Each moderation action gets a unique case ID | ||
| - **Warning System**: Issue and track warnings for users | ||
| - **Mod Notes**: Private notes visible only to moderators | ||
| - **Reason Editing**: Update reasons for past moderation actions | ||
| - **Warning History**: View all warnings for any user | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DB schema docs don’t match actual tables/columns and omit warnings table.
Code creates moderation_cases, moderation_warnings, moderation_notes with created_at/updated_at fields and indexes. README shows cases/modnotes and doesn’t document warnings storage. Aligning this will reduce setup mistakes.
Also applies to: 366-389, 440-442
🤖 Prompt for AI Agents
In @README.md around lines 290 - 297, Update the README’s Moderation Tracking
System docs to match the actual DB schema: rename tables to moderation_cases,
moderation_warnings, and moderation_notes (instead of cases/modnotes), document
the warnings storage (moderation_warnings), and include the created_at and
updated_at timestamp columns plus any relevant indexes present in the code;
ensure column names and index purposes mirror the migrations/schema definitions
(e.g., primary keys, foreign keys to user IDs, and indexes used for lookups) so
the README accurately reflects the implementation.
|
Note Docstrings generation - SUCCESS |
Docstrings generation was requested by @SorynTech. * #40 (comment) The following files were modified: * `Moderationbot.py`
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Add psycopg2 and sslmode requirement to dependencies
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 4
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
Moderationbot.py (1)
1687-1709: Major: Moderation actions don't create database records.The database tracking functions are defined, but existing moderation commands (
/kick,/ban,/mute) don't callcreate_mod_caseto log their actions. This means no moderation cases will be created, making the/casecommand and tracking system non-functional.🔧 Integrate database logging into moderation commands
Example for the
/kickcommand:await member.kick(reason=reason) + + # Log moderation action to database + case_id = await asyncio.to_thread( + create_mod_case, + interaction.guild.id, + member.id, + interaction.user.id, + "kick", + reason + ) + + response_msg = f"✅ {member.mention} has been kicked. Reason: {reason or 'No reason provided'}" + if case_id: + response_msg += f"\n📋 Case ID: #{case_id}" + - await interaction.followup.send( - f"✅ {member.mention} has been kicked. Reason: {reason or 'No reason provided'}" - ) + await interaction.followup.send(response_msg)Apply similar integration to
/ban,/mute, and other moderation commands.Also applies to: 1718-1739, 1777-1799
🤖 Fix all issues with AI agents
In @Moderationbot.py:
- Around line 303-327: The read-only functions (get_mod_case,
get_user_mod_cases, get_user_warnings, get_user_mod_notes) can leak DB
connections when an exception occurs; wrap the DB usage in a try/finally so that
get_db_connection() result is always passed to return_db_connection(conn) (and
any open cursor is closed) in the finally block; specifically, for get_mod_case
move cur.close() and return_db_connection(conn) into a finally, and ensure the
try covers only the execute/fetch logic (or use nested try/except inside
try/finally), and apply the same pattern to the other three functions (functions
named get_user_mod_cases, get_user_warnings, get_user_mod_notes) so connections
are returned to the pool on both success and error.
- Around line 260-301: The exception handler in create_mod_case may reference an
undefined conn if get_db_connection() raises; initialize conn = None before the
try, and in the except use a safe check like "if conn is not None:" before
calling conn.rollback() or return_db_connection(conn); also ensure the
normal-flow return_db_connection(conn) is only called when conn is not None.
Apply the same init-and-safe-check fix to the other CRUD functions mentioned
(add_warning, add_mod_note, update_mod_case_reason, clear_user_warnings) to
avoid NameError on exception.
- Around line 92-254: The init_moderation_database() function is defined but
never invoked; call init_moderation_database() from the bot's on_ready event
handler (the on_ready coroutine/function) during startup—ideally after other
readiness tasks (e.g., after presence/status setup and logging) so the DB is
initialized once the bot is ready; handle its boolean return (log error/disable
features if False) and catch/propagate exceptions so startup doesn't silently
skip DB init.
- Around line 545-552: The close_database() function is never invoked, so DB
connections leak on exit; update the program entry (the __main__ block where
bot.run(...) is called) to wrap bot.run() in a try/finally (or
try/except/finally) and call close_database() in the finally block so
connections are always closed; preserve any existing except crash-logging
behavior and ensure close_database() is called even after exceptions or normal
shutdown.
🧹 Nitpick comments (3)
Moderationbot.py (3)
3063-3074: Refactor: Replace bare except clauses with specific exception handling.Lines 3067 and 3073 use bare
except:clauses which can catch system exceptions likeKeyboardInterruptandSystemExit. This makes debugging harder and can mask unexpected errors.♻️ Use specific exception types
# Try to fetch user and moderator try: target_user = await bot.fetch_user(case['user_id']) target_name = f"{target_user.name} ({target_user.mention})" - except: + except (discord.NotFound, discord.HTTPException): target_name = f"Unknown User (ID: {case['user_id']})" try: moderator = await bot.fetch_user(case['moderator_id']) mod_name = f"{moderator.name} ({moderator.mention})" - except: + except (discord.NotFound, discord.HTTPException): mod_name = f"Unknown Moderator (ID: {case['moderator_id']})"As per static analysis hints (E722).
260-261: Refactor: Use explicitOptionaltype hints.Function parameters with
Nonedefault values should use explicitOptional[T]orT | Nonetype hints for clarity and PEP 484 compliance.♻️ Add explicit Optional annotations
-def create_mod_case(guild_id: int, user_id: int, moderator_id: int, - action_type: str, reason: str = None) -> Optional[int]: +def create_mod_case(guild_id: int, user_id: int, moderator_id: int, + action_type: str, reason: Optional[str] = None) -> Optional[int]:Apply similar changes to:
add_warning(line 392):reason: Optional[str]add_mod_note(line 484):note_text: Optional[str](if applicable)As per static analysis hints (RUF013).
Also applies to: 392-392, 484-484
250-253: Optional refactor: Consider catching more specific database exceptions.The code catches broad
Exceptiontypes in database operations. While functional, catching specificpsycopg2exceptions would make error handling more precise and avoid catching unexpected errors.♻️ Use specific psycopg2 exceptions
+import psycopg2 +from psycopg2 import DatabaseError, OperationalError def create_mod_case(guild_id: int, user_id: int, moderator_id: int, action_type: str, reason: Optional[str] = None) -> Optional[int]: # ... existing code ... - except Exception as e: + except (DatabaseError, OperationalError) as e: print(f"❌ Error creating mod case: {e}") if conn: conn.rollback() return_db_connection(conn) return NoneApply similar changes to other database functions.
As per static analysis hints (BLE001).
Also applies to: 295-300, 352-357, 414-419, 472-477, 506-511
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
Moderationbot.py
🧰 Additional context used
🧠 Learnings (2)
📓 Common learnings
Learnt from: SorynTech
Repo: SorynTech/Discord-Moderation-Bot- PR: 0
File: :0-0
Timestamp: 2025-12-30T01:12:13.273Z
Learning: Remind SorynTech to add database support in the next PR they submit to the Discord-Moderation-Bot repository.
📚 Learning: 2025-12-30T01:12:13.273Z
Learnt from: SorynTech
Repo: SorynTech/Discord-Moderation-Bot- PR: 0
File: :0-0
Timestamp: 2025-12-30T01:12:13.273Z
Learning: Remind SorynTech to add database support in the next PR they submit to the Discord-Moderation-Bot repository.
Applied to files:
Moderationbot.py
🪛 Ruff (0.14.10)
Moderationbot.py
248-248: Consider moving this statement to an else block
(TRY300)
250-250: Do not catch blind exception: Exception
(BLE001)
261-261: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
293-293: Consider moving this statement to an else block
(TRY300)
295-295: Do not catch blind exception: Exception
(BLE001)
324-324: Do not catch blind exception: Exception
(BLE001)
350-350: Consider moving this statement to an else block
(TRY300)
352-352: Do not catch blind exception: Exception
(BLE001)
383-383: Do not catch blind exception: Exception
(BLE001)
412-412: Consider moving this statement to an else block
(TRY300)
414-414: Do not catch blind exception: Exception
(BLE001)
444-444: Do not catch blind exception: Exception
(BLE001)
470-470: Consider moving this statement to an else block
(TRY300)
472-472: Do not catch blind exception: Exception
(BLE001)
504-504: Consider moving this statement to an else block
(TRY300)
506-506: Do not catch blind exception: Exception
(BLE001)
536-536: Do not catch blind exception: Exception
(BLE001)
2858-2858: Use explicit conversion flag
Replace with conversion flag
(RUF010)
3067-3067: Do not use bare except
(E722)
3073-3073: Do not use bare except
(E722)
🔇 Additional comments (2)
Moderationbot.py (2)
282-285: LGTM: SQL injection protection is correctly implemented.All database queries consistently use parameterized queries with proper parameter binding, which effectively prevents SQL injection attacks.
Also applies to: 312-316, 338-343, 369-375, 401-404, 431-436, 458-462, 493-496, 523-528
119-157: LGTM: Well-designed database schema with appropriate indexes.The schema design demonstrates best practices:
- Uses
BIGINTfor Discord snowflake IDs- Includes
TIMESTAMP WITH TIME ZONEfor proper timezone handling- Composite indexes on
(guild_id, user_id)optimize common queries- Separate index on
created_atenables efficient time-based queriesAlso applies to: 162-192, 196-226, 230-238
| def init_moderation_database(): | ||
| """Initialize database with moderation tracking tables""" | ||
| global db_pool | ||
|
|
||
| print("=" * 60) | ||
| print("🗄️ INITIALIZING MODERATION DATABASE") | ||
| print("=" * 60) | ||
|
|
||
| if not SUPABASE_URL: | ||
| print("❌ SUPABASE_URL not found in environment variables!") | ||
| print("⚠️ Moderation tracking features will be disabled.") | ||
| print("Add SUPABASE_URL to your .env file to enable moderation tracking.") | ||
| return False | ||
|
|
||
| try: | ||
| # Create connection pool | ||
| print("🔄 Creating database connection pool...") | ||
| db_pool = SimpleConnectionPool(1, 20, SUPABASE_URL) | ||
| print("✅ Database connection pool created") | ||
|
|
||
| print("🔌 Testing database connection...") | ||
| conn = get_db_connection() | ||
| cur = conn.cursor() | ||
| print("✅ Database connection successful") | ||
|
|
||
| # Create moderation_cases table | ||
| print("📋 Creating 'moderation_cases' table...") | ||
| cur.execute('''CREATE TABLE IF NOT EXISTS moderation_cases | ||
| ( | ||
| case_id | ||
| SERIAL | ||
| PRIMARY | ||
| KEY, | ||
| guild_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| user_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| moderator_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| action_type | ||
| TEXT | ||
| NOT | ||
| NULL, | ||
| reason | ||
| TEXT, | ||
| created_at | ||
| TIMESTAMP | ||
| WITH | ||
| TIME | ||
| ZONE | ||
| DEFAULT | ||
| CURRENT_TIMESTAMP, | ||
| updated_at | ||
| TIMESTAMP | ||
| WITH | ||
| TIME | ||
| ZONE | ||
| DEFAULT | ||
| CURRENT_TIMESTAMP | ||
| )''') | ||
| print("✅ 'moderation_cases' table ready") | ||
|
|
||
| # Create moderation_warnings table | ||
| print("📋 Creating 'moderation_warnings' table...") | ||
| cur.execute('''CREATE TABLE IF NOT EXISTS moderation_warnings | ||
| ( | ||
| warning_id | ||
| SERIAL | ||
| PRIMARY | ||
| KEY, | ||
| guild_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| user_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| moderator_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| reason | ||
| TEXT | ||
| NOT | ||
| NULL, | ||
| created_at | ||
| TIMESTAMP | ||
| WITH | ||
| TIME | ||
| ZONE | ||
| DEFAULT | ||
| CURRENT_TIMESTAMP | ||
| )''') | ||
| print("✅ 'moderation_warnings' table ready") | ||
|
|
||
| # Create moderation_notes table | ||
| print("📋 Creating 'moderation_notes' table...") | ||
| cur.execute('''CREATE TABLE IF NOT EXISTS moderation_notes | ||
| ( | ||
| note_id | ||
| SERIAL | ||
| PRIMARY | ||
| KEY, | ||
| guild_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| user_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| moderator_id | ||
| BIGINT | ||
| NOT | ||
| NULL, | ||
| note_text | ||
| TEXT | ||
| NOT | ||
| NULL, | ||
| created_at | ||
| TIMESTAMP | ||
| WITH | ||
| TIME | ||
| ZONE | ||
| DEFAULT | ||
| CURRENT_TIMESTAMP | ||
| )''') | ||
| print("✅ 'moderation_notes' table ready") | ||
|
|
||
| # Create indexes for better performance | ||
| print("📊 Creating database indexes...") | ||
| cur.execute('''CREATE INDEX IF NOT EXISTS idx_mod_cases_guild_user | ||
| ON moderation_cases(guild_id, user_id)''') | ||
| cur.execute('''CREATE INDEX IF NOT EXISTS idx_mod_cases_created | ||
| ON moderation_cases(created_at)''') | ||
| cur.execute('''CREATE INDEX IF NOT EXISTS idx_mod_warnings_guild_user | ||
| ON moderation_warnings(guild_id, user_id)''') | ||
| cur.execute('''CREATE INDEX IF NOT EXISTS idx_mod_notes_guild_user | ||
| ON moderation_notes(guild_id, user_id)''') | ||
| print("✅ All indexes created") | ||
|
|
||
| conn.commit() | ||
| print("✅ Database changes committed") | ||
| cur.close() | ||
| return_db_connection(conn) | ||
|
|
||
| print("=" * 60) | ||
| print("✅ MODERATION DATABASE INITIALIZED SUCCESSFULLY") | ||
| print("=" * 60) | ||
| return True | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Database initialization error: {e}") | ||
| print("⚠️ Moderation tracking features will be disabled.") | ||
| return False | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Database initialization function is never called.
The init_moderation_database() function is defined but never invoked. Database features will not work until this function is called during bot startup.
🔧 Add initialization to on_ready event
Add the database initialization call in the on_ready event handler after line 1614:
@bot.event
async def on_ready():
global bot_start_time, commands_synced
bot_start_time = datetime.now()
print(f'[{datetime.now()}] {bot.user} has connected to Discord!', flush=True)
+
+ # Initialize moderation database
+ init_moderation_database()
if not commands_synced:Based on learnings, you requested database support be added in this PR, and this is a critical step to make it functional.
Committable suggestion skipped: line range outside the PR's diff.
🧰 Tools
🪛 Ruff (0.14.10)
248-248: Consider moving this statement to an else block
(TRY300)
250-250: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 92 - 254, The init_moderation_database()
function is defined but never invoked; call init_moderation_database() from the
bot's on_ready event handler (the on_ready coroutine/function) during
startup—ideally after other readiness tasks (e.g., after presence/status setup
and logging) so the DB is initialized once the bot is ready; handle its boolean
return (log error/disable features if False) and catch/propagate exceptions so
startup doesn't silently skip DB init.
| def create_mod_case(guild_id: int, user_id: int, moderator_id: int, | ||
| action_type: str, reason: str = None) -> Optional[int]: | ||
| """ | ||
| Create a new moderation case | ||
| Args: | ||
| guild_id: Discord guild ID | ||
| user_id: Target user ID | ||
| moderator_id: Moderator user ID | ||
| action_type: Type of action (kick, ban, mute, etc.) | ||
| reason: Reason for the action | ||
| Returns: | ||
| case_id if successful, None otherwise | ||
| """ | ||
| if db_pool is None: | ||
| return None | ||
|
|
||
| try: | ||
| conn = get_db_connection() | ||
| cur = conn.cursor() | ||
|
|
||
| cur.execute('''INSERT INTO moderation_cases | ||
| (guild_id, user_id, moderator_id, action_type, reason) | ||
| VALUES (%s, %s, %s, %s, %s) RETURNING case_id''', | ||
| (guild_id, user_id, moderator_id, action_type, reason)) | ||
|
|
||
| case_id = cur.fetchone()[0] | ||
| conn.commit() | ||
| cur.close() | ||
| return_db_connection(conn) | ||
|
|
||
| print(f"✅ Created moderation case #{case_id} for user {user_id}") | ||
| return case_id | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Error creating mod case: {e}") | ||
| if conn: | ||
| conn.rollback() | ||
| return_db_connection(conn) | ||
| return None | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Critical: Potential NameError in exception handler.
If get_db_connection() raises an exception at line 279, the variable conn will be undefined, but the exception handler at line 297 attempts to check if conn:, which will raise a NameError.
🔧 Initialize conn before try block
def create_mod_case(guild_id: int, user_id: int, moderator_id: int,
action_type: str, reason: str = None) -> Optional[int]:
"""
Create a new moderation case
Args:
guild_id: Discord guild ID
user_id: Target user ID
moderator_id: Moderator user ID
action_type: Type of action (kick, ban, mute, etc.)
reason: Reason for the action
Returns:
case_id if successful, None otherwise
"""
if db_pool is None:
return None
+ conn = None
try:
conn = get_db_connection()
cur = conn.cursor()
cur.execute('''INSERT INTO moderation_cases
(guild_id, user_id, moderator_id, action_type, reason)
VALUES (%s, %s, %s, %s, %s) RETURNING case_id''',
(guild_id, user_id, moderator_id, action_type, reason))
case_id = cur.fetchone()[0]
conn.commit()
cur.close()
return_db_connection(conn)
print(f"✅ Created moderation case #{case_id} for user {user_id}")
return case_id
except Exception as e:
print(f"❌ Error creating mod case: {e}")
if conn:
conn.rollback()
return_db_connection(conn)
return NoneNote: This same pattern appears in other CRUD functions (add_warning, add_mod_note, update_mod_case_reason, clear_user_warnings) and should be fixed there as well.
📝 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.
| def create_mod_case(guild_id: int, user_id: int, moderator_id: int, | |
| action_type: str, reason: str = None) -> Optional[int]: | |
| """ | |
| Create a new moderation case | |
| Args: | |
| guild_id: Discord guild ID | |
| user_id: Target user ID | |
| moderator_id: Moderator user ID | |
| action_type: Type of action (kick, ban, mute, etc.) | |
| reason: Reason for the action | |
| Returns: | |
| case_id if successful, None otherwise | |
| """ | |
| if db_pool is None: | |
| return None | |
| try: | |
| conn = get_db_connection() | |
| cur = conn.cursor() | |
| cur.execute('''INSERT INTO moderation_cases | |
| (guild_id, user_id, moderator_id, action_type, reason) | |
| VALUES (%s, %s, %s, %s, %s) RETURNING case_id''', | |
| (guild_id, user_id, moderator_id, action_type, reason)) | |
| case_id = cur.fetchone()[0] | |
| conn.commit() | |
| cur.close() | |
| return_db_connection(conn) | |
| print(f"✅ Created moderation case #{case_id} for user {user_id}") | |
| return case_id | |
| except Exception as e: | |
| print(f"❌ Error creating mod case: {e}") | |
| if conn: | |
| conn.rollback() | |
| return_db_connection(conn) | |
| return None | |
| def create_mod_case(guild_id: int, user_id: int, moderator_id: int, | |
| action_type: str, reason: str = None) -> Optional[int]: | |
| """ | |
| Create a new moderation case | |
| Args: | |
| guild_id: Discord guild ID | |
| user_id: Target user ID | |
| moderator_id: Moderator user ID | |
| action_type: Type of action (kick, ban, mute, etc.) | |
| reason: Reason for the action | |
| Returns: | |
| case_id if successful, None otherwise | |
| """ | |
| if db_pool is None: | |
| return None | |
| conn = None | |
| try: | |
| conn = get_db_connection() | |
| cur = conn.cursor() | |
| cur.execute('''INSERT INTO moderation_cases | |
| (guild_id, user_id, moderator_id, action_type, reason) | |
| VALUES (%s, %s, %s, %s, %s) RETURNING case_id''', | |
| (guild_id, user_id, moderator_id, action_type, reason)) | |
| case_id = cur.fetchone()[0] | |
| conn.commit() | |
| cur.close() | |
| return_db_connection(conn) | |
| print(f"✅ Created moderation case #{case_id} for user {user_id}") | |
| return case_id | |
| except Exception as e: | |
| print(f"❌ Error creating mod case: {e}") | |
| if conn: | |
| conn.rollback() | |
| return_db_connection(conn) | |
| return None |
🧰 Tools
🪛 Ruff (0.14.10)
261-261: PEP 484 prohibits implicit Optional
Convert to T | None
(RUF013)
293-293: Consider moving this statement to an else block
(TRY300)
295-295: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 260 - 301, The exception handler in
create_mod_case may reference an undefined conn if get_db_connection() raises;
initialize conn = None before the try, and in the except use a safe check like
"if conn is not None:" before calling conn.rollback() or
return_db_connection(conn); also ensure the normal-flow
return_db_connection(conn) is only called when conn is not None. Apply the same
init-and-safe-check fix to the other CRUD functions mentioned (add_warning,
add_mod_note, update_mod_case_reason, clear_user_warnings) to avoid NameError on
exception.
| def get_mod_case(case_id: int, guild_id: int) -> Optional[Dict]: | ||
| """Get details of a specific moderation case""" | ||
| if db_pool is None: | ||
| return None | ||
|
|
||
| try: | ||
| conn = get_db_connection() | ||
| cur = conn.cursor(cursor_factory=RealDictCursor) | ||
|
|
||
| cur.execute('''SELECT * | ||
| FROM moderation_cases | ||
| WHERE case_id = %s | ||
| AND guild_id = %s''', | ||
| (case_id, guild_id)) | ||
|
|
||
| result = cur.fetchone() | ||
| cur.close() | ||
| return_db_connection(conn) | ||
|
|
||
| return dict(result) if result else None | ||
|
|
||
| except Exception as e: | ||
| print(f"❌ Error getting mod case: {e}") | ||
| return None | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Major: Connection leaks in read-only query functions.
Functions get_mod_case, get_user_mod_cases, get_user_warnings, and get_user_mod_notes don't return connections to the pool if an exception occurs after acquiring the connection. This will eventually exhaust the connection pool.
🔧 Use try-finally to ensure connection return
Example fix for get_mod_case:
def get_mod_case(case_id: int, guild_id: int) -> Optional[Dict]:
"""Get details of a specific moderation case"""
if db_pool is None:
return None
+ conn = None
try:
conn = get_db_connection()
cur = conn.cursor(cursor_factory=RealDictCursor)
cur.execute('''SELECT *
FROM moderation_cases
WHERE case_id = %s
AND guild_id = %s''',
(case_id, guild_id))
result = cur.fetchone()
cur.close()
- return_db_connection(conn)
return dict(result) if result else None
except Exception as e:
print(f"❌ Error getting mod case: {e}")
return None
+ finally:
+ if conn:
+ return_db_connection(conn)Apply this same pattern to the other read-only functions.
Also applies to: 360-386, 422-447, 514-539
🧰 Tools
🪛 Ruff (0.14.10)
324-324: Do not catch blind exception: Exception
(BLE001)
🤖 Prompt for AI Agents
In @Moderationbot.py around lines 303 - 327, The read-only functions
(get_mod_case, get_user_mod_cases, get_user_warnings, get_user_mod_notes) can
leak DB connections when an exception occurs; wrap the DB usage in a try/finally
so that get_db_connection() result is always passed to
return_db_connection(conn) (and any open cursor is closed) in the finally block;
specifically, for get_mod_case move cur.close() and return_db_connection(conn)
into a finally, and ensure the try covers only the execute/fetch logic (or use
nested try/except inside try/finally), and apply the same pattern to the other
three functions (functions named get_user_mod_cases, get_user_warnings,
get_user_mod_notes) so connections are returned to the pool on both success and
error.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In @requirements.txt:
- Around line 4-5: requirements.txt incorrectly includes "sslmode='require'",
which is not a PyPI package and will break pip install; remove that line from
requirements.txt and ensure SSL is passed as a DB connection parameter instead
(e.g., add sslmode='require' to the connection string or to the psycopg2.connect
call in your DB setup, e.g., in Moderationbot.py or whichever module creates the
psycopg2 connection).
📜 Review details
Configuration used: defaults
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
requirements.txt
🧰 Additional context used
🧠 Learnings (1)
📓 Common learnings
Learnt from: SorynTech
Repo: SorynTech/Discord-Moderation-Bot- PR: 0
File: :0-0
Timestamp: 2025-12-30T01:12:13.273Z
Learning: Remind SorynTech to add database support in the next PR they submit to the Discord-Moderation-Bot repository.
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
Summary by CodeRabbit
New Features
Documentation
Chores
✏️ Tip: You can customize this high-level summary in your review settings.