A lightweight “democracy-driven” moderation bot for Discord: members vote on actions (timeout, jail/unjail, delete, channel management, VIP), and when the vote total reaches a threshold, the bot executes the action.
It’s basically crowdsourced moderation with guardrails, plus optional “force” actions for trusted roles/users (with a 1-week cooldown). Because humans needed more buttons.
- Every vote has an action (e.g.,
timeout) and a target (a user, message, or channel). - Votes are weighted by each voter’s configured vote power (default weight =
1). - Votes remain open for a vote window (default: 30 minutes) and then expire if the threshold isn’t reached.
- When the threshold is reached, the bot executes the action and clears the vote.
- Vote-based actions:
- Jail (ban) / Unjail (unban)
- Timeout / Untimeout
- Delete message (by link or reply)
- Make VIP
- Channel actions: Create, Archive, Unarchive, Move, Rename, Reposition
- Weighted voting:
- Configure vote power by role or user override.
- “Force” actions:
#commands for trusted roles/users, enforced with a 7-day cooldown.
- Logging:
- Staff log embeds to a configured logs channel.
- Deletion logging includes message content, embed flattening, attachment URLs, and image attachment capture.
- Persistence:
- SQLite DB stores server config, thresholds, vote powers, force grants, force cooldowns, jailed state.
python -m venv .venv
# Windows:
.venv\Scripts\activate
# macOS/Linux:
source .venv/bin/activate
pip install -r requirements.txtCopy .env.example → .env and fill it in:
DISCORD_TOKEN="YOUR_TOKEN_HERE"
DEFAULT_PREFIX=!
DB_PATH=./data/community_votes.sqlite
LOG_LEVEL=INFO
# Optional: where delete-action image attachments get stored
DELETED_MEDIA_DIR=./data/deleted_mediapython -m community_votes.bootstrap.loader --init-dbpython -m community_votes.bootstrap.loaderAt minimum, the bot needs:
- Read Messages / View Channels
- Send Messages
- Manage Webhooks (recommended, so it can speak using personas; it will fall back to normal sends if it can’t)
- Manage Roles (for jail/unjail and VIP role assignment)
- Moderate Members (for timeouts)
- Manage Messages (for deletion)
- Manage Channels (for create/move/rename/archive/unarchive/reposition)
Also:
- Ensure the bot’s highest role is above the roles it must assign/remove (Jail, Member, VIP).
You must configure your server roles/channels before moderation actions will work correctly.
!cv setup <MemberRole> <JailRole> <VIPRole> <#logs> <#jail>
Accepted inputs:
- Roles: mention (
@Role), raw ID, or exact name (case-insensitive). - Channels: mention (
#channel), raw ID, or exact name (case-insensitive).
Example:
!cv setup @Member @Jail @VIP #staff-logs #jail
!cv show_config
- Default: 30 minutes (
DEFAULT_VOTE_WINDOW_SECONDS = 30 * 60) - Can be overridden per guild in the database (
guild_config.vote_window_seconds).
- Defaults live in
community_votes/config.py(DEFAULT_THRESHOLDS) - Can be overridden per action per guild with:
!vote set-threshold <action> <number>!vote reset-threshold [action]
Show effective thresholds:
!vote show-thresholds
Important note about “sensitive bumps”:
deletein sensitive channels: adds +20 to threshold (implemented)create_channel,move_channel,unarchive_channelin sensitive categories: adds +5 to threshold (implemented)- If you see docs/messages saying +20 for categories, that’s a mismatch with the current code. The code uses +5.
Vote power = how much your vote counts.
Resolution order:
- User override (can be
0to disable voting) - Highest configured role weight
- Default
1
Commands:
!vote set-power <@role|role_id|@user|user_id|name> <weight>
!vote unset-power <@role|role_id|@user|user_id|name>
!vote show-roles
!vote show-power
Some commands have a “force” version that executes immediately (no vote), but only for granted roles/users and only once per action per week.
Grant or revoke force power:
!vote set-forceban-power <@role|role_id|@user|user_id|name> on|off
!vote show-forceban-power
Cooldown:
- 7 days per action (
FORCE_COOLDOWN = timedelta(days=7))
Force examples:
!ban# @user spamming
!timeout# @user 10m chill
!delete# <message link> # or reply to message and run !delete#
!unban# <user_id>
The bot exposes most commands both as:
- direct commands like
!ban, and - subcommands under the hub like
!vote ban
!vote
Shows a help-style “how it works” embed and lists default thresholds.
!ban @user [reason] # aka !jail
!unban <user_id|mention> # aka !unjail
!timeout @user 10m [reason]
!untimeout @user
Delete a message by link:
!delete https://discord.com/channels/<guild>/<channel>/<message>
Or reply to a message and run:
!delete
!make vip @user
# alias: !make_vip @user
!create tc|vc <name> <category_id> [position]
!archive #channel
!unarchive # lists channels inside the archive category
!unarchive #channel <category_id> [position]
!move #channel <category_id> [position]
!rename #channel <new_name>
!reposition #channel <position>
!reposition #channel <category_id> <position>
Archiving is opinionated:
When a vote reaches threshold for archive_channel:
- Sets
@everyone→view_channel = False - Moves the channel into a fixed archive category
That category is configured in code:
community_votes/config.py→ARCHIVE_CATEGORY_ID
If you move servers or recreate categories, update that constant.
Unarchive is only allowed if the channel currently lives in ARCHIVE_CATEGORY_ID, then it:
- restores
@everyoneview - restores
@everyoneconnect for voice/stage channels - moves to the requested category (and optional position)
Also:
- Running
!unarchivewith no args lists channels currently in the archive category.
Default path: ./data/community_votes.sqlite (configurable via DB_PATH in .env)
Tables:
guild_config(roles/channels, vote window, sensitive/protected lists)thresholds(per-guild action overrides)vote_power_roles,vote_power_usersforce_power_roles,force_power_usersforce_cooldownsjailed_users
Schema lives at:
community_votes/db/schema.sql
The schema supports:
sensitive_channel_ids,sensitive_category_idsprotected_channel_ids,protected_category_ids
Currently implemented behaviors:
- Sensitive:
deletevotes get +20 threshold bump if the target message is in a sensitive channel- channel actions
create/move/unarchiveget +5 bump if the destination category is sensitive
- Protected:
- delete action refuses to delete in
protected_channel_ids
- delete action refuses to delete in
You can populate those lists by updating guild_config in the DB (there is no chat command for this yet).
If you set a logs channel via !cv setup, the bot will post staff log embeds.
For delete:
- The bot logs:
- author, channel, message id, jump link (pre-deletion)
- content (trimmed), embeds (flattened/trimmed), attachments list
- Image attachments are also:
- downloaded
- saved to disk under
DELETED_MEDIA_DIR(default./data/deleted_media) - uploaded into the logs channel as attachments for auditability
MIT (see LICENSE).