Pseudonymous Discord chat that still feels like Discord.
Members keep talking in the channels they already use. In immersive channels, Ulofi Alias stops the original message before it appears publicly, then posts it back in the same conversation under that member's persistent alias and avatar. There is no separate anonymous room, compose page, or unfamiliar chat interface.
Ulofi Alias is pseudonymous, not untraceable. When a concrete safety or rules case requires it, an authorized administrator can trace a message, visible alias, or member history back to the Discord account. Identity reveals and other privileged actions are recorded in the audit log.
The bot also brings member verification, AI-assisted review and support, and private tickets into the same community workflow. The source is open so a community can inspect the rules it asks members to trust.
| Capability | What members experience | What administrators retain |
|---|---|---|
| Immersive pseudonymity | Type in an existing channel and appear under a persistent alias and avatar. | Choose immersive and named areas, pause pseudonymous posting, and inspect delivery health. |
| Accountable moderation | Report a message privately or appeal a rejected post without starting a public argument. | Trace a message or alias when needed, review history, block misuse, and audit every privileged action. |
| Member verification | Open one private link, complete Turnstile, and receive the configured Discord role. | Server-side validation, expected-domain checks, bounded risk signals, and verified role assignment. |
| Support and tickets | Ask for private help first, then continue to a human ticket whenever needed. | Private ticket channels, intake fields, staff roles, notes, lifecycle controls, and protected transcripts. |
An administrator enables immersive pseudonymity for the server and chooses any categories or channels that should remain named. In an immersive channel:
- A member types a normal message in Discord.
- Ulofi Alias prevents the account identity and original message from becoming public.
- Eligibility and optional content review run before publication.
- The message returns to the same channel under that member's persistent alias and avatar.
- The protected author mapping remains available only for accountable moderation and retention policy.
Conversation does not move elsewhere. Members can use the message context menu to reply under their alias, report a pseudonymous message, or appeal a rejected post. Reviewed images can be published under the same persona with metadata removed before delivery.
Ticket, announcement, staff, or other selected areas can remain outside immersive mode. Ticket categories created by the bot are kept named so members and support staff know who they are speaking with.
Public participants see the alias, not the member's Discord account. The alias stays consistent so a conversation has continuity without making the account public.
Authorized administrators can:
- reveal the author of one pseudonymous message;
- find a member from a visible alias;
- review the pseudonymous message history linked to a member;
- block or restore a member's access to pseudonymous posting;
- disable one channel or the whole pseudonym system; and
- review the audit trail for reveals and moderation actions.
Reports do not reveal an author to the reporter and never trigger an automatic account penalty. AI review and identity reveal are separate operations; account-level action remains an administrator decision. Retention jobs can unlink older message records from member identity without rewriting the public conversation.
Administrators post a verification panel in Discord. A member receives a private, single-use link that expires after 10 minutes, completes Cloudflare Turnstile on the verification page, and receives the configured role only after the backend confirms the challenge and the Discord role assignment.
Turnstile is validated server-side. The implementation can bind a challenge to the expected action, hostname, and one-time session value, so a successful widget on the wrong site is not treated as completed verification. The link is also bound to a short-lived HttpOnly browser cookie. Role assignment is a durable saga: a restart after Discord accepts the role but before SQLite commits is reconciled instead of granting twice or losing the audit record. Cloudflare Tunnel and trusted-proxy handling are available for self-hosted deployments.
Optional review can pass, hold, or reject pseudonymous text and images before they are published. Members can appeal a rejected post, and held content goes to moderators when human review is needed. Review failures do not silently publish uncertain content, and an AI result does not automatically punish a member.
The /help command gives a private answer from a bounded community knowledge
base. If AI support is unavailable or the question needs a person, the response
keeps the configured human-support path visible.
The ticket panel can collect a category and up to five intake fields before it opens a private Discord channel. Communities can configure staff roles, staff-only notes, additional participants, reminders, automatic closing and deletion, reopening, and signed transcript links. Transcripts stay on the community's server instead of being uploaded to a third-party transcript host. AI support may be offered before the ticket form, but members can always continue to human help. Provisioning, close, reopen, deletion, and the three close notifications use compare-and-set state or a durable outbox. A notification whose Discord send has started but whose result is unknown is not replayed.
- A pseudonym is not absolute anonymity. The deployment stores the protected mapping required for authorized traceability.
- Pseudonymous messages are delivered by the bot. Members cannot directly edit or delete the resulting webhook message; reports and staff tools handle correction or removal.
- Pseudonymous replies use the Discord message context menu, and reviewed image
posting uses
/anonimage, because Discord does not expose every native reply and attachment path to the relay. - Optional AI providers receive only the content submitted to the enabled review or support path. Communities should review provider and retention settings before enabling those integrations.
- The public language is English by default. Discord command names and
descriptions use native locale support, with Traditional Chinese available
as
zh-TW; some advanced administration responses are still being migrated.
This release deliberately has a narrow deployment boundary: one running bot
instance owns exactly one Discord guild. GUILD_ID must name that guild and
the bot refuses its ready sequence when it belongs to zero or multiple guilds.
Do not invite the same token to a second server, and do not run a second
instance against the same database or Discord token.
The database driver is better-sqlite3 13.0.3. Its locked build embeds SQLite
3.53.4; verify the actually deployed library with
SELECT sqlite_version(). Migrations are versioned, schema changes are
checked before they run (the current application schema is v6), and backups use
SQLite's online backup API. The runtime keeps frequently read settings in a
bounded in-memory cache while
SQLite remains the source of truth.
Pseudonymous relay delivery is deliberately at-most-once once a Discord
webhook request starts. Discord webhooks do not expose a caller supplied
idempotency key, so a timeout or an ambiguous remote failure becomes
delivery_unknown for staff review instead of silently retrying and possibly
duplicating a member's post. Work queues, token stores, OAuth state, and
moderation waiters have explicit capacity and expiry bounds; saturation fails
closed or returns a retryable response rather than retaining unbounded work.
AutoMod rules, relay webhooks, aggregate-stat channels, ticket side effects, and verification grants all distinguish desired state from confirmed external state. Reconciliation uses authoritative Discord fetches and deterministic ownership signatures. It adopts an operation that succeeded before a timeout, deduplicates only resources it can prove it owns, and never turns an ambiguous remote result into a false success.
/healthz is a liveness endpoint. /readyz is the deployment readiness
endpoint: it remains 503 until the HTTP server, database, Discord client,
and single-guild boundary are all ready, and returns 503 again during a
graceful shutdown. Neither endpoint includes configuration, secrets, or member
data.
The verification and administration pages use external JavaScript and CSS
files under a restrictive CSP. There are no inline event handlers, no
unsafe-inline in the global policy, and no remote browser-fingerprinting
library. Turnstile is the only third-party executable origin on the verification
page. Mutating APIs require JSON, reject cross-site browser requests, cap bodies
at 32 KiB, and use bounded per-IP rate-limit stores.
Anonymous-message retention clears identity, content, reply targets, moderation reasons, attachment metadata, CDN locations, stale review work, and inactive profile/avatar mappings. Old audit entries discard member targets and free text while retaining the administrator identity only for high-accountability decisions. Ticket retention separately removes transcripts, notes, terminal free text, notification state, and deleted-channel identifiers. Verification records and completed grant metadata have their own retention window. Backup snapshots are age-pruned with the same privacy boundary, and deleted or orphaned custom avatars are removed from both live storage and the backup mirror.
These jobs govern this application's SQLite and filesystem data. Messages already sent to Discord audit/log channels, reverse-proxy access logs, exported backups, and snapshots copied off-host have their own retention boundary and must be managed by the operator.
Requirements: Node.js 22.23.2 or newer and npm. The lockfile is part of the
release artifact; install it exactly with npm ci --include=optional.
cd src
npm ci --include=optional
npm run check
npm test
node tools/test-optional-capabilities.jsCopy .env.example to .env, then replace the obvious placeholders with a
development Discord application and development Turnstile settings. Do not
reuse production credentials locally. A single Discord token must never be used
by two running bot instances.
Start a development instance only after configuration:
npm startDocker Compose is available for a bot plus Cloudflare Tunnel deployment. Review the environment, volume, network, resource limits, and hostname values before using it. The Compose service has a read-only application filesystem, a writable data volume, dropped Linux capabilities, and an explicit Cloudflared version. The test suite never starts Compose automatically.
Image moderation and OCR are optional, platform-dependent capabilities. The smoke test reports their enabled state without printing environment values or loader errors. If image moderation is unavailable, custom avatar and anonymous image submission stay disabled; if OCR is unavailable, its best-effort text extraction layer is skipped while image safety checks remain fail-closed.
src/index.js: Discord and HTTP composition root.src/db.js: SQLite schema and settings access.src/lib/: verification, privacy, moderation, support, tickets, statistics, bounded runtime state, delivery/outbox state machines, backup, and command-localization modules.src/public/: verification and administration pages.src/tools/: local regression and release checks.ARCHITECTURE.md: boundaries, data flow, and extraction plan.DEPLOYMENT.md: generic Docker, Turnstile, and Cloudflare Tunnel deployment guide.
The implementation takes ideas from actively maintained open-source projects, but does not copy their code or stronger copyleft components. Discord.js informs the event and command boundary; Discord's API documentation informs webhook and rate-limit behavior; PluralKit informs in-flight webhook caching and invalid webhook recovery; and discord-tickets informs lifecycle and archive concerns. The local implementation is independently written and remains MIT licensed. See the referenced projects: discord.js, Discord API documentation, PluralKit (AGPL-3.0), and discord-tickets (GPL-3.0). SQLite transaction and backup semantics come from SQLite's own documentation, and the driver boundary follows better-sqlite3.
Verification, tickets, moderation, pseudonymity, backup integrity, and command localization have local regression coverage. The tests use fake identifiers and local fixtures. They do not log in to Discord, call OAuth, send email, upload data, or start the production bot.
cd src
npm test
npm run check
docker compose configSee CONTRIBUTING.md before opening a pull request. Report
security issues privately using SECURITY.md, not a public
issue.
Ulofi Alias is available under the MIT License.