Skip to content

Latest commit

 

History

History
1122 lines (774 loc) · 51 KB

File metadata and controls

1122 lines (774 loc) · 51 KB

Discord admin guide — STFC Tools

How to configure the bot inside Discord after it is deployed. For Cloudflare/Worker install steps, see SETUP.md.

Slash command catalogue (single vs multi-alliance): COMMANDS.md

Admin web UI (optional): ADMIN_WEB.md — Discord OAuth dashboard on Cloudflare Pages (admin-web/). Public Privacy (/privacy) and Terms (/terms) for app verification. New-bot cutover: BOT_MIGRATION.md.

Release versions (MAJOR.MINOR.INCREMENTAL) are listed in VERSION_HISTORY.md. /server status shows the running bot version.

You need the Administrator permission in the Discord server for /server commands.


Before you start

  1. Invite the bot with: Manage Roles, Manage Channels, Manage Nicknames, Send Messages, Attach Files, Embed Links.
  2. Raise the bot in the role hierarchy (see below) — required for role assign and nicknames.
  3. The bot cannot rename the server owner (Discord limitation). Nicknames still work for other members.
  4. Members must allow DMs from server members for the join/DM verification flow.

Confirm the bot is live:

/server gateway
/server status

Role hierarchy (drag the bot up)

Discord only lets a bot grant or remove roles that sit below its own role in the list. If verify fails with Missing Permissions / 50013 on a /roles/… URL, the bot is almost always too low — not missing Administrator.

  1. Open the server → Server Settings (gear) → Roles.
  2. Find the role that belongs to the bot (usually named like the bot / application, e.g. STFC Tools).
    • Tip: open the bot’s member profile → Roles to see which role it has.
  3. Drag that role upward so it sits above:
    • @Member / guest / every rank role (Premier, Commodore, …)
    • every overlay bucket role (Leadership, etc.)
    • roles you expect the bot to edit on normal members
      Higher in the list = higher in the hierarchy (closer to the top / Administrator).
  4. Leave Administrator (human admins) and any roles you do not want the bot to manage above the bot if you prefer — the bot only needs to sit above roles it assigns.
  5. Confirm the bot role still has Manage Roles (and Manage Nicknames if you use nicks).
  6. Retry /server verify (or have the member re-verify).

Do you need Administrator on the bot? No. Hierarchy + Manage Roles is enough. Administrator is a blunt workaround and is broader than necessary.

Admins verifying themselves: your personal admin roles are often near the top. The bot must still be above every STFC role it assigns. It does not need to outrank your personal Admin role unless that role is also in member_roles / buckets.

Check role IDs mentioned in audit errors with:

/server roles

1. Core setup — /server setup

Run once (or again to change settings):

/server setup
  server:108
  region:EU
  mode:single_alliance
  alliance_tag:KWSN
  guest_role:@Guest
  member_roles:@Member
  create_missing_roles:true
  operative_roles:@Operative
  agent_roles:@Agent
  premier_roles:@Premier
  commodore_roles:@Commodore
  admiral_roles:@Admiral
  nickname_template:
Option Required Description
server Yes STFC server number
region No US or EU (default US)
mode No single_alliance or multi_alliance
alliance_tag Yes if single Expected alliance tag; mismatches get guest role
guest_role No Role for wrong-alliance / guest members
member_roles No Base roles granted when alliance matches
create_missing_roles No Create roles by name if they do not exist
operative_rolesadmiral_roles No Extra roles by in-game alliance rank
nickname_template No Nick pattern (see below). Empty = mode default

Role fields accept IDs, @mentions, or names (with create_missing_roles).

Modes

Mode Behaviour
single_alliance Tag must match alliance_tag. Else guest role + periodic re-check. Personal channels can auto-create. Morning alliance roster caches the full member list for daily sync + verify (see § Daily alliance roster).
multi_alliance Any alliance verifies as active. No guest gating. Personal auto-create is off (link existing channels instead). Morning multi roster: server directory + batched scrapes of verified ∪ diplomacy tags; day-over-day moves report; live player-page fallback for untracked/empty tags. Switching from single → multi clears the single-alliance roster cache.

Check config anytime:

/server status

2. Nicknames

On verify (and daily sync), the bot sets the member’s nick from a template.

Defaults (when nickname_template is unset)

Mode Pattern Example
Single alliance {rank_prefix}{player_name} [Admiral] Halcynicon or Halcynicon
Multi alliance [{alliance_tag}]{rank_paren} {player_name} [KWSN] (Admiral) Halcynicon

{rank_prefix} is [Premier] / [Commodore] / [Admiral] only (empty for Operative/Agent).
{rank_paren} is (Rank) when rank is known.

Custom template

/server setup … nickname_template:[{alliance_tag}] {player_name}
Placeholder Meaning
{player_name} In-game name
{alliance_tag} Alliance tag (no brackets)
{rank} Full rank or empty
{rank_prefix} [Admiral] style for leadership ranks
{rank_paren} (Admiral) when rank known

Nicks are truncated to Discord’s 32 character limit.


3. Rank roles and overlay buckets

On verify (and daily sync), an active member receives the union of:

  1. member_roles (everyone who matches the alliance)
  2. The matching per-rank list (premier_roles, admiral_roles, …)
  3. Every overlay bucket whose ranks list includes their in-game rank

