-
Notifications
You must be signed in to change notification settings - Fork 0
Data Contracts
runtime data lives under /data.
repo intent:
-
/datais not supposed to be versioned normally -
.gitignoreignores/data/**/* -
.rsyncignoreexcludes/data/**from deployment sync
so local/dev/prod data has to be managed separately.
expected top-level shape:
{
"accounts": [
{
"username": "string",
"name": "string",
"password": "bcrypt-hash or empty",
"isAdmin": true,
"mustResetPassword": false,
"discordUserId": "optional discord snowflake string",
"allowedPages": ["feed", "journal", "comments", "chat"],
"bookmarks": ["2026-01-01_12-00-00", "journal:12"],
"theme": "default|classic|theme-id",
"glowIntensity": "none|low|medium|high",
"mobileFriendlyView": true,
"onekoEnabled": true,
"colors": {
"bg": "#RRGGBB",
"fg": "#RRGGBB",
"border": "#RRGGBB",
"subtle": "#RRGGBB",
"links": "#RRGGBB"
}
}
]
}notes:
- extra unknown keys can exist and are preserved by
account/admin/edit - bookmarks are the current source of truth for logged-in users
- bookmark ids currently use raw feed ids and
journal:{id}; legacynewsletter:{id}values can exist but are ignored -
theme: defaultis blackprint and uses the base template plus/style.css;theme: classicenables savedcolors; any other valid value refers to a/themes/{theme-id}.jsonfile - legacy
blackprintnormalizes todefault,customnormalizes toclassic, andnewsprintnormalizes towhiteprint -
mustResetPasswordis used by the shared session bootstrap to force first-login password changes -
discordUserIdlinks a site account to a Discord member for bot DMs and notifications -
allowedPagescurrently includes functional grants likefeed,journal,comments, andchat
one-time private conversations live as individual encrypted JSON envelopes:
{
"version": 1,
"cipher": "aes-256-gcm",
"nonce": "base64",
"tag": "base64",
"ciphertext": "base64"
}notes:
- each file is named
{conversationId}.json; new chat ids are 9 lowercase letters/numbers - legacy 32-character lowercase hex ids are still accepted so older active links do not break
- decrypted payloads contain conversation metadata, the recipient label in
name, the recipient cookie hash, and message records - messages may include an
attachmentobject with encrypted blob metadata:id,name,mime, andsize; image/audio/video attachments are served inline through the authorized chat route so they can render or play in the chat UI - messages may include
replyTowith another message id, plusreactionskeyed by valid emoji sequences with active viewer roles such asmanagerorparticipant - conversations may include
participantUsernamewhen a logged-in account claims the invite, orparticipantHashwhen an anonymous browser cookie claims it; the first-open popup copy changes based on that claim type - conversations may include
recipientIntroSeenAtonce the recipient has seen the first-open security/help popup - recipient cookies are HttpOnly and scoped to
/chat - the first non-manager account or anonymous browser to open
/chat/{conversationId}claims the recipient slot - account-linked recipients can delete their own active chat; anonymous cookie-linked recipients cannot
- admins and accounts with
allowedPagescontainingchatcan create, view, and delete conversations without claiming the recipient slot - deleting a conversation unlinks the encrypted JSON file immediately
- encryption uses
FRIDG3_CHAT_KEYwhen set; otherwise the app createsdata/chat/.chat_key - lightweight presence indicators use sidecar files under
data/chat/.presence/{conversationId}.json; current entries storelastSeen,active, and a short-livedtypingUntil, while older timestamp-only entries are still readable - attachments are encrypted AES-256-GCM envelopes under
data/chat/.attachments/{conversationId}/; they are served only through the authorized chat route and are deleted with the conversation - attachment uploads are capped at 8 MB
theme metadata lives as JSON files directly under /themes.
{
"name": "Theme Name",
"html": "template-file.html",
"css": "stylesheet-file.css"
}notes:
- the metadata filename is the saved theme id, for example
/themes/cool.jsonbecomescool -
nameis the label shown in/settings -
htmlandcssmust be relative paths in/themes/lib, for exampleaero/aero.htmlandaero/aero.css - theme asset paths cannot be absolute, contain
.., or use characters outside letters, numbers,.,_,-, and/ - desktop rendering uses both themed HTML and CSS
- mobile rendering keeps
template_mobile.htmland only swaps the CSS
- map of client IP -> unix timestamp array
- used for login throttling
feed post format:
@usernameYYYY-MM-DD HH:MM:SS- body text / BBCode
other file:
-
index.tomlis generated by/feed/index.php
feed bodies can include public voice notes as BBCode:
[audio=/data/audio/voice/example.m4a][name:voice-note.m4a]voice notes are created from temporary [voice:N] editor placeholders, verified at upload time, transcoded to small mono .m4a files, and stored under data/audio/voice/.
per-post replies live in {postId}.json files shaped roughly like:
{
"replies": [
{
"id": "20260413153000_deadbeef",
"username": "toast",
"date": "2026-04-13 15:30:00",
"body": "reply body with BBCode"
}
]
}notes:
- reply ids are generated on write; older data may be normalized into
legacy_*ids at read time - reply bodies can contain image BBCode that points at
/data/images/* - new reply bodies can also contain voice note audio BBCode that points at
/data/audio/voice/*
published journal post:
YYYY-MM-DD- title
- description
- trusted HTML body
draft format:
USER:<username>- title
- description
- optional
FORMAT:html - draft body
without FORMAT:html, preview treats the body as BBCode.
with it, preview treats the body as raw HTML.
entry format:
- timestamp
- display name
- message body
plus:
-
ip_index.jsonfor one-post-per-IP ownership tracking
- uploaded images used across feed, journal, and gallery content
- expected web path is
/data/images/<filename>
artist folders currently include:
frdg3cactile
album JSON shape:
{
"album_name": "string",
"album_caption": "string",
"album_type": "Album|EP|Single|Remix|...",
"album_art": "/data/images/example.jpg",
"album_art_directory": "/data/images/example.jpg",
"order": 6,
"songs": [
{ "name": "Track", "directory": "/data/audio/file.wav" }
]
}album_art_directory is preferred by current code.
- track files referenced by music metadata
- also used by shared playback features
-
data/audio/voice/stores public feed voice notes as compressed.m4afiles
- private contact submissions as
{YYYYMMDDHHMMSS}_{random}.json - each submission stores
id,createdAt, hashed IP, user agent, name, email, message, notification channel id, and optionalnotifyError -
rate_limits.jsonstores hashed client IP keys mapped to recent submission timestamps for throttling - nginx blocks direct web access to this directory; submissions are only shown through the admin-only
/contact?dashboard=1route
- temporary markdown paste records as
{id}.json - ids are 16 lowercase hex characters
- records expire after 30 days and are cleaned up opportunistically on create/view
- unencrypted records store a
markdownstring - encrypted records store only AES-256-GCM ciphertext plus PBKDF2-SHA256 salt/nonce/tag metadata; the password is never stored
-
hard_breakscontrols whether single paragraph newlines render as<br>instead of spaces
- plain text maintenance flag
used key:
{
"discord_feed": "https://discord.com/api/webhooks/..."
}expected shape:
{
"bot": { "token": "...", "client_id": "...", "status": "online|offline" },
"stream": { "url": "http(s)://...", "name": "..." },
"channel": { "id": "...", "name": "..." },
"features": { "auto_play": true, "loop": true },
"groq": {
"api_key": "...",
"model": "llama-3.1-8b-instant",
"vision_model": "meta-llama/llama-4-scout-17b-16e-instruct",
"temperature": 0.8,
"top_p": 0.95,
"max_completion_tokens": 700,
"timeout_seconds": 30,
"max_history_messages": 12,
"max_vision_images": 5
}
}groq powers Toast's AI replies to direct messages only. If api_key is empty, Toast still logs inbound DMs but skips the AI reply. model defaults to llama-3.1-8b-instant, and image/GIF DMs use vision_model, defaulting to Groq's meta-llama/llama-4-scout-17b-16e-instruct. max_history_messages controls how many recent logged DM messages are sent as conversation context, and max_vision_images caps image attachments at Groq's 5-image request limit. Toast also sends Groq a compact summary of its bot duties, including radio playback, slash-command radio controls, account-linking support, and automated notification DMs. When a DM appears to ask about fridg3.org, Toast can also send small relevant context from wiki/Home.md and wiki/Routes-and-Features.md to Groq so replies can describe the site without sounding like developer docs.
AI DM replies are split into sentence-aware Discord messages, usually 2-4 sentences per send depending on sentence length, while still staying below Discord's hard message limit. Toast waits at least 5 seconds before every AI reply chunk so the visible typing state never flashes and instantly dumps a response. Each Discord user has one active AI reply task: if another DM arrives while Toast is generating or pacing an unsent chunk, Toast cancels the unfinished reply and regenerates from the queued inbound DMs combined into one chronological prompt.
Toast's AI prompt includes an exact Discord slash-command allow-list: /play, /stop, /status, and /sendmsg. Website paths such as /feed must be described as fridg3.org pages, not Discord slash commands.
expected shape:
{
"system_prompt": "core Toast personality instructions",
"style_rules": ["optional behavior/style rule"],
"do_not": ["optional constraint"],
"private_lore": "optional lore that Toast only shares when directly asked"
}if this file is missing, empty, or invalid, the bot logs a warning and uses a small built-in Toast fallback prompt. private_lore is included in the system prompt with an explicit guardrail to avoid volunteering it unless the user asks about Toast's origin, lore, backstory, life, or purpose.
- array of timestamped bot status entries
- internal bot dedupe state for sent feed mention/reply notifications
- stores which feed mentions and replies have already triggered DMs
- tracked inbound/outbound DM threads used by
/others/toast-discord-bot/messages - stores per-user profile snapshot data, optional
ai_mutedreply-suppression state, plus message history - an inbound DM containing exactly
CLEARMEMORYacts as a memory boundary for AI replies; future Groq context only includes messages after the newest boundary
- the toast bot exposes localhost-only
POST /contact/notifyon127.0.0.1:8765 -
/contactcalls it after saving a submission - toast sends the alert to Discord channel
1503931489560301609
- Discord export blob used by the archive viewer
shape is roughly:
{
"pages": {
"/": {
"count": 12,
"visitors": {
"<sha256>": 1730931224
}
}
},
"updated_at": "2026-03-02T00:00:00Z"
}- downloadable binaries, archives, presets, and similar files linked from the site