A Discord bot for communities that want to celebrate what their members are up to. Members share accomplishments via a slash command; the server votes by reacting with emoji; temporary roles are granted, extended by new voters, and automatically expire.
Tailored to one server's specific vibe — but the mechanics are general enough to adapt.
/did-a-thing— slash command where members share an accomplishment and get a temporary role- Reaction roles — when a message hits a reaction threshold, the author gets a temporary role
- Combined reaction roles — requires multiple different emoji to all hit threshold on the same message (e.g. "best" + "worst" → a third role)
- Auto-forwarding — optionally forwards the triggering message to a configured channel when a role is first granted
- Forward veto — anyone can react 🚫 on a bot-forwarded message to delete it (e.g. if the original author would rather post it themselves)
- Extensions — each genuinely new reactor adds 4 hours to the role's expiration
- Reaction debouncing — grants/extensions on a message are evaluated once a burst of reactions goes quiet (a few seconds), not once per reaction, and multiple roles affected in that window are announced in a single reply instead of spamming one per role
- Auto-expiry — all roles expire after 16 hours; a worker cleans them up automatically
- Retired, not deleted — however a role's record stops being active (natural expiry, superseded by a later grant of the same role, revoked because reactions dropped, or manually expired via
/squiggle expire), the record is marked spent rather than removed. That message is permanently retired for that role — later reactions on it can't trigger a "new" besting/worsting — and the history still counts toward/squiggle leaderboard /squiggle leaderboard— top 3 members per configured reaction role, open to everyone/squiggle— admin commands: list active temp roles, manually expire a role, manually grant a role, or trigger the worker on demand (Administrator permission required)- Weekly leaderboard post — the same leaderboard is posted publicly every Monday at 9am Central, in each guild's system channel by default (or
workers.leaderboardChannelif set)
- Node.js 22+
- A Discord bot token with the following intents:
Guilds,GuildMembers,GuildMessages,GuildMessageReactions
git clone https://github.com/irrg/squiggle.git
cd squiggle
npm installCopy the example config and fill it in:
cp config/config.json.example config/config.jsonSee Configuration below.
Create a .env file in the project root:
DISCORD_TOKEN=your-bot-token-here
DB_STORAGE=./squiggle.dbDB_STORAGE is the path to the database file. Omit it to use an in-memory database (data lost on restart).
# Development
npm run dev
# Production
npm startconfig/config.json controls everything about how the bot behaves. See config/config.json.example for the full schema. Below is an annotated example:
{
"bot": {
"namePrefix": "prod",
"commandPrefix": "",
"blacklist": ["bot-testing"]
},
"commands": {
"didAThing": [
{
"name": "coding",
"role": "People who wrote some code today",
"color": "#5865F2"
}
]
},
"workers": {
"leaderboardChannel": "squiggle-leaderboard",
"reactionRoles": [
{
"emojiName": "👍",
"threshold": 4,
"roleName": "People who said something good",
"color": "#57F287",
"forwardChannel": "good-posts"
}
],
"combinedReactionRoles": [
{
"emojiNames": ["ThumbsUp", "ThumbsDown"],
"threshold": 4,
"roleName": "People who contain multitudes",
"color": "#FEE75C",
"forwardChannel": "chaotic-neutral-posts"
}
]
}
}bot
namePrefix— prepended to the bot's name in debug messagescommandPrefix— prepended to all slash command nameswhitelist/blacklist— restrict which channels the bot will post in (use one or neither)
commands.didAThing — list of things members can share. Each needs a name (shown in the slash command dropdown), a Discord role name to grant, and a hex color for the embed.
workers.reactionRoles — emoji reaction thresholds. emojiName can be a Unicode emoji or a custom server emoji name. threshold is the number of human reactions required. forwardChannel is optional.
workers.combinedReactionRoles — like reactionRoles, but all emoji in emojiNames must independently hit threshold on the same message.
workers.leaderboardChannel — optional channel name override for the weekly leaderboard post (every Monday at 9am Central). If omitted, each guild's system channel is used (the one Discord designates for join/boost messages — stays correct even if that channel gets renamed), falling back to a channel literally named "general" if no system channel is set. If neither resolves, that guild is skipped that week. The /squiggle leaderboard command works regardless of this setting.
npm test # run test suite
npm run test:watch # watch mode
npm run lint # ESLint
npm run format # Prettier
npm run dev:watch # dev server, restarts on file changesThe bot needs the following permissions in your server:
- Read Messages / View Channels
- Send Messages
- Add Reactions
- Manage Roles (to grant and remove temporary roles)
- Read Message History (to handle partial reactions on old messages)
The bot's role must be positioned above any roles it manages in the server's role list.