The bot-side half of TODO #4. Nothing federates yet; this establishes what would be eligible to, and proves the boundary before there is a hub that could leak across it. - Schema v5 adds visibility, defaulting to private. Upgrading an existing library therefore opts nothing into sharing, which a test on the legacy upgrade path pins down. - list_syncable() is the only function that will ever feed a hub, and it requires both public and approved. Tests force rows into states set_visibility refuses, so the filter is what protects the boundary rather than the caller being careful. - set_visibility refuses anything not approved: media still in the moderation queue cannot be marked shared. - /share lists memes with their current state and asks before making one public, since un-sharing stops future copies but cannot recall those already taken. The wording describes what sharing will mean once a hub exists, because by then warning is too late. - /list shows the same markers. Vault posts have no path into the sync set at all; being in a separate table is what guarantees it, and there is a test saying so. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
6.6 KiB
Memes Bot
A personal Telegram inline bot to store and search media (photos, GIFs, videos, voice, music, stickers, files) by keywords.
Requirements
- A server with Docker installed
- A Telegram bot token from @BotFather
Setup
1. Create the bot in BotFather
/newbot→ get your token/setinline→ enable inline mode, set a placeholder (e.g.Search memes...)
2. Find your Telegram user ID
You need your numeric Telegram user ID to whitelist yourself.
- Easiest: forward any message to @userinfobot — it replies with your ID
- Alternative: open Telegram Web, click your profile, the number in the URL is your ID
- For multiple users: repeat for each person and separate IDs with commas
3. Clone and configure
git clone https://git.goyban.com/goyban/memes_bot
cd memes_bot
cp .env.example .env
Edit .env:
BOT_TOKEN=your_telegram_bot_token_here
ALLOWED_USERS=123456789,987654321
4. Run
docker compose up -d --build
The database will be saved in ./bot_db/media.db.
Usage
Adding media — send the bot a photo, GIF, video, voice message, music file, sticker, or any document, then type keywords when prompted (comma-separated or one per message), then /done.
Round video messages are stored as regular videos: Telegram has no inline result type for video notes, so keeping them as video is what makes them searchable.
Searching inline — in any chat type @yourbotusername keyword to search and send.
Telegram suggests these commands as you type /, with a short description beside each suggestion.
Commands
| Command | Description |
|---|---|
/start |
Start the bot and show usage instructions |
/menu |
Show the available commands |
/list |
Show all saved media |
/vault [search] |
Retrieve a whole post you saved |
/review |
Review pending submissions one by one (admin only) |
/edit [search] |
Select a meme and edit its keywords (admin only) |
/share [search] |
Mark a meme as shared or private (admin only) |
/delete <id> |
Remove an entry (get ID from /list; admin only) |
/cancel |
Cancel current add operation |
The vault — saving whole posts
Send the bot media directly and it behaves as it always has: it extracts the media and asks for keywords.
Forward a post to it instead and it asks what you want:
- Save whole post — keeps the post intact in your vault, caption and all. An album stays an album.
- Extract media — the existing behaviour, adding the media to your searchable library.
Retrieve saved posts with /vault [search], which matches your keywords and the original caption. Each result has a button to forward it back to you, and one to remove it.
Vault posts only come back inside this bot. Telegram's inline mode cannot forward an existing message, so
@yourbotusername keyworddoes not reach the vault. Retrieve the post in the bot chat, then forward it wherever you want.
The vault is per-user and private: it is stored separately from the meme library, every lookup is scoped to its owner, and nothing in it is ever moderated or shared.
Saving stores a pointer to the message in your chat with the bot rather than a copy of the media. If you clear your chat history with the bot, saved posts can no longer be retrieved — the bot tells you when that happens so you can forward them again.
Making the bot public
Set PUBLIC_MODE=true to open the bot to anyone who meets a membership requirement, instead of the ALLOWED_USERS list:
PUBLIC_MODE=true
ADMIN_USERS=123456789
REQUIRED_CHATS=@mychannel,@mygroup
REQUIRE_MODE=all
REQUIRED_CHATS accepts @username or numeric ids. The bot must be an admin in each one, otherwise it cannot check membership and everyone is refused. The bot verifies this at startup and logs an error naming any chat it cannot read. REQUIRE_MODE=all demands every chat; any accepts one. With REQUIRED_CHATS empty the bot is open to everyone.
Users who don't qualify get a message listing what to join. In inline search they see a tappable bar that opens the bot and explains the same thing, rather than empty results with no reason given.
Set
ADMIN_USERSbefore turning this on. With noADMIN_USERS, a private bot treats its users as admins — convenient for a one-person bot, but in public mode that would mean everyone. Public mode therefore grants admin to nobody untilADMIN_USERSis set, and logs an error at startup if you forget.
Membership answers are cached for five minutes, since inline search would otherwise query Telegram on every keystroke. Refusals are cached for only thirty seconds, so a user who has just joined gets in almost immediately.
Accepting submissions
Set ACCEPT_SUBMISSIONS=true in .env to let non-admin users send media too. Their submissions are stored but stay invisible — they don't appear in inline search or /list until a moderator approves them. Admins' own media is still saved directly.
Run /review to work through the queue. Each item is shown with its keywords and four buttons: approve, reject, fix keywords, or stop.
The queue keeps no cursor. /review always fetches the oldest pending item, so you can stop after three and come back tomorrow, and several moderators can review at the same time without stepping on each other — if two open the same item, the first decision wins and the second is told it was already handled.
Leave this off unless you mean it. With
ALLOWED_USERSempty, everyone is an allowed user, so turning submissions on would accept media from anyone who finds the bot.
Sharing (groundwork)
Every meme is private by default. /share [search] lists your memes with 🔒 or 🌐 and lets you flip one. /list shows the same markers.
Nothing is shared yet. This only records your intent. Federation — where independently run bots pool their shared memes — is not built, and no meme leaves this bot until it is. The wording in the confirmation describes what sharing will mean, because once a hub exists it is too late to start warning.
Two rules are enforced in the one query that will ever feed a hub: only approved and public media is eligible. Media awaiting moderation cannot be marked shared at all, and vault posts are in a separate table with no path there whatsoever.
Un-sharing stops future copies but cannot recall copies already taken, which is why the confirmation asks before making something public rather than after.
Moving to another server
The only files you need to transfer are:
.envbot_db/media.db