8ball is only AI-powered for specific users (Wynter/Jared); for everyone
else it's a random static response. Games is the correct category.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Model attribution is now only shown when the LLM actually generated the
response. If the model refused or gave an invalid answer and we fell back
to the static response, no 'via ...' line is shown.
Fallback responses for all three Wynter branches are now randomised pools
so the bot doesn't always give the same flat yes/no phrase regardless of
what Wynter actually typed.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
help: grouped into AI / Games / Random / Server categories with Option B
purple header; descriptions auto-pulled from the command registry.
Model attribution: added _MODEL_DISPLAY map so 'via lotusllm' becomes
'via Llama 3.2 1B', 'via gemma3:latest' becomes 'via Gemma 3 4B', etc.
Config: OLLAMA_MODEL switched from lotusllm to llama3.2:latest; added
BALL_MODEL (sadiq-bd/llama3.2-1b-uncensored) as a dedicated config var
for the 8ball so it stays on the uncensored model without affecting fortune.
Descriptions: fortune -> AI-generated fortune cookie; ask -> Ask LotusBot;
health -> Bot health & stats (admin only).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Responding 'Wynter is too busy...' in third person to someone who just
asked 'will I...' feels disconnected. Changed the prompt to speak
directly to Wynter using you/your, with her name used only for emphasis.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
The LLM was responding with 'She's far too busy...' instead of using
'Wynter' by name. Added explicit instruction to both Wynter branches
to always refer to her by name and never use she/her pronouns.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8ball: color-coded answer text (green=positive, red=negative, amber=neutral)
for both the random and Jared/Wynter AI branches; question shown as small
italic below the answer; AI responses include model attribution.
fortune: teal header, answer in blockquote italics, model attribution shown
only when response came from the LLM (not the static fallback list).
ask: purple header, question in italic, response in blockquote, model
attribution at bottom.
trivia: blue header with category, green reveal answer, model attribution
shown only for LLM-generated questions (not static fallbacks).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Priority Order is stale project tracking that doesn't belong in a README.
vCPUs removed from the infrastructure table — containers are HA and can
migrate between physical hosts so pinning a CPU model is misleading.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each !ask call is stateless — no context is retained between commands,
so ending a response with a question is misleading. Added explicit
instruction to the system prompt to prevent this.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
fortune: generates a fresh witty one-liner via Ollama on every call,
falls back to static list if LLM is unavailable.
ask: switched to /api/chat endpoint with a system prompt for better
conversational quality; now uses ASK_MODEL (default: gemma3:latest)
separately from the 8ball OLLAMA_MODEL so each can be tuned independently.
trivia: LLM generates a fresh question each time (no more repeating the
same 25 questions); supports !trivia <category> with six categories
(gaming, tech, general, movies, music, science); falls back to static
questions if JSON generation fails.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Remove unused imports: logging from bot.py and config.py, RoomMessageText/
UnknownEvent from callbacks.py, functools.partial and MAX_INPUT_LENGTH from
commands.py. Rename unused local variables to _ (resp in cmd_ping, symbols in
render_keyboard_plain, guesses_left in two wordle functions). Move wordle import
to top of commands.py to fix E402.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ruff: add --strip-components=1 to tar extract; the tarball puts the
binary inside ruff-x86_64-unknown-linux-gnu/ not at the root
- gitleaks: path-based allowlists are broken in v8.21.2 --no-git mode
(tested down to bare substrings — still fires). Switched to scanning
only application code directories (matrixbot/, hookshot/, .gitea/,
systemd/, cinny/, landing/) which excludes deploy/ where the
intentional Gitea webhook HMAC secrets live. Also removed the
.gitleaks-baseline.json from the repo (it was flagging itself).
The .gitleaks.toml is kept for any future per-rule overrides.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- ruff: download standalone binary instead of using python3 -m ruff
(runner image lacks the PATH entry for pip-installed bin scripts)
- pip-audit: add python3-venv to apt install (pip-audit creates a venv
internally to resolve deps; ensurepip was missing)
- gitleaks: switch from stopwords allowlist to --baseline-path approach.
Stopwords don't suppress findings from git history scans. The baseline
records the 4 known-intentional webhook HMAC secrets; CI now only
fails on findings NOT in the baseline (i.e. newly introduced secrets)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- All Python jobs now install python3-pip via apt first (runner image
has no pip by default)
- Added secret-scan job: gitleaks v8.21.2 scans full git history on
every push/PR with --redact to avoid leaking found secrets in logs
- Added .gitleaks.toml allowlisting deploy/hooks-lxc*.json files
(webhook HMAC secrets are intentional config, not leaks)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- python3 -m pip works in the act runner where bare 'pip' isn't in PATH
- Added python-audit job: pip-audit checks matrixbot/requirements.txt
against the OSV database for known CVEs on every push/PR
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- When Jared asks a question containing Wynter's name, it now uses a
dedicated mock-Wynter prompt instead of the generic positive-Jared
one. The _is_positive_about_jared guard is also skipped for this
branch so negative words aimed at Wynter don't trigger the fallback.
Fallback changed from "Jared is absolutely right!" (nonsensical for
Wynter questions) to "Sounds about right — Wynter had it coming."
- Added ruff Python lint job to .gitea/workflows/lint.yml covering
matrixbot/ on every push and PR.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Removed standalone matrixbot/deploy.sh — deploy is handled by the existing
webhook system. Added matrixbot/ block to deploy/lxc151-hookshot.sh: on push,
if any matrixbot/ file changed, source files are synced to /opt/matrixbot and
matrixbot.service is restarted automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
All bot source files from LXC 151 (/opt/matrixbot) are now tracked here.
Secrets (.env, credentials.json), venv dirs, and runtime state files
(nio_store, welcome_state.json, wordle_stats.json) are excluded via .gitignore.
Includes deploy.sh to sync files to /opt/matrixbot and restart the service.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously require_at_registration=true caused Cinny to silently complete
the m.login.terms UIA step during registration (~34ms), meaning users were
auto-consented without ever seeing the ToS page.
Setting require_at_registration=false removes the UIA step from registration.
New users start with NULL consent and are blocked by block_events_error on
first message send. Synapse sends a Server Notice DM with the /_matrix/consent
URL, which they must explicitly visit and submit before messaging is unblocked.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Previously only handled ticket_created. Now handles:
- status_changed: shows old → new status with actor name
- comment_added: shows author + 200-char preview (opt-in via MATRIX_NOTIFY_COMMENTS)
- mention: targeted notification when @username used in comment
- assigned: shows new assignee + actor (opt-in via MATRIX_NOTIFY_ASSIGNMENTS)
Unknown events fall back to a debug line rather than being silently dropped.
Avatar updated to ticket emoji via Synapse admin API (mxc already applied live).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- tcp_retries2 reduced from 8 to 5 (~15-30s timeout vs ~90s)
- Unreachable routes added for asymmetric-connectivity servers (bark.lgbt ×2,
parodia.dev, chat.ohaa.xyz, matrix.k8ekat.dev) so outbound attempts fail in
0ms instead of hanging; routes persist via /etc/network/interfaces post-up
- Stuck device_lists_remote_resync entries cleared for dead-server users
- Grafana alert threshold raised 120s→300s, for duration 5m→15m to avoid
false positives from normal 10-min federation backoff cycling
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Presence was incorrectly disabled as a workaround. Root cause of lag spikes was
Linux's default tcp_retries2=15 (~15 min retransmit window) causing hung outbound
TCP connections to slow remote servers (e.g. exp.farm) to block the federation
sender queue for minutes at a time.
Fix applied to /etc/sysctl.d/99-matrix-tuning.conf on LXC 151:
- net.ipv4.tcp_retries2 = 8 (~90s before giving up on stalled connection)
- net.ipv4.tcp_syn_retries = 4 (~45s for initial SYN)
- net.ipv4.tcp_keepalive_probes = 3 (dead conn detected ~6.5 min)
Presence re-enabled in homeserver.yaml (presence: enabled: true).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- draupnir/production.yaml: Add health.healthz (port 8081) and web.abuseReporting
(port 8080) config — healthz was live on LXC but missing from repo; web server
enables Matrix client Report button forwarding to management room (Synapse module
install on LXC 151 still needed to complete the integration)
- README: Add Draupnir port map, abuse reporting setup docs, updated monitoring
section (3 new Prometheus scrape jobs, Draupnir Down alert, Grafana panel count),
add presence-disabled federation lag fix to performance checklist, document
Draupnir healthz/audit DB paths
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Sticky first table column (feature names stay visible while scrolling horizontally)
with opaque background to properly cover scrolled content
- body: align-items flex-start on mobile to prevent vertical clipping
- ≤540px breakpoint: reduced logo, h1, padding, table font/cell sizes,
homeserver code word-break, client card tags stack vertically
- ≤380px breakpoint: further compression for very small phones
- Swipe hint ("← swipe to compare →") shown on touch devices above table,
auto-hides after first scroll via JS
- Privacy strip stacks vertically on small screens
- Footer/legal tighter spacing on mobile
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add 6-client comparison table (Cinny dev, Element X, FluffyChat, Commet, Element, Nheko)
covering platform, security, calling, core features, UX/extras — ✓/~/✗ with context notes
- Note chat.lotusguild.org is the dev/beta branch of Cinny; add link to stable cinny.in
- Add "Dev Branch" purple tag to featured Cinny card
- Expand container to 900px to accommodate table; table scrolls on mobile
- Add encryption architecture note (Vodozemac Rust SDK vs matrix-js-sdk) in table footer
- Add table legend
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Per-LXC deploy scripts (lxc151-hookshot, lxc106-cinny, lxc139-landing, lxc110-draupnir)
- Per-LXC webhook hook configs with unique HMAC-SHA256 secrets
- Livekit graceful restart script + systemd timer (waits for zero active calls)
- Fix hookshot/deploy.sh capitalization bug (Uptime-Kuma, Tinker-Tickets, etc.)
Each LXC independently clones repo and runs its own deploy.sh via adnanh/webhook on port 9000.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add Improvement Audit section tracking all identified gaps and their status
- All critical/high/medium items applied: coturn cert auto-renewal (sync cron
on compute-storage-01), Synapse metrics port locked to 127.0.0.1+10.10.10.29,
well-known matrix endpoints live on lotusguild.org, suppress_key_server_warning,
fail2ban on login endpoint, PostgreSQL autovacuum per-table tuning, LiveKit
VP9/AV1 codecs
- Bot E2EE reset: full store+credentials wipe, stale devices removed, fresh
device BBRZSEUECZ registered
- Checklist updated: LiveKit port range, autovacuum, hardening items, Grafana IP
- Hookshot: Owncast renamed to Livestream in display name (same UUID)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Remove all Huntarr references (webhook removed for security reasons)
- Rewrite transformation functions for all 11 active webhooks via Matrix
state event API — all now handle the full event payload:
- Sonarr/Radarr/Readarr/Lidarr: all event types (Grab, Download, Rename,
Add, Delete, HealthIssue, HealthRestored, ApplicationUpdate) with release
group, download client, upgrade indicator
- Grafana: multi-alert support with per-alert severity/instance/summary,
generator URLs, truncation notice for >5 alerts
- Proxmox: VM/CT name+ID, task type/status, property bag fields
- Uptime Kuma: ping time on UP, downtime duration on DOWN, URL linkified
- Seerr: all notification types, 4K flag, issue type, comment field
- Owncast: all event types (STREAM_STARTED/STOPPED, USER_JOINED, CHAT)
- Bazarr: multi-line message support from Apprise JSON payload
- Tinker-Tickets: preserved as-is (already comprehensive)
- Huntarr state event cleared in room, UUID removed from account_data map
- Owncast and Uptime Kuma functions restored (had lost their functions)
- Hookshot restarted to pick up all changes
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Instead of posting "Check your DMs" when !wordle is used in a public
room, the bot now silently routes the game to DMs and automatically
posts the spoiler-free emoji share grid back to the origin room (e.g.
Commands) when the game ends — win, lose, or give up.
Also removed the "use !wordle share" prompt from win/loss messages
since sharing now happens automatically.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Both services now send notifications to the hookshot webhook endpoint:
- Bazarr: switched from broken Apprise Matrix URL to JSON notifier
with jsons://matrix.lotusguild.org/webhook/<uuid>
- Huntarr: fixed apprise_url from raw https:// to jsons:// scheme
Both hookshot transforms updated to parse Apprise JSON payload:
{version, title, message, type, attachments}
Huntarr avatar set from selfhst icons CDN.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
When !wordle is used in a public room (e.g. Commands), the bot now
sends "📬 Check your DMs to play Wordle!" before redirecting the game
to a DM. Previously the user saw no response at all.
Also refactors handle_wordle to resolve the DM room once and reuse it
across all subcommands, eliminating repeated _get_dm_room calls.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Modular bot using matrix-nio[e2e] with E2EE support, deployed as
systemd service on Synapse LXC. Includes 10 commands: help, ping,
8ball, fortune, flip, roll, random, rps, poll, champion.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>