Rust Discord bot using Serenity. It supports interactive server setup, scoped manager roles/channels, per-guild file storage, retained logs, curated rule JSON, AI-assisted rule summaries, AI rule generation from manager-uploaded files, and report-only AI review of messages in configured bot channels.
- Create an application and bot in the Discord Developer Portal.
- Under Bot → Privileged Gateway Intents, enable Message Content Intent.
- Invite the bot to your server using the
botandapplications.commandsscopes, with View Channels and Send Messages permissions. - Set the
DISCORD_TOKENenvironment variable to the bot token. Keep it out of source control.
With Rust installed and DISCORD_TOKEN set:
cargo run --lockedSend !ping in a server channel the bot can access.
With DISCORD_TOKEN exported in your shell:
docker build -t clause .
docker run --rm --name clause --env DISCORD_TOKEN clauseOr copy .env.example to .env, fill in DISCORD_TOKEN, optionally set
COMMAND_GUILD_ID for one test server, and run Compose:
docker compose up --buildThe Compose setup stores SQLite in the clause-data volume and guild uploads,
retained logs, and quota metadata in the clause-guilds volume. Use
docker compose down to stop the bot while keeping data, or
docker compose down -v to remove the volumes.
The container runs as a non-root user and requires no inbound ports. Stop it with
docker stop clause or docker compose stop; the bot handles SIGTERM and Ctrl+C
for graceful shutdown.
Only run one live Clause process with the same Discord bot token. Running two active deployments with the same token can cause duplicate gateway sessions, confusing command behavior, and race conditions against the same Discord bot identity.
Slash commands are registered globally by default when the bot becomes ready.
For development or a single test server, set COMMAND_GUILD_ID=<guild-id> before
startup to register the current command set directly in that guild. Guild command
registration is much easier to see while iterating; global commands are the
release default.
AI features require an OpenAI-compatible chat endpoint in .env:
API_KEY=...
AI_ENDPOINT_URL=https://api.openai.com/v1/chat/completions
AI_MODEL=gpt-4o-miniWithout those values, the bot still starts. /summary, /rules generate,
/rules from-channel, and message review report or silently skip AI work when AI
is not configured. Bot managers can override the endpoint, model, and API key for
one server with /ai set; /ai show never reveals the stored key.
The GitHub Actions workflow in .github/workflows/docker-image.yml builds the
Dockerfile on pull requests and publishes an image to GitHub Container Registry
on pushes to main, master, and v*.*.* tags. Published images are tagged
with the branch name, Git tag, commit SHA, and latest on the default branch.
After pushing to GitHub, pull the image with:
docker pull ghcr.io/fare-spec/clause:latestTo run the published image with Compose instead of building locally, use the provided GHCR Compose file:
docker compose -f docker-compose.ghcr.yml pull
docker compose -f docker-compose.ghcr.yml up -dThat file uses ghcr.io/fare-spec/clause:latest and keeps the same .env,
clause-data, and clause-guilds volumes as the local build compose file.
Keep DISCORD_TOKEN, API_KEY, and provider settings in .env or deployment
secrets. They are not needed at image build time and should not be baked into the
image.
Run /setup as a server administrator or a member with Manage Server.
The private Discord setup uses two pages with dropdowns, Back/Next, and Save/Cancel:
- Manager roles: select 1–25 roles. Members of any selected role can use
!pingand!config. Administrators and members with Manage Server always retain access. - Bot channels: select 1–25 text channels where commands work after setup. When AI is configured and curated rules exist, ordinary messages in these channels are reviewed against the rules.
- Log channel: the destination for this guild’s logs. The bot needs View Channel, Send Messages, and Embed Links here.
- Rule source channel: optional text channel where bot managers can post rule
notes. Clause sends each manager message in that channel to the configured AI
endpoint, ignores irrelevant messages, and adds or updates extracted rules in
rules/rules.json. Non-manager messages in that channel are ignored. - Logging level: off, error, warn, info (default), or debug.
- Disk retention (page 2): none (default), flagged/managed messages for 7/30/90 days, or all messages and bot actions for 1/7/30 days.
Select your settings and click Save settings. Clause checks that it can
view and send messages in every selected channel. The single log channel may
also be one of the bot channels.
Settings are saved together in SQLite (DATABASE_PATH, default bot.db) and
survive restarts. Cancel leaves previously saved settings intact.
Run /disable as an administrator or member with Manage Server to disable Clause for the server. It marks setup incomplete, clears selected manager roles/channels, removes the server AI override, clears retained local logs, and keeps uploads/rules on disk for later cleanup or reuse. Run /setup again to enable the bot.
Run /setup again to edit existing settings. Only one administrator can configure
a server at a time; reopening your own setup replaces your previous panel. Panels
expire after 15 minutes or a bot restart. Only administrators and members with
Manage Server can change setup, including the manager roles.
Existing single-role/channel configurations migrate automatically on startup.
Saving /setup creates guilds/<guild-id>/, using the stable guild ID as the
folder name. Uploaded files are stored in uploads/ and retained logs in logs/.
Set GUILD_STORAGE_PATH to change the root directory. Existing
files survive repeat setup and cancellation does not create a folder.
Each folder contains storage-limit.json recording the configured guild quota, defaulting to 10 MB / 10 Mo. The quota includes uploads, retained logs, and metadata. Set GUILD_STORAGE_LIMIT_MB or GUILD_STORAGE_LIMIT_BYTES before startup to change it. Registration and uploads check recursive file usage and refuse writes above
the limit. Quota checks and file writes are serialized within the bot process.
This is an application limit, not an OS filesystem quota; run only one bot
process against a storage directory. Setup does not
delete files to make room. If saving the database fails after folder creation,
the folder is retained for a retry.
For Docker, persist /app/guilds with a volume (for example,
--mount type=volume,src=clause-guilds,dst=/app/guilds). A custom storage path
must be writable by the container user (UID 10001).
Members with any configured manager role, administrators, and members with Manage Server can use these commands in a configured bot channel:
/files list [page]: browse upload filenames, sizes, and total guild usage (10 files per page)./files view filename: download a file to inspect its contents./files upload file: select a Discord attachment to save in the guild folder./files remove filename: permanently delete the named file.
All responses are private. storage-limit.json is listed and readable but cannot
be uploaded, replaced, or removed through these commands. The configured quota is
fixed by the bot operator, not by user-supplied JSON. Uploads include metadata in total
usage and never overwrite existing files; remove a file first to replace it.
Removing files is allowed even when the folder is over quota.
Filenames may contain ASCII letters, numbers, spaces, dots, hyphens, and underscores, up to 100 characters. Leading dots, trailing dots/spaces, paths, subfolders, and symlinks are not accepted by file commands. Downloads are also subject to Discord's attachment size limit. Files are stored as data and are never executed by the bot.
Run cargo test --locked (no Discord token or network access required).
Dedicated tests in src/storage/tests.rs and src/files/tests.rs cover file
operations, guild isolation, protected metadata, quota boundaries, unsafe paths,
symlinks on Unix, and manager/channel authorization. Setup persistence and
migration tests remain in src/setup.rs. Filesystem tests use isolated temporary
directories and sparse files for quota checks; they do not touch bot.db or
production guild folders.
Choose a level in /setup; it is saved independently for each guild:
| Level | Records |
|---|---|
off |
No guild logs |
error |
Failed bot operations and response delivery |
warn |
Errors plus rejected file actions, permission denials, and invalid requests |
info |
Warnings/errors plus setup responses, file actions, command replies, and connection notices |
debug |
All of the above plus incoming guild gateway events, including message content |
Logs go only to that guild’s configured log channel. The bot verifies channel ownership before delivery and disables mentions in logs. Restrict the log channel to trusted members using Discord permissions: debug can copy content from other channels the bot can access. The bot does not change channel permissions automatically. Guild payloads are not written to console logs. Disk retention is configured separately; retained logs count toward the same configured quota as uploads.
Debug covers events available through the bot’s enabled gateway intents; it cannot see inaccessible channels or events Discord does not send. DMs, global multi-guild events, credentials, forwarded/referenced message bodies, the bot’s own messages, and incoming events in the log channel are excluded. Large entries are truncated to fit Discord embeds. Delivery uses a bounded queue; records may be dropped if it fills or Discord delivery fails. Log sending never logs itself. Queued entries are checked against current settings again before delivery.
Logging tests are in src/logging/tests.rs, including guild routing, filtering,
credential redaction, response auditing, and Unicode length limits. Database
migration tests also verify the default level and persistence after restart.
guilds/<guild-id>/
storage-limit.json # protected quota metadata
uploads/ # /files upload, list, view, remove
logs/ # bot-owned retained JSON records
Page 2 of /setup asks what to retain and for how long. The default None
keeps no records on disk. Flagged / managed retains only messages that Clause
flags or marks as gray-area during AI rule review, plus the review result.
All retains observed message create/update/delete events and bot action records. It does not fetch historical messages or download
message attachments. Incoming data has the same guild isolation, redaction, and
log-loop exclusions as debug logging. Deletion events contain only what Discord
provides, which may just be message IDs.
Retention is independent of Discord verbosity: off can still retain messages
if an All policy is selected. Retained records preserve their text without the
Discord embed truncation. Queues are bounded and delivery/storage failures can
drop records; this is not a guaranteed audit archive.
Cleanup runs on startup, every minute (including idle guilds), before uploads, and when settings are saved. Records expire based on their capture time and the current policy. Choosing None removes existing bot-owned retained records; switching to flagged-only removes ordinary message/action records; shortening the duration expires older records. Uploads, quota metadata, unknown files, and messages already posted to Discord are never deleted by retention cleanup.
Uploads and log writes share one quota check and lock. Expired logs are removed
before checking space; when the folder is full, new retained records are skipped
and uploads are rejected. Unexpired logs and uploaded files are not evicted.
/files list shows the combined usage, but file commands cannot alter retained
logs or the size-limit JSON. Retained records are available in the host's guild
folder, not through /files.
Existing root-level upload files migrate automatically into uploads/ on startup
or first access. Migration preserves their contents and quota usage. If both
locations already contain the same filename, migration stops without overwriting
either file; resolve the collision on the host. Reserved folder names must be
real directories, not files or symlinks.
When AI is configured and a guild has curated rules, Clause reviews ordinary
messages in configured bot channels against rules/rules.json. A compliant
message produces no public response. A clear violation creates a warning log in
the configured log channel, pings the configured manager roles, records it as a
managed message if retention allows it, and replies in the original channel with
an advisory explanation and matching rule IDs.
If the AI says the message is a gray area, Clause uses the same report and ping
flow but the channel reply says staff were asked to review it because the bot is
unsure. By default Clause does not delete messages, timeout users, ban users, or
apply punishment. Bot managers can enable /ai auto-delete enabled:true; then
only non-manager messages that the AI marks as high/critical violations with at
least 90% confidence are deleted. Managers decide what to do for all other
reports.
The AI request contains the current message content, attachment metadata
(filename, type, size), and the curated rules JSON. It does not download ordinary
message attachments for review. If a manager configured a server-specific AI
provider with /ai set, that provider is used for summaries, rule generation,
channel imports, rule-source messages, and message review in that server.
/disable: administrator/Manage Server command that disables Clause for the server until/setupis run again. It clears configured roles/channels, the server AI override, and retained local logs, while leaving uploads and curated rules on disk./leave confirm delete_data: server-owner command that makes Clause leave the server. Withdelete_data:true, Clause first deletes this guild's SQLite settings, AI override, retained logs, uploads, rules, and guild storage folder./storage: private response with available/used bytes and an uploads, retained logs, and metadata/other breakdown after retention cleanup. Any server member can request the aggregate; no file names or log contents are exposed./metrics show: private manager-only response with aggregate local metrics: storage usage, upload count, retained log size, curated rule count, AI provider source, configured review channel count, manager-role count, AI moderation counters, upload/remove counts, approximate or provider-reported AI token counts, and whether metrics forwarding is allowed. It does not include message text, filenames, rule text, usernames, or API keys./metrics forwarding enabled: manager-only toggle for whether this server allows future aggregate metrics forwarding to an operator/main server. The current build stores and displays the preference but does not implement an external forwarding transport./settings: private response showing this server's log level, retention policy, log channel, optional rule source channel, bot channels, manager roles, metrics forwarding preference, storage summary, and privacy contact. Any server member can use it to understand what Clause is configured to do./summary: private response with an AI-generated Markdown summary of the current curated rules JSON. Any server member can request it when the rules are public; bot managers can always request it. The bot sends onlyrules/rules.json, not arbitrary uploads, to the configured AI endpoint./rules list: private response with a Markdown export of the curated rules when rules are public or the requester can manage the bot./rules generate: manager-only regeneration of the curated rules JSON from files currently stored inuploads/. Clause sends those upload contents to the configured AI endpoint, validates the returned JSON rules, preserves the current public/private visibility setting, and replacesrules/rules.jsononly after a valid response./rules from-channel channel [limit]: manager-only regeneration of the curated rules JSON from up to 1,000 recent non-bot text messages in the selected channel. The AI is instructed to ignore messages that are not rules or rule clarifications.- Rule source channel messages: when configured in
/setup, each message posted there by a bot manager is sent to AI as a rule-import candidate. Relevant results are merged into the curated rules JSON; irrelevant messages are ignored. /rules add id severity text: manager-only add or replace of a rule./rules update id [severity] [text]: manager-only update of an existing rule./rules remove id: manager-only removal of a rule./rules visibility public: manager-only toggle for whether ordinary members can inspect the rules./rules export: manager-only JSON export of the curated rules document./ai show: manager-only display of whether this server has an AI override, the endpoint, and the model. The key is never shown./ai set endpoint model api_key: manager-only server-specific AI provider override. The endpoint must be an HTTPS OpenAI-compatible chat-completions URL./ai clear: manager-only removal of the server AI override, falling back to environment AI settings./ai test: manager-only health check that sends one small prompt to the active AI provider and returns the provider reply or redacted diagnostic details./ai auto-delete enabled: manager-only toggle for deleting non-manager messages only when AI reports a high/critical violation with at least 90% confidence./logs clear confirm:false: private manager-only preview of retained local log usage./logs clear confirm:true: private manager-only deletion of Clause-owned local retained logs. This frees guild quota but does not delete messages already posted in the Discord log channel. If retention still includes bot actions, the cleanup itself may leave a new small audit entry./privacy: privacy policy attachment, current guild logging/retention settings, and the operator's private contact for access, correction, deletion, and reports./terms: terms attachment and operator contact.
The default operator label is Clause operator and the private contact is
git-spectre@proton.me. Set BOT_OPERATOR and PRIVACY_CONTACT to override them;
independent operators must supply their own name and monitored private email
address or support URL. These values appear in policy commands. Empty values
produce an explicit incomplete-contact notice. Policies are embedded at build time; rebuild after editing them.
Publish stable public URLs for PRIVACY.md and TERMS.md
and set the corresponding fields in the Discord Developer Portal.
Raw logs remain restricted: public log access can expose other people's data. Personal-data requests are handled manually by the operator; file-manager access is not a substitute for a request channel available to ordinary users. Document and actually operate identity verification, access/correction/deletion handling (including Discord copies and backups), offboarding and shutdown cleanup. The application does not yet automate those workflows or encrypt host files; use encrypted storage at rest and restrict filesystem/backup access before public use. Logging and All retention should only be enabled when needed for the disclosed function, with appropriate member notice and authority.
Policy references checked on 22 September 2026: Discord Developer Terms, section 5 and Discord Developer Policy. The documents describe current functionality; they do not certify compliance of a particular deployment with Discord's rules or applicable privacy law.
README GENERATED BY AI