Добавлен метод 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 pip install maxapi==0.1
Запуск бота из папки example: Запуск бота из папки https://github.com/love-apples/maxapi/tree/main/example:
python example.py 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.attachment import Attachment
from .types.attachments.image import PhotoAttachmentRequestPayload from .types.attachments.image import PhotoAttachmentRequestPayload
from .types.message import NewMessageLink 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 from .connection.base import BaseConnection
@ -46,12 +47,11 @@ class Bot(BaseConnection):
def __init__(self, token: str): def __init__(self, token: str):
super().__init__() super().__init__()
self.bot = self self.bot = self
self.__token = token self.__token = token
self.params = { self.params = {'access_token': self.__token}
'access_token': self.__token
}
self.marker_updates = None self.marker_updates = None
async def send_message( async def send_message(
@ -336,3 +336,12 @@ class Bot(BaseConnection):
return await GetUpdates( return await GetUpdates(
bot=self, bot=self,
).request() ).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) self._context.update(kwargs)
async def set_state(self, state: State | str = None): async def set_state(self, state: State | str = None):
async with self._lock:
self._state = state self._state = state
async def get_state(self): async def get_state(self):
async with self._lock:
return self._state return self._state
async def clear(self): async def clear(self):
async with self._lock:
self._state = None self._state = None
self._context = {} self._context = {}

View File

@ -57,6 +57,8 @@ class Dispatcher:
return new_ctx return new_ctx
async def handle(self, event_object: UpdateUnion): async def handle(self, event_object: UpdateUnion):
is_handled = False
for handler in self.event_handlers: for handler in self.event_handlers:
if not handler.update_type == event_object.update_type: if not handler.update_type == event_object.update_type:
@ -66,9 +68,9 @@ class Dispatcher:
if not filter_attrs(event_object, *handler.filters): if not filter_attrs(event_object, *handler.filters):
continue continue
memory_context = self.get_memory_context( ids = event_object.get_ids()
*event_object.get_ids()
) memory_context = self.get_memory_context(*ids)
if not handler.state == await memory_context.get_state() \ if not handler.state == await memory_context.get_state() \
and handler.state: and handler.state:
@ -82,14 +84,16 @@ class Dispatcher:
if not key in func_args: if not key in func_args:
del kwargs[key] 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 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): async def start_polling(self, bot: Bot):
self.bot = bot self.bot = bot

View File

@ -5,6 +5,7 @@ from magic_filter import F, MagicFilter
from ..types.command import Command from ..types.command import Command
from ..context.state_machine import State from ..context.state_machine import State
from ..enums.update import UpdateType from ..enums.update import UpdateType
from ..loggers import logger_dp
class Handler: class Handler:
@ -29,3 +30,6 @@ class Handler:
self.state = arg self.state = arg
elif isinstance(arg, Command): 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 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.http_method import HTTPMethod
from ..enums.api_path import ApiPath 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 collections import Counter
from ..types.attachments.image import PhotoAttachmentRequestPayload from ..types.attachments.image import PhotoAttachmentRequestPayload
from ..types.chats import Chat from ..types.chats import Chat
from ..types.command import Command
from ..types.users import BotCommand, User
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath 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_contact import RequestContact
from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton from ..types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
from ..types.command import Command from ..types.command import Command, BotCommand
__all__ = [ __all__ = [
BotCommand,
CallbackButton, CallbackButton,
ChatButton, ChatButton,
LinkButton, LinkButton,

View File

@ -1,4 +1,8 @@
from typing import Optional
from pydantic import BaseModel
class Command: class Command:
def __init__(self, text: str, prefix: str = '/'): def __init__(self, text: str, prefix: str = '/'):
self.text = text self.text = text
@ -7,3 +11,8 @@ class Command:
@property @property
def command(self): 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] bot: Optional[Bot]
def get_ids(self): 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( async def answer(
self, self,

View File

@ -19,4 +19,4 @@ class MessageCreated(Update):
bot: Optional[Bot] bot: Optional[Bot]
def get_ids(self): 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 datetime import datetime
from ..enums.chat_permission import ChatPermission from ..enums.chat_permission import ChatPermission
from ..types.command import BotCommand
class BotCommand(BaseModel):
name: str
description: Optional[str] = None
class User(BaseModel): class User(BaseModel):

View File

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