Skip to content

Repository files navigation

ShellMod — Internal Role Sync Bot

This is a private tool built for our own server. It is not public, not distributed, and not meant for other communities to install or reuse. This document exists so any of mods/admins can operate it correctly.

What it does

ShellMod keeps Discord roles in sync with a Google Sheet we maintain. Instead of manually adding/removing roles for hundreds of members one by one, a mod fills in a spreadsheet (Discord ID or username → role list) and runs one command. The bot also has two manual bulk commands for adding or stripping a specific role from everyone at once.

It does NOT run continuously in the background. It's started manually when needed and can be closed afterward — see "Running it" below.


One-time setup

  1. A Google Cloud service account must be created specifically for this bot. Its credentials (a JSON key) should be stored in .env as GOOGLE_CREDENTIALS:
    • Go to https://console.cloud.google.com → create/select a project → enable Google Sheets API.
    • IAM & Admin → Service Accounts → create one → Keys → Add Key → JSON. Download it.
    • Open the JSON file, minify it to one line.
    • Double check that the Google Sheets API is enabled (otherwise the commands would fail), by navigating to APIs & Services from the side bar, and check the status, in case it was disabled, you'd find "enable" button.

  1. Any Google Sheet used for syncing must be shared with that service account's email address (the client_email field inside the JSON key, looks like something@project-id.iam.gserviceaccount.com). Viewer access is enough. If you create a brand new sheet and forget this step, /sync-sheet will fail with "Could not read the sheet."

  2. For the SHEET_ID, It's simply grabbed from the url itself: https://docs.google.com/spreadsheets/d/[SHEET_ID]/edit?....

  3. A bot app must be created from Discord's Developer Portal with the proper permissions:

    • Go to https://discord.com/developers/applications → New Application.
    • Bot tab → Add Bot → copy the token (this is DISCORD_TOKEN to set in .env).
    • Under Privileged Gateway Intents, enable Server Members Intent
    • OAuth2 → URL Generator: check bot and applications.commands scopes. Under bot permissions check Manage Roles. Copy the generated URL and open it to invite the bot to the server.
    • In Discord, enable Developer Mode (User Settings → Advanced), right-click the server → Copy Server ID → this is SERVER_ID. (without Developer Mode enabled, the Server ID wouldn't be displayed)

  4. The bot's Discord role must sit below Admin/Moderator roles (so it can never touch any critical roles, even by accident from a bad sheet row) and above every role it's meant to manage (Verified, Member, event roles, etc.). If a role you want it to manage is placed above the bot in Server Settings → Roles, every command will silently skip that role or report a "permission denied" error for anyone holding it — this is a hierarchy problem, not a bug, and the fix is always to move the bot's role, never to give it "more permissions."


Running it

cd ShellMod
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
python3 main.py

q, quit or exit + ENTER to stop it when you're done for the day.

You'll see Logged in as ShellMod#XXXX once it's ready. If you don't see this, something in .env is wrong — check the terminal for the exact error before doing anything else.


The Google Sheet — required format

The sheet needs a header row with these exact column names (case-sensitive):

Discord User ID Discord Username Roles
123456789012345678 Verified, Member
somepersonhandle Dev

Column order doesn't matter, and other columns (Notes, Date Added, whatever) can exist anywhere — the bot finds columns by name, not position.

Rules:

  • At least one of Discord User ID or Discord Username must be filled per row. If both are empty, that row is silently skipped.
  • If both ID and Username are filled, ID is tried first. If the ID doesn't resolve to a real member (typo, wrong ID, member left), the bot falls back to trying the username before giving up. So having both filled is a safety net, not a conflict.
  • Discord User ID is far more reliable long-term. usernames can be changed by the person at any time, silently breaking future syncs with no obvious cause. Prefer ID whenever practical.
  • Multiple roles for one member go in the same cell, comma-separated (Verified, Member), not across multiple rows. If someone does end up with two rows for the same ID/username by accident, the bot merges them correctly and won't double-count — but keep it one row per member as the standard.

The header names are configurable

If a mod wants different column names, set these in sheet_config.env (only needed if you want to deviate from the defaults):

ID_HEADER=Discord User ID
USERNAME_HEADER=Discord Username
ROLES_HEADER=Roles

Whatever you put here must match the sheet's header row exactly, including capitalization and spacing. A mismatch (extra space, different casing, singular vs plural) causes /sync-sheet to fail — always double-check this first if it stops working after someone edits the sheet.

Tab name

The bot reads a specific tab, set via SHEET_RANGE in sheet_config.env (default: Sheet1, meaning the whole tab, no column restriction). If a mod renames the tab, sheet_config.env needs to be updated to match, then the bot restarted. A renamed tab with no matching sheet_config.env update will make /sync-sheet fail immediately.


Commands

All three require Administrator permission (and practically, only work in the channel the bot is set up in — see "Access control" below).

/sync-sheet

Reads the Google Sheet and applies whatever roles it lists. Safe to run repeatedly — anyone already correct is left alone.

/role add-all roles:<comma-separated names>

Adds the named role(s) to every member in the server, regardless of the sheet.

/role remove-all

With no argument: strips every role the bot is capable of managing (anything below its position in the hierarchy, excluding other bots' own integration roles) from every member.

/role remove-all roles:<comma-separated names>

Same command, but only removes the specific role(s) named — the safer, targeted version. Use this for retiring an outdated role rather than the no-argument version.


Reading the result card

Every command replies with an embed showing:

  • Updated — members who had at least one role actually changed by this run.
  • Skipped — members who already had exactly the roles they were supposed to have. This is not an error and needs no action — it just means nothing needed to change for them.
  • Errors (N) — members with something a mod needs to look at, each tagged with a reason:
    • <@id> — not found / `id` — not found — the ID or username in the sheet doesn't match any current server member. Check for typos or someone who's left the server.
    • <@id> — role not found — the sheet lists a role name that doesn't exist in the server (typo, or the role was deleted/renamed). Any other valid roles for that same person were still applied — this only flags the specific bad row.
    • <@id> — permission denied — the target role sits above the bot's own role in the hierarchy. Fix is to reposition the bot's role, not to re-run the command.
    • <@id> — action failed — a generic Discord-side failure not covered above; rare.

Mentions in the error list never actually ping/notify the tagged person — they're rendered as clickable names for the mod's convenience only.

A member only ever appears in exactly one of Updated / Skipped per run, never two at once. Unlike errors, in partial success cases.

Large-operation notice

For bulk commands, the reply first shows "Processing N members — large servers can take a while due to Discord's rate limits, this is normal" before the final result replaces it. This is expected, not a sign of the bot being stuck — a full +500-member sync can realistically take several minutes because Discord itself rate-limits how fast role changes can be made, not because of anything wrong with the bot. Every sync after the first full one will be much faster, since it only touches members who actually changed.


Access control

Only the mod-only bot commands channel should have the bot enabled/visible for interaction. This is the practical enforcement layer — the slash commands are also gated by Discord's own Administrator permission check as a backstop, but day-to-day we rely on channel restriction.


Known limitations (by design, not bugs)

  • One Google Sheet at a time (SHEET_ID in sheet_config.env). Multi-sheet support isn't built.
  • No role-removal audit trail/history — this is a live sync tool, not a logging system.
  • The bot must be manually restarted after any .env or sheet_config.envchange — editing the file alone does nothing until the process restarts.

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages