01
Reaction roles end-to-end
Reaction roles are self-service tagging. You post an embed in a public channel, list the roles a member can claim, and pair each role with an emoji. Members react to the emoji and Discord adds the role to their account. Un-react and the role goes away. The bot watches the post for reaction events and applies the mapping you set up. No mod has to be on duty for this to work, which is the point.
- Pick the channel reaction-role posts should live in. This is a one-time setup per guild:
Every future/roles channel channel:#pick-your-roles/roles createpost lands in#pick-your-roles. Members know where to look. - Create the post. Pick a clear title and body so members understand what they are signing up for:
The bot posts the embed in the channel you configured and returns the message ID. You will need that ID for the next step, so copy it now./roles create title:"What region?" body:"React below to pick your region. You can pick more than one." - Add the mappings. One
add-mappingcall per (emoji, role) pair:/roles add-mapping message_id:1234567890 emoji:🦅 role:@USA/roles add-mapping message_id:1234567890 emoji:🇨🇦 role:@Canada/roles add-mapping message_id:1234567890 emoji:🇬🇧 role:@UK
The bot reacts to the post with each emoji as you add it, so members can react without having to type the emoji themselves. It also stores the mapping so reaction events on this message route to the right role./roles add-mapping message_id:1234567890 emoji:🇪🇺 role:@Europe - Confirm what is wired up across the guild:
The bot prints every active reaction-role post and the mappings under each one. Quick way to remember what you posted six months ago./roles list - Remove a post you no longer want:
The bot deletes the embed and clears all the mappings. Members who already claimed roles via the post keep those roles; the bot just stops handling new reactions on the dead message./roles delete message_id:1234567890
The region picker above is a common starter pattern. Pronoun pickers, game pickers ("which games do you play?"), and notification opt-ins ("ping me for events?") all use the same shape. One post per category. One emoji per role. The bot handles the rest.
Tip. The bot needs Manage Roles, and any role you map must sit BELOW the bot's own role in the role list. Discord refuses to let a bot assign a role above its own position. If a mapping silently fails to apply, that is almost always why.
02
Self-serve color roles
Color roles let members pick their own name color from a curated palette without bothering a mod. The bot ships a single command, /color, that presents the available colors as buttons. The member clicks one, the bot swaps their old color role for the new one, and that is the entire interaction. No tickets, no mod queue, no per-member role assignments.
- Run the command as a member:
The bot replies with a button grid of every/colorColor-*role that exists in the guild. The member clicks the color they want. Done. - Swap colors the same way. Re-running
/colorand picking a different button removes the old color role and adds the new one in a single action. Members can change as often as they like. - Remove the color by running
/colorand clicking a "no color" option, which clears everyColor-*role from the member's account. Their name reverts to whatever default Discord renders.
From a mod's perspective, you never have to do anything per-member. The bot handles the whole flow. The only one-time setup is making sure the Color-* roles actually exist in the guild. The bot does not auto-create them, because picking the palette is a judgement call (how many colors, which shades, which order they sort in the member list).
Creating the roles is a one-shot mod task. The recommended path is the GSB-CreateColorRoles SSM document, which spins up a curated palette in your guild in a single call. The local equivalent is npx tsx src/scripts/create-color-roles.ts --guild <id> if you are running the bot yourself. Either way, the script is documented in docs/manual-setup.md under the color-roles entry, so check there for the current parameter set and the exact role list it creates.
Once the roles exist, you can move on. The command is permissionless on the member side (any member can run it), and the bot needs Manage Roles plus a position above every Color-* role in the role list. That is identical to the reaction-role requirement, so if reaction roles work in your guild, color roles will work too.
03
The welcome flow, deeper
The basics live in Setup: pick a channel with /welcome channel, write a template with /welcome message, test it with /welcome test. This sub-section covers the parts you only need once you are running it day-to-day. Template variables, restart safety, and inspection commands.
- The
{user}placeholder is the only template variable, and it expands to a Discord mention of the new member at post time. That mention pings them, which is what causes their client to highlight your welcome channel as having unread activity. Drop it anywhere in the template:
You can use it multiple times in one template ("Hey {user}! {user}, here is what to do first.") but doing so pings the member twice. Usually once is plenty./welcome message template:"Hey {user}, welcome. Read #rules first, then grab roles in #pick-your-roles." - Inspect the current config before you change anything:
The bot prints the configured channel, the current template, and whether the flow is enabled or disabled. Use this when you take over a guild from another mod and want to see what is already set up./welcome show - Preview a real post without waiting for someone to actually join:
The bot posts the template to the configured channel exactly the way it would for a genuine/welcome testguildMemberAddevent. The{user}placeholder expands to your own mention, so you can read what the message looks like and what a real new member would see. - Disable temporarily without losing the config:
The channel and template stay saved. New joins do not produce a post. Re-run/welcome disable/welcome channelto turn it back on later; the saved template is still there.
The flow is restart-safe by design. When the bot reboots, Discord sometimes re-fires guildMemberAdd for members who joined right before the restart. Without protection, those members would get greeted twice. The bot keeps a per-member dedup record with a 1-hour TTL, so a re-fire within an hour is suppressed. The original welcome stands; no double-post.
After the hour passes, the dedup expires. That is intentional. If a member legitimately leaves the guild and rejoins later in the day, they get a fresh welcome, which is the right behavior; they are a returning member and the greeting should reflect that.
Tip. If a user rejoins right after leaving, the 1-hour dedup window means they don't get re-greeted. After an hour, a rejoin re-greets (intended behavior). If you see only the first greeting and not the second, check whether the rejoin happened inside the window.
04
Birthday tracking
The birthday tools turn member birthdays into a recurring small-celebration moment in the guild. Members opt in by registering their birthday with the bot. Once a day, the bot scans for matches and posts a shoutout in the channel you configured. No mod has to remember anything. The whole flow is opt-in, so members who do not want a public birthday post simply do not register one.
- Pick the shoutout channel as a mod with Manage Server:
Every daily shoutout lands in/birthday channel channel:#birthdays#birthdays. Pick a channel that fits the energy of the moment;#generalworks fine if you are a small guild. - Members register their own birthday with the day in
MM-DDformat. Year is intentionally not collected:
The bot stores the registration scoped to this guild and confirms back to the member. Members can re-run this any time to update it, which matters if someone fat-fingered the date the first time./birthday set day:03-14 - Look up a friend's birthday if they have registered one:
The bot returns the friend's saved date, or a "not registered" message if they have not opted in. There is no way to see a date someone has not chosen to share./birthday check user:@Friend - The daily scan runs once per day on a cron schedule. It queries every birthday with today's
MM-DDand posts one shoutout per matching member in the configured channel. If three members share a birthday, three shoutouts post, one after the other.
Behind the scenes, the bot stores birthdays in a per-guild, per-user record keyed on MM-DD. That lets the daily scan be a single efficient query ("find all birthdays on 03-14") instead of a scan over every member's data. Year is not stored because age is sensitive information and not needed for the shoutout. The member knows how old they are; the guild does not need to.
If you set up the channel but later want to turn the feature off, re-run /birthday channel with a channel option pointing at a channel the bot cannot post in, or remove the channel entry by re-running it with no value. Members who already registered keep their registration; the daily scan just stops producing posts.
Tip. Birthdays are per-user-per-guild. The same person in two different guilds can set different display dates if they want. The data is not shared across guilds.
05
Temporary voice channels
Temporary voice channels solve the "all our rooms are either empty or full" problem. You designate one voice channel as a join-to-create hub. When a member joins it, the bot instantly spins up a personal voice channel for them, moves them into it, and deletes it again the moment the last person leaves. Members get a private room on demand without a mod making channels by hand, and your channel list never fills up with abandoned rooms.
- Pick the hub channel as a mod with Manage Server. Make an ordinary voice channel first (call it something like "➕ Join to Create"), then point the bot at it:
From now on, anyone who joins that channel triggers the create-and-move flow. The hub itself stays empty; it is only a trigger./voice hub channel:#Join to Create - Members just join the hub. No command required. The bot creates a channel named after them ("Alex's Channel") in the same category, moves them in, and posts an owner control panel into the new channel's text chat. When everyone leaves, the bot deletes the channel automatically. If a member leaves and rejoins the hub within a few seconds, they land back in their existing room instead of getting a duplicate.
- The owner controls their own room from the panel the bot posts. Only the member who created the channel can use the buttons:
- Rename (✏️) opens a text box to retitle the channel.
- User limit (👥) caps how many people can join, from 2 up to 99, or removes the cap entirely.
- Kick (🚪) disconnects someone from the room.
- Inspect or turn it off as a mod.
/voice showprints the configured hub and any live temp channels (anyone can run it)./voice disableclears the hub so no new channels are created; rooms that already exist are left alone to empty out naturally.
Everything here is per-guild and free. The only setup cost is the one-time /voice hub call. There is no per-member configuration and nothing for mods to clean up afterward.
Tip. The bot needs the Manage Channels and Move Members permissions to create rooms and move joiners. These were added when temp voice channels shipped, so if you invited the bot before that, re-invite it or grant those two permissions to its role — otherwise joining the hub silently does nothing. Discord also rate-limits channel renames to twice per ten minutes, so the Rename button can briefly refuse.
06
Voice activity stats
Voice activity stats reward the people who actually show up in voice. When you turn the feature on, the bot quietly tracks how long each member spends in voice channels and exposes a leaderboard and per-member ranks. It is opt-in per guild and off by default, so existing servers do not suddenly start counting time without you asking.
- Turn tracking on as a mod with Manage Server:
From that point, qualifying voice time accrues for every member. Turning it back off with/voice stats-enable/voice stats-disablestops new counting but keeps every total already recorded — re-enabling later picks up where it left off. - See the leaderboard (any member can run it):
The default window is all-time; pass/voice leaderboard /voice leaderboard window:this-weekwindow:this-weekfor a rolling weekly board. Either way the bot shows the top ten members by time spent in voice. - Check one member's rank with the optional
useroption, defaulting to yourself:
The reply is that member's total voice time and their position on the board./voice rank /voice rank user:@Friend
Time only counts when it represents real participation. A member accrues time when they are not in the AFK channel, not self-deafened, and there is at least one other person in the channel with them. Sitting alone in a channel, or sitting deafened, does not pad anyone's numbers. All-time totals are kept permanently; the weekly window rolls off after 90 days.
Tip. Voice stats track time, not XP — they are separate from the message leveling system below. Enabling one does not enable the other. If a member insists their hours look low, the usual reason is they were alone in a channel or self-deafened, neither of which counts.
07
Leveling — message and voice XP
Message leveling turns regular chatting into a visible progression. Members earn XP for posting, level up on a curve, and can compare standings on a leaderboard. It runs entirely on Discord events and the database — no AI tokens are spent — so it is free and instant. Like voice stats it is opt-in per guild and off by default.
- Switch it on as a mod with Manage Server:
Members start earning XP immediately./level enable/level disablestops new XP but preserves everyone's existing totals, so toggling the feature never wipes progress. - Members track their own progress:
The reply shows the member's current level, total XP, how far they are into the next level, and their rank in the guild. Omit the/level rank /level rank user:@Frienduseroption to see yourself. - See the standings (open to any member):
The bot lists the top ten members by total XP, with medals for the top three./level leaderboard - Tune the economy as a mod. Each control is its own command, and
/level showprints the current settings:
The defaults are 15 XP per message, a 60-second message cooldown, and 10 XP per minute of active voice. Level-up announcements are on by default and post in the channel where the member levelled up; set/level xp-per-message amount:15 /level cooldown seconds:60 /level voice-xp-per-minute amount:10 /level announce enabled:true /level channel channel:#level-ups/level channelto funnel them all to one place instead.
Members earn XP two ways: posting messages, and time spent genuinely active in voice. A once-a-minute sweep awards the voice-xp-per-minute amount to anyone who is not in the AFK channel, not muted or deafened, and sharing a channel with at least one other person — set it to 0 to switch voice XP off entirely. Levels follow a gently accelerating curve (each level costs a little more than the last), and a member's level is always computed from their total XP rather than stored, so changing the XP rates re-derives everyone's level consistently. The message cooldown is per member, per guild: only the first message inside each window earns XP.
Tip. Leveling stays off until a mod runs /level enable, which keeps it from surprising an established server with sudden level-up spam. XP comes from both messages and active voice time; the message cooldown means only your first message per window counts, and voice XP only accrues while you are unmuted and not alone in a channel — so chatting and actually hanging out in voice level you up, but idling, lurking muted, or spamming do not.
08
Personal reminders
Personal reminders let any member ask the bot to ping them later — no permissions, no setup. You can set them relative to now or at an exact date and time, and the bot delivers a DM when they fire, falling back to a mention in the channel if your DMs are closed. Scheduled channel announcements for mods are a separate tool covered in the admin stage.
- Remind me in a while with a relative duration like
10m,2h,3d, or1w:
The bot confirms and fires the reminder two hours later./remind in duration:2h message:"stretch and drink water" - Remind me at a specific time using
YYYY-MM-DD HH:MMin your saved timezone:
Set your timezone first so the bot knows what 19:30 means for you:/remind at when:"2026-06-12 19:30" message:"raid night"
The timezone field has autocomplete, so start typing a city or region and pick from the list./util set-timezone tz:America/New_York - Snooze or dismiss when the reminder arrives. The delivery comes with buttons — +10m, +1h, +1d, and Done — so you can push it back with one tap or mark it handled. Only you can use the buttons on your own reminder.
- Manage your reminders any time:
/remind list /remind edit id:<id> when:"2026-06-13 20:00" /remind cancel id:<id>/remind listshows your active reminders with their short IDs;editandcanceltake that ID (with autocomplete) and let you change the time or text, or drop the reminder entirely.
You can have up to 25 active reminders at once per server. Reminders survive a bot restart — if the bot reboots right as one was due, it catches up and fires it on the next start rather than dropping it.
Tip. Relative reminders (/remind in) ignore timezones entirely — two hours is two hours. Only absolute reminders (/remind at) need your timezone set. If a member's reminder fires at a weird hour, the usual cause is they never ran /util set-timezone and are defaulting to US Eastern.
09
Looking-for-group posts
The looking-for-group board is how members rally a squad without spamming @everyone. Anyone posts an LFG card describing what they want to play; others join by clicking a button on the card. The card tracks who is in, counts down "need N more" until the party is full, and expires on its own so the channel does not fill up with stale posts.
- Post a card in the channel where people look for games:
Only/lfg create game:"Overwatch competitive" slots:5 when:"8pm EST" notes:"Diamond+ preferred"gameis required.slots(2–25) sets the target party size — the card shows "need N more" and flips to FULL when reached; omit it for an open-ended post.whenandnotesare free text, andexpirestakes a duration like30mor2h(default 2 hours, max 12). - Members join by button. The card carries a join/leave button; clicking it adds or removes the member and updates the count live. Joiners run no commands — they just click.
- See what is open, or close a post:
/lfg list /lfg close id:<id>/lfg listshows the guild's active cards with their short IDs;/lfg closeends your post early (it closes on its own at the expiry otherwise).
LFG is free and member-driven — any member with slash-command access can post one. A guild is capped at 25 active cards and each member at 3 of their own, so the board stays readable.
Tip. The bot needs Send Messages in the channel to post the card and keep its count updated. Cards expire on their own, so a forgotten post cleans itself up — but /lfg close is the polite way to take one down the moment your group fills.
10
Pick-up games: split and draft teams
Once a group has gathered and it is time to pick sides, the pick-up-game tools form teams so nobody has to be the bad guy doing it by hand. Two modes: a straight random split, or a captain draft. Both can take an explicit list of players or just pull everyone out of a voice channel.
- Randomly split into even teams:
Choose how many teams (2–8, default 2). Pass/pug split teams:2 channel:#Scrim VC /pug split teams:3 players:@A @B @C @D @E @Fplayersas @mentions, orchannelto pull everyone currently in that voice channel. The bot shuffles and posts the rosters. - Run a captain draft in snake order:
Name the/pug draft captains:@Captain1 @Captain2 channel:#Scrim VCcaptainsas @mentions (that sets the number of teams), or setteamsand let the bot pick captains. Players come fromplayersmentions or the named voicechannel. The bot walks the draft in snake order (1-2-2-1…) so the first-pick advantage evens out.
Both are stateless, free, and game-agnostic — handy for any scrim, game night, or "let us just run customs." Pulling from a voice channel is the fast path: get everyone into the VC, then one command makes the teams.
Tip. Pulling players from a voice channel only sees who is in the channel at the moment you run the command. Get everyone in first, or pass the roster explicitly with player @mentions.
11
Scheduling game sessions with RSVP
Game sessions are how you plan a night ahead of time and see who is actually coming. A member schedules a session with a title and a start time; the bot posts a card with Going, Maybe, and Can't buttons, tracks the roster live, and pings everyone who said Going or Maybe shortly before kickoff so nobody forgets.
- Schedule a session:
/session create title:"Overwatch game night" when:"2026-06-14 20:00" remind:30mtitleandwhenare required;whenisYYYY-MM-DD HH:MMin your saved timezone (set it once with/util set-timezone).remindis how long before start the bot pings attendees (default 30 minutes, max 7 days);descriptionandchannelare optional. - Members RSVP by button. The card carries Going / Maybe / Can't. Clicking sets your status and moves you out of the others; clicking the one you are already on toggles you back out. The roster updates live with no pings — the card just reflects who is in.
- The pre-start ping fires
remindbefore kickoff and @-mentions everyone marked Going or Maybe. This is the one place the feature pings on purpose, so the nudge actually lands. RSVPs close once the session has started. - See or cancel sessions:
/session list /session cancel id:<id>/session listshows upcoming sessions sorted by start time;/session cancelcalls one off (the organizer, or a mod with Manage Messages).
Anyone can schedule a session — it is free and member-driven, capped at 25 upcoming per guild. Sessions are one-off for now; recurring "every Friday" sessions are a planned follow-up.
Tip. The reminder is timezone-aware through your /util set-timezone setting. If a ping fires at the wrong hour, the usual cause is the organizer never set their timezone and defaulted to US Eastern.
12
Fun tools
The fun tools are exactly what they sound like. Light, instant, no setup required. They exist to give members easy ways to break a tie, settle a bet, or post a quick reaction. None of them touch the moderation or AI-access layers; any member who can use slash commands in your guild can use these.
- Flip a coin when you need a random binary decision:
The bot replies with heads or tails. The randomness is real (Node's crypto-grade PRNG), not a weighted pick. Both outcomes are equally likely./fun coinflip - Pick from a list of options when "heads or tails" is not enough:
The command takes a single/fun pick options:"option 1, option 2, option 3"optionsparameter containing a comma-separated list of choices. The bot splits on commas, picks one of the supplied options at random, and announces it. Useful for "what should we play tonight?" or "who buys the next round?". Add as many comma-separated entries as you like inside the one string.
None of these tools require permissions beyond the default member ability to use slash commands. None of them store any state. The bot does not remember what coin flips were heads or what options were picked. They are pure stateless utilities by design.
If your guild has channels where casual tools should not be runnable (an announcements channel, say, or a serious-discussion channel), restrict slash-command usage to specific channels using Discord's built-in Integrations settings under server settings. The bot does not need to enforce that itself; Discord's permission system handles it.
13
Custom lists and random picks
Custom lists are named, reusable pick-lists your server saves once and draws from whenever. Think map pools, game-mode rotations, a roster to draw a winner from, or anything else you would otherwise re-type into /fun pick every time. Mods curate the lists; anyone can pick from them.
- Create or replace a list (Manage Messages):
Items are comma-separated and de-duplicated. Edit a list in place with/list set name:"maps" items:"Ilios, Lijiang, Oasis, Nepal, Busan"/list add name:maps items:"Antarctica"and/list remove name:maps items:"oasis"(matching is case-insensitive), or drop it entirely with/list delete name:maps. - Pick from a list (anyone):
The bot draws/list pick name:maps /list pick name:maps count:3countdistinct items at random (1–25, default 1). Handy for "what map next?" or drawing names without re-typing the pool. - Browse what exists:
/list show name:maps /list all/list showprints one list's items;/list alllists every saved list with its item count. Thenamefield autocompletes from existing lists everywhere it appears.
Lists are case-insensitive by name and shared across the guild, capped at 25 lists of up to 100 items each. Editing is gated to Manage Messages so a shared map pool cannot be wiped by just anyone; picking and viewing are open to all. For a one-off pick you do not want to save, /fun pick still takes a comma-separated list inline.
14
Social link unfurling and translation
Discord's built-in Twitter/X embeds are bad on purpose. The platform deprecated rich previews for X links a while back, so members posting tweets see a stripped-down card with no image and no video. The bot fixes this by quietly watching for Twitter/X URLs and posting a fxtwitter-based embed underneath each one. Original message stays intact. The bot's embed adds the image, the video, and the post text inline.
- Post a tweet URL the same way you always would:
The bot detects the URL, fetches a fxtwitter version of the embed, and replies in the same channel with the rich preview. No mod action required. The original poster does not have to use a special command or remember to convert the URL by hand.https://x.com/SomeAccount/status/1234567890 - For non-English tweets, the embed is enough to see the image or video, but not the text. Use the translation command to get an English version:
The bot detects that the URL is a tweet, fetches the post text via fxtwitter, runs it through the translation pipeline, and posts the translated text in the channel. Source language is auto-detected; target defaults to English. Override the target with the optional/util translate content:https://x.com/SomeAccount/status/1234567890targetoption if you need something else. - Translate raw text the same way when there is no URL:
Same command, same auto-detect, same target default. The bot returns the English translation in a single reply./util translate content:"今日はいい天気ですね"
The auto-unfurl happens for every Twitter/X URL the bot sees in any channel it has read access to. There is no per-guild toggle and no per-channel opt-out. If you do not want the embeds in a specific channel, deny the bot read or send-message permission in that channel and the auto-unfurl stops there.
Translation is decoupled from unfurling on purpose. The unfurl is cheap (a single HTTP fetch to fxtwitter), so doing it on every tweet is fine. Translation is not cheap (it costs LLM tokens), so doing it on every tweet would be wasteful and annoying. Making it on-demand means you spend tokens only on the tweets that actually need translating.
Tip. The translation is on-demand only. The bot does not auto-translate every tweet because most users only need the occasional one. Earlier versions of the bot did auto-translate on post; that behavior was removed because it misfired too often and burned tokens on tweets nobody needed translated.
15
Meme tools
The meme tools are member-facing utilities for turning content into shareable formats. They are not moderation tools and they do not require any per-guild setup. Any member with slash-command access can run them. They exist because turning a 30-second video clip into a GIF, or applying a meme filter to an image, used to require leaving Discord and using a separate app. The bot does it inline.
- Extract a GIF from a video tweet when someone posts a Twitter/X URL with embedded video and you want to reuse the clip. The command lives in the
/fungroup, not/util:
The bot fetches the video, transcodes the first few seconds (or the full clip if it is short enough) into a GIF, and posts the result as a Discord attachment. The conversion runs server-side via ffmpeg, so the member does not need to install anything./fun gif url:https://x.com/SomeAccount/status/1234567890 - Deep-fry an image by attaching it to the command:
Attach the source image in the same Discord upload widget you would use for any attachment. The bot processes the attachment (high-saturation, posterize, JPEG re-compress, sharpen) into the deep-fried meme aesthetic and posts the result in the channel. Original attachment is not preserved; the bot only posts the processed version./fun deep-fry
Both tools enforce file-size limits set by your guild's Discord upload cap. Free guilds cap attachments at 10 MB; boosted guilds get higher caps depending on the boost level. If a video tweet is longer than the GIF conversion can fit inside your cap, the bot returns an error in the channel and does not post a partial result. The same applies to deep-fry: if the input image is too large to process and re-post inside the cap, the bot tells you instead of silently truncating.
Both tools also have an implicit timeout. Transcoding a video to GIF can take several seconds for longer clips, and Discord's slash-command interaction has a fixed deadline. The bot uses a "thinking" placeholder to extend the deadline, but if the conversion still does not finish in time, the bot returns a timeout error. Re-running on a shorter source clip usually resolves it.
If your guild does not want meme tools enabled in specific channels (an announcements channel, for example), deny the bot send-attachment permission in that channel. The slash commands will still show, but the result of the conversion will not post.
Continue your tour
For the full slash-command surface, see the command reference.