Build an anime Telegram bot

A Telegram bot in Python with two commands: /search to find anime and /episodes to list a title's seasons. It uses the AnimeDoc API and runs on the Free plan.

What you need

  • Python 3.10 or newer.
  • A bot token: message @BotFather on Telegram and send /newbot.
  • An AnimeDoc API key. Get one free.
pip install "python-telegram-bot>=21" httpx

The bot

bot.py
import os

import httpx
from telegram import Update
from telegram.ext import Application, CommandHandler, ContextTypes

API = "https://api.animedoc.cloud/api/v2"
HEADERS = {"X-API-Key": os.environ["ANIMEDOC_API_KEY"]}
client = httpx.AsyncClient(timeout=15, headers=HEADERS)


async def call(method: str, path: str, **kwargs):
    res = await client.request(method, API + path, **kwargs)
    if res.status_code == 429:
        raise RuntimeError("Too many requests right now, try again in a minute.")
    res.raise_for_status()
    return res.json()["data"]


async def search(update: Update, context: ContextTypes.DEFAULT_TYPE):
    query = " ".join(context.args)
    if not query:
        await update.message.reply_text("Usage: /search <anime title>")
        return
    try:
        data = await call("POST", "/anime/search/", json={"keyword": query, "page": 1, "perPage": 5})
    except RuntimeError as e:
        await update.message.reply_text(str(e))
        return
    items = data.get("items") or []
    if not items:
        await update.message.reply_text(f"Nothing found for {query!r}.")
        return
    lines = []
    for item in items:
        year = (item.get("releaseDate") or "")[:4]
        rating = item.get("imdbRatingValue") or "–"
        lines.append(f"• {item['title']} ({year}) ⭐ {rating}\n  /episodes {item['detailPath']}")
    await update.message.reply_text("\n".join(lines))


async def episodes(update: Update, context: ContextTypes.DEFAULT_TYPE):
    if not context.args:
        await update.message.reply_text("Usage: /episodes <detailPath from /search>")
        return
    data = await call("GET", "/anime/detail/", params={"detailPath": context.args[0]})
    subject = data["subject"]
    seasons = (data.get("resource") or {}).get("seasons") or []
    if not seasons or seasons[0]["maxEp"] == 0:
        summary = "Movie"
    else:
        summary = ", ".join(f"Season {s['se']}: {s['maxEp']} episodes" for s in seasons)
    await update.message.reply_photo(
        photo=(subject.get("cover") or {}).get("url"),
        caption=f"{subject['title']}\n{summary}\n\n{(subject.get('description') or '')[:600]}",
    )


def main():
    app = Application.builder().token(os.environ["TELEGRAM_BOT_TOKEN"]).build()
    app.add_handler(CommandHandler("search", search))
    app.add_handler(CommandHandler("episodes", episodes))
    app.run_polling()


if __name__ == "__main__":
    main()

Run it

export TELEGRAM_BOT_TOKEN=123456:ABC...
export ANIMEDOC_API_KEY=ad_live_...
python bot.py

In Telegram, send your bot /search frieren, then tap one of the /episodes commands it replies with.

Limits and next steps

  • Each command uses one request. The Free plan's 20 requests a day suits a personal bot; a group bot will want Starter (3,000 a day).
  • Cache results in a dict keyed by query for a few minutes to save requests.
  • To send episodes, add a command that calls Media Sources and links to your own player.