Guests only get guest_role (member/rank/bucket roles are removed).

Per-rank roles

Set on /server setup (operative_roles, agent_roles, premier_roles, commodore_roles, admiral_roles). Use these for roles that belong to one rank only (e.g. @Premier).

Preview the resolved set without changing anything:

/server rank-roles rank:Premier
/server rank-roles rank:Admiral

List Discord role names → IDs:

/server roles
/server roles limit:50

Role fields accept IDs, @mentions, or exact role names (comma-separated). Prefer @mention or ID when matching an existing layout so renames do not create duplicates.

Overlay buckets (shared roles across ranks)

A bucket is a named overlay: “these Discord roles go to anyone whose in-game rank is in this list.” Typical use: Leadership / Officer / Diplomat shared by Premier+Commodore+Admiral.

Option Required Meaning
name Yes Bucket key (e.g. leadership). Re-running the same name replaces that bucket.
ranks Yes Comma-separated: Operative, Agent, Premier, Commodore, Admiral
role_ids No* Discord roles to grant (IDs, mentions, or names). Omit / empty → clears the bucket.
create_if_missing No true = create roles by name if missing. Leave false/off when linking existing roles.

*Omitting role_ids (or passing nothing resolvable) deletes the bucket named in name.

Matching an existing Discord layout (recommended)

If Leadership (and friends) already exist in Server Settings → Roles:

  1. Run /server setup with only the per-rank roles (@Premier, @Commodore, …) — not Leadership in every rank list.
  2. Discover IDs if needed: /server roles.
  3. Point the bucket at the existing roles — do not set create_if_missing:
/server bucket name:leadership ranks:Premier,Commodore,Admiral role_ids:@Leadership

Multiple shared roles in one bucket:

/server bucket name:leadership ranks:Premier,Commodore,Admiral role_ids:@Leadership,@Officer,@Diplomat

Several independent buckets are fine (each has its own name):

/server bucket name:leadership ranks:Premier,Commodore,Admiral role_ids:@Leadership
/server bucket name:diplomats ranks:Commodore,Admiral role_ids:@Diplomat
  1. Confirm: /server rank-roles rank:Premier should list @Premier and everything from buckets that include Premier.
  2. Existing verified members pick up bucket changes on the next daily sync, or after re-verify / /server verify.

Creating roles from scratch

/server bucket name:leadership ranks:Premier,Commodore,Admiral role_ids:Leadership,Officer create_if_missing:true

Only use create_if_missing:true when the names are new. If a role already exists under that name, the bot reuses it.

Update / clear

# Change ranks or roles (same name overwrites)
/server bucket name:leadership ranks:Commodore,Admiral role_ids:@Leadership

# Remove the bucket entirely (keep ranks arg; omit role_ids)
/server bucket name:leadership ranks:Premier,Commodore,Admiral

Setup vs bucket — what goes where?

Role Put it in…
@Member (all alliance matches) /server setupmember_roles
@Premier / @Commodore / … (one rank) /server setuppremier_roles etc.
@Leadership shared by several ranks /server bucket
@Guest /server setupguest_role

Do not paste @Leadership into premier_roles, commodore_roles, and admiral_roles unless you want to maintain that duplication by hand — the bucket is the single source of truth for shared overlays.


3b. Player language (i18n)

Player-facing DMs (and survey delivery) are localized. On first bot contact (join invite), members get a language picker. They can change later with:

/language

Stored per Discord user on verified_players.preferred_locale.

Supported: English, Deutsch, Français, Español, Português, Nederlands, Polski, Italiano, Русский, Türkçe, Magyar.

Admin ephemeral replies and shared channel pins (e.g. exchange hub) stay in English for now.


4. Verification log channel (admin archive)

Posts a summary + screenshot to a staff-only channel on each successful verify (active or guest). Screenshots are still stored in R2 when configured; the log channel is for day-to-day review without digging in storage.

This is separate from the general bot audit log.

Create a private log channel

/channels log create:true

Optional: name:verification-archive

Permissions applied:

  • @everyone — cannot view
  • Bot — can view / send / attach
  • Roles from /channels extra-roles — can view / send

Use an existing staff channel

/channels log channel:#staff-verify-log

Ensure the bot can View Channel, Send Messages, Embed Links, and Attach Files there.

Disable

/channels log clear:true

4b. Bot audit log (admin + automated actions)

A staff-only channel for everything the bot does besides verification screenshots: admin commands, role/channel changes, cron sync summaries, invites, etc.

/channels audit create:true

Optional: name:bot-audit-log

Or link an existing channel:

/channels audit channel:#staff-bot-audit

Disable:

/channels audit clear:true

Same private-channel permission pattern as the verification log (@everyone denied; bot + /channels extra-roles can view). Prefer setting extra-roles first so viewers are included on create.

Logged events include (non-exhaustive): /server setup, verify (brief — screenshots stay on the verification log), guest invites, personal-channel map/rebalance/link, diplomacy config, surveys sent/closed, exchange setup, daily sync / guest re-check summaries, morning alliance roster change report (joins / leaves / ops / rank / renames), batched morning Player activity + Player sync — daily updates tables (welcome DM sent/failed, status/role/channel changes — not one embed per player).


4c. Urgent alerts (action needed)

