Skip to content

Commit

Permalink
* @extra field of incoming updates now available in EXTRA and is empt…
Browse files Browse the repository at this point in the history
…y dict by default. It is useful to store some user data there

* added create_bot_command_filter(command: str) helper to create specified command handler (useful for bots). This filter parses message text as command and puts bot_command and bot_command_args to update.EXTRA

* added Client.bot_command_handler method for registration of text message handlers with texts started with "/"

* added Client.text_message_handler method for registration of text message handlers

* added Client.parse_text method to parse text entities according to parse_mode parameter. By default parse_mode from Client constructor will is used

* New high-level API method Client.edit_message - Edits the text of a message (or a text of a game message). Returns the edited message after the edit is completed on the server side
  • Loading branch information
pylakey committed Apr 11, 2021
1 parent 9068816 commit 37765a5
Show file tree
Hide file tree
Showing 6 changed files with 200 additions and 10 deletions.
21 changes: 21 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# aiotdlib CHANGELOG

### v0.2.0

#### Added

* `@extra` field of incoming updates now available in `EXTRA` attribute of `BaseObject` and is empty dict by default. It
is useful to store some user data there

* New filter factory `create_bot_command_filter(command: str)` to create specified command handler (useful for bots).
This filter parses message text as command and puts `bot_command` and `bot_command_args` to `update.EXTRA`

* `Client.text_message_handler` method for registration of text message handlers
> this method is universal and can be used directly or as decorator
* `Client.bot_command_handler` method for registration of text message handlers with texts started with "/"
> this method is universal and can be used directly or as decorator
* `Client.parse_text` method to parse text entities according to `parse_mode` parameter. By default, `parse_mode`
parameter from constructor will is used

* New high-level API method `Client.edit_message` - Edits the text of a message (or a text of a game message). Returns
the edited message after the edit is completed on the server side

### v0.1.0

#### Added
Expand Down
45 changes: 45 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,51 @@ async def main():
await client.idle()


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
```

### Bot command handler

```python
import asyncio
import logging

from aiotdlib import Client
from aiotdlib.api import UpdateNewMessage

API_ID = 12345
API_HASH = "00112233445566778899aabbccddeeff"
PHONE_NUMBER = ""

client = Client(
api_id=API_ID,
api_hash=API_HASH,
phone_number=PHONE_NUMBER
)


async def on_start_command(client: Client, update: UpdateNewMessage):
await client.send_message(update.message.chat_id, "Have a good day! :)")


# Note: bot_command_handler method is universal and can be used directly or as decorator
# Registering handler for '/help' command
@client.bot_command_handler(command='help')
async def on_help_command(client: Client, update: UpdateNewMessage):
await client.send_message(update.message.chat_id, "I will help you!")


async def main():
# Note: bot_command_handler method is universal and can be used directly or as decorator
# Registering handler for '/start' command
client.bot_command_handler(on_start_command, command='start')

async with client:
await client.idle()


if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
asyncio.run(main())
Expand Down
2 changes: 1 addition & 1 deletion aiotdlib/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
__version__ = "0.1.0"
__version__ = "0.2.0"

from .client import Client
from .filters import FilterCallable
Expand Down
6 changes: 3 additions & 3 deletions aiotdlib/api/base_object.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import logging
from enum import Enum
from typing import Dict, Optional, Type
from typing import Any, Dict, Optional, Type

from pydantic import BaseConfig, BaseModel, Field

Expand All @@ -17,6 +17,7 @@ class Config(BaseConfig):

_all: Optional[Dict[str, Type[BaseObject]]] = {}
ID: str = Field(..., alias='@type')
EXTRA: Optional[dict[str, Any]] = Field({}, alias='@extra')

@staticmethod
def read(data: dict):
Expand All @@ -29,7 +30,6 @@ def read(data: dict):
q_type = data.get('@type')

if not bool(q_type):
logger.warning(f'@type not found in received update. {data}')
return data

q_type = q_type.value if isinstance(q_type, Enum) else q_type
Expand All @@ -40,6 +40,6 @@ def read(data: dict):
return data

for key, value in data.items():
data[key] = value if key == '@extra' else BaseObject.read(value)
data[key] = BaseObject.read(value)

return object_class.read(data)
114 changes: 109 additions & 5 deletions aiotdlib/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
ChatTypeSecret,
ChatTypeSupergroup,
Close,
FormattedText,
InputMessageText,
Message,
MessageSchedulingStateSendAtDate,
Expand All @@ -43,6 +44,7 @@
UserFullInfo,
)
from .client_cache import ClientCache
from .filters import create_bot_command_filter, text_message_filter
from .handlers import FilterCallable, Handler, HandlerCallable
from .middlewares import MiddlewareCallable
from .tdjson import TDJson, TDLibLogVerbosity
Expand Down Expand Up @@ -462,6 +464,44 @@ def add_middleware(self, middleware: MiddlewareCallable):
self.__middlewares.append(middleware)
return middleware

