This commit is contained in:
Денис Семёнов 2025-06-17 23:14:25 +03:00
parent 3f8c68cca4
commit b2283ab538
21 changed files with 437 additions and 63 deletions

View File

@ -15,9 +15,14 @@ bot = Bot('токен')
dp = Dispatcher() dp = Dispatcher()
# Отвечает только на текст "Привет" # Отвечает только на текст "Привет"
@dp.message_created(F.message.body.text == 'Привет') @dp.message_created(F.message.body.text == 'q')
async def hello(obj: MessageCreated): async def hello(obj: MessageCreated):
await obj.message.answer('Привет 👋') msg = await obj.message.answer('Привет 👋')
a = await obj.bot.get_video('f9LHodD0cOJ5BfLGZ81uXgypU1z7PNhJMkmIe_dtEcxfC3V8vxWk65mRJX8MFQ5F9OAs3yDgbUv6DS6X1p7P')
...
# Отвечает только на текст "Клавиатура" # Отвечает только на текст "Клавиатура"
@dp.message_created(F.message.body.text == 'Клавиатура') @dp.message_created(F.message.body.text == 'Клавиатура')
@ -34,7 +39,8 @@ async def hello(obj: MessageCreated):
# Ответчает на коллбек с начинкой "1" # Ответчает на коллбек с начинкой "1"
@dp.message_callback(F.callback.payload == '1') @dp.message_callback(F.callback.payload == '1')
async def _(obj: MessageCallback): async def _(obj: MessageCallback):
await obj.message.answer('Вы нажали на кнопку 1 🤩') a = await obj.answer('test')
...
# Ответчает на коллбек с начинкой "2" # Ответчает на коллбек с начинкой "2"
@dp.message_callback(F.callback.payload == '2') @dp.message_callback(F.callback.payload == '2')
@ -47,4 +53,10 @@ 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 hello(obj: MessageCreated):
# await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}')
pass
dp.handle_webhook(bot) dp.handle_webhook(bot)

View File

@ -1,5 +1,11 @@
from typing import Any, Dict, List from datetime import datetime
from typing import Any, Dict, List, TYPE_CHECKING
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 .enums.parse_mode import ParseMode
from .types.attachments.attachment import Attachment from .types.attachments.attachment import Attachment
@ -11,11 +17,17 @@ 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 .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
@ -62,9 +74,35 @@ 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 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()
@ -86,4 +124,20 @@ class Bot(BaseConnection):
).request() ).request()
async def get_chats(self): async def get_chats(self):
return await GetChats(self).request() return await GetChats(self).request()
async def get_video(self, video_token: str):
return await GetVideo(self, 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()

View File

@ -10,6 +10,9 @@ class BaseConnection:
API_URL = 'https://botapi.max.ru' API_URL = 'https://botapi.max.ru'
def __init__(self):
self.bot = None
async def request( async def request(
self, self,
method: HTTPMethod, method: HTTPMethod,
@ -21,7 +24,7 @@ class BaseConnection:
async with aiohttp.ClientSession(self.API_URL) as s: async with aiohttp.ClientSession(self.API_URL) as s:
r = await s.request( r = await s.request(
method=method.value, method=method.value,
url=path.value, url=path.value if isinstance(path, ApiPath) else path,
**kwargs **kwargs
) )
@ -33,4 +36,14 @@ class BaseConnection:
if is_return_raw: return raw if is_return_raw: return raw
return model(**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

@ -67,7 +67,7 @@ class Dispatcher:
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 handle_webhook(self, bot: Bot, host: str = 'localhost', port: int = 8080):
self.bot = bot self.bot = bot
app = FastAPI() app = FastAPI()
@ -91,11 +91,13 @@ class Dispatcher:
case UpdateType.MESSAGE_CALLBACK: case UpdateType.MESSAGE_CALLBACK:
event_object = MessageCallback(**event_json) event_object = MessageCallback(**event_json)
event_object.message.bot = self.bot event_object.message.bot = self.bot
event_object.bot = self.bot
case UpdateType.MESSAGE_CHAT_CREATED: case UpdateType.MESSAGE_CHAT_CREATED:
event_object = MessageChatCreated(**event_json) event_object = MessageChatCreated(**event_json)
case UpdateType.MESSAGE_CREATED: case UpdateType.MESSAGE_CREATED:
event_object = MessageCreated(**event_json) event_object = MessageCreated(**event_json)
event_object.message.bot = self.bot event_object.message.bot = self.bot
event_object.bot = self.bot
case UpdateType.MESSAGE_EDITED: case UpdateType.MESSAGE_EDITED:
event_object = MessageEdited(**event_json) event_object = MessageEdited(**event_json)
case UpdateType.MESSAGE_REMOVED: case UpdateType.MESSAGE_REMOVED:

View File

@ -4,4 +4,6 @@ class ApiPath(str, Enum):
ME = '/me' ME = '/me'
CHATS = '/chats' CHATS = '/chats'
MESSAGES = '/messages' MESSAGES = '/messages'
UPDATES = '/updates' UPDATES = '/updates'
VIDEOS = '/videos'
ANSWERS = '/answers'

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

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

@ -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,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,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,28 +38,32 @@ 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() try:
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,7 @@
from typing import Optional
from pydantic import BaseModel
class DeletedMessage(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,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

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

@ -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,36 @@ 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,
)
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,73 @@
from typing import Optional from typing import Any, List, Optional, TYPE_CHECKING, Union
from pydantic import BaseModel, Field
from . import Update from . 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]
async def answer(
self,
text: str,
link: NewMessageLink = None,
notify: bool = True,
format: ParseMode = None,
notification: str = None
):
message = MessageForCallback()
message.text = 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,9 +1,19 @@
from typing import Optional from __future__ import annotations
from typing import Any, Optional, TYPE_CHECKING, ForwardRef
from pydantic import Field
from . import Update from . 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]