Optional high-signal channel for events that need an admin to notice quickly — not the full audit trail. Today this includes verification DM blocked (403) when a member’s privacy settings prevent bot DMs.

/channels urgent create:true

Optional: name:bot-urgent

Or link an existing channel:

/channels urgent channel:#staff-urgent

Disable:

/channels urgent clear:true

Same private-channel pattern as audit/log (@everyone denied; bot + /channels extra-roles can view). Prefer setting extra-roles first.

Urgent posts use a short Badgey-style message so they stand out from routine audit embeds. The same event is still written to the audit log when configured.


Player activity (streak + days inactive)

stfc.pro exposes consecutive_days_active on alliance/player pages:

Value Meaning
> 0 Current login streak (days)
0 No current streak (hasn’t logged in within the game’s “active day” window)

Each morning sync (0 6 * * *), for every verified player we successfully look up:

  1. Store activity streak = that value.
  2. If streak > 0 → set days inactive = 0.
  3. If streak === 0 → increment days inactive by 1 (or set to 1 on the first zero day).

We do not change counters when a scrape/lookup fails or omits the field. Streak is read from the alliance roster HTML (consecutive_days_active on each member row) during the morning sync — for everyone on the alliance page (Discord-linked or not). days_inactive increments on that same morning scrape when streak is 0. Individual player pages are only a fallback for linked players missing from the cache.

Commands

/roster inactive min_days:3     # inactive ≥ N days (linked + unlinked; default 1)
/roster activity user:@Name     # show streak + days inactive
/roster activity player:Name    # same for alliance / unlinked (name or STFC id)
/roster set-streak user:@Name value:12   # admin: set streak (value>0 clears inactive)
/roster set-streak player:Name value:12  # admin: unlinked OK; typos → Did you mean? buttons
/roster set-inactive user:@Name value:5  # admin: set days inactive (value>0 clears streak)
/roster set-inactive player:12345 value:5

Use either user: or player: (not both). Exact name/id applies immediately. A near miss shows Did you mean? with Yes, use this player / No.

Roster list lines (ops/grade/rank/inactive) also show streak N / inactive Nd when known.

Audit reports

Morning cron posts Player activity — streak / inactive (ASCII table sections) when anyone:

  • Became inactive (streak hit 0)
  • Returned active (streak returned after inactive days)
  • Still inactive ≥3d (ongoing)

Storage

verified_players.activity_streak, days_inactive, activity_updated_at
alliance_roster_members.activity_streak, days_inactive (same morning scrape; used for unlinked roster lists)
Cached on alliance_roster_members.activity_streak for roster-driven sync.


New guilds start in testing after /server setup. Existing guilds stay live unless you switch.

Mode Behavior
testing Slash replies prefixed [TESTING]. Automated demotions / leave queues are dry-run. No outbound DMs (invites, welcome, CoC, etc.) — preview only via /test-dm to yourself or a nominated user. Manual /roster set-guest blocked.
live Full automation (demotions follow /server demotion policy; invite/welcome DMs resume).

Activity streak / days inactive still update in testing (non-destructive counters) so you can validate the morning report before go-live.

/server deploy                 # show current mode
/server deploy mode:testing    # safe setup
/server deploy mode:live       # go live when happy (usually after a morning cron dry-run)

Shown on /server status.


4d. Daily alliance roster

Each morning (~06:00 UTC, cron 0 6 * * *) the bot refreshes alliance member lists from stfc.pro HTML pages, posts a day-over-day report to the audit channel, then syncs verified Discord players from that cache (with live player-page fallbacks when needed).

Single-alliance

  1. Fetches your one alliance page (/alliances/{id}).
  2. Compares to yesterday’s cached roster.
  3. Posts joins / leaves / ops / rank / renames.
  4. Updates verified members from the cache; missing from roster → leave / wrong-alliance candidates (leave-detection policy).

Multi-alliance

  1. Fetches the server alliance directory (/servers/{n}).
  2. Tracks tags = verified players’ tagsdiplomacy channel map tags.
  3. Scrapes those alliance pages in a batch (cap ~40/run, ~1.2s between pages).
  4. Posts a multi report: alliance moves, joined/left tracked rosters, ops / rank / renames.
  5. Syncs verified players from those caches. If a player isn’t on any scraped roster (new/empty/untracked tag) → live player page. Guests without a player id are skipped.
  6. Posts a second audit digest for verified Discord members who changed alliance and/or Discord roles/rank overlays.
Section Meaning
Alliance moves Same player id, different alliance tag (multi)
Joined / Left tracked roster Appeared on / disappeared from a scraped alliance
Ops up / down Same player id, ops level changed
Rank changes Same player id, alliance rank string changed
Renames Same player id, in-game name changed

First successful roster for a guild is an initial snapshot (no join spam). Unchanged days get a short “no changes” note.

Change sections (joins, leaves, moves, ops, rank, renames) and the morning Player activity audit render as compact ASCII tables in code fences (same engine as /table). Player names appear in the table — Discord mentions do not render inside fences.

Requirements

  • Mode: single_alliance with alliance_tag, or multi_alliance with at least one tracked tag (verified players and/or diplomacy map).
  • Audit channel configured (/channels audit).
  • Single-alliance: stfc_alliance_id (shown on /server status; auto-discovered from a verified profile if missing).

