char-slop/city-role-bot
discord auto role applicator bot for charlotte polycule city
git clone https://git.t4t.associates/char-slop/city-role-bot
f13db32
main
files
| patches/ | |
| src/ | |
| tests/ | |
| .env.example | 306 B |
| .gitignore | 25 B |
| README.md | 6.3 KiB |
| package.json | 678 B |
| pnpm-lock.yaml | 18.0 KiB |
| pnpm-workspace.yaml | 74 B |
| tsconfig.json | 482 B |
City role bot
Starboard
The starboard is configured in src/starboard.ts for guild
1244870846556803074, posting to channel 1559594091497660507.
Messages qualify when five unique people react with any combination of
⭐, 🔥, 😭, and 🍅. Each person counts once across all four emoji and normal/super
reactions. Bot votes and the original author's votes do not count. Reposts show
separate counts, such as ⭐ 3 🔥 5 😭 4, using unique eligible people per
emoji. Zero-count emoji are omitted. The reaction summary is followed on the
next line by the raw, unmasked original-message URL, which Discord can render
as a message link. There is no separate channel mention. These counts can overlap;
qualification still uses five unique people across all eligible emoji, not the
sum of the displayed counts.
All readable message-bearing channels and accessible threads are eligible, including archived threads. Current and previously used starboard destinations and their threads are excluded to avoid reposting old highlights.
Set STARBOARD_EXCLUDED_CHANNEL_IDS in .env to a comma-separated list of
source channel IDs, then restart the bot. It defaults to 1308583779157151804;
an explicitly empty value clears the configurable exclusions. Exclusions also
apply to child channels/threads, live reactions, and candidates already in the
saved backfill queue. Existing Discord reposts are not deleted automatically.
/starboard status shows the configured list.
Restricted-channel messages can be copied into the destination, so give the bot access only to channels whose messages may be shared there.
Reposts include author attribution, text, the first non-spoiler image attachment, attachment links, individual emoji counts, and a link to the original. Replies also include the replied-to author's name, a short text preview, and a link to that message in an embed field. Deleted or inaccessible reply targets display an unavailable notice without preventing the reply from being reposted.
Rich embeds, stickers, polls, and forwarded-message snapshots are not reconstructed; use the original link for those. Text, reply previews, and attachment lists are bounded by Discord's embed limits. Spoiler images are not displayed automatically.
Reposts remain if their score falls below five, and update when reactions or the original text/attachments change. Deleting the original removes its repost. Missing access does not count as deletion. Manually deleting a repost can result in its recreation when the original is next reconciled.
Discord setup
Enable Message Content Intent in the Developer Portal. The bot requests it along with guild message and reaction events.
In source channels it needs View Channel and Read Message History. In the destination it also needs Send Messages and Embed Links. It can enumerate joined archived private threads without Manage Threads; enumerating other archived private threads requires Manage Threads. Discord still determines which history the bot may read. Deleted messages and inaccessible history cannot be recovered.
Backfill
On first startup, the bot starts a full-history scan with no age cutoff. It paginates messages and public/private archived thread lists, respects Discord's rate limits, and pauses briefly between pages to leave room for live events.
Backfill has two resumable phases:
- Collect: scan every accessible source, saving potentially qualifying message IDs and source channel IDs without creating reposts.
- Post: sort candidates by creation order across the whole server, oldest first, then fetch each original again and check its current unique-person eligibility before posting.
Full message contents are not retained in the queue. New live candidates wait while backfill runs, including while it is paused. Candidates arriving during collection join the history queue; those arriving during posting wait until the history queue has been processed. Existing reposts can still update or be deleted.
Mappings and scan checkpoints are saved atomically in data/starboard.json.
Restarts reconcile existing reposts and resume unfinished scans. Connection
failures, timeouts, and server errors are retried with exponential backoff
(1, 2, 4, and 8 seconds). If retries are exhausted, backfill pauses without
advancing its current page or queued candidate. Permission errors are not
retried; inaccessible history or thread-list endpoints are skipped and reported
in logs and status. Recent send retries also use Discord nonce deduplication. Run only
one bot process
against this data directory, and retain it across deployments.
Changing the configured destination archives the previous destination's mappings locally. The new destination reuses the collected history: already-posted originals, remaining history candidates, and waiting live candidates are merged and posted oldest-first. If collection was still running, its cursors are preserved. Old Discord reposts are not edited or deleted. Originals are fetched again and must still qualify before being posted to the new destination.
Legacy state without a destination ID belongs to the original channel
1559288341717844088. State from before the two-phase backfill requires a fresh
history scan because it did not retain a candidate queue.
Server admins with Manage Server can use:
/starboard status— settings, repost count, scan progress, and failures./starboard backfill— resume a paused scan; after a completed scan, start a fresh full-history scan without duplicating tracked reposts.
A completed scan does not automatically restart on every boot. Reactions added while the bot was offline to previously untracked messages require another full scan, or a later live reaction event on those messages. Run backfill again after adding access to previously skipped channels.
Development
pnpm-workspace.yaml applies the checked-in Oceanic 1.14.0 patch in patches/.
It fixes transport failures leaving REST promises unresolved and clears their
request timers. Client errors are logged; starboard operations can then retry
instead of crashing or hanging. Install dependencies with pnpm so this patch
is applied.
pnpm check— type-check source and tests.pnpm test— offline starboard tests (does not connect to Discord).pnpm start— start the bot using.env.