Guides / Telegram Bot
Telegram Bot
Build a Telegram bot with /search and /artist commands that return art metadata in formatted messages. Then add inline query support so users can search from any chat. Uses python-telegram-bot v20+ and Python 3.9+.
Prerequisites
- Python 3.9 or later
- A Telegram bot token. Open Telegram, search for @BotFather, send
/newbot, and follow the prompts. Copy the token it returns.
Set up the project
mkdir lagoon-bot && cd lagoon-bot
pip install python-telegram-bot requests
Set your bot token as an environment variable.
export TELEGRAM_TOKEN=your_bot_token
Build the bot
Create bot.py with the command handlers.
# bot.py
import os
import requests
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes
LAGOON = "https://lagoon.io/api/v1"
def lagoon(endpoint, params):
resp = requests.get(f"{LAGOON}/{endpoint}", params=params)
return resp.json()
async def start(update: Update, context: ContextTypes.DEFAULT_TYPE):
await update.message.reply_text(
"Search art metadata from Lagoon.\n\n"
"/search <query> - search in plain English\n"
"/artist <handle> - look up an artist"
)
async def search(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
await update.message.reply_text("Usage: /search <query>")
return
query = " ".join(context.args)
data = lagoon("search", {"nl": query, "limit": 5})
if not data["ok"]:
await update.message.reply_text(f"Error: {data['error']}")
return
if not data["results"]:
await update.message.reply_text("No results found.")
return
lines = []
for post in data["results"]:
title = post.get("title") or f"Post #{post['id']}"
tags = [t for t in post["tags"] if not t.startswith("rating:")][:5]
line = f"<b>{title}</b>\n"
line += f"{post['artist_handle']} on {post['platform_name']}\n"
line += ", ".join(tags)
if post.get("source_url"):
line += f'\n<a href="{post["source_url"]}">Source</a>'
lines.append(line)
await update.message.reply_html("\n\n".join(lines))
async def artist(update: Update, context: ContextTypes.DEFAULT_TYPE):
if not context.args:
await update.message.reply_text("Usage: /artist <handle>")
return
handle = context.args[0]
data = lagoon("artist", {"handle": handle})
if not data["ok"]:
await update.message.reply_text(f"Artist not found: {handle}")
return
a = data["artist"]
lines = [f"<b>{a.get('display_name') or a['handle']}</b> ({a['post_count']} posts)\n"]
for p in a["platforms"]:
if p.get("profile_url"):
lines.append(f'{p["platform_name"]}: <a href="{p["profile_url"]}">{p["handle"]}</a>')
else:
lines.append(f'{p["platform_name"]}: {p["handle"]}')
await update.message.reply_html("\n".join(lines))
app = Application.builder().token(os.environ["TELEGRAM_TOKEN"]).build()
app.add_handler(CommandHandler("start", start))
app.add_handler(CommandHandler("search", search))
app.add_handler(CommandHandler("artist", artist))
app.run_polling()
Register commands
Register a command menu so users see available commands when they type / in the chat. Send /setcommands to @BotFather, select your bot, and enter:
search - Search art metadata in plain English
artist - Look up an artist across platforms
Run the bot
python bot.py
Try it in a Telegram chat with your bot:
/search blue hair on pixiv from 2025
/search by hews__ on twitter
/artist hews__
Add inline queries
Inline queries work from any Telegram chat. The user types @your_bot_name followed by a query, selects a result from the dropdown, and the formatted result is sent into the conversation.
Enable inline mode through @BotFather: send /setinline, select your bot, and enter a placeholder such as Search art metadata.... The placeholder appears in the input field when a user types your bot's name.
Add these imports to the top of bot.py:
from telegram import InlineQueryResultArticle, InputTextMessageContent
from telegram.ext import InlineQueryHandler
Add this handler above the app = Application... line:
async def inline_search(update: Update, context: ContextTypes.DEFAULT_TYPE):
query = update.inline_query.query
if len(query) < 3:
return
data = lagoon("search", {"nl": query, "limit": 10})
if not data.get("ok"):
return
results = []
for post in data["results"]:
title = post.get("title") or f"Post #{post['id']}"
tags = [t for t in post["tags"] if not t.startswith("rating:")][:5]
text = f"<b>{title}</b>\n"
text += f"By {post['artist_handle']} on {post['platform_name']}\n"
text += ", ".join(tags)
if post.get("source_url"):
text += f"\n{post['source_url']}"
results.append(InlineQueryResultArticle(
id=str(post["id"]),
title=title,
description=f"{post['artist_handle']} on {post['platform_name']}",
input_message_content=InputTextMessageContent(
text, parse_mode="HTML"
),
))
await update.inline_query.answer(results, cache_time=60)
Register the handler alongside the others:
app.add_handler(InlineQueryHandler(inline_search))
Restart the bot and test by typing @your_bot_name sword art in any Telegram chat. Results appear after three characters.
Authenticate with an API key
Unauthenticated requests are rate-limited to 30 per minute. To use a higher-limit tier, set your API key in the environment and update the lagoon() helper to pass it in the request header.
# export LAGOON_KEY=lg_your_key_here
def lagoon(endpoint, params):
headers = {}
api_key = os.environ.get("LAGOON_KEY")
if api_key:
headers["X-API-Key"] = api_key
resp = requests.get(f"{LAGOON}/{endpoint}", params=params, headers=headers)
return resp.json()
Next steps