добавлен блок chats из документации и start_polling к Dispatcher

This commit is contained in:
Денис Семёнов 2025-06-18 17:00:03 +03:00
parent ff19f99704
commit 9a39dce1a6
45 changed files with 1262 additions and 150 deletions

View File

@ -1,62 +1,23 @@
import asyncio
import logging
from maxapi.bot import Bot from maxapi.bot import Bot
from maxapi.dispatcher import Dispatcher from maxapi.dispatcher import Dispatcher
from maxapi.types.updates.message_created import MessageCreated 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.filters import F
logging.basicConfig(level=logging.INFO)
bot = Bot('токен') bot = Bot('токен')
dp = Dispatcher() dp = Dispatcher()
# Отвечает только на текст "Привет"
@dp.message_created(F.message.body.text == 'q')
async def hello(obj: MessageCreated):
msg = await obj.message.answer('Привет 👋')
a = await obj.bot.get_video('f9LHodD0cOJ5BfLGZ81uXgypU1z7PNhJMkmIe_dtEcxfC3V8vxWk65mRJX8MFQ5F9OAs3yDgbUv6DS6X1p7P') # Отвечает на лю
# любое текстовое сообщение
...
# Отвечает только на текст "Клавиатура"
@dp.message_created(F.message.body.text == 'Клавиатура')
async def hello(obj: MessageCreated):
button_1 = CallbackButton(type=ButtonType.CALLBACK, text='Кнопка 1', payload='1', intent=Intent.DEFAULT)
button_2 = CallbackButton(type=ButtonType.CALLBACK, text='Кнопка 2', payload='2', intent=Intent.DEFAULT)
keyboard = ButtonsPayload(buttons=[[button_1], [button_2]])
attachments = [Attachment(type=AttachmentType.INLINE_KEYBOARD, payload=keyboard)]
await obj.message.answer('Привет 👋', attachments=attachments)
# Ответчает на коллбек с начинкой "1"
@dp.message_callback(F.callback.payload == '1')
async def _(obj: MessageCallback):
a = await obj.answer('test')
...
# Ответчает на коллбек с начинкой "2"
@dp.message_callback(F.callback.payload == '2')
async def _(obj: MessageCallback):
await obj.message.answer('Вы нажали на кнопку 2 🥳')
# Отвечает на любое текстовое сообщение
@dp.message_created(F.message.body.text) @dp.message_created(F.message.body.text)
async def hello(obj: MessageCreated): async def hello(obj: MessageCreated):
await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}') await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}')
@dp.message_created() async def main():
async def hello(obj: MessageCreated): await dp.start_polling(bot)
# await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}')
pass
asyncio.run(main())
dp.handle_webhook(bot)

View File

