yukibotv2.0.0

Build bots for YukiChat in Python

yukibot is the official Python library for the YukiChat Bot API. If you have used pyTelegramBotAPI (telebot), you already know it.

Python 3.7+Long polling & webhooksFile uploadsInline buttonsGroup moderation
Instant updatesLong polling: the server answers the moment a message arrives.
No loopsBots never receive their own messages or other bots' messages.
Real filesSend a path, bytes or an open file. Voice notes are converted for you.
Safe by defaultAdmin actions follow the same rights as people in the app.

Quick start

1. In YukiChat, open a chat with @YukiFather and send /newbot MyCoolBot. The name must end with bot. You'll get a token like yk_bot_…. Keep it secret.

2. Install the library:

pip install -U yukibot

3. Write your bot:

from yukibot import YukiBot

bot = YukiBot("yk_bot_YOUR_TOKEN")

@bot.message_handler(commands=["start"])
def start(message):
    bot.reply_to(message, f"Hi @{message.sender.username}! πŸ‘‹")

@bot.message_handler(func=lambda m: True)
def echo(message):
    bot.send_message(message.chat.id, message.text_or_caption)

bot.polling()

Run it, then message your bot in YukiChat. To use it in a group, add it as a member.

Prefer to start from something bigger? The demo bot uses almost every feature on this page.

Handlers

Decorate functions to handle updates. For each update, handlers are tried in the order they were defined and the first match runs, so put specific handlers before catch-alls.

@bot.message_handler(commands=["start", "help"])     # /start, /start@YourBot, /start args (not /startgame)
@bot.message_handler(content_types=["photo", "video"]) # text, photo, video, file, audio, voice, video_note, sticker
@bot.message_handler(regexp=r"(?i)hello")               # matched against text or caption
@bot.message_handler(chat_types=["dm"])                 # dm, group, channel
@bot.message_handler(func=lambda m: len(m.text or "") > 100)

@bot.edited_message_handler()        # someone edited a message
@bot.callback_query_handler(data="like")    # a button was pressed (exact data or a compiled regex)
@bot.poll_handler()                  # vote counts changed in your poll
@bot.poll_answer_handler()           # who voted for what (polls sent with is_anonymous=False)
@bot.chat_member_handler()           # someone joined or left a chat the bot is in
@bot.my_chat_member_handler()        # the bot itself was added/removed (also when someone opens a DM with it)
@bot.message_reaction_handler()      # someone reacted to a message

Useful message helpers: message.command ("start"), message.args (text after the command), message.text_or_caption and message.content_type.

@bot.message_handler(commands=["echo"])
def echo(m):
    bot.reply_to(m, m.args or "Usage: /echo <text>")

@bot.exception_handler
def on_error(exc, update):
    print("handler failed:", exc)   # without this, errors are logged and the bot keeps running

Buttons

Attach an inline keyboard to any message with reply_markup. Each button has a text and either callback_data (sent to your bot when pressed) or a url (opens a link).

kb = {"inline_keyboard": [
    [{"text": "βž–", "callback_data": "dec"}, {"text": "βž•", "callback_data": "inc"}],
    [{"text": "Open YukiChat", "url": "https://yukichat.lol"}],
]}
bot.send_message(chat_id, "Counter: 0", reply_markup=kb)

@bot.callback_query_handler(func=lambda q: q.data in ("inc", "dec"))
def press(q):
    n = int(q.message.text.split(": ")[1]) + (1 if q.data == "inc" else -1)
    bot.edit_message_text(f"Counter: {n}", q.message.message_id, reply_markup=kb)
    bot.answer_callback_query(q.id, f"Now {n}")             # small toast
    # bot.answer_callback_query(q.id, "Saved!", show_alert=True)  # dialog instead

Limits: up to 20 rows, 8 buttons per row, 100 buttons in total. Button text can be 1–64 characters and callback_data 1–256 bytes. Answer a press within 10 minutes, and only once.

Sending files

Every media method accepts an http(s) URL, a file path, bytes or an open file. Local files are uploaded to YukiChat for you.

bot.send_photo(chat_id, "cat.jpg", caption="Cat 🐱")
bot.send_photo(chat_id, "https://example.com/cat.jpg")
bot.send_document(chat_id, open("report.pdf", "rb"))
bot.send_voice(chat_id, "note.ogg")        # converted to a playable voice message
bot.send_video_note(chat_id, "round.mp4")  # converted to a round video
bot.send_media_group(chat_id, [            # sent as one album (1–10 items)
    {"type": "photo", "media": "a.jpg", "caption": "first"},
    {"type": "photo", "media": "https://example.com/b.jpg"},
])
info = bot.upload_file("big.zip")          # just store it: {"url", "name", "size", "mime"}

