Notes

Notes

Operational notes, security guidance, and design decisions.

Security

Never Commit Secrets

The following files contain sensitive data and must never be committed:

FileContains
config.ymlBot token, API keys, database credentials
.envEnvironment variable overrides
patreon_cache.jsonCached Patreon pledge data
mewwme.database.jsonFull database contents (JSON driver)

All of these are listed in .gitignore.

Environment Variable Strategy

The config.yml supports environment variable interpolation using ${VAR} syntax. This allows you to keep secrets in .env while keeping the config structure in version control:

# config.yml (safe to commit as template)
bot:
  TOKEN: ${TOKEN}
 
features:
  RestAPI:
    SessionSecret: ${SESSION_SECRET}
    DiscordClientId: ${DISCORD_CLIENT_ID}
    DiscordClientSecret: ${DISCORD_CLIENT_SECRET}
# .env (never commit)
TOKEN=your_actual_token
SESSION_SECRET=your_64_char_hex
DISCORD_CLIENT_ID=your_client_id
DISCORD_CLIENT_SECRET=your_client_secret

Session Security

SettingDevelopmentProduction
Cookie securefalsetrue (requires HTTPS)
Cookie httpOnlytruetrue
Cookie sameSitelaxnone (cross-site)
Cookie maxAge7 days7 days
Cookie namemewwme-dash.sidmewwme-dash.sid

Production mode is activated by setting NODE_ENV=production.

Generating a Session Secret

node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"

Use the output as your SESSION_SECRET value.

API Authentication

The internal API (/v1/*) uses a shared token for authentication:

Authorization: your_secret_api_token

This token is configured in features.RestAPI.auth. It is used by:

  • The DashboardPlugin proxy (internal calls)
  • The BotWsService (WebSocket bridge)
  • Any external services calling the API directly

IP Whitelist

The features.RestAPI.whitelist array restricts which domains can access the API. When empty, all domains are allowed. In production, set this to your frontend domain:

features:
  RestAPI:
    whitelist: ["meww.me", "api.meww.me"]

Production Deployment Checklist

  1. Set NODE_ENV=production - enables secure cookies and trust proxy.
  2. Use HTTPS - place the API behind a reverse proxy (Nginx) with SSL/TLS.
  3. Use a strong SESSION_SECRET - at least 64 hex characters.
  4. Set the API whitelist - restrict to your domain(s).
  5. Use a process manager - PM2 or Docker for auto-restart.
  6. Configure database security - strong passwords, restricted network access, SSL if supported.
  7. Enable regular backups - for MySQL, use MYSQLBACKUP in config; for others, set up external backups.
  8. Rotate tokens periodically - regenerate bot token and API keys if compromised.
  9. Monitor logs - check logs/ directory for errors.
  10. Set up DNS - configure A records for API and WebServer domains.

Design Decisions

Two Web Servers

The project runs two separate web servers:

ServerFrameworkPortReason
REST APIFastify2555Performance, plugin system, WebSocket support, sessions
WebServerExpress2444Simple webhook handling, separate from the main API

This separation allows the webhook server to be independently secured and scaled.

Mewslink vs Upstream Libraries

Mewslink is a custom Lavalink v4 client built specifically for this project rather than using upstream libraries like shoukaku or erela.js. This provides:

  • Full control over the player lifecycle
  • Custom data store per player
  • Tight integration with the bot's event system
  • Dashboard-specific features (e.g., dashboardEmit)
  • Music Trivia isolation via the isMusicTrivia flag

Database Abstraction

The mewwme.quick.db library with QuickDatabasePlus wrapper provides:

  • Uniform API across all four database drivers
  • In-memory cache with configurable clear interval
  • Schema-free - no migrations needed (tables are key-value stores)
  • Hot-swappable - change the driver in config without code changes

The trade-off is that complex queries (joins, aggregations) are not supported. All data access is key-value based.

Session Storage

Sessions are stored using SessionLoginService, which wraps QuickDatabasePlus. This means sessions are persisted in the same database as all other bot data, using the configured driver.


Prisma Schema

A prisma/schema.prisma file is included in the project. It maps all database tables to Prisma models for PostgreSQL reference modeling. However, the bot currently uses mewwme.quick.db as its ORM, not Prisma.

The Prisma schema serves as:

  • Documentation of the data model
  • A reference for future migration to a type-safe ORM
  • A tool for visualizing the database structure

Localization

Language files are stored in src/languages/<code>/ using YAML format. Each language directory contains:

  • commands.music.yaml - music command responses
  • commands.filter.yaml - filter command responses
  • events.player.yaml - player event messages
  • events.ticket.yaml - ticket system messages
  • interaction.yaml - interaction responses
  • tos.and.privacy.yaml - terms and privacy text

All 22 languages maintain key parity - every key that exists in the English files must exist in all other languages.

The Localization service loads the appropriate language file based on the guild's configured language (bot.LANGUAGE default, overridable per guild via /language).


Broadcast System Internals

The broadcast tip system uses a probability-cooldown mechanism to avoid spamming:

ParameterValueEffect
TRIGGER_PROBABILITY0.1515% chance per targeted command
COOLDOWN_MS86,400,00024-hour cooldown after seeing a tip

After seeing all available tips, the user's history resets and the cycle begins again.

Tips are defined in src/utilities/BroadcastConfig.ts and can include:

  • Rich container mode (title, message, footer, thumbnail, buttons)
  • Simple text mode
  • Interactive command mentions (e.g., </play> renders as clickable)
  • Target-specific commands (only trigger on certain commands)

Timer Limitations

Node.js setTimeout stores delay values as a signed 32-bit integer, limiting the maximum delay to approximately 24.8 days (2,147,483,647 ms). The Music Trivia cleanup task works around this by:

  1. Calculating the delay to the next 25th of the month.
  2. Using setTimeout for that specific duration.
  3. Re-scheduling itself after each execution.

This ensures the timer value never exceeds the 32-bit limit.