Verify / guests

  • Fresh roster (≤36h) hit → use cache; else live lookup.
  • Guest re-check every 6 hours prefers the roster before a live lookup (single-alliance guest path).
  • List roster members with no Discord link: /roster missing-verify (works for multi across all tracked caches).

5. Personal / member channels

Permissions (set this first)

Before creating or linking member channels, configure which Discord roles can see every personal channel (officers, diplomats, etc.):

/channels extra-roles roles:@Officer,@Diplomat

This attaches those roles to the built-in default permission template (same allow bits as the member). No sample channel lock required — confirm with /channels permissions-template-show. Clear with an empty roles: value.

This is not part of /server setup — it is required for personal channels.

When the bot creates a channel, updates one on verify, or links with apply_permissions left on (default), it applies:

Target Access
Bot (first) Role overwrite for the bot’s managed guild role (shows under Roles like Carl-bot): View, Send, Embed, Attach, Read History, Manage Channels, Manage Permissions, Administrator. A prior bot member overwrite on the same id is cleared so Discord does not keep the bot under Members.
@everyone Deny View Channel
The member View, Send, Embed Links, Attach Files, Read History
Extra-roles Same as the member

The bot overwrite is applied before denying @everyone, so the bot never locks itself out. Newly created channels include these overwrites at create time.

If linking an existing private channel fails on permissions, give the bot View Channel (and Manage Channels) on that channel or its category, then retry — or use apply_permissions:false and add the bot overwrite manually.

Do not “Sync now” from the category onto existing member channels if those channels already have per-member allows — Discord sync will wipe individual overwrites. Prefer:

  1. Add the bot on the category and choose not to sync to children, or
  2. Add the bot overwrite on each channel you care about, or
  3. Use /channels link … apply_permissions:true once the bot can see the channel (rewrites that channel only).

Audit existing permissions (read-only)

Before changing anything, dump what Discord currently has on linked channels + channels under your member categories:

/channels permissions-audit
  • Does not sync or rewrite permissions
  • Ephemeral summary with flags (bot_missing_view, linked_member_no_overwrite, …)
  • Full text dump attached to /channels audit when that channel is set (keep this as your record)

Bulk-add overwrites (bot / roles / buckets)

Add or refresh one target’s overwrite across many linked channels without wiping per-member allows (safe for new-bot migration and staff role rollouts).

/channels permissions-apply target:bot
/channels permissions-apply target:bot scope:all dry_run:false
/channels permissions-apply target:role role:@Leadership preset:member dry_run:false
/channels permissions-apply target:extra_roles scope:personal dry_run:false
/channels permissions-apply target:template_roles dry_run:false
Option Notes
target bot · role (+ role:) · extra_roles · template_roles
scope personal (default) · diplomacy · staff_logs · survey_logs · all
preset bot (full bot bits) · member (view/send set) · view_send (lighter)
dry_run Default true — preview; set false to apply
only_missing Default true — skip if target already has View

Posts a detail file to the audit log channel when configured. See also BOT_MIGRATION.md § channel overwrites.

Lock a permission template from a sample channel (optional)

You do not need this if the built-in default + /channels extra-roles is enough.

Once you find a member channel whose overwrites look right (bot can post, member + staff roles correct):

/channels permissions-template-from channel:#good-example

Optional: member:@Owner if the channel isn’t linked yet; sync_extra_roles:false to leave /channels extra-roles unchanged (default true copies role overwrites into extra-roles).

If a locked template already lists role overwrites, those take precedence for personal channels; extra-roles still apply to log/audit/urgent channels.

/channels permissions-template-show
/channels permissions-template-clear

Locked templates are used for new personal channels and for /channels link / verify when apply_permissions is on. Existing channels are not rewritten until you link/re-apply.

Do this before /channels rebalance … create_missing:true or bulk /channels link, so new and rewritten channels get the right access. Changing extra-roles later does not rewrite existing channels until the next create/update/link that applies permissions.

Auto-create (single-alliance)

Buckets use the first letter of the in-game name (AZ). Names starting with a digit or symbol go in # (non-alphabetic), always at the end of the alphabet (e.g. range N-#).

Recommended: let the bot plan and apply categories (handles Discord’s ~50 channels/category limit):

/channels plan
/channels rebalance apply:true create_missing:true

That creates/renames categories like Member Channels A-M / Member Channels N-#, updates the map, moves linked member channels, creates missing ones (if create_missing), and archives unlinked ones.

Large servers take a while: the command shows progress on the slash reply, posts started + finished (or failed) to the audit log, and saves the category map as soon as categories exist so a retry can continue. Re-running apply:true reuses mapped categories (renames ranges in place, creates only extra buckets). If the map was empty after a crash, it also adopts existing categories by matching name (e.g. Member Channels A-L) instead of creating duplicates. Archive scans the current map, the previous map, and leftover Member Channels * categories so unlinked channels left behind by a partial run still get moved.

The planner splits fairly evenly under the soft limit (50 players → two ~25 buckets, not 45+5). Re-run when occupancy nears the limit (/channels status shows counts).

