Imported from XiboxCode/NyawerXibox (
AGENTS.md). Install upstream withnpx skills add XiboxCode/NyawerXibox. Copyright stays with the author.
NyawerXibox — Agent Guide
Commands
bun run dev—wrangler dev(local dev server)bun test—vitest runbun run test:watch—vitest(watch mode)bun run typecheck—tsc --noEmitbun run deploy—wrangler deploy
Architecture
4-layer: Route (src/routes/) → Service (src/services/) → Repository (src/repositories/) → Utils (src/utils/). No controllers/middleware. DI via Elysia .derive() lazy singleton in src/index.ts:41.
App built in buildApp(env) and cached across requests (_cached at src/index.ts:58).
Critical Elysia/CF Worker Gotchas
- Import:
elysia/adapter/cloudflare-worker(nodist/in path) { aot: false }required in Elysia constructor (src/index.ts:40) +CloudflareAdapter.beforeCompilemust be patched to no-op (src/index.ts:20-22) becausenew Function(AOT codegen) is blocked by CF Workers- Must call
.compile()before exporting (src/index.ts:55) - Exported as
{ fetch, scheduled }for CF Workers module format - Env vars injected via
.derive()— available asctx.envin route handlers; cron handler receivesenvas function parameter directly
Redis Quirks
@upstash/redisv1: usezrange(key, 0, 9, { rev: true })notzrevrange.set()returnsPromise<string | null>— cast as needed
MarkdownV2
All Telegram messages use parse_mode: 'MarkdownV2'. Every response string must escape _*[]()~>#+-=|{}.!viaescapeMarkdown()fromsrc/utils/formatter.ts, then restore *→*` for intentional bold.
Helper pattern:
import { escapeMarkdown } from '../utils/formatter'
function fmt(s: string): string {
return escapeMarkdown(s).replace(/\\\*/g, '*')
}
Apply fmt() to every string returned by service functions. Always escape user-controlled values (entry.member, donator_name, etc.) individually before interpolation (never after joining).
Webhook Security
- Trakteer: constant-time comparison via XOR loop (
timingSafeEqualinsrc/routes/webhook.donation.ts:47) onX-Webhook-Tokenheader vsTRAKTEER_WEBHOOK_TOKENenv - Saweria: constant-time comparison via same
timingSafeEqualon?secret=query param vsSAWERIA_SECRETenv (parsed vianew URL(url).searchParams—src/routes/webhook.donation.ts:57)
Both bodies validated with @sinclair/typebox Value.Check() after security check.
Cron
*/5 * * * * (wrangler.toml:6) retries pending_notifications from Redis list, max 5 attempts. Handler in src/lib/cron.ts, wired in src/index.ts:scheduled.
Enqueue Quirk
donation.service.ts:43 uses a dynamic import of @upstash/redis with process.env fallback to enqueue failed notifications — separate from the injected DI Redis instance. This is the only place raw process.env is used.
Env Vars
9 vars in .env.example. Copy to .dev.vars for local dev. Deploy via wrangler secret put <KEY>. CI deploy workflow (.github/workflows/deploy.yml) needs CF_API_TOKEN secret.
Testing
Tests in tests/. Vitest config at vitest.config.ts with globals: true. Currently one test file: tests/services/command.service.test.ts (3 tests covering MarkdownV2 formatting, leaderboard output, stats output).
Adding a New Platform
- Create
src/services/parsers/<platform>.tswith a parse function returningDonation - Register it in
src/services/parsers/index.ts(parsers+platformNames) - Add route
.post('/webhook/<platform>', ...)insrc/routes/webhook.donation.ts - Add env vars to
.env.exampleandsrc/types/config.tsNo changes needed in service/repository layers.