Compare commits

...

5 Commits

91 changed files with 2211 additions and 274 deletions

View File

@ -1,16 +1,28 @@
# maxapi # maxapi
#### Библиотека (like aiogram) для взаимодействия с социальной сетью MAX по Webhook (или подписке бота) #### Библиотека (like aiogram) для взаимодействия с мессенджером MAX
Информация на данный момент: Информация на данный момент:
* Проект не готов, ведется активная разработка * Проект тестируется и активно дорабатывается
* Планируется: * На данный момент имеется:
+ Сокращение импортов в ваших хендлерах (громадные импорты в example.py) Роутеры
+ Сокращение "построения" клавиатур Билдер инлайн клавиатур
+ Разработка контекста бота Этакая машина состояний и контекст к нему
+ Разработка Longpoll метода Поллинг и вебхук методы запуска
+ Доработка базовой составляющей проекта Логгирование
+ и так далее...
```bash
Пример бота описан в example.py
Перед запуском примера установите зависимости:
pip install -r requirements.txt
Запуск бота:
python example.py
```
### Контакты ### Контакты
[Группа MAX](https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8) [Группа MAX](https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8)

View File

@ -1,50 +1,127 @@
from maxapi.bot import Bot import asyncio
from maxapi.dispatcher import Dispatcher import logging
from maxapi.types.updates.message_created import MessageCreated
from maxapi.types.updates.message_callback import MessageCallback
from maxapi.types.attachments.attachment import ButtonsPayload
from maxapi.types.attachments.buttons.callback_button import CallbackButton
from maxapi.types.attachments.attachment import Attachment
from maxapi.enums.attachment import AttachmentType
from maxapi.enums.button_type import ButtonType
from maxapi.enums.intent import Intent
from maxapi.filters import F
from maxapi import Bot, Dispatcher, F
from maxapi.context import MemoryContext, State, StatesGroup
from maxapi.types import Command, MessageCreated, CallbackButton, MessageCallback
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
from for_example import router
logging.basicConfig(level=logging.INFO)
bot = Bot('токен') bot = Bot('токен')
dp = Dispatcher() dp = Dispatcher()
dp.include_routers(router)
# Отвечает только на текст "Привет"
@dp.message_created(F.message.body.text == 'Привет') start_text = '''Мои команды:
/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(obj: MessageCreated, context: MemoryContext):
await context.clear()
await obj.message.answer(f"Ваш контекст был очищен!")
@dp.message_created(Command('data'))
async def hello(obj: MessageCreated, context: MemoryContext):
data = await context.get_data()
await obj.message.answer(f"Ваша контекстная память: {str(data)}")
@dp.message_created(Command('context'))
@dp.message_created(Command('state'))
async def hello(obj: MessageCreated, context: MemoryContext):
data = await context.get_state()
await obj.message.answer(f"Ваше контекстное состояние: {str(data)}")
@dp.message_created(Command('start'))
async def hello(obj: MessageCreated): async def hello(obj: MessageCreated):
await obj.message.answer('Привет 👋') builder = InlineKeyboardBuilder()
# Отвечает только на текст "Клавиатура" builder.row(
@dp.message_created(F.message.body.text == 'Клавиатура') CallbackButton(
async def hello(obj: MessageCreated): text='Ввести свое имя',
button_1 = CallbackButton(type=ButtonType.CALLBACK, text='Кнопка 1', payload='1', intent=Intent.DEFAULT) payload='btn_1'
button_2 = CallbackButton(type=ButtonType.CALLBACK, text='Кнопка 2', payload='2', intent=Intent.DEFAULT) ),
CallbackButton(
text='Ввести свой возраст',
payload='btn_2'
)
)
builder.row(
CallbackButton(
text='Не хочу',
payload='btn_3'
)
)
keyboard = ButtonsPayload(buttons=[[button_1], [button_2]]) await obj.message.answer(
text=start_text,
attachments=[builder.as_markup()] # Для MAX клавиатура это вложение,
) # поэтому она в списке вложений
attachments = [Attachment(type=AttachmentType.INLINE_KEYBOARD, payload=keyboard)] @dp.message_callback(F.callback.payload == 'btn_1')
async def hello(obj: MessageCallback, context: MemoryContext):
await obj.message.answer('Привет 👋', attachments=attachments) await context.set_state(Form.name)
await obj.message.delete()
# Ответчает на коллбек с начинкой "1" await obj.message.answer(f'Отправьте свое имя:')
@dp.message_callback(F.callback.payload == '1')
async def _(obj: MessageCallback):
await obj.message.answer('Вы нажали на кнопку 1 🤩')
# Ответчает на коллбек с начинкой "2"
@dp.message_callback(F.callback.payload == '2')
async def _(obj: MessageCallback):
await obj.message.answer('Вы нажали на кнопку 2 🥳')
# Отвечает на любое текстовое сообщение
@dp.message_created(F.message.body.text)
async def hello(obj: MessageCreated):
await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}')
dp.handle_webhook(bot) @dp.message_callback(F.callback.payload == 'btn_2')
async def hello(obj: MessageCallback, context: MemoryContext):
await context.set_state(Form.age)
await obj.message.delete()
await obj.message.answer(f'Отправьте ваш возраст:')
@dp.message_callback(F.callback.payload == 'btn_3')
async def hello(obj: MessageCallback, context: MemoryContext):
await obj.message.delete()
await obj.message.answer(f'Ну ладно 🥲')
@dp.message_created(F.message.body.text, Form.name)
async def hello(obj: MessageCreated, context: MemoryContext):
await context.update_data(name=obj.message.body.text)
data = await context.get_data()
await obj.message.answer(f"Приятно познакомиться, {data['name'].title()}!")
@dp.message_created(F.message.body.text, Form.age)
async def hello(obj: MessageCreated, context: MemoryContext):
await context.update_data(age=obj.message.body.text)
await obj.message.answer(f"Ого! А мне всего пару недель 😁")
async def main():
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}")

10
maxapi/__init__.py Normal file
View File

@ -0,0 +1,10 @@
from .bot import Bot
from .dispatcher import Dispatcher, Router
from .filters import F
__all__ = [
Bot,
Dispatcher,
F,
Router
]

View File

@ -1,25 +1,58 @@
from typing import Any, Dict, List from datetime import datetime
from typing import Any, Dict, List, TYPE_CHECKING
from .methods.get_updates import GetUpdates
from .methods.remove_member_chat import RemoveMemberChat
from .methods.add_admin_chat import AddAdminChat
from .methods.add_members_chat import AddMembersChat
from .methods.get_members_chat import GetMembersChat
from .methods.remove_admin import RemoveAdmin
from .methods.get_list_admin_chat import GetListAdminChat
from .methods.delete_bot_from_chat import DeleteMeFromMessage
from .methods.get_me_from_chat import GetMeFromChat
from .methods.delete_pin_message import DeletePinMessage
from .methods.get_pinned_message import GetPinnedMessage
from .methods.pin_message import PinMessage
from .methods.delete_chat import DeleteChat
from .methods.send_action import SendAction
from .methods.edit_chat import EditChat
from .methods.get_chat_by_id import GetChatById
from .methods.get_chat_by_link import GetChatByLink
from .methods.send_callback import SendCallback
from .methods.get_video import GetVideo
from .methods.delete_message import DeleteMessage
from .methods.edit_message import EditMessage from .methods.edit_message import EditMessage
from .enums.parse_mode import ParseMode
from .types.attachments.attachment import Attachment
from .types.message import NewMessageLink
from .types.users import BotCommand
from .methods.change_info import ChangeInfo from .methods.change_info import ChangeInfo
from .methods.get_me import GetMe from .methods.get_me import GetMe
from .methods.get_messages import GetMessages from .methods.get_messages import GetMessages
from .methods.get_chats import GetChats from .methods.get_chats import GetChats
from .methods.send_message import SendMessage from .methods.send_message import SendMessage
from .enums.parse_mode import ParseMode
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 .connection.base import BaseConnection from .connection.base import BaseConnection
if TYPE_CHECKING:
from .types.message import Message
class Bot(BaseConnection): class Bot(BaseConnection):
def __init__(self, token: str): def __init__(self, token: str):
super().__init__()
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
async def send_message( async def send_message(
self, self,
@ -44,6 +77,17 @@ class Bot(BaseConnection):
parse_mode=parse_mode parse_mode=parse_mode
).request() ).request()
async def send_action(
self,
chat_id: int = None,
action: SenderAction = SenderAction.TYPING_ON
):
return await SendAction(
bot=self,
chat_id=chat_id,
action=action
).request()
async def edit_message( async def edit_message(
self, self,
message_id: str, message_id: str,
@ -62,13 +106,51 @@ class Bot(BaseConnection):
notify=notify, notify=notify,
parse_mode=parse_mode parse_mode=parse_mode
).request() ).request()
async def delete_message(
self,
message_id: str
):
return await DeleteMessage(
bot=self,
message_id=message_id,
).request()
async def delete_chat(
self,
chat_id: int
):
return await DeleteChat(
bot=self,
chat_id=chat_id,
).request()
async def get_messages(self, chat_id: int = None): async def get_messages(
return await GetMessages(self, chat_id).request() self,
chat_id: int = None,
message_ids: List[str] = None,
from_time: datetime | int = None,
to_time: datetime | int = None,
count: int = 50,
):
return await GetMessages(
bot=self,
chat_id=chat_id,
message_ids=message_ids,
from_time=from_time,
to_time=to_time,
count=count
).request()
async def get_message(self, message_id: str):
return await self.get_messages(message_ids=[message_id])
async def get_me(self): async def get_me(self):
return await GetMe(self).request() return await GetMe(self).request()
async def get_pin_message(self, chat_id: int):
return await GetPinnedMessage(bot=self, chat_id=chat_id).request()
async def change_info( async def change_info(
self, self,
name: str = None, name: str = None,
@ -85,5 +167,172 @@ class Bot(BaseConnection):
photo=photo photo=photo
).request() ).request()
async def get_chats(self): async def get_chats(
return await GetChats(self).request() self,
count: int = 50,
marker: int = None
):
return await GetChats(
bot=self,
count=count,
marker=marker
).request()
async def get_chat_by_link(self, link: str):
"""под вопросом"""
return await GetChatByLink(bot=self, link=link).request()
async def get_chat_by_id(self, id: int):
return await GetChatById(bot=self, id=id).request()
async def edit_chat(
self,
chat_id: int,
icon: PhotoAttachmentRequestPayload = None,
title: str = None,
pin: str = None,
notify: bool = True,
):
return await EditChat(
bot=self,
chat_id=chat_id,
icon=icon,
title=title,
pin=pin,
notify=notify
).request()
async def get_video(self, video_token: str):
return await GetVideo(bot=self, video_token=video_token).request()
async def send_callback(
self,
callback_id: str,
message: 'Message' = None,
notification: str = None
):
return await SendCallback(
bot=self,
callback_id=callback_id,
message=message,
notification=notification
).request()
async def pin_message(
self,
chat_id: int,
message_id: str,
notify: bool = True
):
return await PinMessage(
bot=self,
chat_id=chat_id,
message_id=message_id,
notify=notify
).request()
async def delete_pin_message(
self,
chat_id: int,
):
return await DeletePinMessage(
bot=self,
chat_id=chat_id,
).request()
async def get_me_from_chat(
self,
chat_id: int,
):
return await GetMeFromChat(
bot=self,
chat_id=chat_id,
).request()
async def delete_me_from_chat(
self,
chat_id: int,
):
return await DeleteMeFromMessage(
bot=self,
chat_id=chat_id,
).request()
async def get_list_admin_chat(
self,
chat_id: int,
):
return await GetListAdminChat(
bot=self,
chat_id=chat_id,
).request()
async def add_list_admin_chat(
self,
chat_id: int,
admins: List[ChatAdmin],
marker: int = None
):
return await AddAdminChat(
bot=self,
chat_id=chat_id,
admins=admins,
marker=marker,
).request()
async def remove_admin(
self,
chat_id: int,
user_id: int
):
return await RemoveAdmin(
bot=self,
chat_id=chat_id,
user_id=user_id,
).request()
async def get_chat_members(
self,
chat_id: int,
user_ids: List[int] = None,
marker: int = None,
count: int = None,
):
return await GetMembersChat(
bot=self,
chat_id=chat_id,
user_ids=user_ids,
marker=marker,
count=count,
).request()
async def add_chat_members(
self,
chat_id: int,
user_ids: List[str],
):
return await AddMembersChat(
bot=self,
chat_id=chat_id,
user_ids=user_ids,
).request()
async def kick_chat_member(
self,
chat_id: int,
user_id: int,
block: bool = False,
):
return await RemoveMemberChat(
bot=self,
chat_id=chat_id,
user_id=user_id,
block=block,
).request()
async def get_updates(
self,
):
return await GetUpdates(
bot=self,
).request()

View File

@ -4,12 +4,17 @@ from pydantic import BaseModel
from ..types.errors import Error from ..types.errors import Error
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
from ..loggers import logger_bot
class BaseConnection: class BaseConnection:
API_URL = 'https://botapi.max.ru' API_URL = 'https://botapi.max.ru'
def __init__(self):
self.bot = None
self.session = None
async def request( async def request(
self, self,
method: HTTPMethod, method: HTTPMethod,
@ -18,19 +23,34 @@ class BaseConnection:
is_return_raw: bool = False, is_return_raw: bool = False,
**kwargs **kwargs
): ):
async with aiohttp.ClientSession(self.API_URL) as s:
r = await s.request( if not self.bot.session:
method=method.value, self.bot.session = aiohttp.ClientSession(self.bot.API_URL)
url=path.value,
**kwargs
)
if not r.ok: r = await self.bot.session.request(
raw = await r.text() method=method.value,
return Error(code=r.status, text=raw) url=path.value if isinstance(path, ApiPath) else path,
**kwargs
raw = await r.json() )
if is_return_raw: return raw if not r.ok:
raw = await r.text()
error = Error(code=r.status, text=raw)
logger_bot.error(error)
return error
raw = await r.json()
return model(**raw) if is_return_raw: return raw
model = model(**raw)
if hasattr(model, 'message'):
attr = getattr(model, 'message')
if hasattr(attr, 'bot'):
attr.bot = self.bot
if hasattr(model, 'bot'):
model.bot = self.bot
return model

View File

@ -0,0 +1,36 @@
import asyncio
from typing import Any, Dict
from ..context.state_machine import State, StatesGroup
class MemoryContext:
def __init__(self, chat_id: int, user_id: int):
self.chat_id = chat_id
self.user_id = user_id
self._context: Dict[str, Any] = {}
self._state: State | None = None
self._lock = asyncio.Lock()
async def get_data(self) -> dict[str, Any]:
async with self._lock:
return self._context
async def set_data(self, data: dict[str, Any]):
async with self._lock:
self._context = data
async def update_data(self, **kwargs):
async with self._lock:
self._context.update(kwargs)
async def set_state(self, state: State | str = None):
self._state = state
async def get_state(self):
return self._state
async def clear(self):
self._state = None
self._context = {}

View File

@ -0,0 +1,16 @@
class State:
def __init__(self):
self.name = None
def __set_name__(self, owner, attr_name):
self.name = f'{owner.__name__}:{attr_name}'
def __str__(self):
return self.name
class StatesGroup:
@classmethod
def states(cls) -> list[str]:
return [str(getattr(cls, attr)) for attr in dir(cls)
if isinstance(getattr(cls, attr), State)]

View File

@ -1,54 +1,33 @@
from typing import Callable, List from typing import Callable, List
import uvicorn
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from magic_filter import MagicFilter from fastapi.responses import JSONResponse
from uvicorn import Config, Server
from .filters import filter_m from .filters.handler import Handler
from .types.updates import Update
from .context import MemoryContext
from .types.updates import UpdateUnion
from .types.errors import Error
from .methods.types.getted_updates import process_update_webhook, process_update_request
from .filters import filter_attrs
from .bot import Bot from .bot import Bot
from .enums.update import UpdateType from .enums.update import UpdateType
from .types.updates.bot_added import BotAdded from .loggers import logger_dp
from .types.updates.bot_removed import BotRemoved
from .types.updates.bot_started import BotStarted
from .types.updates.chat_title_changed import ChatTitleChanged
from .types.updates.message_callback import MessageCallback
from .types.updates.message_chat_created import MessageChatCreated
from .types.updates.message_created import MessageCreated
from .types.updates.message_edited import MessageEdited
from .types.updates.message_removed import MessageRemoved
from .types.updates.user_added import UserAdded
from .types.updates.user_removed import UserRemoved
from .loggers import logger
class Handler: app = FastAPI()
def __init__(
self,
*args,
func_event: Callable,
update_type: UpdateType,
**kwargs
):
self.func_event = func_event
self.update_type = update_type
self.filters = []
for arg in args:
if isinstance(arg, MagicFilter):
arg: MagicFilter = arg
self.filters.append(arg)
class Dispatcher: class Dispatcher:
def __init__(self): def __init__(self):
self.event_handlers = [] self.event_handlers: List[Handler] = []
self.contexts: List[MemoryContext] = []
self.bot = None self.bot = None
self.on_started_func = None
self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self) self.message_created = Event(update_type=UpdateType.MESSAGE_CREATED, router=self)
self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self) self.bot_added = Event(update_type=UpdateType.BOT_ADDED, router=self)
@ -61,70 +40,114 @@ class Dispatcher:
self.message_removed = Event(update_type=UpdateType.MESSAGE_REMOVED, router=self) self.message_removed = Event(update_type=UpdateType.MESSAGE_REMOVED, router=self)
self.user_added = Event(update_type=UpdateType.USER_ADDED, router=self) self.user_added = Event(update_type=UpdateType.USER_ADDED, router=self)
self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self) self.user_removed = Event(update_type=UpdateType.USER_REMOVED, router=self)
self.on_started = Event(update_type=UpdateType.ON_STARTED, router=self)
def include_routers(self, *routers: 'Router'): def include_routers(self, *routers: 'Router'):
for router in routers: for router in routers:
for event in router.event_handlers: for event in router.event_handlers:
self.event_handlers.append(event) self.event_handlers.append(event)
def handle_webhook(self, bot: Bot, host: str = '0.0.0.0', port: int = 8080): def get_memory_context(self, chat_id: int, user_id: int):
for ctx in self.contexts:
if ctx.chat_id == chat_id and ctx.user_id == user_id:
return ctx
new_ctx = MemoryContext(chat_id, user_id)
self.contexts.append(new_ctx)
return new_ctx
async def handle(self, event_object: UpdateUnion):
for handler in self.event_handlers:
if not handler.update_type == event_object.update_type:
continue
if handler.filters:
if not filter_attrs(event_object, *handler.filters):
continue
memory_context = self.get_memory_context(
*event_object.get_ids()
)
if not handler.state == await memory_context.get_state() \
and handler.state:
continue
func_args = handler.func_event.__annotations__.keys()
kwargs = {'context': memory_context}
for key in kwargs.copy().keys():
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)
logger_dp.info(f'Обработано: {event_object.update_type}')
break
async def start_polling(self, bot: Bot):
self.bot = bot self.bot = bot
app = FastAPI() logger_dp.info(f'{len(self.event_handlers)} event handlers started')
@app.post("/") if self.on_started_func:
await self.on_started_func()
while True:
try:
events = await self.bot.get_updates()
if isinstance(events, Error):
logger_dp.info(f'Ошибка при получении обновлений: {events}')
continue
self.bot.marker_updates = events.get('marker')
processed_events = await process_update_request(
events=events,
bot=self.bot
)
for event in processed_events:
try:
await self.handle(event)
except Exception as e:
logger_dp.error(f"Ошибка при обработке события: {events['update_type']}: {e}")
except Exception as e:
logger_dp.error(f'Общая ошибка при обработке событий: {e}')
async def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080):
self.bot = bot
if self.on_started_func:
await self.on_started_func()
@app.post('/')
async def _(request: Request): async def _(request: Request):
try: try:
event_json = await request.json() event_json = await request.json()
event = Update(**event_json)
event_object = None event_object = await process_update_webhook(
match event.update_type: event_json=event_json,
case UpdateType.BOT_ADDED: bot=self.bot
event_object = BotAdded(**event_json) )
case UpdateType.BOT_REMOVED:
event_object = BotRemoved(**event_json)
case UpdateType.BOT_STARTED:
event_object = BotStarted(**event_json)
case UpdateType.CHAT_TITLE_CHANGED:
event_object = ChatTitleChanged(**event_json)
case UpdateType.MESSAGE_CALLBACK:
event_object = MessageCallback(**event_json)
event_object.message.bot = self.bot
case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event_json)
case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event_json)
event_object.message.bot = self.bot
case UpdateType.MESSAGE_EDITED:
event_object = MessageEdited(**event_json)
case UpdateType.MESSAGE_REMOVED:
event_object = MessageRemoved(**event_json)
case UpdateType.USER_ADDED:
event_object = UserAdded(**event_json)
case UpdateType.USER_REMOVED:
event_object = UserRemoved(**event_json)
handlers: List[Handler] = self.event_handlers await self.handle(event_object)
for handler in handlers:
if not handler.update_type == event.update_type:
continue
if handler.filters:
if not filter_m(event_object, *handler.filters):
continue
await handler.func_event(event_object)
break
return True return JSONResponse(content={'ok': True}, status_code=200)
except Exception as e: except Exception as e:
print(e) logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {e}")
...
logger.info(f'{len(self.event_handlers)} event handlers started') logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
uvicorn.run(app, host=host, port=port, log_level='critical') config = Config(app=app, host=host, port=port, log_level="critical")
server = Server(config)
await server.serve()
class Router(Dispatcher): class Router(Dispatcher):
@ -139,13 +162,16 @@ class Event:
def __call__(self, *args, **kwargs): def __call__(self, *args, **kwargs):
def decorator(func_event: Callable): def decorator(func_event: Callable):
self.router.event_handlers.append( if self.update_type == UpdateType.ON_STARTED:
Handler( self.router.on_started_func = func_event
func_event=func_event, else:
update_type=self.update_type, self.router.event_handlers.append(
*args, **kwargs Handler(
func_event=func_event,
update_type=self.update_type,
*args, **kwargs
)
) )
)
return func_event return func_event
return decorator return decorator

View File

@ -4,4 +4,10 @@ class ApiPath(str, Enum):
ME = '/me' ME = '/me'
CHATS = '/chats' CHATS = '/chats'
MESSAGES = '/messages' MESSAGES = '/messages'
UPDATES = '/updates' UPDATES = '/updates'
VIDEOS = '/videos'
ANSWERS = '/answers'
ACTIONS = '/actions'
PIN = '/pin'
MEMBERS = '/members'
ADMINS = '/admins'

View File

@ -1,7 +1,7 @@
from enum import Enum from enum import Enum
class ButtonType(Enum): class ButtonType(str, Enum):
REQUEST_CONTACT = 'request_contact' REQUEST_CONTACT = 'request_contact'
CALLBACK = 'callback' CALLBACK = 'callback'
LINK = 'link' LINK = 'link'

View File

@ -0,0 +1,10 @@
from enum import Enum
class ChatPermission(str, Enum):
READ_ALL_MESSAGES = 'read_all_messages'
ADD_REMOVE_MEMBERS = 'add_remove_members'
ADD_ADMINS = 'add_admins'
CHANGE_CHAT_INFO = 'change_chat_info'
PIN_MESSAGE = 'pin_message'
WRITE = 'write'

View File

@ -6,3 +6,4 @@ class HTTPMethod(str, Enum):
GET = 'GET' GET = 'GET'
PATCH = 'PATCH' PATCH = 'PATCH'
PUT = 'PUT' PUT = 'PUT'
DELETE = 'DELETE'

View File

@ -0,0 +1,9 @@
from enum import Enum
class SenderAction(str, Enum):
TYPING_ON = 'typing_on'
SENDING_PHOTO = 'sending_photo'
SENDING_VIDEO = 'sending_video'
SENDING_AUDIO = 'sending_audio'
SENDING_FILE = 'sending_file'
MARK_SEEN = 'mark_seen'

View File

@ -11,4 +11,6 @@ class UpdateType(str, Enum):
MESSAGE_EDITED = 'message_edited' MESSAGE_EDITED = 'message_edited'
MESSAGE_REMOVED = 'message_removed' MESSAGE_REMOVED = 'message_removed'
USER_ADDED = 'user_added' USER_ADDED = 'user_added'
USER_REMOVED = 'user_removed' USER_REMOVED = 'user_removed'
ON_STARTED = 'on_started'

View File

@ -6,7 +6,7 @@ from magic_filter.operations.comparator import ComparatorOperation as mf_compara
F = MagicFilter() F = MagicFilter()
def filter_m(obj, *magic_args): def filter_attrs(obj, *magic_args):
try: try:
for arg in magic_args: for arg in magic_args:

31
maxapi/filters/handler.py Normal file
View File

@ -0,0 +1,31 @@
from typing import Callable
from magic_filter import F, MagicFilter
from ..types.command import Command
from ..context.state_machine import State
from ..enums.update import UpdateType
class Handler:
def __init__(
self,
*args,
func_event: Callable,
update_type: UpdateType,
**kwargs
):
self.func_event = func_event
self.update_type = update_type
self.filters = []
self.state = None
for arg in args:
if isinstance(arg, MagicFilter):
self.filters.append(arg)
elif isinstance(arg, State):
self.state = arg
elif isinstance(arg, Command):
self.filters.insert(0, F.message.body.text == arg.command)

View File

@ -1,4 +1,4 @@
import logging import logging
logging.basicConfig(level=logging.INFO) logger_bot = logging.getLogger('bot')
logger = logging.getLogger('bot') logger_dp = logging.getLogger('dispatcher')

View File

@ -0,0 +1,46 @@
from re import findall
from typing import TYPE_CHECKING, List
from .types.added_admin_chat import AddedListAdminChat
from ..types.users import ChatAdmin
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class AddAdminChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
admins: List[ChatAdmin],
marker: int = None
):
self.bot = bot
self.chat_id = chat_id
self.admins = admins
self.marker = marker
async def request(self) -> AddedListAdminChat:
json = {}
json['admins'] = [admin.model_dump() for admin in self.admins]
json['marker'] = self.marker
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,
model=AddedListAdminChat,
params=self.bot.params,
json=json
)

View File

@ -0,0 +1,42 @@
from re import findall
from typing import TYPE_CHECKING, List
from ..methods.types.added_members_chat import AddedMembersChat
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class AddMembersChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
user_ids: List[int],
):
self.bot = bot
self.chat_id = chat_id
self.user_ids = user_ids
async def request(self) -> AddedMembersChat:
json = {}
json['user_ids'] = self.user_ids
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS,
model=AddedMembersChat,
params=self.bot.params,
json=json
)

View File

@ -0,0 +1,30 @@
from typing import TYPE_CHECKING
from ..methods.types.deleted_bot_from_chat import DeletedBotFromChat
from ..methods.types.deleted_message import DeletedMessage
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class DeleteMeFromMessage(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> DeletedBotFromChat:
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
model=DeletedBotFromChat,
params=self.bot.params,
)

View File

@ -0,0 +1,29 @@
from typing import TYPE_CHECKING
from ..methods.types.deleted_chat import DeletedChat
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class DeleteChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> DeletedChat:
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS.value + '/' + str(self.chat_id),
model=DeletedChat,
params=self.bot.params
)

View File

@ -0,0 +1,33 @@
from typing import TYPE_CHECKING
from ..methods.types.deleted_message import DeletedMessage
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class DeleteMessage(BaseConnection):
def __init__(
self,
bot: 'Bot',
message_id: str,
):
self.bot = bot
self.message_id = message_id
async def request(self) -> DeletedMessage:
params = self.bot.params.copy()
params['message_id'] = self.message_id
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.MESSAGES,
model=DeletedMessage,
params=params,
)

View File

@ -0,0 +1,29 @@
from typing import TYPE_CHECKING
from ..methods.types.deleted_pin_message import DeletedPinMessage
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class DeletePinMessage(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: str,
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> DeletedPinMessage:
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
model=DeletedPinMessage,
params=self.bot.params,
)

View File

@ -0,0 +1,68 @@
from logging import getLogger
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 ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
logger = getLogger(__name__)
if TYPE_CHECKING:
from ..bot import Bot
class EditChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
icon: PhotoAttachmentRequestPayload = None,
title: str = None,
pin: str = None,
notify: bool = True,
):
self.bot = bot
self.chat_id = chat_id
self.icon = icon
self.title = title
self.pin = pin
self.notify = notify
async def request(self) -> Chat:
json = {}
if self.icon:
dump = self.icon.model_dump()
counter = Counter(dump.values())
if not None in counter or \
not counter[None] == 2:
return logger.error(
'Все атрибуты модели Icon являются взаимоисключающими | '
'https://dev.max.ru/docs-api/methods/PATCH/chats/-chatId-'
)
json['icon'] = dump
if self.title: json['title'] = self.title
if self.pin: json['pin'] = self.pin
if self.notify: json['notify'] = self.notify
return await super().request(
method=HTTPMethod.PATCH,
path=ApiPath.CHATS.value + '/' + str(self.chat_id),
model=Chat,
params=self.bot.params,
json=json
)

View File

@ -1,15 +1,11 @@
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING
from aiomax.enums.parse_mode import ParseMode from .types.edited_message import EditedMessage
from ..types.message import NewMessageLink
from ..types.attachments.attachment import Attachment from ..types.attachments.attachment import Attachment
from ..enums.parse_mode import ParseMode
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
from ..types.message import NewMessageLink
from .types.edited_message import EditedMessage
from ..connection.base import BaseConnection from ..connection.base import BaseConnection
@ -23,8 +19,8 @@ class EditMessage(BaseConnection):
bot: 'Bot', bot: 'Bot',
message_id: str, message_id: str,
text: str = None, text: str = None,
attachments: List[Attachment] = None, attachments: List['Attachment'] = None,
link: NewMessageLink = None, link: 'NewMessageLink' = None,
notify: bool = True, notify: bool = True,
parse_mode: ParseMode = None parse_mode: ParseMode = None
): ):
@ -36,7 +32,7 @@ class EditMessage(BaseConnection):
self.notify = notify self.notify = notify
self.parse_mode = parse_mode self.parse_mode = parse_mode
async def request(self) -> 'EditedMessage': async def request(self) -> EditedMessage:
params = self.bot.params.copy() params = self.bot.params.copy()
json = {} json = {}

View File

@ -0,0 +1,36 @@
from re import findall
from typing import TYPE_CHECKING
from ..types.chats import Chat
from ..types.users import User
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetChatById(BaseConnection):
def __init__(
self,
bot: 'Bot',
id: int
):
self.bot = bot
self.id = id
async def request(self) -> Chat:
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.id),
model=Chat,
params=self.bot.params
)

View File

@ -0,0 +1,41 @@
from re import findall
from typing import TYPE_CHECKING
from ..types.chats import Chat
from ..types.users import User
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetChatByLink(BaseConnection):
PATTERN_LINK = r'@?[a-zA-Z]+[a-zA-Z0-9-_]*'
def __init__(
self,
bot: 'Bot',
link: str
):
self.bot = bot
self.link = findall(self.PATTERN_LINK, link)
if not self.link:
return
async def request(self) -> Chat:
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + self.link[-1],
model=Chat,
params=self.bot.params
)

View File

@ -17,13 +17,27 @@ if TYPE_CHECKING:
class GetChats(BaseConnection): class GetChats(BaseConnection):
def __init__(self, bot: 'Bot'): def __init__(
self,
bot: 'Bot',
count: int = 50,
marker: int = None
):
self.bot = bot self.bot = bot
self.count = count
self.marker = marker
async def request(self) -> Chats: async def request(self) -> Chats:
params = self.bot.params.copy()
params['count'] = self.count
if self.marker:
params['marker'] = self.marker
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.CHATS, path=ApiPath.CHATS,
model=Chats, model=Chats,
params=self.bot.params params=params
) )

View File

@ -0,0 +1,34 @@
from re import findall
from typing import TYPE_CHECKING
from ..methods.types.getted_list_admin_chat import GettedListAdminChat
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetListAdminChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> GettedListAdminChat:
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ADMINS,
model=GettedListAdminChat,
params=self.bot.params
)

View File

@ -0,0 +1,34 @@
from typing import TYPE_CHECKING
from ..types.chats import ChatMember, Chats
from ..types.users import User
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetMeFromChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> ChatMember:
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.MEMBERS + ApiPath.ME,
model=ChatMember,
params=self.bot.params
)

View File

@ -0,0 +1,47 @@
from re import findall
from typing import TYPE_CHECKING, List
from ..methods.types.getted_members_chat import GettedMembersChat
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetMembersChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
user_ids: List[str] = None,
marker: int = None,
count: int = None,
):
self.bot = bot
self.chat_id = chat_id
self.user_ids = user_ids
self.marker = marker
self.count = count
async def request(self) -> GettedMembersChat:
params = self.bot.params.copy()
if self.user_ids: params['user_ids'] = ','.join(self.user_ids)
if self.marker: params['marker'] = self.marker
if self.count: params['marker'] = self.count
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS,
model=GettedMembersChat,
params=params
)

View File

@ -1,6 +1,7 @@
from typing import TYPE_CHECKING from datetime import datetime
from typing import TYPE_CHECKING, List
from ..types.message import Messages from ..types.message import Messages
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
@ -13,15 +14,44 @@ if TYPE_CHECKING:
class GetMessages(BaseConnection): class GetMessages(BaseConnection):
def __init__(self, bot: 'Bot', chat_id: int = None): def __init__(
self,
bot: 'Bot',
chat_id: int,
message_ids: List[str] = None,
from_time: datetime | int = None,
to_time: datetime | int = None,
count: int = 50,
):
self.bot = bot self.bot = bot
self.chat_id = chat_id self.chat_id = chat_id
self.message_ids = message_ids
self.from_time = from_time
self.to_time = to_time
self.count = count
async def request(self) -> Messages: async def request(self) -> Messages:
params = self.bot.params.copy() params = self.bot.params.copy()
if self.chat_id: params['chat_id'] = self.chat_id if self.chat_id: params['chat_id'] = self.chat_id
if self.message_ids:
params['message_ids'] = ','.join(self.message_ids)
if self.from_time:
if isinstance(self.from_time, datetime):
params['from_time'] = int(self.from_time.timestamp())
else:
params['from_time'] = self.from_time
if self.to_time:
if isinstance(self.to_time, datetime):
params['to_time'] = int(self.to_time.timestamp())
else:
params['to_time'] = self.to_time
params['count'] = self.count
return await super().request( return await super().request(
method=HTTPMethod.GET, method=HTTPMethod.GET,
path=ApiPath.MESSAGES, path=ApiPath.MESSAGES,

View File

@ -0,0 +1,32 @@
from datetime import datetime
from typing import TYPE_CHECKING, List
from .types.getted_pineed_message import GettedPin
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetPinnedMessage(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
):
self.bot = bot
self.chat_id = chat_id
async def request(self) -> GettedPin:
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
model=GettedPin,
params=self.bot.params
)

View File

@ -0,0 +1,43 @@
from datetime import datetime
from typing import TYPE_CHECKING, List
from ..types.updates import UpdateUnion
from ..methods.types.getted_updates import process_update_request
from ..types.message import Messages
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetUpdates(BaseConnection):
def __init__(
self,
bot: 'Bot',
limit: int = 100,
):
self.bot = bot
self.limit = limit
async def request(self) -> UpdateUnion:
params = self.bot.params.copy()
params['limit'] = self.limit
event_json = await super().request(
method=HTTPMethod.GET,
path=ApiPath.UPDATES,
model=None,
params=params,
is_return_raw=True
)
return event_json

View File

@ -0,0 +1,34 @@
from typing import List, TYPE_CHECKING
from ..types.attachments.video import Video
from .types.edited_message import EditedMessage
from ..types.message import NewMessageLink
from ..types.attachments.attachment import Attachment
from ..enums.parse_mode import ParseMode
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class GetVideo(BaseConnection):
def __init__(
self,
bot: 'Bot',
video_token: str
):
self.bot = bot
self.video_token = video_token
async def request(self) -> Video:
return await super().request(
method=HTTPMethod.GET,
path=ApiPath.VIDEOS.value + '/' + self.video_token,
model=Video,
params=self.bot.params,
)

View File

@ -0,0 +1,42 @@
from datetime import datetime
from typing import TYPE_CHECKING, List
from .types.pinned_message import PinnedMessage
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class PinMessage(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
message_id: str,
notify: bool = True
):
self.bot = bot
self.chat_id = chat_id
self.message_id = message_id
self.notify = notify
async def request(self) -> PinnedMessage:
json = {}
json['message_id'] = self.message_id
json['notify'] = self.notify
return await super().request(
method=HTTPMethod.PUT,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.PIN,
model=PinnedMessage,
params=self.bot.params,
json=json
)

View File

@ -0,0 +1,36 @@
from typing import TYPE_CHECKING
from .types.removed_admin import RemovedAdmin
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class RemoveAdmin(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
user_id: int
):
self.bot = bot
self.chat_id = chat_id
self.user_id = user_id
async def request(self) -> RemovedAdmin:
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + \
ApiPath.MEMBERS + ApiPath.ADMINS + '/' + str(self.user_id),
model=RemovedAdmin,
params=self.bot.params,
)

View File

@ -0,0 +1,42 @@
from typing import TYPE_CHECKING, List
from .types.removed_member_chat import RemovedMemberChat
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class RemoveMemberChat(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int,
user_id: int,
block: bool = False,
):
self.bot = bot
self.chat_id = chat_id
self.user_id = user_id
self.block = block
async def request(self) -> RemovedMemberChat:
params = self.bot.params.copy()
params['chat_id'] = self.chat_id
params['user_id'] = self.user_id
params['block'] = str(self.block).lower()
return await super().request(
method=HTTPMethod.DELETE,
path=ApiPath.CHATS.value + '/' + str(self.chat_id) + ApiPath.MEMBERS,
model=RemovedMemberChat,
params=params,
)

View File

@ -0,0 +1,43 @@
from typing import List, TYPE_CHECKING
from ..enums.sender_action import SenderAction
from ..methods.types.sended_action import SendedAction
from .types.sended_message import SendedMessage
from ..types.message import NewMessageLink
from ..types.attachments.attachment import Attachment
from ..enums.parse_mode import ParseMode
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
class SendAction(BaseConnection):
def __init__(
self,
bot: 'Bot',
chat_id: int = None,
action: SenderAction = SenderAction.TYPING_ON
):
self.bot = bot
self.chat_id = chat_id
self.action = action
async def request(self) -> SendedAction:
json = {}
json['action'] = self.action.value
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.CHATS + '/' + str(self.chat_id) + ApiPath.ACTIONS,
model=SendedAction,
params=self.bot.params,
json=json
)

View File

@ -0,0 +1,53 @@
from typing import List, TYPE_CHECKING
from ..methods.types.sended_callback import SendedCallback
from .types.sended_message import SendedMessage
from ..types.attachments.attachment import Attachment
from ..enums.parse_mode import ParseMode
from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath
from ..connection.base import BaseConnection
if TYPE_CHECKING:
from ..bot import Bot
from ..types.message import Message
class SendCallback(BaseConnection):
def __init__(
self,
bot: 'Bot',
callback_id: str,
message: 'Message' = None,
notification: str = None
):
self.bot = bot
self.callback_id = callback_id
self.message = message
self.notification = notification
async def request(self) -> SendedCallback:
try:
params = self.bot.params.copy()
params['callback_id'] = self.callback_id
json = {}
if self.message: json['message'] = self.message.model_dump()
if self.notification: json['notification'] = self.notification
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.ANSWERS,
model=SendedCallback,
params=params,
json=json
)
except Exception as e:
print(e)
...

View File

@ -2,15 +2,12 @@
from typing import List, TYPE_CHECKING from typing import List, TYPE_CHECKING
from ..enums.parse_mode import ParseMode from .types.sended_message import SendedMessage
from ..types.message import NewMessageLink from ..types.message import NewMessageLink
from ..types.attachments.attachment import Attachment from ..types.attachments.attachment import Attachment
from ..enums.parse_mode import ParseMode
from ..enums.http_method import HTTPMethod from ..enums.http_method import HTTPMethod
from ..enums.api_path import ApiPath from ..enums.api_path import ApiPath
from .types.sended_message import SendedMessage
from ..connection.base import BaseConnection from ..connection.base import BaseConnection
@ -41,7 +38,7 @@ class SendMessage(BaseConnection):
self.notify = notify self.notify = notify
self.parse_mode = parse_mode self.parse_mode = parse_mode
async def request(self) -> 'SendedMessage': async def request(self) -> SendedMessage:
params = self.bot.params.copy() params = self.bot.params.copy()
json = {} json = {}
@ -53,7 +50,7 @@ class SendMessage(BaseConnection):
json['disable_link_preview'] = str(self.disable_link_preview).lower() json['disable_link_preview'] = str(self.disable_link_preview).lower()
if self.attachments: json['attachments'] = \ if self.attachments: json['attachments'] = \
[att.model_dump() for att in self.attachments] [att.model_dump() for att in self.attachments]
if not self.link is None: json['link'] = self.link.model_dump() if not self.link is None: json['link'] = self.link.model_dump()
if not self.notify is None: json['notify'] = self.notify if not self.notify is None: json['notify'] = self.notify

View File

@ -0,0 +1,9 @@
from typing import List, Optional
from pydantic import BaseModel
from ...types.chats import ChatMember
class AddedListAdminChat(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,9 @@
from typing import List, Optional
from pydantic import BaseModel
from ...types.chats import ChatMember
class AddedMembersChat(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,7 @@
from typing import Optional
from pydantic import BaseModel
class DeletedBotFromChat(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,7 @@
from typing import Optional
from pydantic import BaseModel
class DeletedChat(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,7 @@
from typing import Optional
from pydantic import BaseModel
class DeletedMessage(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,7 @@
from typing import Optional
from pydantic import BaseModel
class DeletedPinMessage(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -1,7 +1,7 @@
from typing import Optional
from pydantic import BaseModel from pydantic import BaseModel
from ...types.message import Message
class EditedMessage(BaseModel): class EditedMessage(BaseModel):
message: Message success: bool
message: Optional[str] = None

View File

@ -0,0 +1,9 @@
from typing import List, Optional
from pydantic import BaseModel
from ...types.chats import ChatMember
class GettedListAdminChat(BaseModel):
members: List[ChatMember]
marker: Optional[int] = None

View File

@ -0,0 +1,9 @@
from typing import List, Optional
from pydantic import BaseModel
from ...types.chats import ChatMember
class GettedMembersChat(BaseModel):
members: List[ChatMember]
marker: Optional[int] = None

View File

@ -0,0 +1,8 @@
from typing import Optional
from pydantic import BaseModel
from ...types.message import Message
class GettedPin(BaseModel):
message: Optional[Message] = None

View File

@ -0,0 +1,75 @@
from typing import TYPE_CHECKING
from ...enums.update import UpdateType
from ...types.updates.bot_added import BotAdded
from ...types.updates.bot_removed import BotRemoved
from ...types.updates.bot_started import BotStarted
from ...types.updates.chat_title_changed import ChatTitleChanged
from ...types.updates.message_callback import MessageCallback
from ...types.updates.message_chat_created import MessageChatCreated
from ...types.updates.message_created import MessageCreated
from ...types.updates.message_edited import MessageEdited
from ...types.updates.message_removed import MessageRemoved
from ...types.updates.user_added import UserAdded
from ...types.updates.user_removed import UserRemoved
if TYPE_CHECKING:
from ...bot import Bot
async def get_update_model(event: dict, bot: 'Bot'):
event_object = None
match event['update_type']:
case UpdateType.BOT_ADDED:
event_object = BotAdded(**event)
case UpdateType.BOT_REMOVED:
event_object = BotRemoved(**event)
case UpdateType.BOT_STARTED:
event_object = BotStarted(**event)
case UpdateType.CHAT_TITLE_CHANGED:
event_object = ChatTitleChanged(**event)
case UpdateType.MESSAGE_CALLBACK:
event_object = MessageCallback(**event)
case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event)
case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event)
case UpdateType.MESSAGE_EDITED:
event_object = MessageEdited(**event)
case UpdateType.MESSAGE_REMOVED:
event_object = MessageRemoved(**event)
case UpdateType.USER_ADDED:
event_object = UserAdded(**event)
case UpdateType.USER_REMOVED:
event_object = UserRemoved(**event)
if hasattr(event_object, 'bot'):
event_object.bot = bot
if hasattr(event_object, 'message'):
event_object.message.bot = bot
return event_object
async def process_update_request(events: dict, bot: 'Bot'):
events = [event for event in events['updates']]
objects = []
for event in events:
objects.append(
await get_update_model(
bot=bot,
event=event
)
)
return objects
async def process_update_webhook(event_json: dict, bot: 'Bot'):
return await get_update_model(
bot=bot,
event=event_json
)

View File

@ -0,0 +1,7 @@
from typing import Optional
from pydantic import BaseModel
class PinnedMessage(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,7 @@
from typing import List, Optional
from pydantic import BaseModel
class RemovedAdmin(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,9 @@
from typing import List, Optional
from pydantic import BaseModel
from ...types.chats import ChatMember
class RemovedMemberChat(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,7 @@
from typing import Optional
from pydantic import BaseModel
class SendedAction(BaseModel):
success: bool
message: Optional[str] = None

View File

@ -0,0 +1,14 @@
from typing import TYPE_CHECKING, Any, Optional
from pydantic import BaseModel, Field
if TYPE_CHECKING:
from ...bot import Bot
class SendedCallback(BaseModel):
success: bool
message: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]

View File

@ -1,3 +1,4 @@
from typing import Any
from pydantic import BaseModel from pydantic import BaseModel
from ...types.message import Message from ...types.message import Message

View File

@ -0,0 +1,49 @@
from ..types.updates.bot_added import BotAdded
from ..types.updates.bot_removed import BotRemoved
from ..types.updates.bot_started import BotStarted
from ..types.updates.chat_title_changed import ChatTitleChanged
from ..types.updates.message_callback import MessageCallback
from ..types.updates.message_chat_created import MessageChatCreated
from ..types.updates.message_created import MessageCreated
from ..types.updates.message_edited import MessageEdited
from ..types.updates.message_removed import MessageRemoved
from ..types.updates.user_added import UserAdded
from ..types.updates.user_removed import UserRemoved
from ..types.attachments.attachment import PhotoAttachmentPayload
from ..types.attachments.attachment import OtherAttachmentPayload
from ..types.attachments.attachment import ContactAttachmentPayload
from ..types.attachments.attachment import ButtonsPayload
from ..types.attachments.attachment import StickerAttachmentPayload
from ..types.attachments.buttons.callback_button import CallbackButton
from ..types.attachments.buttons.chat_button import ChatButton
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
__all__ = [
CallbackButton,
ChatButton,
LinkButton,
RequestContact,
RequestGeoLocationButton,
Command,
PhotoAttachmentPayload,
OtherAttachmentPayload,
ContactAttachmentPayload,
ButtonsPayload,
StickerAttachmentPayload,
BotAdded,
BotRemoved,
BotStarted,
ChatTitleChanged,
MessageCallback,
MessageChatCreated,
MessageCreated,
MessageEdited,
MessageRemoved,
UserAdded,
UserRemoved
]

View File

@ -1,13 +1,9 @@
from typing import List, Optional, Union from typing import List, Optional, Union
from pydantic import BaseModel from pydantic import BaseModel
from ...types.attachments.buttons.chat_button import ChatButton from ...types.attachments.buttons import InlineButtonUnion
from ...types.attachments.buttons.request_contact import RequestContact
from ...types.attachments.buttons.request_geo_location_button import RequestGeoLocationButton
from ...types.attachments.buttons.link_button import LinkButton
from ...types.users import User from ...types.users import User
from ...enums.attachment import AttachmentType from ...enums.attachment import AttachmentType
from .buttons.callback_button import CallbackButton
AttachmentUnion = [] AttachmentUnion = []
@ -34,15 +30,7 @@ class ContactAttachmentPayload(BaseModel):
class ButtonsPayload(BaseModel): class ButtonsPayload(BaseModel):
buttons: List[List[ buttons: List[List[InlineButtonUnion]]
Union[
LinkButton,
CallbackButton,
RequestGeoLocationButton,
RequestContact,
ChatButton
]
]]
class Attachment(BaseModel): class Attachment(BaseModel):

View File

@ -1,12 +1,15 @@
from typing import Literal from typing import Union
from pydantic import BaseModel
from ....enums.button_type import ButtonType from .callback_button import CallbackButton
from .chat_button import ChatButton
from .link_button import LinkButton
from .request_contact import RequestContact
from .request_geo_location_button import RequestGeoLocationButton
InlineButtonUnion = Union[
class Button(BaseModel): CallbackButton,
type: ButtonType ChatButton,
text: str LinkButton,
RequestContact,
class Config: RequestGeoLocationButton
use_enum_values = True ]

View File

@ -0,0 +1,12 @@
from typing import Literal
from pydantic import BaseModel
from ....enums.button_type import ButtonType
class Button(BaseModel):
type: ButtonType
text: str
class Config:
use_enum_values = True

View File

@ -1,9 +1,12 @@
from typing import Optional from typing import Optional
from maxapi.enums.button_type import ButtonType
from ....enums.intent import Intent from ....enums.intent import Intent
from . import Button from .button import Button
class CallbackButton(Button): class CallbackButton(Button):
type: ButtonType = ButtonType.CALLBACK
payload: Optional[str] = None payload: Optional[str] = None
intent: Intent intent: Intent = Intent.DEFAULT

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from ....types.attachments.buttons import Button from .button import Button
class ChatButton(Button): class ChatButton(Button):

View File

@ -1,6 +1,6 @@
from typing import Optional from typing import Optional
from ....types.attachments.buttons import Button from .button import Button
class LinkButton(Button): class LinkButton(Button):

View File

@ -1,4 +1,4 @@
from ....types.attachments.buttons import Button from .button import Button
class RequestContact(Button): class RequestContact(Button):

View File

@ -1,4 +1,4 @@
from ....types.attachments.buttons import Button from .button import Button
class RequestGeoLocationButton(Button): class RequestGeoLocationButton(Button):

View File

@ -1,6 +1,14 @@
from typing import Literal from typing import Literal, Optional
from pydantic import BaseModel
from .attachment import Attachment from .attachment import Attachment
class PhotoAttachmentRequestPayload(BaseModel):
url: Optional[str] = None
token: Optional[str] = None
photos: Optional[str] = None
class Image(Attachment): class Image(Attachment):
type: Literal['image'] = 'image' type: Literal['image'] = 'image'

View File

@ -1,9 +1,10 @@
from typing import Optional from typing import Literal, Optional
from .attachment import Attachment from .attachment import Attachment
class Share(Attachment): class Share(Attachment):
type: Literal['share'] = 'share'
title: Optional[str] = None title: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
image_url: Optional[str] = None image_url: Optional[str] = None

View File

@ -1,16 +1,35 @@
from typing import Literal, Optional from typing import TYPE_CHECKING, Any, Literal, Optional
from pydantic import BaseModel from pydantic import BaseModel, Field
from .attachment import Attachment from .attachment import Attachment
if TYPE_CHECKING:
from ...bot import Bot
class VideoUrl(BaseModel):
mp4_1080: Optional[str] = None
mp4_720: Optional[str] = None
mp4_480: Optional[str] = None
mp4_360: Optional[str] = None
mp4_240: Optional[str] = None
mp4_144: Optional[str] = None
hls: Optional[str] = None
class VideoThumbnail(BaseModel): class VideoThumbnail(BaseModel):
url: str url: str
class Video(Attachment): class Video(Attachment):
type: Literal['video'] = 'video' type: Optional[Literal['video']] = 'video'
token: Optional[str] = None
urls: Optional[VideoUrl] = None
thumbnail: VideoThumbnail thumbnail: VideoThumbnail
width: Optional[int] = None width: Optional[int] = None
height: Optional[int] = None height: Optional[int] = None
duration: Optional[int] = None duration: Optional[int] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional['Bot']

View File

@ -1,8 +1,10 @@
from typing import Optional from typing import List, Optional, Union
from pydantic import BaseModel from pydantic import BaseModel
from ..types.users import User from ..types.users import User
from ..types.users import User
class Callback(BaseModel): class Callback(BaseModel):
timestamp: int timestamp: int

View File

@ -1,8 +1,10 @@
from pydantic import BaseModel from pydantic import BaseModel, field_validator
from typing import List, Optional from typing import Dict, List, Optional
from enum import Enum from enum import Enum
from datetime import datetime from datetime import datetime
from ..enums.chat_permission import ChatPermission
from ..types.users import User from ..types.users import User
from ..types.message import Message from ..types.message import Message
@ -20,6 +22,7 @@ class ChatStatus(str, Enum):
class Icon(BaseModel): class Icon(BaseModel):
url: str url: str
class Chat(BaseModel): class Chat(BaseModel):
chat_id: int chat_id: int
type: ChatType type: ChatType
@ -29,7 +32,7 @@ class Chat(BaseModel):
last_event_time: int last_event_time: int
participants_count: int participants_count: int
owner_id: Optional[int] = None owner_id: Optional[int] = None
participants: None = None participants: Optional[Dict[str, datetime]] = None
is_public: bool is_public: bool
link: Optional[str] = None link: Optional[str] = None
description: Optional[str] = None description: Optional[str] = None
@ -38,9 +41,26 @@ class Chat(BaseModel):
chat_message_id: Optional[str] = None chat_message_id: Optional[str] = None
pinned_message: Optional[Message] = None pinned_message: Optional[Message] = None
@field_validator('participants', mode='before')
@classmethod
def convert_timestamps(cls, value: Dict[str, int]) -> Dict[str, datetime]:
return {
key: datetime.fromtimestamp(ts / 1000)
for key, ts in value.items()
}
class Config: class Config:
arbitrary_types_allowed=True arbitrary_types_allowed=True
class Chats(BaseModel): class Chats(BaseModel):
chats: List[Chat] = [] chats: List[Chat] = []
marker: Optional[int] = None
class ChatMember(User):
last_access_time: Optional[int] = None
is_owner: Optional[bool] = None
is_admin: Optional[bool] = None
join_time: Optional[int] = None
permissions: Optional[List[ChatPermission]] = None

9
maxapi/types/command.py Normal file
View File

@ -0,0 +1,9 @@
class Command:
def __init__(self, text: str, prefix: str = '/'):
self.text = text
self.prefix = prefix
@property
def command(self):
return self.prefix + self.text

View File

@ -87,7 +87,10 @@ class Message(BaseModel):
body: Optional[MessageBody] = None body: Optional[MessageBody] = None
stat: Optional[MessageStat] = None stat: Optional[MessageStat] = None
url: Optional[str] = None url: Optional[str] = None
bot: Optional[Any] = None bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
async def answer(self, async def answer(self,
text: str = None, text: str = None,
@ -97,8 +100,7 @@ class Message(BaseModel):
notify: bool = True, notify: bool = True,
parse_mode: ParseMode = None parse_mode: ParseMode = None
): ):
bot: Bot = self.bot return await self.bot.send_message(
return await bot.send_message(
chat_id=self.recipient.chat_id, chat_id=self.recipient.chat_id,
user_id=self.recipient.user_id, user_id=self.recipient.user_id,
text=text, text=text,
@ -108,10 +110,43 @@ class Message(BaseModel):
notify=notify, notify=notify,
parse_mode=parse_mode parse_mode=parse_mode
) )
async def edit(
self,
text: str = None,
attachments: List[Attachment] = None,
link: NewMessageLink = None,
notify: bool = True,
parse_mode: ParseMode = None
):
return await self.bot.edit_message(
message_id=self.body.mid,
text=text,
attachments=attachments,
link=link,
notify=notify,
parse_mode=parse_mode
)
async def delete(self):
return await self.bot.delete_message(
message_id=self.body.mid,
)
async def pin(self, notify: bool = True):
return await self.bot.pin_message(
chat_id=self.recipient.chat_id,
message_id=self.body.mid,
notify=notify
)
class Messages(BaseModel): class Messages(BaseModel):
messages: List[Message] messages: List[Message]
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
class NewMessageLink(BaseModel): class NewMessageLink(BaseModel):

View File

@ -1,11 +1,27 @@
from pydantic import BaseModel from typing import Union
from ...types.updates.bot_added import BotAdded
from ...enums.update import UpdateType from ...types.updates.bot_removed import BotRemoved
from ...types.updates.bot_started import BotStarted
from ...types.updates.chat_title_changed import ChatTitleChanged
from ...types.updates.message_callback import MessageCallback
from ...types.updates.message_chat_created import MessageChatCreated
from ...types.updates.message_created import MessageCreated
from ...types.updates.message_edited import MessageEdited
from ...types.updates.message_removed import MessageRemoved
from ...types.updates.user_added import UserAdded
from ...types.updates.user_removed import UserRemoved
class Update(BaseModel): UpdateUnion = Union[
update_type: UpdateType BotAdded,
timestamp: int BotRemoved,
BotStarted,
class Config: ChatTitleChanged,
arbitrary_types_allowed=True MessageCallback,
MessageChatCreated,
MessageCreated,
MessageEdited,
MessageRemoved,
UserAdded,
UserRemoved
]

View File

@ -1,8 +1,21 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
from ...types.users import User from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class BotAdded(Update): class BotAdded(Update):
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.user.user_id)

View File

@ -1,8 +1,21 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
from ...types.users import User from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class BotRemoved(Update): class BotRemoved(Update):
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.user.user_id)

View File

@ -1,10 +1,23 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
from ...types.users import User from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class BotStarted(Update): class BotStarted(Update):
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
user_locale: Optional[str] = None user_locale: Optional[str] = None
payload: Optional[str] = None payload: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.user.user_id)

View File

@ -1,9 +1,22 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
from ...types.users import User from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class ChatTitleChanged(Update): class ChatTitleChanged(Update):
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
title: Optional[str] = None title: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.user.user_id)

View File

@ -1,11 +1,76 @@
from typing import Optional from typing import Any, List, Optional, TYPE_CHECKING, Union
from . import Update from pydantic import BaseModel, Field
from .update import Update
from ...types.callback import Callback from ...types.callback import Callback
from ...types.message import Message from ...types.message import Message
from ...enums.parse_mode import ParseMode
from ...types.message import NewMessageLink
from ...types.attachments.share import Share
from ..attachments.buttons.attachment_button import AttachmentButton
from ..attachments.sticker import Sticker
from ..attachments.file import File
from ..attachments.image import Image
from ..attachments.video import Video
from ..attachments.audio import Audio
if TYPE_CHECKING:
from ...bot import Bot
class MessageForCallback(BaseModel):
text: Optional[str] = None
attachments: Optional[
List[
Union[
AttachmentButton,
Audio,
Video,
File,
Image,
Sticker,
Share
]
]
] = []
link: Optional[NewMessageLink] = None
notify: Optional[bool] = True
format: Optional[ParseMode] = None
class MessageCallback(Update): class MessageCallback(Update):
message: Message message: Message
user_locale: Optional[str] = None user_locale: Optional[str] = None
callback: Callback callback: Callback
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.message.recipient.chat_id, self.message.recipient.user_id)
async def answer(
self,
notification: str,
new_text: str = None,
link: NewMessageLink = None,
notify: bool = True,
format: ParseMode = None,
):
message = MessageForCallback()
message.text = new_text
message.attachments = self.message.body.attachments
message.link = link
message.notify = notify
message.format = format
return await self.bot.send_callback(
callback_id=self.callback.callback_id,
message=message,
notification=notification
)

View File

@ -1,11 +1,24 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from ...types.chats import Chat from ...types.chats import Chat
from . import Update from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageChatCreated(Update): class MessageChatCreated(Update):
chat: Chat chat: Chat
title: Optional[str] = None title: Optional[str] = None
message_id: Optional[str] = None message_id: Optional[str] = None
start_payload: Optional[str] = None start_payload: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, 0)

View File

@ -1,9 +1,22 @@
from typing import Optional from __future__ import annotations
from typing import Any, Optional, TYPE_CHECKING, ForwardRef
from . import Update from pydantic import Field
from .update import Update
from ...types.message import Message from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageCreated(Update): class MessageCreated(Update):
message: Message message: Message
user_locale: Optional[str] = None user_locale: Optional[str] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.message.recipient.chat_id, self.message.recipient.user_id)

View File

@ -1,6 +1,19 @@
from . import Update from typing import TYPE_CHECKING, Any, Optional
from pydantic import Field
from .update import Update
from ...types.message import Message from ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageEdited(Update): class MessageEdited(Update):
message: Message message: Message
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.message.recipient.chat_id, self.message.recipient.user_id)

View File

@ -1,9 +1,21 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
if TYPE_CHECKING:
from ...bot import Bot
class MessageRemoved(Update): class MessageRemoved(Update):
message_id: Optional[str] = None message_id: Optional[str] = None
chat_id: Optional[int] = None chat_id: Optional[int] = None
user_id: Optional[int] = None user_id: Optional[int] = None
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.user_id)

View File

@ -0,0 +1,11 @@
from pydantic import BaseModel
from ...enums.update import UpdateType
class Update(BaseModel):
update_type: UpdateType
timestamp: int
class Config:
arbitrary_types_allowed=True

View File

@ -1,10 +1,23 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
from ...types.users import User from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class UserAdded(Update): class UserAdded(Update):
inviter_id: Optional[int] = None inviter_id: Optional[int] = None
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.inviter_id)

View File

@ -1,10 +1,22 @@
from typing import Optional from typing import TYPE_CHECKING, Any, Optional
from . import Update from pydantic import Field
from .update import Update
from ...types.users import User from ...types.users import User
if TYPE_CHECKING:
from ...bot import Bot
class UserRemoved(Update): class UserRemoved(Update):
admin_id: Optional[int] = None admin_id: Optional[int] = None
chat_id: Optional[int] = None chat_id: Optional[int] = None
user: User user: User
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
def get_ids(self):
return (self.chat_id, self.admin_id)

View File

@ -2,6 +2,8 @@ from pydantic import BaseModel
from typing import List, Optional from typing import List, Optional
from datetime import datetime from datetime import datetime
from ..enums.chat_permission import ChatPermission
class BotCommand(BaseModel): class BotCommand(BaseModel):
name: str name: str
@ -22,5 +24,10 @@ class User(BaseModel):
class Config: class Config:
json_encoders = { json_encoders = {
datetime: lambda v: int(v.timestamp() * 1000) # Конвертация datetime в Unix-время (ms) datetime: lambda v: int(v.timestamp() * 1000)
} }
class ChatAdmin(BaseModel):
user_id: int
permissions: List[ChatPermission]

View File

@ -0,0 +1,18 @@
from ..enums.attachment import AttachmentType
from ..types.attachments.attachment import Attachment, ButtonsPayload
class InlineKeyboardBuilder:
def __init__(self):
self.payload = []
def row(self, *buttons):
self.payload.append([*buttons])
def as_markup(self):
return Attachment(
type=AttachmentType.INLINE_KEYBOARD,
payload=ButtonsPayload(
buttons=self.payload
)
)

5
requirements.txt Normal file
View File

@ -0,0 +1,5 @@
aiohttp==3.11.16
fastapi==0.115.13
magic_filter==1.0.12
pydantic==2.11.7
uvicorn==0.34.3