Option Default Description
soft_limit 45 Target max channels per category (headroom under Discord’s 50)
name_template Member Channels {range} Category name; {range}A-M, N-#, etc.
rename_categories true Rename existing mapped categories to match new ranges
create_categories true Create extra categories when more buckets are needed
create_missing false Create personal channels for verified players who have none
archive_unlinked true Move text channels in member categories that are not linked to any player into the archive
archive_category Existing Discord category to use as archive
archive_name Member Channels Archive Find or create archive category by name
apply false Preview only unless true

First-time / migration workflow:

  1. /channels extra-roles — who can see all member channels (see above).
  2. Verify players (/verify or /server verify).
  3. /channels link for members who already have a channel (rebalance will not guess links by name).
  4. /channels plan — review suggested ranges, missing channels, and unlinked channels.
  5. /channels rebalance apply:true create_missing:true — splits categories, creates missing channels, moves linked ones, archives unlinked ones.

Manual map (if you prefer to create categories yourself):

/server categories
/channels map category_map:A-M=111...,N-#=222...

Or one range at a time: range:A-M + category_id:….

On verify, the bot creates a private channel for the member in the matching category (name slug from player name), with access for the member + extra-roles. Channels within each member category are kept in alphabetical order (rebalance re-sorts all buckets; create/link/rename re-sorts that category).

Clear mappings (disables auto-create):

/channels map clear:true

Link existing channels (any mode)

If the server already has member or diplomacy channels, do not recreate them — link them:

/channels link channel:#halcynicon player:Halcynicon

Pick a text (or announcement) channel — not a category. If linking fails because the bot can’t see the channel, give the bot View Channel there (private member channels often deny @everyone), then retry.

/channels link channel:#kwsn-diplomacy player:301268920
/channels link channel:#some-channel user:@Member
Option Description
channel Existing text channel (required)
player In-game name, STFC player ID, or Discord snowflake
user Discord member (alternative to player)
apply_permissions Default true — rewrite perms for member + extra-roles. Set false to only store the link and leave existing permissions alone

Channel names always follow the current in-game player name (slugified). Create, link, rebalance, verify, and the daily player sync rename (and re-bucket) when the name no longer matches. Slugs fold common lookalikes first (e.g. Łl, βb, accents stripped) before Discord-safe sanitizing.

Examples for an existing framework:

/channels link channel:#adam-diplomacy player:Adam apply_permissions:false

Status:

/channels status

5b. Diplomacy channels (multi-alliance)

One shared text channel per alliance tag (not per player). Typical use: everyone can see the channel; only leadership ranks and/or a Diplomat role can write.

Discord cannot gate on in-game rank directly — write access uses the Discord roles assigned for those ranks (commodore_roles / admiral_roles from /server setup) plus any write_roles you configure.

Multi-alliance servers often have dozens of tags — Discord’s 50 channels per category limit applies. Prefer letter-bucket categories (same idea as personal channels) via sync_all, not a single category:.

Configure

/diplomacy
  enable:true
  everyone_can_view:true
  write_roles:Diplomat
  write_ranks:Commodore,Admiral
  name_template:diplomacy-{tag}
Option Meaning
enable Turn feature on and save options
disable Stop auto-create (keeps linked channels)
everyone_can_view @everyone can view; send still denied (default true)
view_roles Extra viewer roles (especially if everyone cannot view)
write_roles Roles that can send (e.g. Diplomat) — created by name if missing
write_ranks In-game ranks whose Discord rank roles may write
category Legacy single parent category (used only when no letter-bucket map yet)
name_template Channel name; {tag} → alliance tag (default diplomacy-{tag}). Tags are latinized like personal channels (βb, Łl, …)
sync_all Plan/create letter-bucket categories, rename/move channels, A–Z sort, optional archive
plan With sync_all: preview buckets only (no Discord writes)
soft_limit With sync_all: max channels per category (10–50, default 45)
category_name_template With sync_all: category name; {range} → e.g. A-M (default Diplomacy Channels {range})
create_missing With sync_all: also create channels for alliance tags on verified players
archive_unlinked With sync_all: move unlinked channels under diplomacy categories to archive (default true)

Create for a tag

/diplomacy create_tag:KWSN

Also happens automatically on verify/sync in multi_alliance mode when diplomacy is enabled and the player has an alliance tag. Existing channels are renamed to the current slug and moved into the letter-bucket category (or legacy category if no map).

Sync / rebalance (letter buckets)

Preview splits:

/diplomacy sync_all:true plan:true soft_limit:45 create_missing:true

Apply (creates Diplomacy Channels A-M-style categories as needed, moves channels by tag first letter, archives unlinked):

/diplomacy sync_all:true create_missing:true

Same spirit as personal-channel rebalance. Progress posts on the slash command; audit gets started + finished. After the first successful sync, status shows the category map (ranges → categories) instead of a single legacy category.

Adopt an existing channel

/diplomacy link_tag:KWSN channel:#kwsn-diplo
/diplomacy link_tag:KWSN channel:#kwsn-diplo apply_permissions:false

Status

/diplomacy

(with no action options) prints the current diplomacy config, category map, and tag→channel map.


6. Member verification flow

What members do

  1. Join the server → bot DMs them (if DMs allowed), or
  2. Admin runs /server test-invite, or
  3. Member runs /verify link:https://stfc.pro/players/… (optional screenshot attachment)

DM flow: screenshot (optional depending on policy) → stfc.pro link → roles / nick / channel / log post.

Manual verify (existing servers)

