Skip to content

Conversation

@SorynTech
Copy link
Owner

@SorynTech SorynTech commented Jan 9, 2026

Summary by CodeRabbit

  • New Features

    • Full moderation tracking with persistent DB-backed cases, warnings, and mod notes; view and edit case details via a new case command and enhanced moderation commands
    • Moderation tracking can be toggled by configuration
  • Documentation

    • Expanded README: moderation tracking details, command reference, database guidance, debug tooling, renamed voice-moderation commands, status/progress updates
  • Chores

    • Added database client dependency for PostgreSQL support

✏️ Tip: You can customize this high-level summary in your review settings.

Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
Signed-off-by: Soryn Tech <zippydrawzstudioz@gmail.com>
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Caution

Review failed

The pull request is closed.

📝 Walkthrough

Walkthrough

Added a PostgreSQL-backed moderation data layer and connection pooling to Moderationbot.py, new CRUD functions for cases/warnings/notes, a /case slash command and DB-integrated moderation commands, init/cleanup helpers, and expanded README and requirements for database usage. (<=50 words)

Changes

Cohort / File(s) Summary
Database layer & moderation logic
Moderationbot.py
Added global db_pool and SUPABASE_URL gating, connection helpers (get_db_connection, return_db_connection, close_database, init_moderation_database), created tables and indexes, and implemented CRUD/DAO functions: create_mod_case, get_mod_case, update_mod_case_reason, get_user_mod_cases, add_warning, get_user_warnings, clear_user_warnings, add_mod_note, get_user_mod_notes.
Slash commands / command handlers
Moderationbot.py
Added /case command handler and integrated DB-backed flows for mod commands (warn/warnings/clearwarns/modnote/reason), with new error handling scaffolding and updated embed/message formatting to include DB-sourced fields.
Documentation
README.md
Added "Debug Files" token script, new "Moderation Tracking" section with schema and DB notes, updated command listings and counts, WIP/planned commands, progress stats, and technical/database details.
Dependencies
requirements.txt
Appended database dependency entries (including psycopg2 and duplicated HTTP libs); requires validation and cleanup.

Sequence Diagram

sequenceDiagram
    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)
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested labels

documentation

🐰
I hopped through code to plant a seed,
Cases logged for every deed,
Warnings, notes, and reasons clear,
A carrot for each mod to cheer,
Hooray — the garden’s tracked this year! 🥕

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Added Database support for moderation commands' directly reflects the main change: introducing database functionality to support moderation operations with persistent storage for cases, warnings, and notes.
Docstring Coverage ✅ Passed Docstring coverage is 82.35% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between f15744d and a4c1078.

📒 Files selected for processing (2)
  • Moderationbot.py
  • requirements.txt

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@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: 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: Duplicate on_command_error handler 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.timedelta will raise (you imported timedelta directly).
datetime here is the datetime class (from from datetime import datetime, timedelta), so datetime.timedelta(...) is invalid. Use timedelta(...).


2533-2537: Bug: datetime.datetime.now() will raise (same import issue).
Use datetime.now() (or import the module as import datetime).


1610-1637: Database initialization and cleanup not wired into startup/shutdown.

The init_moderation_database() and close_database() functions are defined (lines 92 and 545 respectively), and comments at lines 557–561 explicitly document that they should be called in on_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() to on_ready() with a guard to handle multiple reconnections, and add a finally block in the __main__ section to call close_database() on exit.

README.md (1)

103-110: Owner command name mismatch vs code: README says /updatemode, code registers name="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 use i; rename to _i to reduce noise.
  • rolemembers chunking 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

📥 Commits

Reviewing files that changed from the base of the PR and between 57de272 and 5226c32.

📒 Files selected for processing (2)
  • Moderationbot.py
  • README.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.md
  • Moderationbot.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.

Comment on lines +62 to +66
import psycopg2
from psycopg2.extras import RealDictCursor
from psycopg2.pool import SimpleConnectionPool
from typing import Optional, List, Dict

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