Polls

bot.send_poll(chat_id, "Best pet?", ["🐱 Cat", "🐢 Dog"], is_anonymous=False)

@bot.poll_answer_handler()
def voted(a):
    print(a.user.username, "chose", a.option_ids)

bot.stop_poll(message_id)   # closes it and returns the final counts

Polls appear as buttons that show live counts. Tapping your choice again takes your vote back. Set allows_multiple_answers=True to let people pick several options.

Groups & admin rights

A bot in a group has the same rights as a person with the same role. Add it as a member to read and send messages. Make it an admin to moderate, and give it only the rights it needs.

RightNeeded for
delete_messagesdelete_message on other people's messages (a bot can always delete its own)
ban_usersban_chat_member, kick_chat_member, unban_chat_member, restrict_chat_member, set_chat_permissions
pin_messagespin_chat_message, unpin_chat_message, unpin_all_chat_messages
change_infoset_chat_title, set_chat_description, set_chat_photo, set_chat_theme, set_chat_background
invite_usersexport_chat_invite_link
add_adminspromote_chat_member (the bot can only grant rights it has itself)
post_messagesposting in channels
import time

@bot.message_handler(commands=["mute"], chat_types=["group"])
def mute(m):
    if not m.reply_to_message:
        return bot.reply_to(m, "Reply to someone with /mute")
    user_id = m.reply_to_message["from"]["id"]
    bot.restrict_chat_member(m.chat.id, user_id, {"can_send_messages": False},
                             until_date=int(time.time()) + 600)   # 10 minutes
    bot.reply_to(m, "πŸ”‡ Muted for 10 minutes")

Nobody can ban, demote or restrict the owner. Admins can't be restricted; demote them first. Group slow mode and member permissions apply to bots that aren't admins.

Per-user storage

Store any JSON value (up to 64 KB) per user on the server, so simple bots don't need a database.

bot.set_bot_data(user_id, "settings", {"lang": "en", "score": 10})
bot.get_bot_data(user_id, "settings")   # β†’ {"lang": "en", "score": 10}, or None
bot.delete_bot_data(user_id, "settings")

Webhooks

Instead of polling, YukiChat can send each update to your server as a POST with a JSON body.

bot.set_webhook("https://example.com/yuki", secret_token="long-random-string")

# Flask example
from flask import Flask, request
app = Flask(__name__)

@app.post("/yuki")
def hook():
    ok = bot.process_webhook(request.get_json(), "long-random-string", request.headers)
    return ("", 200) if ok else ("", 403)

Errors & limits

When the API refuses a request, the library raises YukiAPIError with .error_code, .description and .retry_after. Network errors, 429 and 5xx answers are retried automatically: YukiBot(token, retries=3, timeout=30).

from yukibot import YukiAPIError

try:
    bot.ban_chat_member(chat_id, user_id)
except YukiAPIError as e:
    bot.send_message(chat_id, f"Can't ban: {e.description}")   # e.error_code == 403
CodeMeaning
400Bad Request: a missing or invalid parameter, or the message/chat wasn't found
401Unauthorized: wrong or revoked token
403Forbidden: the bot isn't in the chat or lacks an admin right
404Method doesn't exist
409getUpdates was called while a webhook is set
429Too many requests. Wait retry_after seconds
501Method not supported by YukiChat yet
LimitValue
API calls30 per second per bot (bursts up to 60)
Messages into one chatbursts of 20, then 1 per second
Text / caption8192 / 2048 characters
Pending updateskept for 24 hours, at most 1000 per bot
getUpdatesup to 100 updates per call, long-poll timeout up to 50 s
Self-destructing messagesexpires_in from 1 s to 1 year

Objects & updates

Every object is a dict that also supports dot access, and missing keys read as None. Because from is a Python keyword, the author is message.sender.

Message

{
  "message_id": 812, "date": 1792000000, "type": "photo",
  "chat": {"id": 148, "type": "group", "title": "Friends", "username": null},
  "from": {"id": 150, "username": "alice", "is_bot": false},
  "text": "…",                       // text messages only
  "photo": "https://…", "media": "https://…", "caption": "…",   // media messages
  "reply_to_message": {"message_id": 800, "text": "…", …},
  "media_group_id": "g_…", "edit_date": 1792000100, "reply_markup": {…}
}

The media field is named after the type: photo, video, document (type file), audio, voice, video_note or sticker. media always holds the same URL. Voice and audio messages also include duration.

Update kinds

KeyContents
messageA new message from a person (Message)
edited_messageAn edited text message (Message)
callback_queryid, from, message, data
pollid, question, options[{text, voter_count}], total_voter_count, is_closed
poll_answerpoll_id, user, option_ids
chat_member / my_chat_memberchat, user, event ("join" or "leave"), date
message_reactionchat, message_id, user, new_reaction, reactions (counts)

