Hi there!
These bots just send latest hot submissions from subreddit /r/%subreddit% to telegram channel @r_%subreddit% hourly. For example /r/gifs → @r_gifs.
If you want to obtain your own channel for /r/%subreddit%:
- Create new public telegram channel
@r_%subreddit%. - Add
@reddit2telegram_botas administrator to this channel. - Make a pull request to this repo with new script to make posts (use
reddit2telegram/channels/r_funnny/app.pyas draft), and the tags related (create a tags.txt containing at least 3 tags, separated by spaces and starting with #). - Then I will make it alive :)
- For any questions do not hesitate to contact me in @r_channels group.
Patreon: donate
Complete list of all active channels (more than 800) is available at our channel @reddit2telegram.
Which means you really shouldn't expect much from it. I wrote it over the weekend to solve my own pain. No state-of-art kubernetes bullshit, no architecture patterns, even no tests at all. It's here just to show people what a pet-project might look like.
This code has been written for fun, not for business. There is usually a big difference. Like between riding a bike on the streets and cycling in the wild for fun :)
© from infomate.club
Easiest way is to ask for new channel at @r_channels. Or follow the manual as stated above. :)
But if you are geek enough then install mongodb, ffmpeg, python and setup cron:
46 * * * * /root/reddit2telegram/auto_update.sh
* * * * * /root/reddit2telegram/reddit2telegram/cron_thread_job.shVERSION records the current stable version, starting at 1.0.0. See
Changelog.md for release notes. Every change goes through a PR,
including maintenance and channel additions. Maintainers may merge their own
PRs after validation; direct pushes to master are prohibited.
The compatibility contract covers documented CLI arguments and configuration,
persisted MongoDB formats requiring migration, and established posting behavior.
Following SemVer, incompatible changes increment major,
compatible new features/channels and deprecations increment minor, and compatible
fixes and maintenance increment patch. Each PR makes one release increment and
includes one SemVer: major, SemVer: minor, or SemVer: patch line in its body.
The initial release PR uses SemVer: bootstrap.
On a fresh topic branch, prepare and validate a release:
git config core.hooksPath scripts/hooks
git fetch origin
git switch -c fix/example origin/master
python3 scripts/release.py bump patch --summary 'Describe the change for operators.'
python3 scripts/release.py validate --base-ref origin/master --bump patch
python3 -m unittest discover -s tests -p test_release.py
git push -u origin HEADInclude VERSION, Changelog.md, and the implementation in the PR. Refresh the
bump if another release merges first. Use squash or merge commits. After a
validated PR merges, GitHub Actions publishes the matching vX.Y.Z tag and
release notes; existing tags are immutable.
Release checks use no application credentials and never send Telegram messages.
The consumer keeps at most consumer.max_pending submitted jobs (default: pool
size). Two video jobs may download, mux, and upload concurrently. Uploads stream
from disk, and every attempt removes its private files before returning a
pipeline slot, including oversized rejections and interrupted uploads. Media
HTTP sessions close after each job; verified TLS contexts are shared across
connections. On glibc systems,
pool.malloc_arenas defaults to four to reduce memory retained by native
allocation pools. Once per minute, a consumer above 256 MiB collects dead job
cycles and returns unused native heap pages to the OS. SIGTERM/SIGINT stop new
claims and drain submitted work.
Reddit listings retain at most 25 candidates per page, while still scanning the configured total candidate limit. Media classification uses cached listing fields so absent gallery/crosspost fields do not fetch full comment trees.
Resource monitoring includes the worker, ffmpeg children, and cgroup file cache.
The kernel peak catches spikes between samples. These targets use decimal bytes:
500 MB during normal operation and 1 GB during spikes. Run a single sample from
cron, or add --seconds 86400 --interval 5 for a full-day observation:
python3 scripts/monitor_resources.py --log logs/service_resources.jsonlOffline tests exercise real local TLS and a 49 MiB multipart upload without
production credentials. Install test dependencies with
python -m pip install -r requirements.txt -r tests/requirements.txt.
For a fresh checkout, copy tests/offline-config.yml to
reddit2telegram/configs/prod.yml before python -m pytest -q. GitHub Actions
does this automatically; live tests remain disabled.
Live integration tests send real messages to @r_channels_test and use Reddit API.
R2T_LIVE_TESTS=1 /root/reddit2telegram/.venv/bin/python -m unittest tests/test_live_send.pyLive resource regression tests verify actual Telegram message IDs, repeated-job
and exception cleanup, six concurrent video jobs with the two-pipeline limit,
photo/GIF/gallery delivery, video download and audio merging, and live
Reddit-to-Telegram supply. They generate media locally and serve video inputs
on loopback; Telegram receives real uploads. Messages are paced, and media files
are isolated from production's temporary-file cleaner. All sends target
@r_channels_test; the configured bot must have posting rights there.
R2T_LIVE_TESTS=1 .venv/bin/python -m unittest tests/test_live_resources.py -vLeave R2T_LIVE_TESTS unset during offline test runs.
tech_anniversary runs daily at 11:55 UTC without fetching a Reddit listing.
On a channel's anniversary, its introduction features @reddit2telegram, then
the @r_channels community, followed by the birthday greeting and a paginated
directory of other scheduled channels. A short birthday notice also goes to
the main channel, community, and developer chat. Identical destinations are
deduplicated. Announcements use text, so unavailable Reddit media cannot block
them.
The default anniversary date is the earliest successfully sent post in
urls, never the start of statistics collection. This is an earliest-known-post
date, not a verified Telegram creation date. It is saved once in settings
under _id: r2t_anniversary_date:<lowercase channel> so history cleanup cannot
move it. A channel document's optional anniversary_date (YYYY-MM-DD or a
BSON datetime) overrides that default. Channels without either date are skipped
and reported; one missing or invalid channel does not prevent other sends.
February 29 anniversaries are observed on February 28 in non-leap years.
Annual delivery records use _id: r2t_anniversary_delivery:<channel>:<UTC year>
in settings. Their message text and directory order are frozen when queued;
each acknowledged Telegram message ID is saved before the next send. Daily
runs resume outstanding messages from earlier days and never repeat completed
parts. Channels removed from the schedule are not sent queued retries. Every
invocation also leaves a kind: r2t_anniversary_run record containing its start,
finish, outcome, due/completed/pending counts, missing history, and errors. The
next lease owner marks a crashed sweep as interrupted. The hourly task cleaner
does not remove any of these records. No historical
anniversaries are backfilled on installation.
Inspect dates, upcoming/due channels, a complete message preview, pending parts, and the last seven days of run history without writing state or sending messages:
.venv/bin/python scripts/anniversary_report.py
.venv/bin/python scripts/anniversary_report.py --date 2027-02-12 --days 30A sweep lease prevents overlapping workers. Telegram API rejections are safe to
retry, while timeouts and interrupted requests are recorded as uncertain.
Telegram does not provide an idempotency key for sends, so these parts require
checking the destination before another attempt. After that check, an operator
can conditionally update the event in settings: select its _id and
deliveries.<index>.state: uncertain; set that part's state to sent and its
message_ids to the verified IDs if it arrived, or reset it to pending if it
did not. The next scheduled run resumes the remaining parts. A partial run is
reported as a supply failure, with the retained run ID for diagnosis. The
existing supplier.py --test --sub tech_anniversary path sends only a sample to
@r_channels_test and leaves production anniversary records untouched.