No description
  • TypeScript 88.1%
  • JavaScript 6.1%
  • Nix 5.8%
Find a file
2026-08-16 22:37:21 -07:00
scripts Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
src Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
.envrc Add nix flake with node 26 and pnpm 2026-08-16 19:55:34 -07:00
.gitignore Add nix flake with node 26 and pnpm 2026-08-16 19:55:34 -07:00
.nvmrc Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
.prettierrc Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
devvit.json Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
eslint.config.js Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
flake.lock Add nix flake with node 26 and pnpm 2026-08-16 19:55:34 -07:00
flake.nix Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
package.json Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
pnpm-lock.yaml Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
pnpm-workspace.yaml Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
README.md Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
tsconfig.json Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00
vite.config.ts Implement the rlfriends bot functionality into a devvit bot 2026-08-16 22:37:21 -07:00

rlfriendbot

rlfriendbot helps moderate subreddits where people post to find teammates or recruit for gaming communities. It does three things: it spots repeat recruitment posts from the same author and reports them to the modqueue for a human to judge, it mirrors moderator activity to the mod team's Discord, and it flags heavily-reported content.

Who it is for: moderators of "looking for group" and community-recruitment subreddits, where the same organisers post repeatedly and the problem is noticing it, not deciding what to do about it.

Built for r/RocketLeagueFriends, replacing a self-hosted bot, but it is configured entirely from the app's settings page and nothing about it is specific to that subreddit.

Critical operational notes

Read these before installing.

  1. This app never removes or bans anything. The strongest thing it does is report a post to your modqueue, which is exactly what a normal user report does. Every decision stays with a human moderator.

  2. Every feature is off by default. A fresh install does nothing until you switch something on. This lets you run it alongside an existing bot and compare before handing over.

  3. Discord is optional and send-only. The app posts to Discord webhooks you supply. It cannot read Discord, and there is no Discord bot to add to your server. With no webhook configured it still works; it just stays quiet.

  4. The keyword list is pre-filled with r/RocketLeagueFriends' list, which includes specific community names and usernames that mean nothing on another subreddit. Replace it with your own before switching tracking on.

  5. Moderator menu actions only appear in official Reddit apps and on new Reddit. Moderators on old.reddit.com with Toolbox will not see them.

What it does

Each feature is independent and separately switchable.

Repeat recruitment post tracking

Community organisers tend to post the same recruitment pitch over and over. This watches new posts for keywords you configure — community names, words like "recruit" or "members", or specific usernames — and counts how many an author makes in a rolling window, 14 days by default.

Past the threshold it reports the post to your modqueue, with a note saying how long ago that author first posted. It also posts to Discord with the keywords that matched, so the mod team can see why.

Nothing is removed. The report simply puts it in front of a moderator.

Counts expire on their own at the end of the window. Moderators can check or clear anyone's count from the Community post count menu action — useful when someone was flagged unfairly.

Mod log to Discord

Every post removal, comment removal, and ban is posted to your Discord mod log channel with the moderator's name, the content, and a link. Moderators or bots you list are skipped.

Report escalation

When a post or comment reaches a configured number of reports, it is posted to Discord. At higher thresholds it pings a moderator role. The defaults alert at 3, 5, and every count above 8, pinging at 8 and 13.


Setup

1. Install and open the settings

Install the app, then open its settings page for your subreddit (Mod Tools → Apps → rlfriendbot). Nothing happens until you configure it.

2. Connect Discord (do this first)

In Discord, open the channel you want moderation logs in, then Edit Channel → Integrations → Webhooks → New Webhook → Copy Webhook URL.

Paste it into Discord → Mod log webhook URL. Treat this URL as a password — anyone holding it can post to that channel.

Setting What it does
Mod log webhook URL Where removals, bans, and reports go. Leave empty to disable all Discord output.
Error log webhook URL Where the app reports its own failures. Falls back to the mod log.
Mod role ID to ping Numeric Discord role ID pinged on high report counts. Leave empty to never ping.

To get a role ID: enable Developer Mode in Discord's Advanced settings, then right-click the role and choose Copy ID.

3. Turn on the read-only features

Both only observe and report, so they are safe to enable immediately.

Group Setting Notes
Mod log → Discord Log removals and bans to Discord Add usernames under Moderators to ignore to skip other bots
Report escalation Alert on reported posts and comments Thresholds and ping counts are comma-separated lists

4. Turn on recruitment tracking

Replace the keyword list first. The default is r/RocketLeagueFriends' own list and will not make sense elsewhere.

