A Python-based application that scrapes public Telegram channel preview pages (https://t.me/s/{channel}) and forwards messages—including text, images, and videos—to Discord channels using Discord's modern Components V2 (Layouts) system.
It features native process management for scraping multiple channels concurrently, a Flask health/status server, and built-in automatic Git log persistence using either Personal Access Tokens (PATs) or GitHub Apps.
Disgram uses a master-worker process structure. The master process runs a status watchdog and a Flask API server, while spawning isolated worker subprocesses for each scraped Telegram channel.
The master process manages lifecycle supervision for all channel subprocesses and exposes administrative API routes.
graph TD
Start[Start: main.py] --> InitGit[Initialize GitLogManager]
InitGit --> SpawnBots[Spawn Subprocesses: webhook.py per channel]
InitGit --> SpawnFlask[Spawn Flask Server Thread]
SpawnBots --> WatchdogLoop[Watchdog Loop: Every 30s]
WatchdogLoop --> CheckHealth{Subprocesses Alive?}
CheckHealth -- No --> RestartDead[Restart Dead Subprocess]
RestartDead --> WatchdogLoop
CheckHealth -- Yes --> CheckZombies{Logs Fresh?}
CheckZombies -- No (Zombie detected) --> RestartAll[Restart All Subprocesses]
RestartAll --> WatchdogLoop
CheckZombies -- Yes --> WatchdogLoop
SpawnFlask --> FlaskThread[Flask Thread: Listen on PORT]
FlaskThread --> ServeAPI["Serve API Endpoints<br>(GET /, GET /health, GET /logs, GET /app-logs,<br>GET /git-status, POST /force-commit, POST /logs/clear)"]
classDef proc fill:#3b82f6,stroke:#1d4ed8,color:#fff
classDef decis fill:#f59e0b,stroke:#b45309,color:#fff
classDef api fill:#8b5cf6,stroke:#6d28d9,color:#fff
class Start,InitGit,SpawnBots,SpawnFlask,WatchdogLoop,RestartDead,RestartAll,FlaskThread proc
class CheckHealth,CheckZombies decis
class ServeAPI api
Each channel worker scrapes content from Telegram and forwards it via Discord Layouts (with built-in failover capabilities).
graph TD
Startbot[Start Subprocess] --> ScraperLoop[Scraper Loop]
ScraperLoop --> FetchTG[Fetch Public Telegram Preview via HTTP]
FetchTG --> ParseTG{New Message Found?}
ParseTG -- No --> Cooldown[Wait Cooldown]
Cooldown --> FetchTG
ParseTG -- Yes --> GetMedia[Download Media Concurrently <max 8 workers>]
GetMedia --> BuildLayout[Build Discord Component V2 Layout]
BuildLayout --> SendWebhook[Send Webhook Message]
SendWebhook --> Success{Send Successful?}
Success -- Yes --> UpdateLog[Update Disgram.log with Marker]
UpdateLog --> Cooldown
Success -- No (Payload Too Large 413) --> ApplyFallback[Targeted Video Fallback]
ApplyFallback --> LoopMedia[Iterate Downloaded Media]
LoopMedia --> ExcludeVideos{Is Video & Size > 10MB?}
ExcludeVideos -- Yes --> ReplaceWithCDN[Replace with Telegram CDN URL]
ExcludeVideos -- No --> KeepAttachment[Keep as File Attachment]
ReplaceWithCDN --> BuildFallbackLayout[Build Fallback Layout]
KeepAttachment --> BuildFallbackLayout
BuildFallbackLayout --> RetryWebhook[Retry Send Webhook]
RetryWebhook --> SuccessFallback{Retry Successful?}
SuccessFallback -- Yes --> UpdateLog
SuccessFallback -- No --> PlainTextFallback[Final Plain Text Fallback]
PlainTextFallback --> SuccessFinal{Final Send Successful?}
SuccessFinal -- Yes --> UpdateLog
SuccessFinal -- No --> LogError[Log Error]
LogError --> Cooldown
classDef loop fill:#10b981,stroke:#047857,color:#fff
classDef decis fill:#f59e0b,stroke:#b45309,color:#fff
classDef fallback fill:#ef4444,stroke:#b91c1c,color:#fff
class Startbot,ScraperLoop,FetchTG,Cooldown,GetMedia,BuildLayout,SendWebhook,UpdateLog,BuildFallbackLayout,RetryWebhook,PlainTextFallback,LogError loop
class ParseTG,Success,ExcludeVideos,SuccessFallback,SuccessFinal decis
class ApplyFallback,LoopMedia,ReplaceWithCDN,KeepAttachment fallback
- No Bot Accounts Required: Operates using public Telegram preview pages.
- Discord Components V2: Utilizes the layout model (
Container,TextDisplay, andMediaGallery) rather than legacy embeds for cleaner rendering. - Grouped Media Processing: Support for up to 10 images and videos inside a single gallery component, preserved in their original visual order of appearance.
- Unsupported Large Videos: Automatically detects videos that are too large to play or download inline in the Telegram web preview. It extracts their durations and thumbnails, forwarding them in the gallery with a descriptive label (e.g.
Media is too big (0:17)). - Document & File Placeholders: Identifies attached documents and files from the preview, extracting their filenames and appending them as clean formatted placeholders (e.g., "-# Attached file(s):
README.md") above the message link. - Collision-free Parallel Downloading: Uses a thread pool capped at 8 workers to fetch media assets concurrently, utilizing unique filenames (timestamp, index, and random UUID chunks) to prevent concurrent download collisions in the webhook attachments.
- Targeted Video Fallback: If an upload fails with
413 Payload Too Large, the bot filters out video files larger than 10MB, replacing them with their Telegram CDN links, while still uploading smaller videos and images as attachments. - Process Isolation: Each channel scrapes in its own subprocess. If a subprocess crashes or hangs (zombie detection), the main watchdog restarts it.
- Git Log Persistence: Automatically commits and pushes log files back to GitHub periodically using PAT or GitHub App authorization.
- Monitoring endpoints: Health checks, real-time log viewers, git status debug tools, and log clearing via HTTP.
- Python 3.8 or higher
- Git (configured on host system or running environment)
- A Discord Webhook URL
-
Clone the repository:
git clone https://github.com/SimpNick6703/Disgram.git cd Disgram -
Install dependencies:
pip install -r requirements.txt
Duplicate .env.example to .env and fill out your variables:
cp .env.example .envDISCORD_WEBHOOK_URL: Your Discord webhook.DISCORD_THREAD_ID(Optional): ID of a specific Discord thread to forward messages into.TELEGRAM_CHANNELS: Comma-separated list of Telegram channel links.EMBED_COLOR: Color hex (e.g.,89a7d9) for Discord layout container borders.
Tip
Starting from specific messages: To begin scraping from a specific message number, append the message ID to the channel URL: https://t.me/channel_name/1234.
Disgram automatically commits and pushes its state and application logs back to GitHub.
USE_GIT(Optional): Set tofalseto completely disable Git setup, initialization, commits, and pushes (ideal for simple self-hosted setups with no logs tracking using Git). Defaults totrue.
If USE_GIT is true, you can authenticate using one of two options:
-
GITHUB_APP_IDorGITHUB_APP_CLIENT_ID: Identifies the registered GitHub App. -
GITHUB_APP_INSTALLATION_ID: The installation ID of the App on the target repository. -
GITHUB_APP_PRIVATE_KEY_PATH: Local path to the private key (.pemfile), or... -
GITHUB_APP_PRIVATE_KEY: Raw multiline PEM string (e.g.,"-----BEGIN RSA PRIVATE KEY-----\nMIIE...\n-----END RSA PRIVATE KEY-----"). Ideal for cloud deployments like Azure. -
GITHUB_REPO_URL: The repository Git clone URL (https://github.com/username/repo.git). -
GITHUB_DEPLOY_BRANCH: The branch to push logs to (e.g.,azure-prod).
GITHUB_TOKEN: Your personal access token with repository write scope.
You can customize how log pushes to GitHub are scheduled using two mutually exclusive modes:
graph TD
Start[Daemon Thread Check] --> CheckMode{Is COMMIT_MODE == scheduled?}
CheckMode -- No --> Skip[Skip Scheduled Logic]
CheckMode -- Yes --> CheckTime{Is Scheduled UTC Hour?}
CheckTime -- No --> WaitNext[Wait 60s]
WaitNext --> Start
CheckTime -- Yes --> CheckGrace{Time since last commit >= 45m?}
CheckGrace -- No --> LogCooldown[Log Cooldown Active & Skip]
LogCooldown --> WaitNext
CheckGrace -- Yes --> CheckChanges{Has Log Changes?}
CheckChanges -- No --> LogNoChanges[Log 'No Changes' & Skip]
LogNoChanges --> WaitNext
CheckChanges -- Yes --> AuthApp[Get/Refresh GitHub Token]
AuthApp --> GitCommit[Git Commit & Push logs to deployment branch]
GitCommit --> UpdateTime[Update last_commit_time]
UpdateTime --> WaitNext
classDef proc fill:#3b82f6,stroke:#1d4ed8,color:#fff
classDef decis fill:#f59e0b,stroke:#b45309,color:#fff
classDef git fill:#10b981,stroke:#047857,color:#fff
class Start,Skip,WaitNext,LogCooldown,LogNoChanges,UpdateTime proc
class CheckMode,CheckTime,CheckGrace,CheckChanges decis
class AuthApp,GitCommit git
graph TD
Start[Daemon Thread Check] --> CheckMode{Is COMMIT_MODE == interval?}
CheckMode -- No --> Skip[Skip Interval Logic]
CheckMode -- Yes --> CheckInterval{Time since last commit >= LOG_COMMIT_INTERVAL?}
CheckInterval -- No --> WaitNext[Wait 60s]
WaitNext --> Start
CheckInterval -- Yes --> CheckChanges{Has Log Changes?}
CheckChanges -- No --> LogNoChanges[Log 'No Changes' & Reset timer]
LogNoChanges --> WaitNext
CheckChanges -- Yes --> AuthApp[Get/Refresh GitHub Token]
AuthApp --> GitCommit[Git Commit & Push logs to deployment branch]
GitCommit --> UpdateTime[Update last_commit_time]
UpdateTime --> WaitNext
classDef proc fill:#3b82f6,stroke:#1d4ed8,color:#fff
classDef decis fill:#f59e0b,stroke:#b45309,color:#fff
classDef git fill:#10b981,stroke:#047857,color:#fff
class Start,Skip,WaitNext,LogNoChanges,UpdateTime proc
class CheckMode,CheckInterval,CheckChanges decis
class AuthApp,GitCommit git
COMMIT_MODE: The scheduling strategy. Options:"interval"(default) or"scheduled".- Interval Settings (if
COMMIT_MODE=interval):LOG_COMMIT_INTERVAL: Time in seconds between pushes (e.g.3600for 1 hour).
- Scheduled Settings (if
COMMIT_MODE=scheduled):COMMIT_SCHEDULE: Preset execution time ("hourly","every_2h","custom").COMMIT_CUSTOM_HOURS: Comma-separated list of UTC hours (e.g.,"0,6,12,18"for midnight, 6 AM, noon, and 6 PM UTC).
STARTUP_GRACE_PERIOD: Period in seconds (default600= 10 minutes) during which background commits are blocked on startup. This acts as a cooldown buffer to avoid rapid redeployment loops when deploying via a CI/CD pipeline triggered by code pushes to the same branch.
-
Start the bot manually:
python main.py
-
Start the bot via Azure startup script:
bash start.sh
The startup script verifies and configures git credentials globally, sets the default branch, configures credential helpers, and launches the application.
-
Stop the bot: Press
Ctrl + C(which triggers a final log push to git and terminates all worker processes).
The Flask server listens on PORT (default: 5000):
GET /: Displays repository description, status, and list of endpoints.GET /health: Health diagnostic response indicating if processes are alive and returns Discord webhook rate limit statuses.GET /logs: Displays contents ofDisgram.log(scraped messages history).GET /app-logs: Displaysapp.log(internal system, Flask, and process manager activity).GET /git-status: Status diagnostics for git persistence (commit mode, last/next scheduled commits, elapsed time, hash, and status).POST /force-commit: Bypasses schedule cooldowns to immediately commit and push logs to the remote repository.POST /logs/clear: Wipes historical entries fromDisgram.logbut automatically extracts and preserves the latest processed message URLs for each channel as the new markers to prevent forwarding duplicate messages.
- Scraping Limits: Since logs are fetched from public preview pages (
/s/{channel}), only public Telegram channels are supported. - Compressed Media Quality: Telegram scales down image quality on its preview web server; higher-resolution assets must be extracted natively.
- Media Exclusions: The scraper does not support uncompressed file attachments, documents, or message replies.
This project is licensed under the MIT License - see the LICENSE file for details.