Skip to content
This repository was archived by the owner on Oct 7, 2026. It is now read-only.
arslan-charyyevPublic archive

About

A Telegram bot for downloading videos and photos from TikTok and Instagram

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Repository files navigation

🦕 Dinogram 📨

Common Changelog

Dinogram is a Telegram bot that can download public videos & photos from social media platforms (TikTok, Instagram, YouTube & Pinterest) and send them to a Telegram chat. To use it, add the bot to a group, send it a direct message with a link to a post, or tag it in any chat (see Inline mode), and it will respond with the corresponding media items.

demo.mp4

Note

There is no shared instance to try. Social media platforms rate-limit a busy bot hard, so the bot is meant to be self-hosted. To learn more, refer to the Deployment section of this document.

Supported social media platforms:

  • TikTok
    • Videos with captions are supported.
    • Photos with title, captions, and music are supported.
    • Posts that are private or require sign-in are NOT supported.
  • Instagram
    • Reels and posts with mixed content (photos & videos) are supported.
    • Posts that are private or require sign-in are supported if authentication cookie is provided.
    • Instagram has severe rate limits for non-authenticated users: 200 requests per hour. Therefore, frequent errors caused by rate limits are to be expected.
  • YouTube (see YouTube)
    • Videos and their audio, in the quality that you choose.
    • Shorts come at once, in the best quality that fits.
    • Playlists, live streams, and premieres are NOT supported.
  • Pinterest
    • Pins with an image, a GIF, a video, several pages, or a carousel are supported, with no login.
    • A pin of a video from another site, such as YouTube or Vimeo, gets that video: the bot handles a YouTube link itself, and replies with any other link.
    • Boards, profiles, and pins that only signed-in users may see are NOT supported.

Extra bot features:

Note

⚙️ denotes config keys (environment variables)

🗨️ denotes bot settings (/settings command in chat)

  • Use authentication cookies provided in chat (🗨️) by pre-defined admin (⚙️: BOT_ADMINS)
  • Automatically resend messages when hitting flood control limits.
  • Truncates long captions to avoid hitting the max character count limit.
  • Configurable reply behavior (⚙️: WITH_CAPTION,SEND_AS_REPLY,SHOW_CAPTION_ABOVE_MEDIA).
  • Tag the bot in any chat, including a private chat with another person (⚙️: INLINE_ENABLED, INLINE_STORAGE_CHAT). See Inline mode.
  • Restrict bot access by user ID or chat ID with a chat command (/allow). See Access control.
  • Report errors to original chat (⚙️SEND_ERRORS) or pre-configured recipients(⚙️ REPORT_ERRORS_TO).
  • Configurable Bot API server URL (⚙️ BOT_API_ROOT). It can be used to increase max file size limit.

Tip

You can use @raw_data_bot to get the user or chat ID

🏷️ Inline mode

Inline mode lets you tag the bot in any chat, even in a private chat with another person, where the bot is not a member. Type @your_bot followed by a link in the message field, wait for the result card, then tap it. Your own account sends a placeholder message, and the bot replaces that message with the downloaded media.

Telegram keeps inline mode off by default. Therefore the owner of the bot must change two settings in the BotFather:

  • /setinline — enable inline mode, and give a placeholder text, for example Paste a link…
  • /setinlinefeedback — set the probability to Enabled (100%), because the bot starts the download only after Telegram reports the chosen result.

An inline message cannot carry a fresh upload. Thus the bot first sends the media to a storage chat, and then shows the resulting file in the inline message. The bot deletes the storage message immediately after the upload, because the file stays available through its ID. When INLINE_STORAGE_CHAT is 0 (the default), the bot uses the private chat of the user who made the request. That user must start the bot first, or the upload fails.

An inline message holds one media item, because Telegram has no album result in inline mode. Thus a post with more items gets one result for each item, and you tap the item that you want to send. The sent item has a 📥 Get all button, which opens the private chat with the bot, and the bot sends the whole post there. The button works for 30 days after the last share of the post.

Note

The bot downloads the post details while it builds the result list. When the platform is too slow to answer, the list shows one result for the first item instead.

For a YouTube link, the result list shows one result for each video quality and audio quality, with its size. When YouTube answers slowly, the list shows a medium-quality video and an audio result instead.

▶️ YouTube

A YouTube link turns the "Processing" message into a card with the thumbnail of the video and two buttons: 🎬 Video and 🎵 Audio. The next step lists the qualities with their sizes, and the card then turns into the file. In a group, a member who presses a button gets a private picker that only they see (a Telegram ephemeral message), and the file names that member. A Short skips the card.