Setting What it does
Track and report repeat recruitment posts Master switch
Report once an author has posted more than this many times 1 reports the author's second post in the window
Tracking window in days Default 14
Keywords One per line

How keywords match:

  • A keyword matches if it appears anywhere in the post's title or body. recruit matches "recruiting now" and "we recruit weekly".
  • A keyword also matches if it is exactly the author's username. This is for flagging specific known accounts. It is an exact match on purpose — otherwise a keyword like group would flag anyone named grouped_up.
  • Matching ignores capitalisation on both sides.

Leaving the list empty falls back to the built-in list rather than tracking nothing, so an install cannot silently do nothing.

Start with the tracking on and the report threshold set high, watch the Discord channel for a few days, and tune the keywords before lowering it.

Moderator actions

Long-press a post or comment in the Reddit app, or use the subreddit menu.

Action Where What it does
Community post count post, comment, subreddit Show or clear an author's tracked post count

Opened from a post or comment, the username is filled in for you. The field stays editable so you can look someone else up.

Forms must be completed within 10 minutes of opening — a Reddit security limit.

What it stores

Only counters, with automatic expiry. There is no external database and nothing is shared between subreddits.

Data Kept for
Per-author recruitment post counts The tracking window, 14 days by default, from the author's first post in it
Which posts have been counted The window plus one hour
Duplicate-delivery guards 24 hours

Post and comment content is sent to your Discord webhook when the relevant feature is on. That is the only place data leaves Reddit.

Outbound network access

Domain Why
discord.com Posting moderation notifications to Discord webhooks. Send-only — never read.

discord.com is on Devvit's global fetch allowlist, so no approval is needed. The app sends no analytics and contacts no other host.

Troubleshooting

Nothing appears in Discord. Check the mod log webhook URL is set, and that at least one feature is switched on — everything defaults to off.

Nothing is ever reported. Either tracking is off, the keyword list does not match how people actually word posts on your subreddit, or the report threshold is higher than anyone has reached in the window.

Too much is reported. Broad keywords like group and discord catch a lot of ordinary posts. Narrow the list, or raise the report threshold.

A moderator cannot see the menu action. They are on old Reddit or a third-party app. Menu actions render only in official Reddit clients.


For developers

App: https://developers.reddit.com/apps/rlfriendbot

Node 26 and pnpm, both supplied by the flake:

nix develop            # node 26.x + pnpm 11.x on PATH
pnpm install
pnpm run login         # devvit login, one time
pnpm run check         # typecheck, lint, validate devvit.json, unit tests
pnpm run dev           # devvit playtest against the subreddit in devvit.json `dev`

Playtesting targets r/rlfriendbot_dev (set in devvit.json under dev.subreddit). Nothing points at r/RocketLeagueFriends until the cutover phase — and devvit upload alone cannot get you there: an unpublished app only installs on subreddits with fewer than 200 subscribers. Reaching a real subreddit needs devvit publish and app review.

Layout

src/
  index.ts               Hono server; mounts /internal/{triggers,menu,form}
  routes/                HTTP entrypoints named in devvit.json
  core/                  settings, Discord webhooks, Redis helpers
  features/              one module per behaviour

Every endpoint in routes/ must have a matching entry in devvit.json — triggers under triggers, menu actions under menu.items, and form handlers under forms.

Pure logic lives in Devvit-free modules (community-match.ts, report-thresholds.ts, expiry.ts) so it can be unit tested outside the runtime; the Devvit-importing module wraps it.

Design notes

  • No database. Counters are Redis keys whose TTL is the tracking window, so nothing has to sweep expired records and the author's first post in the window is derivable from when the key expires.
  • Triggers are not exactly-once. A per-post marker key means a redelivered onPostSubmit cannot inflate an author's count.
  • Discord is outbound-only — incoming webhooks, no bot token, nothing read back.
  • redis.expireTime() returns seconds remaining, not the absolute timestamp its docstring promises. core/expiry.ts decides from magnitude, so it stays correct if that is ever fixed.
  • A self post's url is a permalink path, not an absolute URL — it has to be expanded before it is any use as a link.
  • onModAction carries no ban details, so the reason is read back off getModerationLog.

Before adding anything flair-dependent

Nothing here reads post or user flair today. If that changes, check whether r/RocketLeagueFriends requires a flair at submit time: where it does not, onPostSubmit fires before the flair exists and an onPostFlairUpdate handler is needed as well.