Добавлен метод set_my_commands у bot

This commit is contained in:
Денис Семёнов 2025-06-19 23:28:41 +03:00
parent 9591780152
commit 85f58913c3
15 changed files with 218 additions and 32 deletions

View File

@ -19,7 +19,7 @@
pip install maxapi==0.1
Запуск бота из папки example:
Запуск бота из папки https://github.com/love-apples/maxapi/tree/main/example:
python example.py
```

151
example.py Normal file
View File

@ -0,0 +1,151 @@
import asyncio
import logging
from maxapi import Bot, Dispatcher, F
from maxapi.context import MemoryContext, State, StatesGroup
from maxapi.types import Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
from for_example import router
logging.basicConfig(level=logging.INFO)
bot = Bot('f9LHodD0cOL5NY7All_9xJRh5ZhPw6bRvq_0Adm8-1bZZEHdRy6_ZHDMNVPejUYNZg7Zhty-wKHNv2X2WJBQ')
dp = Dispatcher()
dp.include_routers(router)
start_text = '''Пример чат-бота для MAX 💙
Мои команды:
/clear очищает ваш контекст
/state или /context показывают ваше контекстное состояние
/data показывает вашу контекстную память
'''
class Form(StatesGroup):
name = State()
age = State()
@dp.on_started()
async def _():
logging.info('Бот стартовал!')
@dp.message_created(Command('clear'))
async def hello(event: MessageCreated, context: MemoryContext):
await context.clear()
await event.message.answer(f"Ваш контекст был очищен!")
@dp.message_created(Command('data'))
async def hello(event: MessageCreated, context: MemoryContext):
data = await context.get_data()
await event.message.answer(f"Ваша контекстная память: {str(data)}")
@dp.message_created(Command('context'))
@dp.message_created(Command('state'))
async def hello(event: MessageCreated, context: MemoryContext):
data = await context.get_state()
await event.message.answer(f"Ваше контекстное состояние: {str(data)}")
@dp.message_created(Command('start'))
async def hello(event: MessageCreated):
builder = InlineKeyboardBuilder()
builder.row(
CallbackButton(
text='Ввести свое имя',
payload='btn_1'
),
CallbackButton(
text='Ввести свой возраст',
payload='btn_2'
)
)
builder.row(
CallbackButton(
text='Не хочу',
payload='btn_3'
)
)
await event.message.answer(
text=start_text,
attachments=[builder.as_markup()] # Для MAX клавиатура это вложение,
) # поэтому она в списке вложений
@dp.message_callback(F.callback.payload == 'btn_1')
async def hello(event: MessageCallback, context: MemoryContext):
await context.set_state(Form.name)
await event.message.delete()
await event.message.answer(f'Отправьте свое имя:')
@dp.message_callback(F.callback.payload == 'btn_2')
async def hello(event: MessageCallback, context: MemoryContext):
await context.set_state(Form.age)
await event.message.delete()
await event.message.answer(f'Отправьте ваш возраст:')
@dp.message_callback(F.callback.payload == 'btn_3')
async def hello(event: MessageCallback, context: MemoryContext):
await event.message.delete()
await event.message.answer(f'Ну ладно 🥲')
@dp.message_created(F.message.body.text, Form.name)
async def hello(event: MessageCreated, context: MemoryContext):
await context.update_data(name=event.message.body.text)
data = await context.get_data()
await event.message.answer(f"Приятно познакомиться, {data['name'].title()}!")
@dp.message_created(F.message.body.text, Form.age)
async def hello(event: MessageCreated, context: MemoryContext):
await context.update_data(age=event.message.body.text)
await event.message.answer(f"Ого! А мне всего пару недель 😁")
async def main():
await bot.set_my_commands(
BotCommand(
name='/start',
description='Перезапустить бота'
),
BotCommand(
name='/clear',
description='Очищает ваш контекст'
),
BotCommand(
name='/state',
description='Показывают ваше контекстное состояние'
),
BotCommand(
name='/data',
description='Показывает вашу контекстную память'
),
BotCommand(
name='/context',
description='Показывают ваше контекстное состояние'
)
)
await dp.start_polling(bot)
# await dp.handle_webhook(
# bot=bot,
# host='localhost',
# port=8080
# )
asyncio.run(main())

10
for_example.py Normal file
View File

@ -0,0 +1,10 @@
from maxapi import F, Router
from maxapi.types import Command, MessageCreated
router = Router()
@router.message_created(Command('router'))
async def hello(obj: MessageCreated):
file = __file__.split('\\')[-1]
await obj.message.answer(f"Пишу тебе из роута {file}")

View File

@ -34,7 +34,8 @@ from .enums.sender_action import SenderAction
from .types.attachments.attachment import Attachment
from .types.attachments.image import PhotoAttachmentRequestPayload
from .types.message import NewMessageLink
from .types.users import BotCommand, ChatAdmin
from .types.users import ChatAdmin
from .types.command import BotCommand
from .connection.base import BaseConnection
@ -46,12 +47,11 @@ class Bot(BaseConnection):
def __init__(self, token: str):
super().__init__()
self.bot = self
self.__token = token
self.params = {
'access_token': self.__token
}
self.params = {'access_token': self.__token}
self.marker_updates = None
async def send_message(
@ -335,4 +335,13 @@ class Bot(BaseConnection):
):
return await GetUpdates(
bot=self,
).request()
async def set_my_commands(
self,
*commands: BotCommand
):
return await ChangeInfo(
bot=self,
commands=list(commands)
).request()

View File

@ -26,11 +26,14 @@ class MemoryContext:
self._context.update(kwargs)
async def set_state(self, state: State | str = None):
self._state = state
async with self._lock:
self._state = state
async def get_state(self):
return self._state
async with self._lock:
return self._state
async def clear(self):
self._state = None
self._context = {}
async with self._lock:
self._state = None
self._context = {}

View File

@ -57,6 +57,8 @@ class Dispatcher:
return new_ctx
async def handle(self, event_object: UpdateUnion):
is_handled = False
for handler in self.event_handlers:
if not handler.update_type == event_object.update_type:
@ -66,9 +68,9 @@ class Dispatcher:
if not filter_attrs(event_object, *handler.filters):
continue
memory_context = self.get_memory_context(
*event_object.get_ids()
)
ids = event_object.get_ids()
memory_context = self.get_memory_context(*ids)
if not handler.state == await memory_context.get_state() \
and handler.state:
@ -82,14 +84,16 @@ class Dispatcher:
if not key in func_args:
del kwargs[key]
if kwargs:
await handler.func_event(event_object, **kwargs)
else:
await handler.func_event(event_object, **kwargs)
await handler.func_event(event_object, **kwargs)
logger_dp.info(f'Обработано: {event_object.update_type}')
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
is_handled = True
break
if not is_handled:
logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
async def start_polling(self, bot: Bot):
self.bot = bot

View File

@ -5,6 +5,7 @@ from magic_filter import F, MagicFilter
from ..types.command import Command
from ..context.state_machine import State
from ..enums.update import UpdateType
from ..loggers import logger_dp
class Handler:
@ -28,4 +29,7 @@ class Handler:
elif isinstance(arg, State):
self.state = arg
elif isinstance(arg, Command):
self.filters.insert(0, F.message.body.text == arg.command)
self.filters.insert(0, F.message.body.text == arg.command)
else:
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
f'регистрации функции `{func_event.__name__}`')

View File

@ -2,7 +2,8 @@
from typing import Any, Dict, List, TYPE_CHECKING
from ..types.users import BotCommand, User
from ..types.users import User
from ..types.command import BotCommand
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath

View File

@ -5,10 +5,8 @@ from typing import Any, Dict, List, TYPE_CHECKING
from collections import Counter
from ..types.attachments.image import PhotoAttachmentRequestPayload
from ..types.chats import Chat
from ..types.users import BotCommand, User
from ..types.command import Command
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath

View File

@ -21,9 +21,10 @@ from ..types.attachments.buttons.link_button import LinkButton
from ..types.attachments.buttons.request_contact import RequestContact
from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
from ..types.command import Command
from ..types.command import Command, BotCommand
__all__ = [
BotCommand,
CallbackButton,
ChatButton,
LinkButton,

View File

@ -1,4 +1,8 @@
from typing import Optional
from pydantic import BaseModel
class Command:
def __init__(self, text: str, prefix: str = '/'):
self.text = text
@ -6,4 +10,9 @@ class Command:
@property
def command(self):
return self.prefix + self.text
return self.prefix + self.text
class BotCommand(BaseModel):
name: str
description: Optional[str] = None

View File

@ -51,7 +51,7 @@ class MessageCallback(Update):
bot: Optional[Bot]
def get_ids(self):
return (self.message.recipient.chat_id, self.message.recipient.user_id)
return (self.message.recipient.chat_id, self.callback.user.user_id)
async def answer(
self,

View File

@ -19,4 +19,4 @@ class MessageCreated(Update):
bot: Optional[Bot]
def get_ids(self):
return (self.message.recipient.chat_id, self.message.recipient.user_id)
return (self.message.recipient.chat_id, self.message.sender.user_id)

View File

@ -3,11 +3,7 @@ from typing import List, Optional
from datetime import datetime
from ..enums.chat_permission import ChatPermission
class BotCommand(BaseModel):
name: str
description: Optional[str] = None
from ..types.command import BotCommand
class User(BaseModel):

View File

@ -2,7 +2,7 @@ from setuptools import setup, find_packages
setup(
name="maxapi",
version="0.1",
version="0.3",
packages=find_packages(),
description="Библиотека для взаимодействия с API мессенджера MAX",
long_description=open("README.md", encoding='utf-8').read(),