Telegram bot that instantly replies with file_id for stickers and custom_emoji_id for Premium emoji.
Tap-to-copy, no fluff β built for developers, sticker makers, and bot builders.
| Feature | What you get |
|---|---|
| Sticker IDs | file_id, file_unique_id, set_name, emoji, custom_emoji_id (if type custom_emoji) |
| Premium Emoji IDs | custom_emoji_id + default emoji for any message / caption containing Premium emoji |
| Copy-friendly | Every ID in its own <code> block β one tap to copy on mobile/desktop |
| Smart parsing | Correct UTF-16 handling for surrogate pairs, deduplication, per-field entity resolution |
| Reply Keyboard | Persistent keyboard β π¨ Sticker Guide, β¨ Emoji Guide, βΉοΈ About, π Language, β Hide |
| Multilingual | π¬π§ English Β· π·πΊ Π ΡΡΡΠΊΠΈΠΉ Β· πΊπΏ OΚ»zbekcha β per-user, /lang or π button |
| Silent by design | No spam β custom emoji handler silent, plain text gently guided only in PM |
| Production ready | Rotating logs, graceful shutdown, HTML escaping, /help + BotFather menu |
Supported sticker kinds: Static Β· Animated (.tgs) Β· Video (.webm) Β· Custom Emoji
# 1. Clone
git clone https://github.com/algorithco/idstickbot.git
cd idstickbot
# 2. Environment
python -m venv .venv
# Windows
.venv\Scripts\activate
# Linux / macOS
source .venv/bin/activate
pip install -r requirements.txt
# 3. Configure
cp .env.example .env
# then edit .env and set your token from @BotFather
# 4. Run
python bot.pyOpen Telegram β talk to your bot β send a sticker or a Premium emoji.
# Get a token from @BotFather β /newbot
BOT_TOKEN=123456789:AAA-your-botfather-token-here
# Optional β drop queued updates on restart (default: false)
DROP_PENDING_UPDATES=false| Variable | Required | Default | Description |
|---|---|---|---|
BOT_TOKEN |
β | β | Bot token from @BotFather |
DROP_PENDING_UPDATES |
β | false |
true = discard updates received while offline |
Token is read with
(os.getenv("BOT_TOKEN") or "").strip()β whitespace is ignored and missing token exits with a clear message.
After /start a persistent keyboard appears:
| Button | Action |
|---|---|
π¨ Sticker Guide |
Step-by-step how to get file_id / file_unique_id / set_name |
β¨ Emoji Guide |
How Telegram Premium emoji works + how to send one to get custom_emoji_id |
βΉοΈ About |
About + inline buttons β GitHub / Share bot |
π Language |
Inline selector β π¬π§ / π·πΊ / πΊπΏ (also /lang) |
The keyboard is resize_keyboard + is_persistent with placeholder Send a sticker or Premium emojiβ¦.
| Command | Description |
|---|---|
/start |
Show help + reply keyboard (auto-detects Telegram language) |
/help |
Alias for /start |
/lang |
Choose language β π¬π§ English / π·πΊ Π ΡΡΡΠΊΠΈΠΉ / πΊπΏ OΚ»zbekcha |
Send any sticker β the bot replies threaded (new design):
π¬ Video sticker
file_id:
<code>CAACAgIAAxkBAAEf...</code>
file_unique_id:
<code>AgADr...</code>
Set: <code>MySetByBot</code>
Emoji: π
Tap any code block to copy π
- Includes
file_unique_id(stable across bots) andset_nameto fetch the whole set viagetStickerSet. - Works with forwarded stickers and all types.
Send any message or caption containing Premium emoji (including photo captions):
β¨ Custom emoji IDs β tap to copy:
π <code>5368324170671202286</code>
β€οΈ <code>5431449001532594346</code>
- Deduplicates same
custom_emoji_idin one message (first-seen order). - Correctly resolves text vs. caption offsets β even with emoji like
π³οΈβπorπthat use surrogate pairs.
In private chats, other text gets a gentle hint with the keyboard; groups stay silent to avoid spam. Buttons are handled before the fallback.
Bot speaks English, Π ΡΡΡΠΊΠΈΠΉ, OΚ»zbekcha β per-user stored in user_langs.json:
- Auto-detect from Telegram
language_codeon first/start - Change anytime via
π Languagebutton or/langβ inline selector - Keyboard, help, guides, sticker headers and footers all localize
- BotFather menu is localized too (
set_my_commandswithlanguage_code)
idstickbot/
βββ bot.py # Main bot β i18n, keyboards, handlers, UTF-16 parsing
βββ requirements.txt # aiogram, python-dotenv
βββ .env.example # Template for BOT_TOKEN
βββ .env # Your real token (gitignored)
βββ run.bat # Windows auto-restart loop
βββ bot.log # Rotating log (5 MB Γ 5, gitignored)
βββ restart.log # Restart history (gitignored)
βββ user_langs.json # Per-user language prefs (gitignored)
Telegram sends offset/length in UTF-16 code units, not Python characters. A naive text[offset:offset+length] breaks on π (2 units) or ZWJ sequences.
raw = text.encode("utf-16-le")
units = len(raw) // 2
chunk = raw[offset*2 : (offset+length)*2]
char = chunk.decode("utf-16-le")Bounds are checked before slicing, and text vs. caption entities are resolved against their own field to avoid offset mismatch.
Telegram clients make <code>...</code> tappable. One ID per block = one tap copies exactly that ID β no selection dance.
f"<code>{html.escape(file_id)}</code>"All user-visible strings are html.escaped for ParseMode.HTML safety.
You asked for free hosting β this uses Render (web service, free) + Supabase (Postgres, free). I donβt read your
.env; you set the keys yourself on Render/Supabase dashboards.
| Service | What it does | Free tier |
|---|---|---|
| Render | Hosts bot.py 24/7 (polling + /health keep-alive) |
750 h/mo, sleeps after 15 min idle β we add health endpoint + optional pinger |
| Supabase | Stores user_langs (user_id β en/ru/uz) so language survives Render restarts |
500 MB DB, 2 projects, pauses after 7 days idle β one click resume |
- Go to supabase.com β New Project (no credit card), pick region (e.g.
Frankfurtclosest to Render) β wait ~2 min - In Supabase: SQL Editor β New query β paste
supabase_setup.sql(in repo) β Runcreate table public.user_langs (user_id bigint primary key, lang text check (lang in ('en','ru','uz')), updated_at timestamptz default now()); alter table public.user_langs enable row level security; -- then policies & grants (see file)
- Project Settings β API β Copy:
Project URL(SUPABASE_URL) andanon publicorpublishable key(SUPABASE_KEY)- Keep the
service_rolekey secret β not needed for bot
- Keep the
- Push this repo to GitHub (already
algorithco/idstickbot) - dashboard.render.com β New β Web Service β Connect
algorithco/idstickbot - Settings:
- Name:
idstickbotΒ· Region:FrankfurtΒ· Branch:mainΒ· Runtime:PythonΒ· Plan:Free - Build Command:
pip install -r requirements.txt - Start Command:
python bot.py - Health Check Path:
/health(bot exposes/and/healthviaaiohttpwhenPORTis set)
- Name:
- Environment β Add:
Render injects
BOT_TOKEN=β¦ (from @BotFather, you already have) SUPABASE_URL=https://YOUR_PROJECT.supabase.co SUPABASE_KEY=YOUR_ANON_OR_PUBLISHABLE_KEY # optional: DROP_PENDING_UPDATES=falsePORTautomatically (e.g. 10000). Locally you can leave it empty β bot just polls. - Create Web Service β Deploy. Watch logs:
Health server listening on 0.0.0.0:10000+Run polling for bot @idstickrobot
Why health server? Render free web services sleep after 15 min without inbound HTTP. A polling bot makes no inbound requests, so it would sleep. This bot starts a tiny
aiohttpserver onPORT(/and/health) so Render sees it as healthy. For 100% no-sleep on free, also add a pinger (e.g.cron-job.orgβhttps://YOUR_RENDER_URL/healthevery 14 min) or userender.yaml(included) which setshealthCheckPath: /health.
Repo includes render.yaml so Render can auto-create the service from code:
services:
- type: web
name: idstickbot
env: python
region: frankfurt
plan: free
buildCommand: pip install -r requirements.txt
startCommand: python bot.py
healthCheckPath: /health
envVars:
- key: BOT_TOKEN
sync: false
- key: SUPABASE_URL
sync: false
- key: SUPABASE_KEY
sync: falseIn Render: New β Blueprint β Connect repo β Apply β then set the 3 env vars.
- If
SUPABASE_URL+SUPABASE_KEYare set βbot.pyuses Supabaseuser_langstable (upsertonuser_id). Works on Renderβs ephemeral filesystem. - If not set β falls back to local
user_langs.json(fine for Windows/Linux). You can run locally without Supabase.
Test locally without Supabase:
pip install -r requirements.txt
python bot.py # uses user_langs.jsonTest with Supabase locally:
# .env
SUPABASE_URL=https://xxx.supabase.co
SUPABASE_KEY=eyJ...
python bot.pyNo credentials are read by the assistant β you paste them only in Render dashboard and your local .env (gitignored).
run.bat loops forever and logs restarts:
@echo off
cd /d "%~dp0"
:loop
echo [%date% %time%] Starting bot...>> restart.log
".venv\Scripts\python.exe" bot.py
echo [%date% %time%] Bot exited code %errorlevel% >> restart.log
timeout /t 5 /nobreak >nul
goto loopManual start (hidden):
Start-Process -FilePath ".\run.bat" -WindowStyle Hidden
Get-Content bot.log -Tail 20Auto-start at logon (no admin needed):
Already configured via:
- Registry:
HKCU\Software\Microsoft\Windows\CurrentVersion\RunβIDStickBot - Startup shortcut:
%APPDATA%\Microsoft\Windows\Start Menu\Programs\Startup\IDStickBot.lnk
Scheduled Task (admin, survives reboot before logon):
# Run as Administrator
$A = New-ScheduledTaskAction -Execute "C:\path\to\idstickbot\run.bat" -WorkingDirectory "C:\path\to\idstickbot"
$T = @((New-ScheduledTaskTrigger -AtStartup),(New-ScheduledTaskTrigger -AtLogOn))
$S = New-ScheduledTaskSettingsSet -AllowStartIfOnBatteries -DontStopIfGoingOnBatteries -ExecutionTimeLimit 0 -RestartCount 3 -RestartInterval (New-TimeSpan -Minutes 1)
$P = New-ScheduledTaskPrincipal -UserId "$env:USERDOMAIN\$env:USERNAME" -LogonType S4U -RunLevel Highest
Register-ScheduledTask -TaskName IDStickBot -Description "idstickbot polling" -Action $A -Trigger $T -Settings $S -Principal $P -Force# /etc/systemd/system/idstickbot.service
[Unit]
Description=idstickbot
After=network.target
[Service]
WorkingDirectory=/opt/idstickbot
ExecStart=/opt/idstickbot/.venv/bin/python bot.py
Restart=always
RestartSec=5
EnvironmentFile=/opt/idstickbot/.env
[Install]
WantedBy=multi-user.targetsudo systemctl enable --now idstickbot
journalctl -u idstickbot -fFROM python:3.12-slim
WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt
COPY bot.py .env.example ./
CMD ["python", "bot.py"]- File:
bot.logviaRotatingFileHandlerβ5 MB Γ 5(bot.log,bot.log.1β¦), UTF-8, also mirrored to console. - Format:
%(asctime)s %(levelname)-8s %(name)s - %(message)s - Restart history:
restart.log(only when usingrun.bat) - Both are
.gitignored. - Log level
INFOβ every handled update is logged byaiogram.dispatcher.
Tail logs:
Get-Content bot.log -Tail 50 -Wait
Get-Content restart.log -Tail 20# Lint / type check (optional)
pip install ruff mypy
ruff check bot.py
mypy bot.py
# Quick smoke test
python -m py_compile bot.py
python -c "import bot; print(bot.extract_custom_emojis)"Environment: Python 3.12, aiogram 3.30, python-dotenv 1.2.3
Pin exact versions for reproducibility: pip freeze > requirements.lock
- Never commit
.envβ.gitignorecovers.env,.env.*,*.env(.env.exampleis the only exception). - OneDrive / cloud sync: If the project lives under
OneDrive\Desktop,.envis synced to the cloud. Consider moving toC:\Projects\...or excluding.envfrom sync, and rotate the token via@BotFatherβ/mybotsβAPI TokenβRevokeif exposed. - All IDs are escaped before sending with
ParseMode.HTML.
PRs welcome β keep the scope tight (sticker / emoji IDs). For larger features open an issue first.
- Fork & create a branch
python -m py_compile bot.pymust pass- Commit with a clear message and push
MIT β do what you want, just keep the notice.
Made with β€οΈ for Telegram bot developers
@idstickrobot Β· algorithco/idstickbot