Files
signal-bot/README.md
T
james 1523bdd48d Fetch YouTube at up to 1080p instead of 360p
YouTube's only muxed mp4 is format 18 (360p), so the muxed-first selector
was posting 360p even when 1080p was available. Split streams are now
preferred for YouTube and merged locally; every other host keeps the
muxed-first chain unchanged (verified identical selection for Instagram,
X and Reddit).

Three things the obvious version got wrong:

- A height<=1080 cap excludes portrait video: a 1080p Short or reel is
  1080x1920. Resolution is capped with -S res:1080 instead, which keys
  off the smaller dimension.

- h264 is a hard first preference, not an -S sort key. At equal
  resolution YouTube's VP9/AV1 renditions are smaller and win on size,
  but neither reliably plays in an mp4 container on older Signal
  clients. Dropping a resolution rung to stay on h264 is the better
  trade, and is usually smaller too: a 10-min video takes 480p h264
  (37 MB) over 720p AV1 (78 MB).

- The audio cap matters. YouTube's 388k track is 29 MB by itself on a
  10-min video, enough to push an otherwise-fine merge past the 100 MB
  Signal limit; abr<=160 takes the 129k track instead.

The 88 MB video cap makes yt-dlp step down automatically on long videos,
so a 3-min video lands at 1080p (80 MB) and a 10-min one at 480p.

Clips get the same treatment — they're YouTube-only, and 60s at 1080p is
only ~29 MB. /force is deliberately left muxed-first: it exists for the
biggest, slowest videos, where 1080p would risk the rsync timeout.

Measured, old -> new: 3-min 360p/11 MB -> 1080p/80 MB; 10-min 360p/27 MB
-> 480p/37 MB; portrait Short 360x640/6 MB -> 1080x1920/23 MB. Verified
end to end: real downloads decode as h264+aac, clip lands at exactly
60.000s.
2026-08-14 14:30:11 -04:00

80 lines
3.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# signal-bot
A Signal group-chat bot that grabs videos from links people post (X/Twitter,
Instagram, YouTube, TikTok, Reddit) 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](https://github.com/yt-dlp/yt-dlp).
Videos over 100 MB are auto re-encoded with ffmpeg to fit. YouTube is fetched
as h264 up to 1080p, stepping down a resolution rung on longer videos so the
merge lands under the Signal size limit without a re-encode; other hosts take
their muxed rendition as-is.
- **`/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, TikTok,
and Reddit (`/comments/` permalinks, `/s/` app share links, `redd.it` and
`v.redd.it` shortlinks). Reddit posts with no video stay silent.
## 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](https://github.com/bbernhard/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 **Node >= 22** on PATH (for `yt-dlp-ejs`'s challenge
solver). If your distro only ships an older Node, install a standalone Node 22+
and point `bot.py`'s `_NODE22` at it. Note: YouTube cookies are deliberately
withheld (`_cookie_args`) — sending account cookies forces yt-dlp onto a player
client whose media URLs 403; cookieless YouTube requests work.
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.