Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ bun run lint # run oxlint
bun run fmt # format with oxfmt (fmt:check to verify)
```

There are no tests in this project.
Run `bun test tests/purge-command.test.js` for the mocked bulk-purge regression tests (no Slack or database access required).

## Architecture

Expand Down Expand Up @@ -60,6 +60,10 @@ when it already matches the home workspace.

**Rate limiter** (`lib/ratelimiter.js`): Handles Slack API rate limits with exponential backoff. Used by `lib/purge.js` for batch message deletion.

**Bulk purge** (`lib/commands/purge.js`): `/pro purge <count> [true|false]` is the Discord-`?purge`-style bulk delete — it grabs the last `<count>` (max 100) top-level messages from `conversations.history` (thread replies aren't returned by that endpoint, so they're excluded from the count automatically) and confirms via a modal before deleting anything. The trailing `true`/`false` argument controls thread replies: `false` (default) deletes only the matched top-level messages, same batch-delete path (`RateLimiter.deleteBatch`) as before, logged via dedicated `logPurge`/`notifyPurgeDeletion`/`publicLogPurge` exports (parallel to `logThread`/`notifyThreadDeletion`/`publicLogThread` but worded for a purge instead of a single thread). `true` instead routes any matched message that has replies through `purge()` from `lib/purge.js` — the same full thread-destroy path as the Destroy Thread shortcut, with its own retry loop and logging — so parent and replies are removed together instead of leaving an orphaned thread behind. Because deletion only ever considers messages `conversations.history` can currently see, running `true` again later does not retroactively clean up threads whose parent was already removed by an earlier `false` run — that orphaned parent ts is no longer a message history can return.

The confirmation modal also takes two independent ways to protect messages, both parsed by `parseTimestampToken` from either a raw Slack ts or a permalink's `p<16 digits>` suffix: a "keep these messages" list (individually excluded messages never count toward `<count>` — history keeps paging back to make up the difference), and a "keep everything between two messages" range (exactly two links define an inclusive span; any other count of links is treated as a mistake and aborts the purge with nothing deleted, rather than guessing). A checkbox controls whether the kept range counts toward `<count>`: off (default) behaves like the individual exclusions and digs further back; on treats `<count>` as a fixed window so protected messages reduce how many actually get deleted. History and reply scans use the user token. With thread deletion enabled, all reply pages are checked before selecting a thread; if any reply is excluded or inside the kept range, the whole thread is skipped without consuming the count.

**Web dashboard and API** (`lib/web/`): A Hono app served by `lib/web/server.js`, mounted by
`index.js` alongside the bot and also runnable on its own with `bun run start:web`. Sign-in is
Slack OAuth through Better Auth (`auth.js`), restricted to the workspace the bot is installed in.
Expand Down
4 changes: 4 additions & 0 deletions lib/commands/help.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ const MODERATOR_CMDS = [
];

const MANAGER_CMDS = [
{
cmd: "/pro purge <count> [true|false]",
desc: "Bulk-delete the last <count> top-level messages; `true` also nukes their threads",
},
{ cmd: "/pro embeds", desc: "Manage blacklisted embeds" },
{ cmd: "/pro welcome [set|remove|view]", desc: "Manage the welcome message for new peeps!" },
{ cmd: "/pro gate set [button|phrase]", desc: "Require an acknowledgement before posting" },
Expand Down
Loading
Loading