James Price c7eb101ff0 Fix bug pass: async offload, resource leaks, media/parse correctness, deploy hardening
bot.py:
- Run all yt-dlp/ffmpeg/ffprobe/file-IO/base64 work off the event loop via
  asyncio.to_thread, bounded by a Semaphore(2); the loop no longer freezes
  bot-wide during downloads/encodes.
- download_attachments=False; VideoTracker fetches only video attachments lazily
  instead of the library base64-ing every attachment of every message.
- last_video keyed per (group, sender) with group-latest fallback so /speed and
  /rev stop silently targeting a stranger's video; proactive TTL sweeps free the
  big base64 blobs and bound recent_urls growth.
- _reencode guards 0/NaN ffprobe duration and adds -maxrate/-bufsize; /rev now
  size-checks + re-encodes + faststart like the other paths.
- Twitter URL regex no longer merges space-separated links (keeps i/web/status);
  dedupe repeated URLs within a message; mark-handled only on success/no-media so
  a corrective edit can retry; 'unsupported url' surfaced instead of silently dropped.
- All sends wrapped (catch SendMessageError); base64 decode guarded; edit/sync
  attachment envelopes handled; /cookies temp file created 0600.

deploy:
- Pin signalbot==1.1.0, yt-dlp floor, add missing yt-dlp-ejs.
- Pin signal-cli-rest-api by digest + add /v1/health healthcheck.
- Restart=always (the library never exits non-zero when wedged).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-15 18:43:41 -04:00

signal-bot

A Signal group-chat bot that grabs videos from links people post (X/Twitter, Instagram, YouTube, TikTok) and posts them back into the chat, plus a couple of toys for messing with the last video.

What it does

  • Auto-download — watches every group message for a supported link and replies with the video. Powered by yt-dlp. Videos over 100 MB are auto re-encoded with ffmpeg to fit.
  • /speed [n] — re-posts the last video sped up n× (default 2×), pitch-shifted like tape. e.g. /speed 4, /speed 0.5.
  • /rev — re-posts the last video reversed (audio too).
  • /cookies (DM, admins only) — paste exported .instagram.com cookie lines to refresh Instagram auth without touching the server.

Supported links: x.com / twitter.com (and fxtwitter/vxtwitter/fixupx wrappers), instagram.com/reel|p, youtube.com / youtu.be / Shorts, and TikTok.

How it's wired

Signal  ──►  signal-cli-rest-api (json-rpc, :8080)  ──►  bot.py (signalbot lib)
                  [docker-compose.yml]                      [systemd service]
                                                                  │
                                                       yt-dlp + ffmpeg + cookies.txt
  • bot.py connects to a signal-cli-rest-api daemon over a websocket in json-rpc mode.
  • docker-compose.yml runs that daemon; the linked-account data lives in a ./signal-cli-data volume (gitignored — it holds private keys).
  • signal-bot.service.example is the systemd unit that runs bot.py.

Setup

  1. Run signal-cli-rest-api and link it as a device to your Signal account:

    docker compose up -d
    

    Then link via the QR at http://127.0.0.1:8080/v1/qrcodelink?device_name=bot (scan from Signal → Settings → Linked devices). Keep signal-cli up to date — an out-of-date signal-cli silently stops receiving messages when Signal changes its server protocol (getServerGuid must not be null in the logs); docker compose pull && docker compose up -d fixes it.

  2. Python env:

    python3 -m venv venv
    venv/bin/pip install -r requirements.txt
    

    YouTube also needs a system node on PATH (for yt-dlp-ejs).

  3. Cookies: copy cookies.txt.example to cookies.txt and fill in real exported cookies for the sites you want auth'd. (gitignored)

  4. Service: copy signal-bot.service.example to signal-bot.service, set your user / phone number / paths, install it:

    sudo cp signal-bot.service /etc/systemd/system/
    sudo systemctl enable --now signal-bot
    

Notes

  • The bot only acts in group chats (except /cookies, which is DM-only).
  • Reply-targeting (e.g. "speed up this video") was intentionally dropped: Signal Desktop strips quote metadata from synced transcripts, so /speed and /rev always act on the last video seen in the group.
Description
Signal group-chat video-grabber bot
Readme 174 KiB
Languages
Python 100%