The bot runs yt-dlp with ffmpeg, which the Docker image includes. yt-dlp uses Deno from the image to solve the challenges of YouTube.

  • Video is H.264 MP4 up to 1080p, because every Telegram client plays it. Larger sizes exist only as VP9 or AV1, which some clients show as a black screen.
  • Audio is the M4A (AAC) track of YouTube, which Telegram plays in its music player.
  • The menu hides every quality that is larger than the upload limit (⚙️ UPLOAD_LIMIT_MB), and every kind that is longer than its length limit (⚙️ YOUTUBE_MAX_VIDEO_MINUTES, YOUTUBE_MAX_AUDIO_MINUTES).
  • The bot keeps the ID of every file that it sends, so a repeated request comes at once.
  • Each user runs one download at a time, and two downloads run in parallel.

YouTube often blocks the IP addresses of servers with "Sign in to confirm you're not a bot". An admin can then set a YouTube cookie in the settings (🗨️). The bot tries without the cookie first, because YouTube can ban the account of the cookie. Export the cookie from a private browser window with a spare account, and close that window right after the export.

YouTube breaks yt-dlp often, so the image uses its nightly build. The bump-yt-dlp.yml workflow opens a pull request every day when a new nightly build exists.

🔔 YouTube subscriptions

In a private chat with the bot, you can follow a YouTube channel, and its new videos arrive in the chat by themselves. Send a channel link, such as https://www.youtube.com/@RickAstleyYT, or /subscribe with a link to the channel or to one of its videos. A menu then asks three things:

  • the format: 🎬 high (1080p), medium (720p), low (360p), or 🎵 audio;
  • the frequency: ⚡ as soon as posted, 📅 once a day, or 🗓 once a week;
  • whether Shorts come too.

/subscriptions lists the channels that you follow. There you change the format, the frequency, and the Shorts, or unsubscribe.

The bot reads the RSS feed of each channel every 15 minutes (⚙️ SUBSCRIPTION_CHECK_MINUTES). The feed is not the video player, so the sign-in wall of YouTube does not block it; only the download of a new video can meet the wall. Only videos that come out after the subscription arrive. A daily or weekly subscription sends up to ten videos per batch, a day or a week after the last batch. A premiere, or a video that the sign-in wall blocks, gets up to eight tries, and then the bot sends its link instead. A user can follow up to 20 channels. ⚙️ SUBSCRIPTIONS_ENABLED turns the feature off.

🔐 Access control

The bot serves everyone while ⚙️ BOT_ADMINS is empty. As soon as it names somebody, the gate closes: an admin always passes, and everybody else needs an entry in the whitelist.

An admin changes the whitelist in chat, so a new user needs no redeploy. The change survives a restart, because the whitelist lives in the database of the bot (⚙️ DATA_DIR). BOT_ADMINS is the only access setting in the config.

One entry holds one ID. A user ID admits that person in every chat. A chat ID admits every member of that chat, which is how a whole group gets access.

Command Description
/allow Adds an entry to the whitelist.
/deny Removes an entry from the whitelist.
/allowed Lists the admins and the whitelist.

Only an admin (⚙️ BOT_ADMINS) runs these commands and /settings. The command menu of an admin shows them; the menu of everybody else does not. Telegram shows that menu only after the admin has started the bot, and the bot sets it when it starts. Thus a new admin sees it after the next restart. /allow and /deny find their target in three ways:

  • With arguments, they act on each ID: /allow 12345 -1009876
  • In a reply, they act on the author of the message that you reply to.
  • With neither, they act on the current chat. Thus an admin opens a whole group with one /allow inside it.

A refused user gets an answer that names their user ID and the chat ID, which is what they send to an admin to ask for access.

/allowed asks Telegram for every name again, so the names are current. A name links to the user or the group, if Telegram gives the bot an address: a username, or the invite link of a group where the bot is an admin. Invite links show only in a private chat with the bot, so the members of a group never see them. An entry marked "⚠️ not visible to the bot" is a user who has not started the bot, or has blocked it, or a group that the bot is no longer a member of.

Note

The ⚙️ WHITELIST setting is gone, and the bot ignores it. Add its IDs with /allow 12345 -1009876.

🔮 Future plans:

  • TikTok Authentication (to access posts that require sign-in)
  • Automated authentication
  • Rate limiter (to give everyone a fair chance)

🚀 Deployment

Regardless of the deployment method, you need to obtain a token from the BotFather.

Coolify

Coolify is one way to run it. A push of a version tag runs publish-release.yml, which builds the image, pushes it to GHCR, and then calls deploy-to-coolify.yml.

That workflow runs coolify_deploy.py, which holds the whole Coolify config of the app — image, resource limits, and every environment variable — in its SPEC and ENVS tables. Edit them, push a tag, and CI reconciles the app: the first run creates the project and the application, and every later run PATCHes them. It also creates the persistent volumes once: /app/data, which is where the settings and the whitelist live, and /app/downloads, which holds the YouTube downloads.

