streamlens
Live streams in one Telegram message that keeps itself up to date. Hand it a list of streams (or a list of channels) and a bot token; it posts who is live, how many viewers they have, how that changed, and a slideshow of frames from the last 20 minutes for each stream.
- No server, no database. Pure Python; state is one small SQLite file.
- Bring your own data (
Publisher.publish([...])) or let it watch channels for you (streamlens watch). - YouTube, Twitch, Kick out of the box; any other platform if you pass the numbers yourself.
- Safe updates: the new message is sent first, then the old one is deleted; a failed send never empties the chat.
- English and Russian texts, time zone for the footer, size limits of Telegram handled for you.
It grew out of a production Airflow + Postgres pipeline that tracks streams around the clock; this is the same
message format, extracted into something you can pip install.
Quick start
pip install streamlens
You need a bot token from @BotFather and a chat id. Add the bot to your group (or make it an admin of your channel) and check that it can post:
export STREAMLENS_TOKEN="123456:ABC..."
export STREAMLENS_CHAT_ID="-1001234567890" # group / channel id, or your own user id
streamlens check
How to find the chat id: add @RawDataBot to the group, or open
https://api.telegram.org/bot<TOKEN>/getUpdatesafter writing anything to your bot.
1. You have the data
from streamlens import Publisher, Stream
publisher = Publisher(token="123456:ABC...", chat_id="-1001234567890", lang="en", tz="Europe/Berlin")
publisher.publish([
Stream(name="Some Twitch channel", url="https://www.twitch.tv/some_channel", online=12400),
Stream(name="Some YouTube channel", url="https://www.youtube.com/@some_channel/live", online=830,
title="Late night talk", image=open("frame.jpg", "rb").read()),
])
Call publish every ~5 minutes (cron, Airflow, a loop). Only name and url are required; the platform is detected
from the URL. Each call stores the frame you pass in image, so the slideshow now / 5 / 10 / 15 / 20 min ago
builds up by itself. The change in viewers versus ~10 minutes ago is computed from the history the same way.
From the shell, with a JSON or CSV file (or - for stdin):
streamlens send examples/streams.json
cat streams.csv | streamlens send - # columns: name,url,online[,title,image,started_at,...]
streamlens send examples/streams.json --dry-run --chat-id 1 # print instead of sending
2. You only have channel links
pip install "streamlens[frames]" # + ffmpeg on PATH, for YouTube/Kick frames
streamlens watch examples/channels.txt --every 5
channels.txt is one URL per line (Name | URL to set a display name). Every 5 minutes it finds out who is live,
grabs a frame and updates the message.
| Platform | Live status | Viewers | Frame | Notes |
|---|---|---|---|---|
| YouTube | yt-dlp | yes | from the stream (ffmpeg) | use the channel /live URL |
| Twitch | public preview | with a free Twitch app: set TWITCH_CLIENT_ID, TWITCH_CLIENT_SECRET |
public preview | works without keys, just no viewer count |
| Kick | yt-dlp | no (Kick has no public count) | from the stream (ffmpeg) | pass your own numbers via Publisher if you have them |
Age-restricted YouTube streams need a cookies.txt of a YouTube account (--cookies); it is used only for those.
Options
Option (Publisher(...) / CLI flag) |
Default | What it does |
|---|---|---|
lang / --lang |
en |
en or ru |
tz / --tz |
UTC |
time zone of the "updated" footer, e.g. Europe/Moscow |
mode / --mode |
auto |
rich (frames), text (plain), auto = rich, falls back to text if the Bot API has no rich messages |
split / --split |
one message | several messages, e.g. --split twitch+kick --split youtube |
sort / --sort |
asc |
asc: the biggest stream ends up at the bottom, next to the input field |
min_online / --min-online |
0 |
hide small streams |
exclude / --exclude |
none | names or URLs that are never shown |
state / --state |
./streamlens.db |
where message ids and frames are kept |
streamlens clear deletes the messages the bot posted. --dry-run works everywhere and never touches Telegram.
Deploy
Anything that runs a command every few minutes works. Pick one.
Long-running process (systemd, tmux, a VPS): streamlens watch channels.txt --every 5
cron: */5 * * * * streamlens watch /path/channels.txt --once (env vars in the crontab or an env file)
Docker:
cp examples/channels.txt channels.local.txt # edit
printf 'STREAMLENS_TOKEN=...\nSTREAMLENS_CHAT_ID=...\n' > .env
docker compose up -d
The image contains ffmpeg and Node.js (the JS runtime that yt-dlp needs for YouTube). State lives in a volume.
Existing pipeline (Airflow, a script): import Publisher and call publish() with your data on your schedule.
Telegram limits it handles for you
- One rich message takes at most 50 pictures. With many streams the frames per stream go 5 → 4 → 3 → 2 → 1, and above 50 streams only the biggest ones get a frame.
- Telegram clients fold long rich messages behind "Show more" after roughly 20 pictures in the visible part. This is a client behaviour; no Bot API setting for it is documented. Measured on real messages: the fewer frames per stream, the more streams stay visible before the fold.
- Bot tokens never appear in error messages or logs.
Development
git clone https://github.com/klipbn/streamlens && cd streamlens
pip install -e ".[dev,frames]"
pytest && ruff check .
MIT © Alexey Voronko. Russian version: README.ru.md.
Release files for streamlens 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| streamlens-0.1.0.tar.gz | 22.9 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| streamlens-0.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 46.1 kB
Release files / streamlens-0.1.0.tar.gz
| Download URL | streamlens-0.1.0.tar.gz |
|---|---|
| Size | 22.9 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
13393cd232cf6b480ef6545714cb6439a357827c3d2390e9e263ecafae6c79a0
|
|
BLAKE2b-256 checksum How to use checksums |
8984bf5c3c6def817be0d1bff8d25529dc87fbce79a845064746dd3faf0fbbff
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|
Release files / streamlens-0.1.0-py3-none-any.whl
| Download URL | streamlens-0.1.0-py3-none-any.whl |
|---|---|
| Size | 23.2 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
3e147dc73bb47b1c3d6e92cd494d9e1dccab0b2d24239fdafce0640537963afa
|
|
BLAKE2b-256 checksum How to use checksums |
4578e99d7926ae57d32e4abac325d73aec4fe0584d5a1ee04d5fc83fd15fafd0
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.5
|