Admins can link a Discord member to an stfc.pro profile without the DM flow — useful when onboarding a server that already has members:

/server verify user:@Them link:https://stfc.pro/players/…
/server verify user:@Them link:https://stfc.pro/players/… screenshot:<file>

This runs the same pipeline as self-verify (roles, nickname template, personal/diplomacy channels, verification log). The log embed notes Manual by @Admin. Repeat once per member; alliance guest rules still apply in single-alliance mode.

Requires Administrator. Set the archive channel first (/channels log) so staff can audit these posts.

Admin testing

/server test-invite              # DM yourself
/server test-invite user:@Them
/verify link:https://stfc.pro/players/…
/server test-reset               # clear your record to re-test
/server test-reset user:@Them

Guest re-check (single-alliance)

Wrong alliance (or empty alliance tag on stfc.pro) → guest candidate. Behavior depends on demotion policy:

Policy Confirmed mismatch / empty tag Player missing on stfc.pro API / network error
approval (default) Queue → urgent channel Approve/Reject Queue → urgent Approve/Reject Skip (never change roles)
yolo (auto) Apply guest immediately Queue 1h recheck; apply guest only if still missing Skip (never change roles)

The 6-hour guest poll and morning sync prefer the cached alliance roster when fresh: if a guest appears on the roster, they are promoted without a per-player stfc.pro hit. If the morning roster succeeds and an active member is absent, they become a leave-detection candidate.

If a personal-channel archive category is configured, applying guest moves their channel there.

/server demotion                          # show policy
/server demotion policy:approval
/server demotion policy:yolo
/server demotion list:true
/roster set-guest user:@Member reason:left alliance

Urgent digest buttons: Approve all / Reject all. Individuals: /roster set-guest.

Multi-alliance: empty alliance tags are normal — never auto-queued or set to guest for “no tag.” Use /roster set-guest manually if needed.

Unverified members (any mode)

/roster unverified                      # Discord members with no STFC link (Admin or assistant roles)
/roster unverified set_guest:true       # Admin: assign guest_role + remove member/rank roles for everyone listed

Bulk set-guest requires guest_role from /server setup. Never-verified users are roles-only (no new verified_players row). Excluded users and bots are skipped.

Alliance members missing Discord verify

Opposite direction: players on the morning alliance roster cache who are not linked as active/guest on this Discord server.

/roster missing-verify

Lists in-game name, player id, ops, and rank. Requires a cached alliance roster (after morning sync). Guests count as linked.

  • single_alliance: everyone on the configured alliance page.
  • multi_alliance: everyone on successfully scraped tracked alliances (verified tags ∪ diplomacy map). See COMMANDS.md.

Reports by ops grade vs in-game rank

Command What it counts / lists
/roster grades Ops grade buckets G3–G7 (from ops level)
/roster grade grade:6 Verified players at that grade
/roster ranks In-game alliance rank (Operative, Agent, Premier, …)
/roster rank rank:Admiral Verified players with that alliance rank
/roster ops min:50 Ops level range

Player list replies use ASCII tables by default with Previous / Next / Full list / Table buttons. Options: sort:, format:list, visibility:public (default private), include_unlinked (default true — alliance members with no Discord link show as DC=no / status unlinked), page:. Private replies also get Post to channel. /roster missing-verify still lists only unlinked. Mentions on /roster unverified / activity. Streak comes from the alliance page.

On multi-alliance, alliance tag/rank changes on daily sync update nick + rank roles; they are not auto-set to guest (use /roster set-guest if needed).


7. Resource exchange (/exchange)

Cross-alliance resource matching (best for multi_alliance). Same-alliance donors are never notified. Verified players only.

Setup (admin)

Two layouts:

Layout Behaviour
hub One channel; each resource gets a pinned post with buttons
category One text channel per resource under a category; each pinned
/exchange setup layout:hub channel:#resource-exchange
/exchange setup layout:category create_category:true category_name:Resource Exchange
/exchange setup admin_roles:Officer

Bot role must sit above the Donor / Need roles it creates.

Resources

/exchange resource create name:Crystal
/exchange resource list
/exchange resource disable name:Crystal

Creates Discord roles {Name} Donor and {Name} Need, posts + pins Register as donor / Stop donating / I need this / I no longer need this.

Player flow

  1. Donor registers (button or /exchange donate resource:Crystal)
  2. Recipient taps I need this (or /exchange need) → eligible donors get a DM (name + ops) with Help / Ignore
  3. First Help wins → recipient gets donor details + Completed / Ask again
  4. Ask again re-notifies current cross-alliance donors
  5. I no longer need this cancels an open/claimed request (e.g. resolved offline); notifies the claimer if someone had claimed Help

Same-alliance donors are never notified (tags compared case-insensitively).

Slash donate / need / undonate must be run in that resource’s channel (hub or dedicated).


8. Surveys & polls (/survey)