def text_message_handler(self, function: HandlerCallable = None):
"""
Registers event handler with predefined filter text_message_filter
which allows only UpdateNewMessage with MessageText content
Note: this method is universal and can be used directly or as decorator
"""
if callable(function):
self.add_event_handler(
function,
API.Types.UPDATE_NEW_MESSAGE,
filters=text_message_filter
)
else:
return self.on_event(
API.Types.UPDATE_NEW_MESSAGE,
filters=text_message_filter
)

def bot_command_handler(self, function: HandlerCallable = None, *, command: str = None):
"""
Registers event handler with predefined filter bot_command_handler
which allows only UpdateNewMessage with MessageText content and text of message starts with "/"
Note: this method is universal and can be used directly or as decorator
"""
if callable(function):
self.add_event_handler(
function,
API.Types.UPDATE_NEW_MESSAGE,
filters=create_bot_command_filter(command=command)
)
else:
return self.on_event(
API.Types.UPDATE_NEW_MESSAGE,
filters=create_bot_command_filter(command=command)
)

async def send(self, query: Union[dict, BaseObject], *, request_id: str = None) -> AsyncResult:
if not self.__running:
raise RuntimeError('Client not started')
Expand Down Expand Up @@ -597,6 +637,26 @@ async def authorize(self):

await asyncio.sleep(0.1)

async def parse_text(self, text: str, *, parse_mode: str = None) -> FormattedText:
"""
Parses Bold, Italic, Underline, Strikethrough, Code, Pre, PreCode,
TextUrl and MentionName entities contained in the text.
"""
# ParseTextEntities can be called synchronously
if parse_mode is None:
parse_mode = self.parse_mode
else:
parse_mode = parse_mode.lower()

return await self.execute(ParseTextEntities(
text=text,
parse_mode=(
TextParseModeHTML()
if parse_mode == 'html' else
TextParseModeMarkdown(version=2)
)
))

# API methods shorthands
async def send_message(
self,
Expand Down Expand Up @@ -653,11 +713,7 @@ async def send_message(
else:
scheduling_state = None

# ParseTextEntities can be called synchronously
formatted_text = await self.execute(ParseTextEntities(
text=text,
parse_mode=TextParseModeHTML() if self.parse_mode == 'html' else TextParseModeMarkdown(version=2)
))
formatted_text = await self.parse_text(text)

return await self.api.send_message(
chat_id=chat_id,
Expand All @@ -677,6 +733,54 @@ async def send_message(
skip_validation=True
)

async def edit_message(
self,
chat_id: int,
message_id: int,
text: str,
*,
reply_markup: ReplyMarkup = None,
disable_web_page_preview: bool = False,
clear_draft: bool = True,
):
"""
Edits the text of a message (or a text of a game message).
Returns the edited message after the edit is completed on the server side
Params:
chat_id (:class:`int`)
The chat the message belongs to
message_id (:class:`int`)
Identifier of the message
text (:class:`str`)
New text of the message
reply_markup (:class:`ReplyMarkup`)
The new message reply markup; for bots only
disable_web_page_preview (:class:`bool`)
True, if rich web page previews for URLs in the message text should be disabled
clear_draft (:class:`bool`)
True, if a chat message draft should be deleted
"""
formatted_text = await self.parse_text(text)

return await self.api.edit_message_text(
chat_id=chat_id,
message_id=message_id,
reply_markup=reply_markup,
input_message_content=InputMessageText.construct(
text=formatted_text,
disable_web_page_preview=disable_web_page_preview,
clear_draft=clear_draft,
),
skip_validation=True
)

async def get_main_list_chats(self, limit: int = 25) -> list[Chat]:
"""
Returns an ordered list of chats in a main chat list.
Expand Down
22 changes: 21 additions & 1 deletion aiotdlib/filters.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,25 @@


# Some predefined filters
async def text_message(update: BaseObject):
async def text_message_filter(update: BaseObject):
return isinstance(update, UpdateNewMessage) and isinstance(update.message.content, MessageText)


def create_bot_command_filter(command: str = None):
async def bot_command_filter(update: BaseObject) -> bool:
if not isinstance(update, UpdateNewMessage) or not isinstance(update.message.content, MessageText):
return False

message_text = update.message.content.text.text

if not message_text.startswith('/'):
return False

[bot_command, *bot_command_args] = message_text.lstrip('/').split()

update.EXTRA['bot_command'] = bot_command
update.EXTRA['bot_command_args'] = bot_command_args

return not bool(command) or command == bot_command

return bot_command_filter

0 comments on commit 37765a5

Please sign in to comment.