@ -1,21 +1,41 @@
from datetime import datetime from datetime import datetime
from typing import Any, Dict, List, TYPE_CHECKING 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.send_callback import SendCallback
from .methods.get_video import GetVideo from .methods.get_video import GetVideo
from .methods.delete_message import DeleteMessage 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: if TYPE_CHECKING:
@ -32,6 +52,7 @@ class Bot(BaseConnection):
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,
@ -56,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,
@ -83,6 +115,15 @@ class Bot(BaseConnection):
bot=self, bot=self,
message_id=message_id, message_id=message_id,
).request() ).request()
async def delete_chat(
self,
chat_id: int
):
return await DeleteChat(
bot=self,
chat_id=chat_id,
).request()
async def get_messages( async def get_messages(
self, self,
@ -107,6 +148,9 @@ class Bot(BaseConnection):
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,
@ -123,11 +167,43 @@ 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): async def get_video(self, video_token: str):
return await GetVideo(self, video_token).request() return await GetVideo(bot=self, video_token=video_token).request()
async def send_callback( async def send_callback(
self, self,
@ -140,4 +216,123 @@ class Bot(BaseConnection):
callback_id=callback_id, callback_id=callback_id,
message=message, message=message,
notification=notification 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() ).request()

View File

@ -12,6 +12,7 @@ class BaseConnection:
def __init__(self): def __init__(self):
self.bot = None self.bot = None
self.session = None
async def request( async def request(
self, self,
@ -21,29 +22,29 @@ class BaseConnection:
is_return_raw: bool = False, is_return_raw: bool = False,
**kwargs **kwargs
): ):
async with aiohttp.ClientSession(self.API_URL) as s: s = self.bot.session
r = await s.request( r = await s.request(
method=method.value, method=method.value,
url=path.value if isinstance(path, ApiPath) else path, url=path.value if isinstance(path, ApiPath) else path,
**kwargs **kwargs
) )
if not r.ok: if not r.ok:
raw = await r.text() raw = await r.text()
return Error(code=r.status, text=raw) return Error(code=r.status, text=raw)
raw = await r.json() raw = await r.json()
if is_return_raw: return raw if is_return_raw: return raw
model = model(**raw) model = model(**raw)
if hasattr(model, 'message'): if hasattr(model, 'message'):
attr = getattr(model, 'message') attr = getattr(model, 'message')
if hasattr(attr, 'bot'): if hasattr(attr, 'bot'):
attr.bot = self.bot attr.bot = self.bot
if hasattr(model, 'bot'): if hasattr(model, 'bot'):
model.bot = self.bot model.bot = self.bot
return model return model

View File

@ -1,10 +1,14 @@
from typing import Callable, List from typing import Callable, List
import aiohttp
from fastapi.responses import JSONResponse
import uvicorn import uvicorn
from fastapi import FastAPI, Request from fastapi import FastAPI, Request
from magic_filter import MagicFilter from magic_filter import MagicFilter
from .methods.types.getted_updates import process_update_webhook, process_update_request
from .filters import filter_m from .filters import filter_m
from .types.updates import Update from .types.updates import Update
@ -24,6 +28,9 @@ from .types.updates.user_removed import UserRemoved
from .loggers import logger from .loggers import logger
app = FastAPI()
class Handler: class Handler:
def __init__( def __init__(
@ -67,50 +74,51 @@ class Dispatcher:
for event in router.event_handlers: for event in router.event_handlers:
self.event_handlers.append(event) self.event_handlers.append(event)
async def start_polling(self, bot: Bot):
self.bot = bot
self.bot.session = aiohttp.ClientSession(self.bot.API_URL)
while True:
try:
events = await self.bot.get_updates()
for event in events:
handlers: List[Handler] = self.event_handlers
for handler in handlers:
if not handler.update_type == event.update_type:
continue
if handler.filters:
if not filter_m(event, *handler.filters):
continue
await handler.func_event(event)
break
except Exception as e:
print(e)
...
logger.info(f'{len(self.event_handlers)} event handlers started')
def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080): def handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080):
self.bot = bot self.bot = bot
self.bot.session = aiohttp.ClientSession(self.bot.API_URL)
app = FastAPI()
@app.post("/") @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
event_object.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
event_object.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 handlers: List[Handler] = self.event_handlers
for handler in handlers: for handler in handlers:
if not handler.update_type == event.update_type: if not handler.update_type == event_object.update_type:
continue continue
if handler.filters: if handler.filters:
@ -120,7 +128,7 @@ class Dispatcher:
await handler.func_event(event_object) await handler.func_event(event_object)
break break
return True return JSONResponse(content={'ok': True}, status_code=200)
except Exception as e: except Exception as e:
print(e) print(e)
... ...

View File

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

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

@ -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

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

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,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

@ -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 maxapi.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 maxapi.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

@ -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,50 @@
from datetime import datetime
from typing import TYPE_CHECKING, List
from ..methods.types.getted_updates import process_update_request
from ..enums.update import UpdateType
from ..types.updates import Update
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) -> Messages:
params = self.bot.params.copy()
params['limit'] = self.limit
if self.bot.marker_updates:
params['marker'] = self.bot.marker_updates
event_json = await super().request(
method=HTTPMethod.GET,
path=ApiPath.UPDATES,
model=Messages,
params=params,
is_return_raw=True
)
return await process_update_request(
event_json=event_json,
bot=self.bot
)

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 maxapi.enums.sender_action import SenderAction
from maxapi.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

@ -39,31 +39,27 @@ class SendMessage(BaseConnection):
self.parse_mode = parse_mode self.parse_mode = parse_mode
async def request(self) -> SendedMessage: async def request(self) -> SendedMessage:
try: params = self.bot.params.copy()
params = self.bot.params.copy()
json = {} json = {}
if self.chat_id: params['chat_id'] = self.chat_id if self.chat_id: params['chat_id'] = self.chat_id
elif self.user_id: params['user_id'] = self.user_id elif self.user_id: params['user_id'] = self.user_id
json['text'] = self.text json['text'] = self.text
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
if not self.parse_mode is None: json['format'] = self.parse_mode.value if not self.parse_mode is None: json['format'] = self.parse_mode.value
return await super().request( return await super().request(
method=HTTPMethod.POST, method=HTTPMethod.POST,
path=ApiPath.MESSAGES, path=ApiPath.MESSAGES,
model=SendedMessage, model=SendedMessage,
params=params, params=params,
json=json json=json
) )
except Exception as e:
print(e)
...

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 DeletedPinMessage(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 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 maxapi.types.message import Message
class GettedPin(BaseModel):
message: Optional[Message] = None

View File

@ -0,0 +1,97 @@
from typing import TYPE_CHECKING
from maxapi.enums.update import UpdateType
from ...types.updates import Update
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 maxapi.bot import Bot
async def process_update_request(event_json: dict, bot: 'Bot'):
events = [event for event in event_json['updates']]
bot.marker_updates = event_json.get('marker')
objects = []
for event in events:
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.message.bot = bot
event_object.bot = bot
case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event)
case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event)
event_object.message.bot = bot
event_object.bot = bot
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)
objects.append(event_object)
return objects
async def process_update_webhook(event_json: dict, bot: 'Bot'):
event = Update(**event_json)
event_object = None
match event.update_type:
case UpdateType.BOT_ADDED:
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 = bot
event_object.bot = bot
case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event_json)
case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event_json)
event_object.message.bot = bot
event_object.bot = 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)
return event_object

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

@ -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,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

View File

@ -132,6 +132,13 @@ class Message(BaseModel):
return await self.bot.delete_message( return await self.bot.delete_message(
message_id=self.body.mid, 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):

View File

@ -51,16 +51,16 @@ class MessageCallback(Update):
bot: Optional[Bot] bot: Optional[Bot]
async def answer( async def answer(
self, self,
text: str, notification: str,
new_text: str = None,
link: NewMessageLink = None, link: NewMessageLink = None,
notify: bool = True, notify: bool = True,
format: ParseMode = None, format: ParseMode = None,
notification: str = None
): ):
message = MessageForCallback() message = MessageForCallback()
message.text = text message.text = new_text
message.attachments = self.message.body.attachments message.attachments = self.message.body.attachments
message.link = link message.link = link
message.notify = notify message.notify = notify

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]