disbot-a4 — Simple, robust role selector bot
Author: Lari Natri, 2025
License: MIT
A simple, robust Discord role-picker bot built with py-cord and packaged
for Docker Compose.
- Language/Libs: Python 3.12, py-cord
only
- Persistence: SQLite (stdlib), bind-mounted
./data
- Daily inactivity cleanup removes all pickable roles from users inactive
for N days (default 14).
- Auto-refresh updates all status panels and DM panels at a configurable
interval (default 5 minutes).
- Restart-safe: DB + tasks resume; re-create UI via
/role-admin-post-status
(or auto-post on startup if configured)
- Public status panels: list pickable roles with member counts and
"(inactive)/(full)" hints from configurable min/max values.
- UI: Personal role picker via
/roles (sent to your DMs) with buttons to
toggle your roles.
- UI: User slash commands for listing and toggling roles.
- UI: Admin slash commands to configure pickable roles, limits, inactivity
timeout, refresh cadence, list members, manage users, and post status panels.
1) Quick start
- Discord Developer Portal setup
Discord Developer Portal
- Create Application -> Bot: copy the token to
secrets/discord_token.txt.
- Privileged Gateway Intents (Bot settings -> Privileged Gateway Intents):
- Enable Server Members Intent
- (Optional) Enable Message Content Intent if you want the bot to
track activity from messages for inactivity cleanup. Otherwise the bot
still works; you can set activity via role interactions only.
- Invite the bot with scopes:
bot, applications.commands
- Bot Permissions: at minimum grant
- Manage Roles
- View Channel
- Send Messages
- Read Message History
- Manage Messages
- Copy the generated Invite URL and invite the bot to your server.
- Grab your Guild ID (enable Developer Mode in Discord client ->
right-click server icon -> Copy Server ID) and set it in
.env as
GUILD_ID.
Role hierarchy: Move the bot's highest role above every pickable
role you want it to manage. The bot cannot add/remove roles equal to or above
its top role. Managed/integration roles cannot be assigned by bots.
Create secrets & env
mkdir -p secrets data
sudo chown -R 10001:10001 data
echo "YOUR_BOT_TOKEN_HERE" > secrets/discord_token.txt
cp .env.example .env
# Edit .env: set GUILD_ID and (optionally) ROLE_PANEL_CHANNEL_ID, etc.
Build & run
docker compose up -d --build --remove-orphans
Add selectable roles for the server
On Discord server, add selectable roles with slash command:
/role-admin-add-pickable:@Role (optional min_users, max_users)
See all commands with /role-help
Post the role panel
- If you set
ROLE_PANEL_CHANNEL_ID in .env, the bot will auto-post the
panel on startup.
- Otherwise, in any channel, run (as an admin):
/role-admin-post-status
2) Environment variables
| Name |
Required |
Default |
Notes |
GUILD_ID |
Yes |
— |
Guild (server) ID where the bot operates & registers commands (fast sync). |
ROLE_PANEL_CHANNEL_ID |
No |
— |
If set, bot auto-posts the panel on startup to this channel. |
INACTIVITY_DAYS_DEFAULT |
No |
14 |
Initial value for inactivity (can be changed with slash command). |
REFRESH_MINUTES_DEFAULT |
No |
'5' |
Initial value for refreshing role status on posts (can be changed with slash command). |
DATABASE_PATH |
No |
/app/data/bot.db |
SQLite DB path (persisted via volume ./data:/app/data). |
LOG_LEVEL |
No |
INFO |
DEBUG, INFO, WARNING, ERROR. |
TZ |
No |
Europe/Helsinki |
Provided via Docker environment in docker-compose.yml. |
Secret (via Docker secrets):
./secrets/discord_token.txt — put your bot token here (no quotes, single
line).
3) What the bot does
Public status panels
- Posted with /role-admin-post-status (admin-only; posts a message visible
to everyone).
- Lists each pickable role, current member count, plus (inactive)
if count < min and (full) if count ≥ max (when configured).
- Contains a Refresh button.
- The bot stores all posted panel message IDs and auto-refreshes them (see
below).
Personal DM role picker
- Users run /roles to receive a DM with a personalized panel.
- Buttons show primary/secondary style depending on whether the user currently
has each role.
- If DMs are closed, the bot shows a one-off ephemeral panel in the channel
instead (not tracked for auto-refresh).
Auto-refresh
- Refresh cadence is configurable globally per guild via /role-admin-refresh
(minutes).
- The bot updates:
- All public status panels it has posted in that guild.
- All tracked DM panels it sent with
/roles.
- Also refreshes promptly after admin config changes and user role toggles.
Inactivity cleanup
- Daily, removes all pickable roles from members whose last activity
timestamp is older than N days (configurable).
- "Activity" is updated on any message or role interaction handled by the bot.
- Default inactivity days come from
INACTIVITY_DAYS_DEFAULT and can be
changed live using /role-admin-set-inactivity-days.
What counts as activity?
- Any message a user sends in the guild updates their
last_active
timestamp.
- The daily job removes selectable roles from users with no messages for
longer than
inactivity_days.
You can change inactivity_days anytime using /role-admin-set-inactivity-days.
Default is taken from INACTIVITY_DAYS_DEFAULT when the bot first sees the
guild.
4) Commands
User commands
- /roles
Open your personal role picker (DM). Shows the server name & ID in the
message.
> NOTE: The UI shows at most 25 role buttons in a single panel message
> due to Discord limits.
- /role-list
List the pickable roles you currently have (ephemeral reply).
- /role-pick <role>
Add yourself to a pickable role.
- /role-unpick <role>
Remove yourself from a pickable role.
- /role-toggle <role>
Toggle a pickable role on/off.
- /role-help
Show all user commands. If you're an admin, admin commands are listed as
well.
Admin commands
- /role-admin-post-status
Post a public role status panel in the current channel (anyone can see
it). Panel lines include min/max-based labels: "(inactive)" if count < min
and "(full)" if count ≥ max.
- /role-admin-add-pickable <role> [min] [max]
Add or update a pickable role and optional min/max hints.
- /role-admin-remove-pickable <role>
Remove a role from the pickable list.
- /role-admin-set-role-limits <role> [min] [max]
Adjust min/max hints for a pickable role.
- /role-admin-set-inactivity-days <days>
Set inactivity timeout in days (default: 14).
- /role-admin-set-refresh-interval <minutes>
Set auto-refresh interval in minutes (default: 5).
- /role-admin-refresh-now
Trigger an immediate refresh
- /role-admin-list-users
List all pickable roles and the users in each (ephemeral to the admin).
- /role-admin-list-config
Show current configuration (inactivity days, refresh minutes, pickable roles
with limits).
- /role-admin-add-user-for-role <user> <role>
Add a specific user to a pickable role (admin-controlled).
- /role-admin-remove-user-from-role <user> <role>
Remove a specific user from a pickable role (admin-controlled).
All admin commands respond ephemerally to the invoking admin. Public
status panels are visible to everyone.
5) Persistence
- SQLite database at
DATABASE_PATH (default /app/data/bot.db), bind-mounted
to ./data/bot.db by docker-compose.
- Tables:
settings (inactivity_days, refresh_minutes)
roles (pickable role IDs + optional min/max)
activity (last activity per user)
panels (public panel messages for auto-refresh)
dm_panels (user DM panels for auto-refresh)
Back up by copying the data/ directory (container must be stopped for
consistent copy if heavy write load is ongoing).
6) Troubleshooting
Buttons say "I cannot manage that role due to role hierarchy/permissions."
- Grant the bot Manage Roles permission.
- Move the bot's highest role above all pickable roles.
- Don't use managed/integration roles as pickables.
"Missing Access (50001)" when posting a status panel
Verify the bot has View Channel and Send Messages in that channel.
Avoid forum roots; use a text channel or a thread.
Commands don't show up
If GUILD_ID is set, commands register immediately for that guild. Without
it, global registration can take a while. Set GUILD_ID to your server ID
in .env for instant registration during setup.
Inactivity cleanup appears to skip
Ensure GUILD_ID is set to your target guild. The daily task only runs for
GUILD_ID (by design).
DM panel not received
User may have DMs closed. The bot will fallback with an ephemeral panel (not
tracked for auto-refresh).