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.message_created(F.message.body.text == 'Привет')
@dp.message_created(F.message.body.text == 'q')
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 == 'Клавиатура')
@ -34,7 +39,8 @@ async def hello(obj: MessageCreated):
# Ответчает на коллбек с начинкой "1"
@dp.message_callback(F.callback.payload == '1')
async def _(obj: MessageCallback):
await obj.message.answer('Вы нажали на кнопку 1 🤩')
a = await obj.answer('test')
...
# Ответчает на коллбек с начинкой "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}')
@dp.message_created()
async def hello(obj: MessageCreated):
# await obj.message.answer(f'Повторяю за вами: {obj.message.body.text}')
pass
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 .enums.parse_mode import ParseMode
from .types.attachments.attachment import Attachment
@ -11,11 +17,17 @@ from .methods.get_messages import GetMessages
from .methods.get_chats import GetChats
from .methods.send_message import SendMessage
from .connection.base import BaseConnection
if TYPE_CHECKING:
from .types.message import Message
class Bot(BaseConnection):
def __init__(self, token: str):
super().__init__()
self.bot = self
self.__token = token
self.params = {
'access_token': self.__token
@ -62,9 +74,35 @@ class Bot(BaseConnection):
notify=notify,
parse_mode=parse_mode
).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):
return await GetMessages(self, chat_id).request()
async def get_messages(
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):
return await GetMe(self).request()
@ -86,4 +124,20 @@ class Bot(BaseConnection):
).request()
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'
def __init__(self):
self.bot = None
async def request(
self,
method: HTTPMethod,
@ -21,7 +24,7 @@ class BaseConnection:
async with aiohttp.ClientSession(self.API_URL) as s:
r = await s.request(
method=method.value,
url=path.value,
url=path.value if isinstance(path, ApiPath) else path,
**kwargs
)
@ -33,4 +36,14 @@ class BaseConnection:
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:
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
app = FastAPI()
@ -91,11 +91,13 @@ class Dispatcher:
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:

View File

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

View File

@ -6,3 +6,4 @@ class HTTPMethod(str, Enum):
GET = 'GET'
PATCH = 'PATCH'
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 aiomax.enums.parse_mode import ParseMode
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 ..types.message import NewMessageLink
from .types.edited_message import EditedMessage
from ..connection.base import BaseConnection
@ -23,8 +19,8 @@ class EditMessage(BaseConnection):
bot: 'Bot',
message_id: str,
text: str = None,
attachments: List[Attachment] = None,
link: NewMessageLink = None,
attachments: List['Attachment'] = None,
link: 'NewMessageLink' = None,
notify: bool = True,
parse_mode: ParseMode = None
):
@ -36,7 +32,7 @@ class EditMessage(BaseConnection):
self.notify = notify
self.parse_mode = parse_mode
async def request(self) -> 'EditedMessage':
async def request(self) -> EditedMessage:
params = self.bot.params.copy()
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 ..enums.http_method import HTTPMethod
@ -13,15 +14,44 @@ if TYPE_CHECKING:
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.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:
params = self.bot.params.copy()
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(
method=HTTPMethod.GET,
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 ..enums.parse_mode import ParseMode
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 .types.sended_message import SendedMessage
from ..connection.base import BaseConnection
@ -41,28 +38,32 @@ class SendMessage(BaseConnection):
self.notify = notify
self.parse_mode = parse_mode
async def request(self) -> 'SendedMessage':
params = self.bot.params.copy()
async def request(self) -> SendedMessage:
try:
params = self.bot.params.copy()
json = {}
json = {}
if self.chat_id: params['chat_id'] = self.chat_id
elif self.user_id: params['user_id'] = self.user_id
if self.chat_id: params['chat_id'] = self.chat_id
elif self.user_id: params['user_id'] = self.user_id
json['text'] = self.text
json['disable_link_preview'] = str(self.disable_link_preview).lower()
if self.attachments: json['attachments'] = \
[att.model_dump() for att in self.attachments]
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.parse_mode is None: json['format'] = self.parse_mode.value
json['text'] = self.text
json['disable_link_preview'] = str(self.disable_link_preview).lower()
if self.attachments: json['attachments'] = \
[att.model_dump() for att in self.attachments]
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.parse_mode is None: json['format'] = self.parse_mode.value
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.MESSAGES,
model=SendedMessage,
params=params,
json=json
)
return await super().request(
method=HTTPMethod.POST,
path=ApiPath.MESSAGES,
model=SendedMessage,
params=params,
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 ...types.message import Message
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 ...types.message import Message

View File

@ -1,16 +1,35 @@
from typing import Literal, Optional
from pydantic import BaseModel
from typing import TYPE_CHECKING, Any, Literal, Optional
from pydantic import BaseModel, Field
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):
url: str
class Video(Attachment):
type: Literal['video'] = 'video'
type: Optional[Literal['video']] = 'video'
token: Optional[str] = None
urls: Optional[VideoUrl] = None
thumbnail: VideoThumbnail
width: Optional[int] = None
height: 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 ..types.users import User
from ..types.users import User
class Callback(BaseModel):
timestamp: int

View File

@ -87,7 +87,10 @@ class Message(BaseModel):
body: Optional[MessageBody] = None
stat: Optional[MessageStat] = 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,
text: str = None,
@ -97,8 +100,7 @@ class Message(BaseModel):
notify: bool = True,
parse_mode: ParseMode = None
):
bot: Bot = self.bot
return await bot.send_message(
return await self.bot.send_message(
chat_id=self.recipient.chat_id,
user_id=self.recipient.user_id,
text=text,
@ -108,10 +110,36 @@ class Message(BaseModel):
notify=notify,
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):
messages: List[Message]
bot: Optional[Any] = Field(default=None, exclude=True)
if TYPE_CHECKING:
bot: Optional[Bot]
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 ...types.callback import Callback
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):
message: Message
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 ...types.message import Message
if TYPE_CHECKING:
from ...bot import Bot
class MessageCreated(Update):
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]