Chat types are dm, group, channel and saved.

Raw HTTP API

You can call the API from any language. The base URL is https://vps.yukichat.lol:8443.

# token in a header (recommended: it never appears in URLs or logs)
curl -X POST https://vps.yukichat.lol:8443/botapi/sendMessage \
  -H "Authorization: Bearer yk_bot_YOUR_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"chat_id": 148, "text": "Hello"}'

# Telegram-style URL also works
curl https://vps.yukichat.lol:8443/botyk_bot_YOUR_TOKEN/getMe

How it works

YukiChat appsweb Β· Android
β†’ REST + WebSocket β†’
YukiChat serverFastAPI Β· SQLite
Update queueone per bot Β· kept 24 h
β†’ long polling or webhook β†’
Your botyukibot or any HTTP client

When someone sends a message, presses a button, joins or reacts in a chat with your bot, the server puts an update in that bot's queue. get_updates returns them in order and waits briefly when the queue is empty. With a webhook, the server pushes them to your URL. Updates are removed once you confirm them with offset or your webhook answers 2xx.

Upgrading from 1.x

1.x code keeps working: the camelCase methods (sendMessage, editMessageText…) still exist. Things to know:

Demo bot

A complete bot with a menu, a counter, a poll, notes, moderation and welcome messages:

import os, random, time
from yukibot import YukiBot, YukiAPIError

bot = YukiBot(os.environ["YUKI_TOKEN"], skip_pending=True)
MENU = {"inline_keyboard": [[{"text": "🎲 Dice", "callback_data": "dice"},
                             {"text": "🌐 YukiChat", "url": "https://yukichat.lol"}]]}

@bot.message_handler(commands=["start"])
def start(m):
    bot.reply_to(m, f"Hi @{m.sender.username}!", reply_markup=MENU)

@bot.callback_query_handler(data="dice")
def dice(q):
    bot.answer_callback_query(q.id)
    bot.send_chat_action(q.message.chat.id, "typing")
    bot.send_message(q.message.chat.id, f"🎲 {random.randint(1, 6)}")

@bot.message_handler(commands=["remember"])
def remember(m):
    bot.set_bot_data(m.sender.id, "note", m.args)
    bot.set_message_reaction(m.message_id, "πŸ‘Œ")

@bot.message_handler(commands=["recall"])
def recall(m):
    bot.reply_to(m, bot.get_bot_data(m.sender.id, "note") or "Nothing saved yet")

@bot.message_handler(commands=["secret"])
def secret(m):
    bot.send_message(m.chat.id, "🀫 Gone in 15 seconds", expires_in=15)

@bot.message_handler(content_types=["photo"])
def photo(m):
    bot.set_message_reaction(m.message_id, "πŸ”₯")

@bot.chat_member_handler()
def welcome(e):
    if e.event == "join" and not e.user.is_bot:
        bot.send_message(e.chat.id, f"Welcome, @{e.user.username}! πŸ‘‹")

bot.set_my_commands([{"command": "start", "description": "Menu"},
                     {"command": "remember", "description": "Save a note"},
                     {"command": "recall", "description": "Show your note"}])
bot.polling()

Method reference

All methods of YukiBot. The camelCase name in grey is the raw API method, which also works as a Python alias. ADMIN = needs an admin right in the chat.

Changelog

2.0.0 October 15, 2026

NEWLong polling with instant delivery; webhooks with a secret header, ordered delivery and retries.

NEWHandlers for edits, button presses, polls, poll answers, joins/leaves, the bot's own membership and reactions. Added exception_handler.

NEWFile uploads from paths, bytes or open files; albums; upload_file.

NEWReal polls with vote counts; stop_poll. Buttons can open URLs, and answer_callback_query shows a toast or dialog.

NEWget_me, unban_chat_member, kick_chat_member, unpin_all_chat_messages, delete_chat_photo, get_my_commands, delete_my_commands, delete_bot_data, delete_sticker_set, get_user.

CHANGEDsnake_case method names (camelCase kept as aliases). The token is sent in a header. YukiAPIError replaces the bare Exception. Automatic retries and timeouts. The cache was removed.

FIXEDA new bot no longer receives the whole chat history, and bots no longer see their own messages (echo loops). Commands match exactly. Edits and deletes show up live without chat_id. set_my_commands works without a scope. Documents and video notes have the right types.

FIXEDAdmin methods now check rights: bots can't kick the owner, and restricting someone actually stops them posting.

1.1.1 June 24, 2026

FIXEDUpdated the API URL.

1.1.0 April 26, 2026

NEWMedia, chat management, stickers, profile, storage and history methods; expires_in for self-destructing messages.