git ls-files | head -30

Repository: 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.txt

Repository: SorynTech/Discord-Moderation-Bot-

Length of output: 104


🏁 Script executed:

head -100 Moderationbot.py

Repository: SorynTech/Discord-Moderation-Bot-

Length of output: 3211


🏁 Script executed:

grep -n "psycopg2\|ssl\|SSL\|supabase\|Supabase" Moderationbot.py

Repository: SorynTech/Discord-Moderation-Bot-

Length of output: 245


🏁 Script executed:

grep -n "SimpleConnectionPool\|getconn\|connect\|sslmode\|SSL" Moderationbot.py | head -30

Repository: SorynTech/Discord-Moderation-Bot-

Length of output: 1360


🏁 Script executed:

sed -n '70,150p' Moderationbot.py

Repository: SorynTech/Discord-Moderation-Bot-

Length of output: 2684


🏁 Script executed:

cat README.md

Repository: 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.

Comment on lines 62 to 76
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

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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 2

Repository: 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 -20

Repository: 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 5

Repository: 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 -100

Repository: 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 2

Repository: 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 15

Repository: 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.

Suggested change
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.

Comment on lines +82 to +90
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)

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

🧩 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 -100

Repository: 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), and add_mod_note (line 508) check if conn: in except handlers without initializing conn beforehand. If an exception occurs at get_db_connection() or cursor creation, this raises UnboundLocalError.
  • 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.

Comment on lines +3064 to +3074
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']})"
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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
Comment on lines 3172 to 3176
dm_embed = discord.Embed(
title=f"⚠️ Warning in {interaction.guild.name}",
description=f"You have been warned by a moderator.",
color=discord.Color.orange()
)
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

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.

Comment on lines +121 to +130
### 📋 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 |

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Comment on lines 131 to 139
### 🔊 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 |

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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 /dc requires “Move Members”, but this repo intentionally uses send_polls to 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.

Comment on lines +290 to +297
### 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

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Jan 9, 2026

Note

Docstrings generation - SUCCESS
Generated docstrings for this pull request at #41

coderabbitai bot added a commit that referenced this pull request Jan 9, 2026
Docstrings generation was requested by @SorynTech.

* #40 (comment)

The following files were modified:

* `Moderationbot.py`
SorynTech and others added 2 commits January 9, 2026 13:28
Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
Add psycopg2 and sslmode requirement to dependencies
Copy link
Contributor

@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: 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 call create_mod_case to log their actions. This means no moderation cases will be created, making the /case command and tracking system non-functional.

🔧 Integrate database logging into moderation commands

Example for the /kick command:

     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 like KeyboardInterrupt and SystemExit. 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 explicit Optional type hints.

Function parameters with None default values should use explicit Optional[T] or T | None type 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 Exception types in database operations. While functional, catching specific psycopg2 exceptions 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 None

Apply 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

📥 Commits

Reviewing files that changed from the base of the PR and between 5226c32 and 0e68c1f.

📒 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 BIGINT for Discord snowflake IDs
  • Includes TIMESTAMP WITH TIME ZONE for proper timezone handling
  • Composite indexes on (guild_id, user_id) optimize common queries
  • Separate index on created_at enables efficient time-based queries

Also applies to: 162-192, 196-226, 230-238

Comment on lines +92 to +254
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

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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.

Comment on lines +260 to +301
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

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🔴 Critical

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 None

Note: 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.

Suggested change
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.

Comment on lines +303 to +327
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

Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

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.

Copy link
Contributor

@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: 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

📥 Commits

Reviewing files that changed from the base of the PR and between 0e68c1f and f15744d.

📒 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.

SorynTech and others added 4 commits January 9, 2026 13:33
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>
@SorynTech SorynTech merged commit 29e6252 into main Jan 9, 2026
1 check was pending
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants