Добавлены примеры и обновлена модель Update
This commit is contained in:
parent
ab52abc474
commit
ee58238261
84
README.md
84
README.md
@ -1,84 +0,0 @@
|
||||
# Асинхронный MAX API
|
||||
|
||||
[](https://pypi.org/project/maxapi/)
|
||||
[](https://pypi.org/project/maxapi/)
|
||||
[](https://love-apples/maxapi/blob/main/LICENSE)
|
||||
|
||||
---
|
||||
|
||||
## 📦 Установка
|
||||
|
||||
```bash
|
||||
pip install maxapi
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
```python
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.types import BotStarted, Command, MessageCreated
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot('тут_ваш_токен')
|
||||
dp = Dispatcher()
|
||||
|
||||
|
||||
@dp.bot_started()
|
||||
async def bot_started(event: BotStarted):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text='Привет! Отправь мне /start'
|
||||
)
|
||||
|
||||
|
||||
@dp.message_created(Command('start'))
|
||||
async def hello(event: MessageCreated):
|
||||
await event.message.answer(f"Пример чат-бота для MAX 💙")
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📚 Документация
|
||||
|
||||
В разработке...
|
||||
|
||||
---
|
||||
|
||||
## 🧩 Возможности
|
||||
|
||||
- ✅ Роутеры
|
||||
- ✅ Билдер инлайн клавиатур
|
||||
- ✅ Простая загрузка медиафайлов
|
||||
- ✅ MagicFilter
|
||||
- ✅ Внутренние функции моделей
|
||||
- ✅ Контекстный менеджер
|
||||
- ✅ Поллинг
|
||||
- ✅ Вебхук
|
||||
- ✅ Логгирование
|
||||
|
||||
---
|
||||
|
||||
|
||||
## 💬 Обратная связь и поддержка
|
||||
|
||||
- MAX: [Чат](https://max.ru/join/IPAok63C3vFqbWTFdutMUtjmrAkGqO56YeAN7iyDfc8)
|
||||
- Telegram: [@loveappless](https://t.me/loveappless)
|
||||
---
|
||||
|
||||
## 📄 Лицензия
|
||||
|
||||
Этот проект распространяется под лицензией MIT. См. файл [LICENSE](LICENSE) для подробностей.
|
@ -1,31 +0,0 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.types import BotStarted, Command, MessageCreated
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot('тут_ваш_токен')
|
||||
dp = Dispatcher()
|
||||
|
||||
|
||||
@dp.bot_started()
|
||||
async def bot_started(event: BotStarted):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text='Привет! Отправь мне /start'
|
||||
)
|
||||
|
||||
|
||||
@dp.message_created(Command('start'))
|
||||
async def hello(event: MessageCreated):
|
||||
await event.message.answer(f"Пример чат-бота для MAX 💙")
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
24
examples/echo/main.py
Normal file
24
examples/echo/main.py
Normal file
@ -0,0 +1,24 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.filters import F
|
||||
from maxapi.types import MessageCreated
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot('тут_ваш_токен')
|
||||
dp = Dispatcher()
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.text)
|
||||
async def echo(event: MessageCreated):
|
||||
await event.message.answer(f"Повторяю за вами: {event.message.body.text}")
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
122
examples/events/main.py
Normal file
122
examples/events/main.py
Normal file
@ -0,0 +1,122 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from maxapi import Bot, Dispatcher
|
||||
from maxapi.types import (
|
||||
BotStarted,
|
||||
Command,
|
||||
MessageCreated,
|
||||
CallbackButton,
|
||||
MessageCallback,
|
||||
BotAdded,
|
||||
ChatTitleChanged,
|
||||
MessageEdited,
|
||||
MessageRemoved,
|
||||
UserAdded,
|
||||
UserRemoved
|
||||
)
|
||||
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot('тут_ваш_токен')
|
||||
dp = Dispatcher()
|
||||
|
||||
|
||||
@dp.message_created(Command('start'))
|
||||
async def hello(event: MessageCreated):
|
||||
builder = InlineKeyboardBuilder()
|
||||
|
||||
builder.row(
|
||||
CallbackButton(
|
||||
text='Кнопка 1',
|
||||
payload='btn_1'
|
||||
),
|
||||
CallbackButton(
|
||||
text='Кнопка 2',
|
||||
payload='btn_2',
|
||||
)
|
||||
)
|
||||
builder.add(
|
||||
CallbackButton(
|
||||
text='Кнопка 3',
|
||||
payload='btn_3',
|
||||
)
|
||||
)
|
||||
|
||||
await event.message.answer(
|
||||
text='Привет!',
|
||||
attachments=[
|
||||
builder.as_markup(),
|
||||
] # Для MAX клавиатура это вложение,
|
||||
) # поэтому она в списке вложений
|
||||
|
||||
|
||||
@dp.bot_added()
|
||||
async def bot_added(event: BotAdded):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat.id,
|
||||
text=f'Привет чат {event.chat.title}!'
|
||||
)
|
||||
|
||||
|
||||
@dp.message_removed()
|
||||
async def message_removed(event: MessageRemoved):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text='Я всё видел!'
|
||||
)
|
||||
|
||||
|
||||
@dp.bot_started()
|
||||
async def bot_started(event: BotStarted):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text='Привет! Отправь мне /start'
|
||||
)
|
||||
|
||||
|
||||
@dp.chat_title_changed()
|
||||
async def chat_title_changed(event: ChatTitleChanged):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Крутое новое название "{event.chat.title}!"'
|
||||
)
|
||||
|
||||
|
||||
@dp.message_callback()
|
||||
async def message_callback(event: MessageCallback):
|
||||
await event.answer(
|
||||
new_text=f'Вы нажали на кнопку {event.callback.payload}!'
|
||||
)
|
||||
|
||||
|
||||
@dp.message_edited()
|
||||
async def message_edited(event: MessageEdited):
|
||||
await event.message.answer(
|
||||
text='Вы отредактировали сообщение!'
|
||||
)
|
||||
|
||||
|
||||
@dp.user_removed()
|
||||
async def user_removed(event: UserRemoved):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'{event.from_user.first_name} кикнул {event.user.first_name} 😢'
|
||||
)
|
||||
|
||||
|
||||
@dp.user_added()
|
||||
async def user_added(event: UserAdded):
|
||||
await event.bot.send_message(
|
||||
chat_id=event.chat_id,
|
||||
text=f'Чат "{event.chat.title}" приветствует вас, {event.user.first_name}!'
|
||||
)
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
48
examples/magic_filters/magic_filters.py
Normal file
48
examples/magic_filters/magic_filters.py
Normal file
@ -0,0 +1,48 @@
|
||||
import asyncio
|
||||
import logging
|
||||
|
||||
from maxapi import Bot, Dispatcher, F
|
||||
from maxapi.types import MessageCreated
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
bot = Bot('тут_ваш_токен')
|
||||
dp = Dispatcher()
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.text == 'привет')
|
||||
async def on_hello(event: MessageCreated):
|
||||
await event.message.answer('Привет!')
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.text.lower().contains('помощь'))
|
||||
async def on_help(event: MessageCreated):
|
||||
await event.message.answer('Чем могу помочь?')
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.text.regexp(r'^\d{4}$'))
|
||||
async def on_code(event: MessageCreated):
|
||||
await event.message.answer('Принят 4-значный код')
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.attachments)
|
||||
async def on_attachment(event: MessageCreated):
|
||||
await event.message.answer('Получено вложение')
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.text.len() > 20)
|
||||
async def on_long_text(event: MessageCreated):
|
||||
await event.message.answer('Слишком длинное сообщение')
|
||||
|
||||
|
||||
@dp.message_created(F.message.body.text.len() > 0)
|
||||
async def on_non_empty(event: MessageCreated):
|
||||
await event.message.answer('Вы что-то написали.')
|
||||
|
||||
|
||||
async def main():
|
||||
await dp.start_polling(bot)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
@ -6,7 +6,7 @@ from maxapi.context import MemoryContext, State, StatesGroup
|
||||
from maxapi.types import BotStarted, Command, MessageCreated, CallbackButton, MessageCallback, BotCommand
|
||||
from maxapi.utils.inline_keyboard import InlineKeyboardBuilder
|
||||
|
||||
from example.router_for_example import router
|
||||
from router import router
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
|
||||
@ -158,4 +158,5 @@ async def main():
|
||||
# )
|
||||
|
||||
|
||||
asyncio.run(main())
|
||||
if __name__ == '__main__':
|
||||
asyncio.run(main())
|
@ -1,6 +1,8 @@
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, TYPE_CHECKING
|
||||
|
||||
from maxapi.methods.download_media import DownloadMedia
|
||||
|
||||
from .methods.get_upload_url import GetUploadURL
|
||||
from .methods.get_updates import GetUpdates
|
||||
from .methods.remove_member_chat import RemoveMemberChat
|
||||
@ -570,7 +572,7 @@ class Bot(BaseConnection):
|
||||
"""Получает участников чата.
|
||||
|
||||
:param chat_id: ID чата
|
||||
:param user_ids: Фильтр по ID пользователей
|
||||
:param user_ids: Список ID участников
|
||||
:param marker: Маркер для пагинации
|
||||
:param count: Количество участников
|
||||
|
||||
@ -584,6 +586,28 @@ class Bot(BaseConnection):
|
||||
marker=marker,
|
||||
count=count,
|
||||
).request()
|
||||
|
||||
async def get_chat_member(
|
||||
self,
|
||||
chat_id: int,
|
||||
user_id: int,
|
||||
) -> GettedMembersChat:
|
||||
|
||||
"""Получает участника чата.
|
||||
|
||||
:param chat_id: ID чата
|
||||
:param user_id: ID участника
|
||||
|
||||
:return: Участник
|
||||
"""
|
||||
|
||||
members = await self.get_chat_members(
|
||||
chat_id=chat_id,
|
||||
user_ids=[user_id]
|
||||
)
|
||||
|
||||
if members.members:
|
||||
return members.members[0]
|
||||
|
||||
async def add_chat_members(
|
||||
self,
|
||||
@ -673,4 +697,28 @@ class Bot(BaseConnection):
|
||||
return await ChangeInfo(
|
||||
bot=self,
|
||||
commands=list(commands)
|
||||
).request()
|
||||
|
||||
async def download_file(
|
||||
self,
|
||||
path: str,
|
||||
url: str,
|
||||
token: str
|
||||
):
|
||||
|
||||
"""
|
||||
Скачивает медиа с указанной ссылки по токену, сохраняя по определенному пути
|
||||
|
||||
:param path: Путь сохранения медиа
|
||||
:param url: Ссылка на медиа
|
||||
:param token: Токен медиа
|
||||
|
||||
:return: Числовой статус
|
||||
"""
|
||||
|
||||
return await DownloadMedia(
|
||||
bot=self,
|
||||
path=path,
|
||||
media_url=url,
|
||||
media_token=token
|
||||
).request()
|
@ -1,13 +1,19 @@
|
||||
import os
|
||||
|
||||
from typing import TYPE_CHECKING
|
||||
|
||||
import aiofiles
|
||||
import aiohttp
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
from ..exceptions.invalid_token import InvalidToken
|
||||
|
||||
from ..types.errors import Error
|
||||
from ..enums.http_method import HTTPMethod
|
||||
from ..enums.api_path import ApiPath
|
||||
from ..enums.upload_type import UploadType
|
||||
|
||||
from ..loggers import logger_bot, logger_connection
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -65,6 +71,9 @@ class BaseConnection:
|
||||
)
|
||||
except aiohttp.ClientConnectorDNSError as e:
|
||||
return logger_connection.error(f'Ошибка при отправке запроса: {e}')
|
||||
|
||||
if r.status == 401:
|
||||
raise InvalidToken('Неверный токен!')
|
||||
|
||||
if not r.ok:
|
||||
raw = await r.json()
|
||||
@ -124,4 +133,33 @@ class BaseConnection:
|
||||
data=form
|
||||
)
|
||||
|
||||
return await response.text()
|
||||
return await response.text()
|
||||
|
||||
async def download_file(
|
||||
self,
|
||||
path: str,
|
||||
url: str,
|
||||
token: str,
|
||||
):
|
||||
"""
|
||||
Скачивает медиа с указанной ссылки по токену, сохраняя по определенному пути
|
||||
|
||||
:param path: Путь сохранения медиа
|
||||
:param url: Ссылка на медиа
|
||||
:param token: Токен медиа
|
||||
|
||||
:return: Числовой статус
|
||||
"""
|
||||
|
||||
headers = {
|
||||
'Authorization': f'Bearer {token}'
|
||||
}
|
||||
|
||||
async with aiohttp.ClientSession() as session:
|
||||
async with session.get(url, headers=headers) as response:
|
||||
|
||||
if response.status == 200:
|
||||
async with aiofiles.open(path, 'wb') as f:
|
||||
await f.write(await response.read())
|
||||
|
||||
return response.status
|
@ -2,6 +2,7 @@ from typing import Callable, List
|
||||
|
||||
from fastapi import FastAPI, Request
|
||||
from fastapi.responses import JSONResponse
|
||||
from magic_filter import MagicFilter
|
||||
from uvicorn import Config, Server
|
||||
from aiohttp import ClientConnectorError
|
||||
|
||||
@ -33,6 +34,8 @@ class Dispatcher:
|
||||
def __init__(self):
|
||||
self.event_handlers: List[Handler] = []
|
||||
self.contexts: List[MemoryContext] = []
|
||||
self.routers: List[Router] = []
|
||||
self.filters: List[MagicFilter] = []
|
||||
self.bot = None
|
||||
self.on_started_func = None
|
||||
|
||||
@ -65,9 +68,24 @@ class Dispatcher:
|
||||
"""
|
||||
|
||||
for router in routers:
|
||||
for event in router.event_handlers:
|
||||
self.event_handlers.append(event)
|
||||
self.routers.append(router)
|
||||
|
||||
async def __ready(self, bot: Bot):
|
||||
self.bot = bot
|
||||
await self.check_me()
|
||||
|
||||
self.routers += [self]
|
||||
|
||||
handlers_count = 0
|
||||
for router in self.routers:
|
||||
for handler in router.event_handlers:
|
||||
handlers_count += 1
|
||||
|
||||
logger_dp.info(f'{handlers_count} событий на обработку')
|
||||
|
||||
if self.on_started_func:
|
||||
await self.on_started_func()
|
||||
|
||||
def __get_memory_context(self, chat_id: int, user_id: int):
|
||||
|
||||
"""Возвращает или создает контекст для чата и пользователя.
|
||||
@ -95,40 +113,51 @@ class Dispatcher:
|
||||
Args:
|
||||
event_object: Объект события для обработки
|
||||
"""
|
||||
ids = event_object.get_ids()
|
||||
|
||||
is_handled = False
|
||||
|
||||
for router in self.routers:
|
||||
|
||||
if is_handled:
|
||||
break
|
||||
|
||||
if router.filters:
|
||||
if not filter_attrs(event_object, *router.filters):
|
||||
continue
|
||||
|
||||
for handler in router.event_handlers:
|
||||
|
||||
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):
|
||||
if not handler.update_type == event_object.update_type:
|
||||
continue
|
||||
|
||||
ids = event_object.get_ids()
|
||||
if handler.filters:
|
||||
if not filter_attrs(event_object, *handler.filters):
|
||||
continue
|
||||
|
||||
memory_context = self.__get_memory_context(*ids)
|
||||
|
||||
if not handler.state == await memory_context.get_state() \
|
||||
and handler.state:
|
||||
continue
|
||||
|
||||
func_args = handler.func_event.__annotations__.keys()
|
||||
memory_context = self.__get_memory_context(*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}
|
||||
kwargs = {'context': memory_context}
|
||||
|
||||
for key in kwargs.copy().keys():
|
||||
if not key in func_args:
|
||||
del kwargs[key]
|
||||
for key in kwargs.copy().keys():
|
||||
if not key in func_args:
|
||||
del kwargs[key]
|
||||
|
||||
if handler.middleware:
|
||||
await handler.middleware()
|
||||
|
||||
await handler.func_event(event_object, **kwargs)
|
||||
await handler.func_event(event_object, **kwargs)
|
||||
|
||||
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
|
||||
logger_dp.info(f'Обработано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
|
||||
|
||||
is_handled = True
|
||||
break
|
||||
is_handled = True
|
||||
break
|
||||
|
||||
if not is_handled:
|
||||
logger_dp.info(f'Проигнорировано: {event_object.update_type} | chat_id: {ids[0]}, user_id: {ids[1]}')
|
||||
@ -140,14 +169,7 @@ class Dispatcher:
|
||||
Args:
|
||||
bot: Экземпляр бота
|
||||
"""
|
||||
|
||||
self.bot = bot
|
||||
await self.check_me()
|
||||
|
||||
logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
|
||||
|
||||
if self.on_started_func:
|
||||
await self.on_started_func()
|
||||
await self.__ready(bot)
|
||||
|
||||
while True:
|
||||
try:
|
||||
@ -184,11 +206,7 @@ class Dispatcher:
|
||||
port: Порт для сервера
|
||||
"""
|
||||
|
||||
self.bot = bot
|
||||
await self.check_me()
|
||||
|
||||
if self.on_started_func:
|
||||
await self.on_started_func()
|
||||
await self.__ready(bot)
|
||||
|
||||
@app.post('/')
|
||||
async def _(request: Request):
|
||||
@ -206,7 +224,6 @@ class Dispatcher:
|
||||
except Exception as e:
|
||||
logger_dp.error(f"Ошибка при обработке события: {event_json['update_type']}: {e}")
|
||||
|
||||
logger_dp.info(f'{len(self.event_handlers)} событий на обработку')
|
||||
config = Config(app=app, host=host, port=port, log_level="critical")
|
||||
server = Server(config)
|
||||
|
||||
@ -231,8 +248,10 @@ class Event:
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
def decorator(func_event: Callable):
|
||||
|
||||
if self.update_type == UpdateType.ON_STARTED:
|
||||
self.router.on_started_func = func_event
|
||||
|
||||
else:
|
||||
self.router.event_handlers.append(
|
||||
Handler(
|
||||
|
4
maxapi/exceptions/download_file.py
Normal file
4
maxapi/exceptions/download_file.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
class NotAvailableForDownload(BaseException):
|
||||
...
|
4
maxapi/exceptions/invalid_token.py
Normal file
4
maxapi/exceptions/invalid_token.py
Normal file
@ -0,0 +1,4 @@
|
||||
|
||||
|
||||
class InvalidToken(BaseException):
|
||||
...
|
@ -6,48 +6,15 @@ from magic_filter.operations.comparator import ComparatorOperation as mf_compara
|
||||
F = MagicFilter()
|
||||
|
||||
|
||||
def filter_attrs(obj, *magic_args):
|
||||
def filter_attrs(obj: object, *filters: MagicFilter) -> bool:
|
||||
"""
|
||||
Применяет один или несколько фильтров MagicFilter к объекту.
|
||||
|
||||
:param obj: Любой объект с атрибутами (например, event/message)
|
||||
:param filters: Один или несколько MagicFilter выражений
|
||||
:return: True, если все фильтры возвращают True, иначе False
|
||||
"""
|
||||
try:
|
||||
for arg in magic_args:
|
||||
|
||||
attr_last = None
|
||||
method_found = False
|
||||
|
||||
operations = arg._operations
|
||||
if isinstance(operations[-1], mf_call):
|
||||
operations = operations[:len(operations)-2]
|
||||
method_found = True
|
||||
elif isinstance(operations[-1], mf_func):
|
||||
operations = operations[:len(operations)-1]
|
||||
method_found = True
|
||||
elif isinstance(operations[-1], mf_comparator):
|
||||
operations = operations[:len(operations)-1]
|
||||
|
||||
for element in operations:
|
||||
if attr_last is None:
|
||||
attr_last = getattr(obj, element.name)
|
||||
else:
|
||||
attr_last = getattr(attr_last, element.name)
|
||||
|
||||
if attr_last is None:
|
||||
break
|
||||
|
||||
if isinstance(arg._operations[-1], mf_comparator):
|
||||
return attr_last == arg._operations[-1].right
|
||||
|
||||
if not method_found:
|
||||
return bool(attr_last)
|
||||
|
||||
if attr_last is None:
|
||||
return False
|
||||
|
||||
if isinstance(arg._operations[-1], mf_func):
|
||||
func_operation: mf_func = arg._operations[-1]
|
||||
return func_operation.resolve(attr_last, attr_last)
|
||||
else:
|
||||
method = getattr(attr_last, arg._operations[-2].name)
|
||||
args = arg._operations[-1].args
|
||||
|
||||
return method(*args)
|
||||
except Exception as e:
|
||||
...
|
||||
return all(f.resolve(obj) for f in filters)
|
||||
except Exception:
|
||||
return False
|
@ -2,9 +2,14 @@ from typing import Callable
|
||||
|
||||
from magic_filter import F, MagicFilter
|
||||
|
||||
from ..filters.middleware import BaseMiddleware
|
||||
|
||||
from ..types.command import Command
|
||||
|
||||
from ..context.state_machine import State
|
||||
|
||||
from ..enums.update import UpdateType
|
||||
|
||||
from ..loggers import logger_dp
|
||||
|
||||
|
||||
@ -36,10 +41,11 @@ class Handler:
|
||||
:param kwargs: Дополнительные параметры (не используются)
|
||||
"""
|
||||
|
||||
self.func_event = func_event
|
||||
self.update_type = update_type
|
||||
self.func_event: Callable = func_event
|
||||
self.update_type: UpdateType = update_type
|
||||
self.filters = []
|
||||
self.state = None
|
||||
self.state: State = None
|
||||
self.middleware: BaseMiddleware = None
|
||||
|
||||
for arg in args:
|
||||
if isinstance(arg, MagicFilter):
|
||||
@ -48,6 +54,8 @@ class Handler:
|
||||
self.state = arg
|
||||
elif isinstance(arg, Command):
|
||||
self.filters.insert(0, F.message.body.text.startswith(arg.command))
|
||||
elif isinstance(arg, BaseMiddleware):
|
||||
self.middleware = arg
|
||||
else:
|
||||
logger_dp.info(f'Обнаружен неизвестный фильтр `{arg}` при '
|
||||
f'регистрации функции `{func_event.__name__}`')
|
6
maxapi/filters/middleware.py
Normal file
6
maxapi/filters/middleware.py
Normal file
@ -0,0 +1,6 @@
|
||||
from ..types.updates import UpdateUnion
|
||||
|
||||
|
||||
class BaseMiddleware:
|
||||
def __init__(self):
|
||||
...
|
52
maxapi/methods/download_media.py
Normal file
52
maxapi/methods/download_media.py
Normal file
@ -0,0 +1,52 @@
|
||||
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 ..enums.upload_type import UploadType
|
||||
|
||||
from ..connection.base import BaseConnection
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..bot import Bot
|
||||
|
||||
|
||||
class DownloadMedia(BaseConnection):
|
||||
|
||||
"""
|
||||
Класс для скачивания медиафайлов.
|
||||
|
||||
Args:
|
||||
bot (Bot): Экземпляр бота для выполнения запроса.
|
||||
media_url (str): Ссылка на медиа.
|
||||
media_token (str): Токен медиа.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
bot: 'Bot',
|
||||
path: str,
|
||||
media_url: str,
|
||||
media_token: str
|
||||
):
|
||||
self.bot = bot
|
||||
self.path = path
|
||||
self.media_url = media_url
|
||||
self.media_token = media_token
|
||||
|
||||
async def request(self) -> int:
|
||||
|
||||
"""
|
||||
Выполняет GET-запрос для скачивания медиафайла
|
||||
|
||||
Returns:
|
||||
int: Код операции.
|
||||
"""
|
||||
|
||||
return await super().download_file(
|
||||
path=self.path,
|
||||
url=self.media_url,
|
||||
token=self.media_token
|
||||
)
|
@ -60,7 +60,9 @@ class GetMembersChat(BaseConnection):
|
||||
|
||||
params = self.bot.params.copy()
|
||||
|
||||
if self.user_ids: params['user_ids'] = ','.join(self.user_ids)
|
||||
if self.user_ids:
|
||||
self.user_ids = [str(user_id) for user_id in self.user_ids]
|
||||
params['user_ids'] = ','.join(self.user_ids)
|
||||
if self.marker: params['marker'] = self.marker
|
||||
if self.count: params['marker'] = self.count
|
||||
|
||||
|
@ -19,34 +19,77 @@ if TYPE_CHECKING:
|
||||
|
||||
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)
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id)
|
||||
event_object.from_user = event_object.callback.user
|
||||
|
||||
case UpdateType.MESSAGE_CHAT_CREATED:
|
||||
event_object = MessageChatCreated(**event)
|
||||
event_object.chat = event_object.chat
|
||||
|
||||
case UpdateType.MESSAGE_CREATED:
|
||||
event_object = MessageCreated(**event)
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id)
|
||||
event_object.from_user = event_object.message.sender
|
||||
|
||||
case UpdateType.MESSAGE_EDITED:
|
||||
event_object = MessageEdited(**event)
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.message.recipient.chat_id)
|
||||
event_object.from_user = event_object.message.sender
|
||||
|
||||
case UpdateType.MESSAGE_REMOVED:
|
||||
event_object = MessageRemoved(**event)
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
|
||||
event_object.from_user = await bot.get_chat_member(
|
||||
chat_id=event_object.chat_id,
|
||||
user_id=event_object.user_id
|
||||
)
|
||||
|
||||
case UpdateType.USER_ADDED:
|
||||
event_object = UserAdded(**event)
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
|
||||
event_object.from_user = event_object.user
|
||||
|
||||
case UpdateType.USER_REMOVED:
|
||||
event_object = UserRemoved(**event)
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
|
||||
event_object.from_user = await bot.get_chat_member(
|
||||
chat_id=event_object.chat_id,
|
||||
user_id=event_object.admin_id
|
||||
) if event_object.admin_id else None
|
||||
|
||||
if event['update_type'] in (UpdateType.BOT_ADDED,
|
||||
UpdateType.BOT_REMOVED,
|
||||
UpdateType.BOT_STARTED,
|
||||
UpdateType.CHAT_TITLE_CHANGED):
|
||||
event_object.chat = await bot.get_chat_by_id(event_object.chat_id)
|
||||
event_object.from_user = event_object.user
|
||||
|
||||
if hasattr(event_object, 'bot'):
|
||||
event_object.bot = bot
|
||||
|
||||
if hasattr(event_object, 'message'):
|
||||
event_object.message.bot = bot
|
||||
|
||||
for attachment in event_object.message.body.attachments:
|
||||
if hasattr(attachment, 'bot'):
|
||||
attachment.bot = bot
|
||||
|
||||
return event_object
|
||||
|
||||
|
@ -1,14 +1,19 @@
|
||||
from typing import List, Optional, Union
|
||||
from pydantic import BaseModel
|
||||
from typing import TYPE_CHECKING, Any, List, Optional, Union
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...exceptions.download_file import NotAvailableForDownload
|
||||
|
||||
from ...types.attachments.upload import AttachmentUpload
|
||||
|
||||
from ...types.attachments.buttons import InlineButtonUnion
|
||||
from ...types.users import User
|
||||
|
||||
from ...enums.attachment import AttachmentType
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class StickerAttachmentPayload(BaseModel):
|
||||
|
||||
"""
|
||||
@ -98,6 +103,36 @@ class Attachment(BaseModel):
|
||||
ButtonsPayload,
|
||||
StickerAttachmentPayload
|
||||
]] = None
|
||||
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
class Config:
|
||||
use_enum_values = True
|
||||
use_enum_values = True
|
||||
|
||||
async def download(
|
||||
self,
|
||||
path: str
|
||||
):
|
||||
|
||||
"""
|
||||
Скачивает медиа, сохраняя по определенному пути
|
||||
|
||||
:param path: Путь сохранения медиа
|
||||
|
||||
:return: Числовой статус
|
||||
"""
|
||||
|
||||
if not hasattr(self.payload, 'token') or \
|
||||
not hasattr(self.payload, 'url'):
|
||||
raise NotAvailableForDownload()
|
||||
|
||||
elif not self.payload.token or not self.payload.url:
|
||||
raise NotAvailableForDownload(f'Медиа типа `{self.type}` недоступно для скачивания')
|
||||
|
||||
return await self.bot.download_file(
|
||||
path=path,
|
||||
url=self.payload.url,
|
||||
token=self.payload.token,
|
||||
)
|
@ -1,7 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
@ -18,12 +16,10 @@ class BotAdded(Update):
|
||||
Attributes:
|
||||
chat_id (Optional[int]): Идентификатор чата, куда добавлен бот.
|
||||
user (User): Объект пользователя-бота.
|
||||
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
user: User
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
@ -1,7 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
@ -18,12 +16,10 @@ class BotRemoved(Update):
|
||||
Attributes:
|
||||
chat_id (Optional[int]): Идентификатор чата, из которого удалён бот.
|
||||
user (User): Объект пользователя-бота.
|
||||
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
user: User
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
@ -1,8 +1,7 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
@ -19,14 +18,12 @@ class BotStarted(Update):
|
||||
user (User): Пользователь (бот).
|
||||
user_locale (Optional[str]): Локаль пользователя.
|
||||
payload (Optional[str]): Дополнительные данные.
|
||||
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
user: User
|
||||
user_locale: Optional[str] = None
|
||||
payload: Optional[str] = None
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
@ -1,7 +1,5 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
@ -19,13 +17,11 @@ class ChatTitleChanged(Update):
|
||||
chat_id (Optional[int]): Идентификатор чата.
|
||||
user (User): Пользователь, совершивший изменение.
|
||||
title (Optional[str]): Новое название чата.
|
||||
bot (Optional[Any]): Ссылка на экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
chat_id: Optional[int] = None
|
||||
user: User
|
||||
title: Optional[str] = None
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
@ -1,6 +1,6 @@
|
||||
from typing import Any, List, Optional, TYPE_CHECKING, Union
|
||||
from typing import List, Optional, TYPE_CHECKING, Union
|
||||
|
||||
from pydantic import BaseModel, Field
|
||||
from pydantic import BaseModel
|
||||
|
||||
from .update import Update
|
||||
|
||||
@ -21,6 +21,8 @@ from ..attachments.audio import Audio
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
from ...types.chats import Chat
|
||||
from ...types.users import User
|
||||
|
||||
|
||||
class MessageForCallback(BaseModel):
|
||||
@ -65,16 +67,11 @@ class MessageCallback(Update):
|
||||
message (Message): Сообщение, на которое пришёл callback.
|
||||
user_locale (Optional[str]): Локаль пользователя.
|
||||
callback (Callback): Объект callback.
|
||||
bot (Optional[Any]): Экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
message: Message
|
||||
user_locale: Optional[str] = None
|
||||
callback: Callback
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
@ -89,7 +86,7 @@ class MessageCallback(Update):
|
||||
|
||||
async def answer(
|
||||
self,
|
||||
notification: str,
|
||||
notification: str = None,
|
||||
new_text: str = None,
|
||||
link: NewMessageLink = None,
|
||||
notify: bool = True,
|
||||
|
@ -1,24 +1,15 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
from typing import Optional
|
||||
|
||||
from ...types.chats import Chat
|
||||
|
||||
from .update import Update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class MessageChatCreated(Update):
|
||||
chat: Chat
|
||||
title: Optional[str] = None
|
||||
message_id: 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)
|
||||
return (self.chat.chat_id, self.chat.owner_id)
|
@ -1,15 +1,10 @@
|
||||
from __future__ import annotations
|
||||
from typing import Any, Optional, TYPE_CHECKING
|
||||
|
||||
from pydantic import Field
|
||||
from typing import Optional, TYPE_CHECKING
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.message import Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class MessageCreated(Update):
|
||||
|
||||
@ -19,16 +14,11 @@ class MessageCreated(Update):
|
||||
Attributes:
|
||||
message (Message): Объект сообщения.
|
||||
user_locale (Optional[str]): Локаль пользователя.
|
||||
bot (Optional[Any]): Экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
message: Message
|
||||
user_locale: Optional[str] = None
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
"""
|
||||
|
@ -1,14 +1,7 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.message import Message
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class MessageEdited(Update):
|
||||
|
||||
@ -17,15 +10,10 @@ class MessageEdited(Update):
|
||||
|
||||
Attributes:
|
||||
message (Message): Объект измененного сообщения.
|
||||
bot (Optional[Any]): Экземпляр бота, не сериализуется.
|
||||
"""
|
||||
|
||||
message: Message
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
"""
|
||||
|
@ -1,12 +1,7 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
from typing import Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class MessageRemoved(Update):
|
||||
|
||||
@ -17,16 +12,11 @@ class MessageRemoved(Update):
|
||||
message_id (Optional[str]): Идентификатор удаленного сообщения. Может быть None.
|
||||
chat_id (Optional[int]): Идентификатор чата. Может быть None.
|
||||
user_id (Optional[int]): Идентификатор пользователя. Может быть None.
|
||||
bot (Optional[Bot]): Объект бота, исключается из сериализации.
|
||||
"""
|
||||
|
||||
message_id: Optional[str] = None
|
||||
chat_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):
|
||||
|
||||
|
@ -1,7 +1,14 @@
|
||||
from pydantic import BaseModel
|
||||
from __future__ import annotations
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
from pydantic import BaseModel, Field
|
||||
|
||||
from ...enums.update import UpdateType
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
from ...types.chats import Chat
|
||||
from ...types.users import User
|
||||
|
||||
|
||||
class Update(BaseModel):
|
||||
|
||||
@ -15,6 +22,15 @@ class Update(BaseModel):
|
||||
|
||||
update_type: UpdateType
|
||||
timestamp: int
|
||||
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
from_user: Optional[Any] = Field(default=None, exclude=True)
|
||||
chat: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
from_user: Optional[User]
|
||||
chat: Optional[Chat]
|
||||
|
||||
class Config:
|
||||
arbitrary_types_allowed=True
|
@ -1,16 +1,10 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
from typing import Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class UserAdded(Update):
|
||||
|
||||
"""
|
||||
@ -20,17 +14,12 @@ class UserAdded(Update):
|
||||
inviter_id (Optional[int]): Идентификатор пользователя, добавившего нового участника. Может быть None.
|
||||
chat_id (Optional[int]): Идентификатор чата. Может быть None.
|
||||
user (User): Объект пользователя, добавленного в чат.
|
||||
bot (Optional[Bot]): Объект бота, исключается из сериализации.
|
||||
"""
|
||||
|
||||
inviter_id: Optional[int] = None
|
||||
chat_id: Optional[int] = None
|
||||
user: User
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
"""
|
||||
|
@ -1,14 +1,9 @@
|
||||
from typing import TYPE_CHECKING, Any, Optional
|
||||
|
||||
from pydantic import Field
|
||||
from typing import Optional
|
||||
|
||||
from .update import Update
|
||||
|
||||
from ...types.users import User
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ...bot import Bot
|
||||
|
||||
|
||||
class UserRemoved(Update):
|
||||
|
||||
@ -19,17 +14,12 @@ class UserRemoved(Update):
|
||||
admin_id (Optional[int]): Идентификатор администратора, удалившего пользователя. Может быть None.
|
||||
chat_id (Optional[int]): Идентификатор чата. Может быть None.
|
||||
user (User): Объект пользователя, удаленного из чата.
|
||||
bot (Optional[Bot]): Объект бота, исключается из сериализации.
|
||||
"""
|
||||
|
||||
admin_id: Optional[int] = None
|
||||
chat_id: Optional[int] = None
|
||||
user: User
|
||||
bot: Optional[Any] = Field(default=None, exclude=True)
|
||||
|
||||
if TYPE_CHECKING:
|
||||
bot: Optional[Bot]
|
||||
|
||||
def get_ids(self):
|
||||
|
||||
"""
|
||||
|
Loading…
x
Reference in New Issue
Block a user