← back

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

Charlotte Somadd tomato to starboardf13db32

main
3 folders7 files

files

patches/
src/
tests/
.env.example306 B
.gitignore25 B
README.md6.3 KiB
package.json678 B
pnpm-lock.yaml18.0 KiB
pnpm-workspace.yaml74 B
tsconfig.json482 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:

  1. Collect: scan every accessible source, saving potentially qualifying message IDs and source channel IDs without creating reposts.
  2. 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:

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.