A manual run of the workflow covers the two cases that a tag push does not. With no version, it redeploys latest, which is how a rotated token reaches the container, because every run re-syncs the whole environment. With a version, it redeploys that release, which is the rollback path.

Repository secrets: COOLIFY_URL, COOLIFY_TOKEN, BOT_TOKEN, BOT_ADMINS, REPORT_ERRORS_TO. The URL and the Telegram IDs are secrets too, because this repository is public, and so are its workflow logs. A log prints the value of a variable, but it masks a secret.

Note

The bot talks to a local Bot API server at http://telegram-bot-api:8081, which raises the upload limit from 50 MB to 2 GB. That container runs beside Coolify, not inside it, so the application needs its Connect to Predefined Network option on. Without that option Coolify isolates the container, the name does not resolve, and every upload fails.

Important

YouTube videos go to the Bot API server by their path (⚙️ UPLOAD_BY_PATH), so that a 2 GB video never passes through the bot. Thus the Bot API container needs two things before a release with YouTube support goes out:

  • the environment variable TELEGRAM_LOCAL=1, which starts the server in --local mode;
  • the downloads volume of the bot, mounted at /app/downloads, the same path as in the bot container. Coolify creates the volume on the first deploy, and prefixes its name with the UUID of the application, so docker volume ls | grep dinogram-downloads shows the full name.

For example: docker run ... -e TELEGRAM_LOCAL=1 -v <uuid>-dinogram-downloads:/app/downloads aiogram/telegram-bot-api

Docker

One-liner with default configuration:

docker run ghcr.io/arslan-charyyev/dinogram:latest -e BOT_TOKEN=your:token

Docker Compose

You may refer to compose.prod.yml for an example of production deployment using docker compose.

Note

Dinogram stores its runtime settings in a sqlite database file. By default the database file is created in deno's cache folder, which you can find by using the following command: deno info | grep storage. This folder can be changed using the DATA_DIR environment variable.

The docker image uses the DATA_DIR variable to set this directory to /app/data/. To persist the data across container ups & downs, the compose.prod.yml file maps this directory to a docker volume.

⚙️ Config

There are 2 ways to configure the bot:

  • via environment variables (during deployment)
  • via bot settings (during operation)

Environment variables

The only required config variable is the BOT_TOKEN. You can set either via a .env file or via environment variables.

For the other config options and their descriptions, please refer to the config.ts file.

Bot settings

These settings can be configured by sending the /settings command to the bot, and changing them interactively.

settings.mp4

🛠️ Development setup

System requirements

Steps:

  • Clone this repository and open it in VS Code.
  • Initialize environment variables:
    cp .template.env .env
  • Update BOT_TOKEN variable in the .env file with your bot token.
  • For YouTube, install yt-dlp and ffmpeg. The live YouTube test runs only when yt-dlp is installed.
  • Run the project using the main configuration in VS Code.

Local Bot API server

The hosted Bot API limits uploads to 50 MB. To test larger files, and uploads by path, run a local Bot API server beside the bot:

  • Set TELEGRAM_API_ID and TELEGRAM_API_HASH in the .env file. You get both on my.telegram.org.
  • Set BOT_API_ROOT=http://localhost:8081, UPLOAD_BY_PATH=true, and DOWNLOAD_DIR to the absolute path of an existing directory. The server mounts that directory at the same path, so it can read the files of the bot.
  • Start the server with docker compose -f compose.bot-api.yml up -d, and then the bot with deno task main.

On start, the bot logs out of the hosted Bot API. After that, the hosted Bot API refuses the token for 10 minutes, so wait that long before you go back to it.

Useful commands

deno task deps:cache # Download all dependencies
deno task main
deno task issues:check # Check project issues
deno task test # Run all tests

The following builds the Dinogram docker image and starts corresponding container, alongside Bot API server.

devbox run docker

Dependency considerations

All libraries are fetched from jsr or npm. However, there are some points to know:

  • grammy and its plugins come from npm, because deno.land/x stops at grammY 1.45.1. All of them must come from the same source, or two copies of grammY end up in the dependency graph.
  • @grammyjs/parse-mode stays on 1.x, because 2.x removed hydrateReply and replyFmt, and turned bold() and similar functions into template tags.

🙏🏻 Acknowledgements

  • TeleTok - inspiration for the TikTok video downloader.

⚖️ License

MIT (c) 2024 Arslan Charyyev

About

A Telegram bot for downloading videos and photos from TikTok and Instagram

Topics

Resources

Stars

2 stars

Watchers

1 watching

Forks

Releases

Packages

Used by

Contributors

Languages