twocents/open source

Search messages and transcripts

Search local history and export or back up stored data.

Message capture

messageChannelIDs in initialization/config updates maps to GUILDSCRIBE_MESSAGE_CHANNEL_IDS (a JSON array encoded like the voice allowlist). It defaults to []. Capture includes those guild channels and accessible child threads, independently of voice recording. Human, bot, and webhook messages are retained. No DMs, history backfill, deletion tracking, read/unread state, or attachment binary downloading is implemented.

When enabled, the bot requests GuildMessages and privileged MessageContent intents. Enable Message Content in the Discord application settings and grant View Channel and Read Message History; doctor checks configured message channels. Voice-only configurations request no message intents. New messages are inserted and edits update the row for that Discord message ID without replacing the local search row ID. Partial updates fetch the full message within the allowed scope; failed fetches are reported without replacing saved content. Older edit revisions cannot overwrite newer ones. Writes are ordered and drained on shutdown; failures are logged without message text or attachment URLs. Offline gateway gaps are not backfilled.

Search saved history

The IDs below are placeholders. Use channel and user IDs from your Discord server, and replace RECORDING_ID with an ID from guildscribe recordings list.

guildscribe messages list --channel 222222222222222222 --limit 50
guildscribe messages search '"project budget"' --author 333333333333333333
guildscribe transcript get RECORDING_ID --include-segments
guildscribe transcript search budget --speaker 333333333333333333

These commands work without Discord access; only retry loads the configured transcription hook. See the reference for request and result fields. An absent database returns database_not_found without creating or migrating anything. Start the daemon explicitly to initialize or import storage.

Message list/search accept channel and author filters. --include-threads requires a channel and includes its directly nested threads. Transcript search accepts channel and speaker user ID filters. Both accept --after (inclusive) and --before (exclusive) ISO 8601 timestamps with explicit timezones. Message date filters use Discord creation time; transcript filters use recording start time. Search accepts FTS5 token, phrase, and boolean expressions, up to 1000 characters; malformed expressions produce an invalid-input error.

Pages default to 50 rows and allow 1–200 through --limit. Results contain items and nullable nextCursor. Supply that cursor with the same search and filters to continue. Messages sort oldest-first by creation time and message ID; transcript matches sort by recording start, recording ID, and segment index. Cursors continue after the last row from the previous page and require the same filters. Results can change between pages if messages are edited or transcripts are replaced. Discord IDs remain strings; absolute result timestamps are UTC Unix milliseconds.

Transcript reads return lifecycle metadata and segment count without content by default. --include-segments adds a page of ordered segments, including speaker snapshots; --limit and --cursor page those segments. Search results include full matched text, speaker fields, recording/channel context, and start/end offsets. Single-message reads include attachment metadata and embeds.

Export a transcript

Choose a new absolute destination:

guildscribe transcript export RECORDING_ID --format vtt --destination /absolute/path/meeting.vtt

Completed transcripts can be exported to a new absolute file path with --format txt|json|srt|vtt (default txt). JSON exports carry version 1, metadata, and ordered segments; subtitles use recording-relative timing and retain overlapping segments. Each export reports its byte count and SHA-256. --dry-run validates and computes the export without creating a file. Existing files are never overwritten. Exports are separate files; they do not replace the stored transcript.

Retry a failed transcript

Put the recording ID in a request file, then preview the retry:

guildscribe transcript retry --request /absolute/path/retry.json --dry-run

See transcription for the request and the command to submit it.

Retry takes { "recordingID": "<UUID>" } via --request <file-or-dash>. Only failed transcription transitions to pending; pending/running requests are idempotent no-ops and completed transcription is rejected. The configured hook and complete recording are required. --dry-run reports the pending plan without changing state. Guildscribe checks that the status and attempt have not changed before saving the retry, so two callers cannot queue the same failed attempt twice. Attempts increment when the daemon actually starts the hook, not when the CLI queues it. The running daemon checks saved pending requests every two seconds without overlapping polls, and runs at most one hook. If it is stopped, work waits until the next start. The CLI never launches a competing hook or starts the daemon itself.

Check and back up the database

guildscribe database status
guildscribe database verify
guildscribe database backup --destination /absolute/path/guildscribe-backup.sqlite

Database status reports schema version, database/WAL sizes, and counts for the four storage tables. Verification reads a consistent in-memory SQLite snapshot and checks database integrity, foreign keys, and both external-content FTS indexes against their source text. Its memory requirement scales with the database size. Failed checks return ok: false and a nonzero exit; verification does not repair indexes. Search rebuild is intentionally not exposed.

Database backup uses SQLite's online backup API, includes committed WAL data, and publishes to a new absolute destination with exclusive creation. It reports bytes and SHA-256. --dry-run checks paths without creating output. The backup contains all local database content, including messages; media and participant/ interval files need separate backups. If a command times out, its changes may already have taken effect. Inspect the destination or retry state before repeating it.

On this page