Button surveys for verified players (DM or personal channel). Votes land in a private log channel (default #survey-{id}). Results use ASCII tables (buttons stay on the message — never inside tables).

Who can create

Setting Default
Creators Administrators only
Log / results viewers Survey creator + creator roles + Administrators
Log channel name {id}-{title}
/survey creators                          # show current settings
/survey creators roles:Officer,Leadership
/survey creators results_roles:Officer
/survey creators log_name:poll-{id}
/survey creators create_category:true category_name:Surveys
/survey creators category:#ExistingCategory
/survey creators clear_category:true

Use role IDs or <@&id> mentions, comma-separated. Empty roles clears back to admins-only.

Log channels are private: @everyone cannot see them. Access is granted to the bot, the member who created the survey, configured creator roles, and results_roles. Discord Administrators can still see them via admin override.

Category: New survey logs go under the configured server default category (if set). Override per survey with /survey create … log_category:#Events. create_category:true makes a private default category (name Surveys unless category_name is set). Does not move already-created channels.

log_name placeholders: {id} / {n} (survey number), {title} (slugified survey title, or survey if unset). Default {id}-{title} → e.g. #3-ops-readiness. Applies to new surveys only (rename old #survey-1 channels manually if you want).

Empty log_name resets to {id}-{title}.

Create → test → send

/survey create title:"Ops readiness" question:"Ready for the event?" options:Yes|No|Maybe closes_in:48h target:grade grades:5,6
Option Purpose
question Shown to players
options A|B|C2–5 answers (Discord button limit)
title Player-facing heading in DM / personal channel (default: localized Survey #id); also used in log channel name
closes_in Optional auto-close after Approve & send — e.g. 30m, 12h, 7d (max 90 days). Omit = manual /survey close only
target all · role · rank · level · grade · users
delivery dm (default) or personal_channel (falls back to DM)
grades / ranks / roles / users / ops_min / ops_max Filters for the chosen target
alliance_tags Optional extra filter (comma-separated tags)
log_category Optional category for this survey’s log channel (else server default from /survey creators)

After create you get an ephemeral draft with buttons:

  1. Test to me — delivers using the survey’s delivery setting (dm → your DMs; personal_channel → your linked personal channel). Draft clicks are not counted.
  2. Approve & send — creates private log channel (name from log_name template), DMs/posts buttons to matched players, logs each vote; starts the closes_in clock if set
  3. Cancel — deletes the draft

Results & close

/survey list
/survey results id:12
/survey close id:12

/survey results shows a Summary vote table and a Who voted table.

Auto-close: When closes_in was set, the bot soft-closes the survey at the deadline (votes stop counting; Discord buttons may still appear). Cron checks about every 5 minutes; a late click also triggers close. Existing surveys without a deadline are unchanged.


9. Other /server commands

Command Purpose
/server status Full config summary
/server gateway Gateway WebSocket health
/server roles List Discord roles + IDs
/server categories List categories + IDs
/server rank-roles Preview roles for a rank
/server bucket Configure overlay buckets
/channels … Personal channels + verification log

10. Utility commands (everyone)

Command Purpose
/player Live stfc.pro lookup (needs /server setup)
/roster … Roster reports — Admin or /server assistant roles (see below)
/lookup Coordinate share-string lookup
/table / /tablehelp CSV → ASCII table
/survey … Surveys / polls (creator roles; see §8)
/exchange … Resource exchange (see §7)

11. Troubleshooting

Symptom Likely fix
Roles not assigned / 50013 on /roles/… Raise bot in role hierarchy; bot needs Manage Roles (Administrator not required)
Nickname fails (403) Manage Nicknames; bot role above member; owner cannot be renamed
No verification DM / DM open failed: 403 Member privacy: User Settings → Privacy or server privacy — allow DMs from server members; or they blocked the bot. Use /verify in-channel as fallback; retry with /server test-invite after they fix privacy. After one 403 the bot stops auto-retrying that member (and posts to /channels urgent if set)
Repeat “Verification invite failed” for people already verified Fixed: manual /server verify marks invited; already active/guest are skipped; clobbered pending_* rows with player data are auto-restored. Redeploy to pick up — no re-verify needed if Discord roles still look correct
“Server not configured” Run /server setup
Log channel silent /channels log set; bot can attach files; redeploy after feature add
Personal channel not created Single-alliance + category map set; check /channels status
/channels link fails / “not a text channel” Pick a text channel (not a category). Redeploy + re-register commands
/channels link permission overwrite fails Bot needs Manage Channels + View Channel on that channel/category. After deploy, link still saves and reports which overwrites failed; bot is granted View/Send first so it can post surveys
Diplomacy channel not created Multi-alliance + /diplomacy enable:true; rank write roles must exist from setup
Link finds no player Member must verify first, or use user:@Member
stfc.pro lookup fails Bot falls back to HTML player page lookup for numeric IDs; confirm URL/server/region
Survey create denied Admin or /survey creators role; run /server setup first
Survey DM missing Member allows DMs from server members; bot can message them
Zero matched players Check target filters vs verified roster (/survey list shows target count)
Exchange no donors notified Need cross-alliance donors; verify alliance tags differ
Exchange role assign fails Bot role above {Resource} Donor / Need roles

Data-processing consent (GDPR) + optional CoC

Two separate gates:

Gate Command When Purpose
Data consent /server consent Before screenshot / stfc.pro lookup Link Discord ↔ player identity; Yes/No buttons; logged to audit
Code of conduct /server agreement After verify (guest lounge until accept) Optional server rules; can still link a CoC channel

Legal templates (customise before publishing): Privacy Policy · Terms of Service. Link stable hosted copies from your consent DM or CoC channel when you enable /server consent.

/server consent
/server consent enabled:true version:2026-07
/server agreement enabled:true timing:after_verify channel:#code-of-conduct version:2026-07

Existing / manually verified members stuck as guest until CoC (e.g. DMs blocked): mark accepted and restore member roles without them tapping Agree:

/server agreement backfill:true
/server agreement user:@Player

Records agreement_method: admin_backfill, stamps the current CoC version, then runs the same role/nick/channel grant as the Agree button. Alliance guests stay on guest role. Large servers continue in ~20s Worker chunks via WORKER_URL (must be set in .env). Guild owner / higher-than-bot roles are skipped for Discord mutations (CoC still stamped).

Onboarding order when data consent is on: language (if needed) → consent Yes/No → verification instructions → screenshot + link → (optional CoC) → welcome DM.

Bump version on either gate to re-prompt after policy changes.


Welcome DM (post-onboarding)

After a member reaches full access (verified, and agreement accepted if that gate is enabled), the bot can DM a welcome message once.

Retries: each player gets at most 2 automatic send attempts (first failure + one retry on a later verify/sync/agreement). After that, auto-send stops (no more audit spam). Admins can force another try with /server welcome send_user:@Them force:true.

Manual verify: /server verify does not send the welcome DM by default (avoids DMing already-onboarded members). Pass send_welcome:true when you want it.

Hybrid setup:

  1. Post a welcome message in any channel the bot can read (include recommended channels as normal #channel mentions).
  2. Copy Message Link → configure the bot.
  3. The bot fetches that post’s content (and embeds) and appends a link to the member’s personal channel.
/server welcome
/server welcome enabled:true message_link:https://discord.com/channels/<guild>/<channel>/<message>
/server welcome preview:true
/server welcome clear:true
/server welcome send_user:@Member force:true   # manual / forced retry
/server onboarding                            # show full path + order of gates
/server verify user:@Them link:… send_welcome:true

Optional: put {personal_channel} in the source post where you want the personal channel mention mid-text (the bot still appends the personal-channel line at the end).

Bot needs View Channel + Read Message History on the source channel. Guests / wrong-alliance members do not receive this DM.

Preview DMs (no status change)

/test-dm kind:invite
/test-dm kind:agreement user:@Them
/test-dm kind:welcome
/test-dm kind:demote_mismatch   # choice label: Guest mismatch preview
/test-dm kind:all

These send real-looking DMs marked as admin previews. Buttons do not record agreement or restart verification. Prefer this over /server test-invite when testing copy/layout for already-verified members.

/server test-invite is for live onboarding of not-yet-verified users (can set pending_screenshot). /server test-reset clears verification for a true re-test.


DM assistant (Badgey / HAL)

Verified members (and admins) can DM the bot outside of verification:

Message Behaviour
Unrecognized question / request HAL: I'm sorry {name}, I'm afraid I can't do that.
Unknown / not verified Badgey invite to join & verify
menu / admin / help Admin button wizard (Administrator or Manage Server required)
Roster questions (e.g. “how many G6?”) Allowed for admins, or roles set below

Prefer slash commands for listings: /roster grades, /roster grade grade:6, /roster ranks, /roster rank rank:Admiral, /roster ops min:50, /roster unverified, /roster missing-verify, /roster set-guest, /roster status.

Admin wizards (DM → menu): Server status, Server setup (core fields), Verification log, Audit log.

/server assistant                          # show settings
/server assistant roles:@Officer,@Leadership
/server assistant roles:                   # clear → admins only
/server assistant ai:true                  # guild flag only; still needs ENABLE_WORKERS_AI + DM_AI_ENABLED

Cost: Roster answers and wizards use D1 + buttons — no Workers AI. Optional AI intent assist is off by default and hard-capped per day if enabled.


Exclude users (bots / never-verify)

Discord bots are skipped automatically (no invite DMs). For other accounts that should never verify:

/server exclude-add user:@OtherBot reason:MEE6 twin
/server exclude-remove user:@OtherBot
/server exclude-list

Excluded users never get verification invite DMs and are omitted from /roster unverified.


Quick checklist (new alliance server)

  1. Bot invited; role near top of list
  2. /server setup with server, region, mode, tag, roles
  3. /channels extra-roles — officers/roles that see all member channels (not part of setup)
  4. /channels audit create:true — general bot audit (admin + automated actions)
  5. /channels urgent create:true — high-signal alerts (DM blocked, etc.; optional)
  6. /channels log create:true — verification archive (screenshots; separate from audit)
  7. Members pick language on first DM (or /language) — player-facing messages are localized
  8. Link existing member channels with /channels link if needed
  9. /channels plan then /channels rebalance apply:true create_missing:true (or manual /channels map)
  10. Optional: nickname_template, rank roles, /server bucket
  11. Multi-alliance: /diplomacy enable:true write_roles:Diplomat write_ranks:Commodore,Admiral
  12. Optional: /survey creators for officers who may poll the alliance
  13. Optional: /exchange setup + /exchange resource create for cross-alliance resources
  14. /server test-invite → verify yourself → check roles, log, personal/diplomacy channels
  15. Existing members: /server verify user:@Them link:https://stfc.pro/… (repeat as needed)
  16. /server status looks correct
  17. Optional: /server assistant roles:… for who may ask roster questions in DMs
  18. Optional: DM the bot as admin and say menu to try guided setup