Metadata-Version: 2.1
Name: yukibot
Version: 2.0.0
Summary: The official Python library for YukiChat bots: long polling, webhooks, file uploads, inline buttons.
Home-page: https://github.com/Vasiliy-katsyka/yukibot
Author: YukiChat
Author-email: kviappsgames@gmail.com
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Operating System :: OS Independent
Requires-Python: >=3.7
Description-Content-Type: text/markdown
Requires-Dist: requests >=2.25.1

# YukiBot

The official Python library for [YukiChat](https://yukichat.lol) bots. If you know `telebot`, you already know it.

## Installation
```bash
pip install -U yukibot
```

Create a bot by sending `/newbot MyCoolBot` to **@YukiFather** in YukiChat. You'll get a `yk_bot_...` token.

## Quick start

```python
from yukibot import YukiBot

bot = YukiBot("yk_bot_your_token_here")

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

@bot.message_handler(content_types=["photo"])
def photo(message):
    bot.reply_to(message, "Nice photo!")

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

bot.polling()
```

`polling()` uses long polling: the server answers the moment something arrives. A bot never receives its own messages or other bots' messages, so echo bots can't loop.

## Buttons

```python
kb = {"inline_keyboard": [[
    {"text": "👍 Like", "callback_data": "like"},
    {"text": "Website", "url": "https://yukichat.lol"},
]]}
bot.send_message(chat_id, "Do you like it?", reply_markup=kb)

@bot.callback_query_handler(data="like")
def liked(call):
    bot.answer_callback_query(call.id, "Thanks!")            # toast; show_alert=True for a dialog
    bot.edit_message_text("You liked it ❤️", call.message.message_id)
```

## Files

Every `send_*` method takes a URL, a file path, `bytes` or an open file:

```python
bot.send_photo(chat_id, "cat.jpg", caption="Cat")
bot.send_document(chat_id, open("report.pdf", "rb"))
bot.send_voice(chat_id, "note.ogg")          # converted to a playable voice message
bot.send_media_group(chat_id, [{"type": "photo", "media": "a.jpg"}, {"type": "photo", "media": "b.jpg"}])
```

## Other updates

```python
@bot.edited_message_handler()
def edited(message): ...

@bot.chat_member_handler()
def joined_or_left(event):       # event.user, event.chat, event.event == "join" | "leave"
    ...

@bot.my_chat_member_handler()
def added(event): ...            # the bot was added to / removed from a chat (also when someone opens a DM with it)

@bot.poll_answer_handler()
def voted(answer): ...           # for polls sent with is_anonymous=False

@bot.message_reaction_handler()
def reacted(event): ...

@bot.exception_handler
def on_error(exc, update):       # default: logged, the bot keeps running
    ...
```

## Webhooks

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

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

Updates are delivered in order and retried if your server is down. While a webhook is set, `get_updates()` returns error 409.

## Errors

API errors raise `YukiAPIError` with `.error_code`, `.description` and `.retry_after`. Network errors, `429` and `5xx` answers are retried automatically (`YukiBot(token, retries=3, timeout=30)`).

## Groups

To manage a group, a bot must be an admin with the matching right: `ban_chat_member` needs *ban users*, `pin_chat_message` needs *pin messages*, `delete_message` for other people's messages needs *delete messages*, and so on.

## Upgrading from 1.x

1.x code keeps working: the camelCase methods (`sendMessage`, `editMessageText`, …) are still there. Changes:

- `sendChatAction(chat_id, action)` now needs the chat id.
- `content_type='photo'` still works, but prefer `content_types=['photo']`.
- Commands match exactly: `commands=['start']` no longer fires on `/startgame`, but does on `/start@YourBot`.
- The client-side cache was removed, so admin checks are never stale.
