Releases: GeiserX/Telegram-Archive
Releases · GeiserX/Telegram-Archive
v7.7.0
Security
- Viewer now fails closed when credentials are missing — If
VIEWER_USERNAME/VIEWER_PASSWORDare not configured, the HTTP API and WebSocket endpoint reject access unlessALLOW_ANONYMOUS_VIEWER=trueis explicitly set. - Restricted media access is enforced consistently — Media, thumbnails, avatars, and non-chat folders now share centralized chat ACL checks, preventing restricted users from reading
_sharedfiles or unrelated chat media. - No-download users can no longer fetch original or thumbnail bytes — Accounts and share tokens with
no_download=truereceive metadata only; direct original media and generated thumbnail URLs return 403, while UI avatars remain available. - Internal push events require a secret off-loopback —
/internal/pushrequiresINTERNAL_PUSH_SECRETfor non-loopback/private-network callers, reducing spoofing risk between co-located containers. - WebSocket upgrades validate origin — Cross-origin WebSocket connections must be same-origin or explicitly allowed by
CORS_ORIGINS. - Non-interactive auth hash files are owner-only — Persisted
phone_code_hashsidecar files are now created with0600permissions.
Fixed
- Scheduled backups no longer overlap — The scheduler uses a backup lock so initial and cron-triggered jobs cannot run concurrently.
- FloodWait handling is explicit and bounded — One-shot Telegram API calls now retry through shared helpers and abort instead of sleeping when Telegram asks for waits above
MAX_FLOOD_WAIT_SECONDS. - FloodWait env parsing is resilient — Invalid
MAX_FLOOD_RETRIESandMAX_FLOOD_WAIT_SECONDSvalues fall back to safe defaults instead of crashing imports. - Media downloads finalize atomically — Temporary
.partfiles are moved into place only when an actual file exists, preserving Telethon-selected extensions and avoiding bogus stored paths. - Telegram contact, geo, and poll media are metadata-only — These message types no longer trigger file download attempts.
- Database URL precedence is consistent — Entrypoint migrations and realtime notifier/listener mode detection now honor
DATABASE_URLbeforeDB_TYPE, includingpostgres://,postgresql://,postgresql+asyncpg://, and SQLite URLs. - Database migration coverage includes app-state tables — SQLite-to-PostgreSQL migration now includes viewer accounts, sessions, tokens, folders, forum topics, push subscriptions, and settings.
- Share token URLs avoid query-string leakage — Generated links use
#token=fragments and preserve subpath deployments.
Changed
- Deletion listening is safer by default —
LISTEN_DELETIONSnow defaults tofalseso archives do not mirror Telegram deletions unless explicitly configured. - Docker examples pin the 7.7.0 release — Compose and README snippets now reference
drumsergio/telegram-archive:7.7.0anddrumsergio/telegram-archive-viewer:7.7.0. - Viewer compose binds to localhost by default — The example viewer service binds
127.0.0.1:8000:8000and documents reverse-proxy/auth requirements before public exposure. - CI and release checks are stricter — Docker publish workflows run ruff and pytest before publishing, shellcheck tracks
main, Docker Hub description sync covers both images, and release checks match the documented local test command.
Documentation
- Viewer authentication setup is documented — README and
.env.examplenow show required viewer credentials and the explicit anonymous opt-in. - Chat include filters are documented as allow-lists — Examples now correctly show
CHAT_TYPES=groups,channelswhen including one specific channel alongside groups. - Operational safety docs were refreshed — README and
.env.examplenow describe deletion mirroring, flood-wait controls, proxy header trust, and internal push secrets.
Tests
- Added regression coverage for fail-closed viewer auth, no-download media restrictions, thumbnail ACLs, WebSocket subscription filtering, internal push auth, scheduler locking, flood-wait aborts, atomic downloads,
DATABASE_URLbehavior, non-interactive auth hash reuse, and migration model enumeration.
📋 Full changelog: docs/CHANGELOG.md
v7.6.4
Fixed
- Improved General topic test suite — Renamed unprofessional test data, removed redundant
@pytest.mark.asynciodecorators (project usesasyncio_mode = "auto"), converted setup to a proper pytest fixture, and added edge case tests for nonexistent topics,topic_id=0, and topic+search filter interaction. Contributed by @tondeaf in #122 (follow-up).
📋 Full changelog: docs/CHANGELOG.md
v7.6.3
Fixed
- Edit notifications no longer silently dropped on long messages — The 500-char truncation guard only protected
data["message"]["text"](new_message path), leavingdata["new_text"](edit path) unprotected. A 4096-char emoji edit could produce a 16KB payload exceeding PostgreSQL's 8KB NOTIFY limit, causing a silentpg_notifyerror. Both paths are now truncated via a shared_truncate_notify_data()helper. (#123 follow-up) - Use
pg_notify()with bound parameters for PostgreSQL NOTIFY — Replaces f-string SQL interpolation that was vulnerable to asyncpg$Nplaceholder parsing and fragile manual single-quote escaping. Contributed by @tondeaf in #123. - Push secret comparison is now timing-safe —
/internal/pushendpoint used!=for bearer token comparison; switched tosecrets.compare_digest()consistent with the rest of the auth layer. - Test assertions use stable
TextClause.textattribute — Replacedstr(stmt)withstmt.textfor SQLAlchemy SQL assertions, avoiding reliance on undocumented__str__behavior.
📋 Full changelog: docs/CHANGELOG.md
v7.6.2
Fixed
- FloodWaitError no longer crashes
get_dialogs()orget_me()— PR #124 setflood_sleep_threshold=0globally but only wrapped 2 of ~20 API call sites. The unwrappedget_dialogs()andget_me()calls could crash the entire backup or prevent startup. Both are now wrapped with bounded flood-wait retry logic. - Negative
e.secondsfrom Telegram no longer causes zero-delay retry storms — Sleep duration is now clamped tomax(0, ...)on both the iterator wrapper and the new one-shot retry helper. - Invalid
FLOOD_WAIT_LOG_THRESHOLDenv var no longer crashes mid-backup — Bareint()parsing replaced with defensivetry/exceptthat falls back to the default of 10 seconds. iter_messages_with_flood_retrynow rejectsreverse=False— The resume tracking (max(resume_from, msg.id)) is only correct for ascending iteration. AValueErroris now raised ifreverse=Trueis not passed, preventing silent data corruption from future misuse.- Documented
FLOOD_WAIT_LOG_THRESHOLD— Added to.env.examplealongside the other logging variables.
📋 Full changelog: docs/CHANGELOG.md
v7.6.1
Fixed
- Forwarded media from private channels no longer creates broken placeholders — When a message forwarded from a private channel contains a document with an inaccessible file reference (
media.document=None),_get_media_type()now correctly returnsNoneinstead of"document". Previously this caused a brokentelegram_file_idof"None", a failed download attempt, and a misleading "Will download on next backup" placeholder that would never resolve. Applies to both scheduled backup and real-time listener (#125)
📋 Full changelog: docs/CHANGELOG.md
v7.6.0 — Topic Filtering & Symlink Fix
Added
- Topic filtering for forum supergroups — New
SKIP_TOPIC_IDSenvironment variable to exclude specific topics from backup while keeping the rest of the chat. Format:chat_id:topic_id,.... Works in both scheduled backup and real-time listener flows (#117)
Fixed
- Dangling dedup symlinks no longer cause infinite redownload loops — When
DEDUPLICATE_MEDIAis enabled andVERIFY_MEDIAruns, dangling symlinks (where the target was renamed by Telethon) are now detected viaos.path.lexists()instead ofos.path.exists(), which follows symlinks. The download return value is now captured to use the actual on-disk filename for symlink targets. Stale symlinks are removed before recreation to preventErrno 17(file exists) errors. Applies to both scheduled backup and real-time listener (#115)
📋 Full changelog: docs/CHANGELOG.md
v7.5.0 — SOCKS5 Proxy Support
What's New
SOCKS5 Proxy Support (#104)
You can now route all Telegram connections through a SOCKS5 proxy — useful in regions where Telegram is blocked or behind corporate firewalls.
Configuration (all optional):
TELEGRAM_PROXY_TYPE=socks5
TELEGRAM_PROXY_ADDR=127.0.0.1
TELEGRAM_PROXY_PORT=1080
TELEGRAM_PROXY_USERNAME=
TELEGRAM_PROXY_PASSWORD=
TELEGRAM_PROXY_RDNS=falseProxy support is applied consistently across all code paths: backup, real-time listener, auth setup, and standalone scripts.
Improvements
- Validation hardening: port range (1–65535), username/password pairing, boolean RDNS parsing, and case-insensitive proxy type
- Security: proxy endpoint details logged at DEBUG (not INFO) to avoid exposing infrastructure topology
- Test isolation: proxy integration tests use scoped fixtures instead of global
sys.modulesmutation - Dependency: added
python-socks[asyncio]>=2.7.1(required by Telethon for SOCKS5 transport)
Contributors
Thanks to @samnyan for the proxy feature contribution!
What's Changed
- Add Supporters section to README by @GeiserX in #102
- Add Codecov coverage badge to README by @GeiserX in #103
- feat: Add proxy support for telegram client. by @samnyan in #104
New Contributors
Full Changelog: v7.4.2...v7.5.0
v7.4.2 — Correctness & Hygiene
Correctness & Release Hygiene
Fixes
- Listener shutdown KeyError (Medium):
_log_stats()referenced non-existent keys fromMassOperationProtector.get_stats(). A clean shutdown would raiseKeyError. Fixed to use actual keys (rate_limits_triggered,operations_blocked,chats_rate_limited). - Pin/unpin realtime (Low): Full pipeline now works end-to-end: listener emits
PIN→ notifier delivers →handle_realtime_notification()forwards to WebSocket → browser reloads pinned messages. Previously the relay inmain.pywas missing, making the frontend handler dead code. - pyproject.toml version sync (Low): Was stuck at
7.2.0since v7.2.0. Now synced with__init__.pyat7.4.2. - WebSocket subscribe ACL (Low): Server now sends
subscribe_denied(instead ofsubscribed) when a restricted user attempts to subscribe to a chat outside their allowed list. Frontend logs the denial.
What's Changed
Full Changelog: v7.4.1...v7.4.2
v7.4.1 — Security Hardening Round 2
Security Hardening (Round 2)
Fixes
- Avatar ACL bypass (Medium): Restricted users can no longer access avatars outside their allowed chats.
serve_media()andserve_thumbnail()now extractchat_idfrom avatar filenames and enforce per-chat scoping. - Push endpoint spoofing (Medium):
/internal/pushnow supports an optionalINTERNAL_PUSH_SECRETenv var as a bearer token. Prevents co-tenant containers from spoofing live events to connected browsers. - Reaction recovery data loss (Medium):
insert_reactions()now retries ALL reactions after a sequence reset, not just the row that triggered the duplicate-key error. Previously, thereturnafter a single retry silently dropped remaining reactions. - Push unsubscribe ownership (Low):
POST /api/push/unsubscribeis now scoped to the requesting user'susername, preventing cross-user endpoint removal.
New Environment Variable
INTERNAL_PUSH_SECRET: Optional shared secret for/internal/pushendpoint. Set the same value on both backup and viewer containers in multi-tenant Docker environments. If unset, IP-only auth is used (backward compatible).
What's Changed
Full Changelog: v7.4.0...v7.4.1
v7.4.0 — Security Hardening
Security Hardening
Addresses multiple security findings from code review.
Fixes
- XSS (High):
linkifyText()now percent-encodes raw"and'in URLs before inserting intohrefattributes.escapeHtml()viatextContent/innerHTMLdoes not escape quotes. - Stats filter (Medium): Fixed JSON string-key vs
inttype mismatch that caused per-chat filtering to silently fail. Also removesmedia_files/total_size_mbfor restricted users (no per-chat breakdown available). - Deletion path (Medium): Unknown-chat deletions now resolve the chat ID from DB first, apply rate limiting, skip ambiguous message IDs (same ID in multiple chats), and send viewer notifications.
- Folders (Low): Restricted users no longer see empty folder names/emoticons for folders with 0 accessible chats.
- Push endpoint (Low):
/internal/pushaccepts loopback + RFC1918/Docker private IPs to support split-container SQLite mode viaVIEWER_HOST/VIEWER_PORT.
Breaking Changes
delete_message_by_id_any_chat()replaced byresolve_message_chat_id()in the database adapter. The old method deleted from ALL chats with a matching message ID — the new approach resolves to a single chat first and skips ambiguous cases.
What's Changed
Full Changelog: v7.3.2...v7.4.0