Conversation
* Fix ruff issues and pytest failures - Installed missing dependencies (tgcrypto) to fix pytest failures. - Replaced star imports with explicit imports in `pyrogram/types/__init__.py` to resolve F405 issues. - Fixed unused imports in sub-package `__init__.py` files using explicit re-exports (as Class). - Added `__hash__ = None` to `Object` and `TLObject` to resolve PLW1641. - Updated compiler scripts to use `pathlib` and resolved other ruff warnings (B007, PERF401, C901). - Corrected grouping of story-related types in the main types entry point. Co-authored-by: 5hojib <[email protected]> * Fix ruff issues and pytest failures - Installed missing dependencies (tgcrypto) to fix pytest failures. - Replaced star imports with explicit imports in `pyrogram/types/__init__.py`. - Fixed unused imports in sub-package `__init__.py` files using `__all__` for re-exports. - Added `__hash__ = None` to `Object` and `TLObject` to resolve PLW1641. - Updated compiler scripts to use `pathlib` and resolved other ruff warnings (B007, PERF401, C901). - Corrected grouping of story-related types in the main types entry point. Co-authored-by: 5hojib <[email protected]> * Fix ruff issues and pytest failures - Installed missing dependencies (tgcrypto) to fix pytest failures. - Replaced star imports with explicit imports in `pyrogram/types/__init__.py`. - Fixed unused imports in sub-package `__init__.py` files using `__all__` for re-exports. - Added `__hash__ = None` to `Object` and `TLObject` to resolve PLW1641. - Updated compiler scripts to use `pathlib` and resolved other ruff warnings (B007, PERF401, C901). - Corrected grouping of story-related types in the main types entry point. Co-authored-by: 5hojib <[email protected]> --------- Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Added `[tool.pytest.ini_options]` to `pyproject.toml` with: - `asyncio_mode = "auto"` to automatically handle `async def` tests. - `asyncio_default_test_loop_scope = "function"` to comply with newer `pytest-asyncio` requirements. This resolves the issue where pytest failed with "async def functions are not natively supported". Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
This commit adds a new script `compiler/api/scrape_docs.py` that scrapes the latest Telegram MTProto API documentation from Corefork. It also updates `compiler/api/docs.json` with the newly scraped data, providing better descriptions and parameter documentation for many API methods and types. The generated files in `pyrogram/raw` were verified to be in sync with the updated documentation. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Signed-off-by: 5hojib <[email protected]>
- Added FactCheck, StarsAmount, and SuggestedPost high-level types. - Added missing fields to Message, User, Chat, and ChatPrivileges types. - Updated send_message, send_photo, send_video, send_animation, and send_media_group methods with missing parameters. - Fixed Gift._parse to handle optional fields correctly. - Added get_input_quick_reply_shortcut utility. - Ensured compatibility with the latest MTProto layer. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Signed-off-by: 5hojib <[email protected]>
Replaced blocking pathlib.Path methods (is_file, unlink, mkdir) with their asynchronous equivalents from anyio.Path in async functions. Added anyio as a dependency to the project. This prevents the event loop from being blocked during filesystem operations, improving performance and reliability of the library. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Signed-off-by: 5hojib <[email protected]>
This change introduces the `ButtonStyle` enum in `pyrogram.enums` with `PRIMARY`, `DANGER`, and `SUCCESS` values. It also updates `InlineKeyboardButton`, `KeyboardButton`, and `InlineKeyboardButtonBuy` to accept this enum for their `style` parameter, making it easier to style buttons without manually instantiating `KeyboardButtonStyle`. A new static method `_parse` was added to `KeyboardButtonStyle` to handle the conversion from `ButtonStyle` to `KeyboardButtonStyle`. Example usage: ```python InlineKeyboardButton(text="E", switch_inline_query="E", style=ButtonStyle.SUCCESS) ``` Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Signed-off-by: 5hojib <[email protected]>
This commit adds an `icon` parameter (of type `int`) to the high-level `InlineKeyboardButton`, `KeyboardButton`, and `InlineKeyboardButtonBuy` classes. This allows users to directly set a custom icon for buttons without having to manually construct a `KeyboardButtonStyle` object. The `KeyboardButtonStyle` class was also updated to ensure `icon` is treated as an integer throughout the library's high-level API. Docstrings were updated for all modified classes. Co-authored-by: google-labs-jules[bot] <161369871+google-labs-jules[bot]@users.noreply.github.com>
Reviewer's GuideRefactors type exports to be explicit, adds new business, messaging, chat, user, and payment features (Stars, gifts, suggested posts, fact-checking, button styles, stories), introduces async filesystem handling with anyio, extends message-sending APIs, and adds new tooling for docs scraping and async testing support. Sequence diagram for sending media with async filesystem and suggested postssequenceDiagram
actor Bot
participant Client
participant Utils
participant AsyncFS as AsyncPath
participant Telegram as TelegramAPI
Bot->>Client: send_photo(chat_id, photo, schedule_repeat_period, allow_paid_stars, quick_reply_shortcut, send_as, background, clear_draft, update_stickersets_order, suggested_post)
activate Client
Client->>AsyncFS: is_file(photo)
alt local file
AsyncFS-->>Client: True
Client->>Client: save_file(photo)
Client->>Telegram: messages.UploadMedia(peer, file)
Telegram-->>Client: InputMedia
else remote or file_id
AsyncFS-->>Client: False
Client->>Client: prepare InputMedia without upload
end
alt quick_reply_shortcut provided
Client->>Utils: get_input_quick_reply_shortcut(shortcut)
Utils-->>Client: InputQuickReplyShortcut
end
alt send_as provided
Client->>Client: resolve_peer(send_as)
end
alt suggested_post provided
Client->>Client: suggested_post.write()
Client-->>Client: raw.types.SuggestedPost
end
Client->>Telegram: messages.SendMedia(schedule_repeat_period, allow_paid_stars, background, clear_draft, update_stickersets_order, quick_reply_shortcut, send_as, suggested_post, allow_paid_floodskip, other params...)
Telegram-->>Client: Updates
Client-->>Bot: Message
deactivate Client
Class diagram for button classes and ButtonStyleclassDiagram
class AutoName {
}
class ButtonStyle {
<<enum>>
PRIMARY
DANGER
SUCCESS
}
AutoName <|-- ButtonStyle
class KeyboardButtonStyle {
+bool bg_primary
+bool bg_danger
+bool bg_success
+int icon
+write() raw.types.KeyboardButtonStyle
+_parse(style) KeyboardButtonStyle
}
class InlineKeyboardButton {
+str text
+str url
+str callback_data
+str switch_inline_query
+str switch_inline_query_current_chat
+CallbackGame callback_game
+bool requires_password
+str copy_text
+KeyboardButtonStyle style
+int icon
+InlineKeyboardButton__init__(text, url, callback_data, switch_inline_query, switch_inline_query_current_chat, callback_game, requires_password, copy_text, style, icon)
+read(b) InlineKeyboardButton
}
class InlineKeyboardButtonBuy {
+str text
+KeyboardButtonStyle style
+int icon
+InlineKeyboardButtonBuy__init__(text, style, icon)
+read(b) InlineKeyboardButtonBuy
}
class KeyboardButton {
+str text
+RequestPeerTypeChat request_chat
+RequestPeerTypeUser request_user
+WebAppInfo web_app
+KeyboardButtonStyle style
+int icon
+KeyboardButton__init__(text, request_chat, request_user, web_app, style, icon)
+read(b) KeyboardButton
}
KeyboardButtonStyle ..> ButtonStyle : _parse(style)
InlineKeyboardButton --> KeyboardButtonStyle : style
InlineKeyboardButtonBuy --> KeyboardButtonStyle : style
KeyboardButton --> KeyboardButtonStyle : style
File-Level Changes
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
Summary of ChangesHello @5hojib, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request integrates several new Telegram API features, particularly those related to paid content, suggested posts, and advanced message sending options. It also refactors file operations to leverage asynchronous Highlights
Changelog
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Hey - I've found 4 issues, and left some high level feedback:
- In
Gift._parseyou now returnNonefor unknownstar_gifttypes while the return type is annotated asGift; consider either widening the return annotation toGift | Noneor raising explicitly so callers don't silently getNonewhere aGiftis expected. utils.get_input_quick_reply_shortcutis declaredasyncbut does not perform any asynchronous work; making it a regular function would simplify the API and avoid unnecessaryawaitusage at all call sites.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `Gift._parse` you now return `None` for unknown `star_gift` types while the return type is annotated as `Gift`; consider either widening the return annotation to `Gift | None` or raising explicitly so callers don't silently get `None` where a `Gift` is expected.
- `utils.get_input_quick_reply_shortcut` is declared `async` but does not perform any asynchronous work; making it a regular function would simplify the API and avoid unnecessary `await` usage at all call sites.
## Individual Comments
### Comment 1
<location> `pyrogram/types/business/stars_amount.py:29-30` </location>
<code_context>
+ self.amount = amount
+ self.nanos = nanos
+
+ async def write(self) -> raw.base.StarsAmount:
+ if self.nanos:
+ return raw.types.StarsAmount(amount=self.amount, nanos=self.nanos)
+ return raw.types.StarsTonAmount(amount=self.amount)
</code_context>
<issue_to_address>
**issue (bug_risk):** Treat `nanos=0` as a valid value rather than falling back to `StarsTonAmount`.
`if self.nanos:` treats `0` as falsy, so `StarsTonAmount` will be used whenever `nanos` is `0`, which is wrong if `0` is a valid value. Use an explicit `is not None` check instead:
```python
aasync def write(self) -> raw.base.StarsAmount:
if self.nanos is not None:
return raw.types.StarsAmount(amount=self.amount, nanos=self.nanos)
return raw.types.StarsTonAmount(amount=self.amount)
```
</issue_to_address>
### Comment 2
<location> `pyrogram/methods/messages/edit_message_media.py:94-95` </location>
<code_context>
).values()
if isinstance(media, types.InputMediaPhoto):
- if isinstance(media.media, io.BytesIO) or Path(media.media).is_file():
+ if isinstance(media.media, io.BytesIO) or (
+ isinstance(media.media, str)
+ and await AsyncPath(media.media).is_file()
</code_context>
<issue_to_address>
**issue (bug_risk):** The new `str` type guard accidentally drops support for `Path` objects as media inputs.
This used to work because `Path(media.media).is_file()` accepted `pathlib.Path` directly. With the new `isinstance(media.media, str)` guard, `Path` values will fall through to the remote/ID handling path instead of being treated as files. To keep the guard while preserving behavior, consider checking for `os.PathLike` (or explicitly including `Path`) before calling `AsyncPath`:
```python
from os import PathLike
is_bytes_io = isinstance(media.media, io.BytesIO)
is_path_like = isinstance(media.media, (str, PathLike))
if is_bytes_io or (is_path_like and await AsyncPath(media.media).is_file()):
...
```
</issue_to_address>
### Comment 3
<location> `pyrogram/methods/messages/edit_inline_media.py:67-68` </location>
<code_context>
parse_mode = media.parse_mode
is_bytes_io = isinstance(media.media, io.BytesIO)
- is_uploaded_file = is_bytes_io or Path(media.media).is_file()
+ is_uploaded_file = is_bytes_io or (
+ isinstance(media.media, str) and await AsyncPath(media.media).is_file()
+ )
</code_context>
<issue_to_address>
**issue (bug_risk):** Same `str`-only file check here will ignore `Path` media inputs.
By limiting the check to `isinstance(media.media, str)`, `pathlib.Path` inputs will no longer be treated as local files and will instead be sent as non-uploaded media. To preserve backwards compatibility and keep this consistent with `edit_message_media`, consider checking for `str | os.PathLike` (or an equivalent approach) here as well.
</issue_to_address>
### Comment 4
<location> `compiler/api/scrape_docs.py:32` </location>
<code_context>
+sem = asyncio.Semaphore(MAX_TASKS)
+
+
+client = httpx.AsyncClient()
+
+
</code_context>
<issue_to_address>
**suggestion (bug_risk):** Creating the async HTTP client at import time can lead to lifecycle issues.
Creating `httpx.AsyncClient()` at import time means any import of this module allocates a client and connection pool, even if `main()` is never called, and makes cleanup harder to guarantee if errors occur before `client.aclose()`.
Prefer constructing and closing the client within `main()` (or an async context manager), for example:
```python
async def main():
async with httpx.AsyncClient() as client:
... # pass client down or accept it as a parameter
```
This keeps side effects out of imports and ties the client lifetime to `main()` execution.
Suggested implementation:
```python
import asyncio
import json
import re
from pathlib import Path
import httpx
from lxml import html
BASE_URL = "https://corefork.telegram.org/"
MAX_TASKS = 10
sem = asyncio.Semaphore(MAX_TASKS)
ROOT_DIR = Path.cwd().absolute()
```
To fully implement the lifecycle-safe client handling you described, you will also need to:
1. Locate the `async def main(...):` (or equivalent entrypoint) in `compiler/api/scrape_docs.py` and change it to create the client in a context manager, e.g.:
```python
async def main(...):
async with httpx.AsyncClient() as client:
await run_scrape(client, ...)
```
2. Update functions that currently rely on a global `client` (e.g. ones doing `await client.get(...)`) to instead accept `client: httpx.AsyncClient` as a parameter, and pass the `client` object through from `main()`.
3. Remove any remaining references to a global `client` variable in this module to ensure there is no accidental use of a stale or unclosed client.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| async def write(self) -> raw.base.StarsAmount: | ||
| if self.nanos: |
There was a problem hiding this comment.
issue (bug_risk): Treat nanos=0 as a valid value rather than falling back to StarsTonAmount.
if self.nanos: treats 0 as falsy, so StarsTonAmount will be used whenever nanos is 0, which is wrong if 0 is a valid value. Use an explicit is not None check instead:
aasync def write(self) -> raw.base.StarsAmount:
if self.nanos is not None:
return raw.types.StarsAmount(amount=self.amount, nanos=self.nanos)
return raw.types.StarsTonAmount(amount=self.amount)| if isinstance(media, types.InputMediaPhoto): | ||
| if isinstance(media.media, io.BytesIO) or Path(media.media).is_file(): | ||
| if isinstance(media.media, io.BytesIO) or ( |
There was a problem hiding this comment.
issue (bug_risk): The new str type guard accidentally drops support for Path objects as media inputs.
This used to work because Path(media.media).is_file() accepted pathlib.Path directly. With the new isinstance(media.media, str) guard, Path values will fall through to the remote/ID handling path instead of being treated as files. To keep the guard while preserving behavior, consider checking for os.PathLike (or explicitly including Path) before calling AsyncPath:
from os import PathLike
is_bytes_io = isinstance(media.media, io.BytesIO)
is_path_like = isinstance(media.media, (str, PathLike))
if is_bytes_io or (is_path_like and await AsyncPath(media.media).is_file()):
...| is_bytes_io = isinstance(media.media, io.BytesIO) | ||
| is_uploaded_file = is_bytes_io or Path(media.media).is_file() | ||
| is_uploaded_file = is_bytes_io or ( |
There was a problem hiding this comment.
issue (bug_risk): Same str-only file check here will ignore Path media inputs.
By limiting the check to isinstance(media.media, str), pathlib.Path inputs will no longer be treated as local files and will instead be sent as non-uploaded media. To preserve backwards compatibility and keep this consistent with edit_message_media, consider checking for str | os.PathLike (or an equivalent approach) here as well.
| sem = asyncio.Semaphore(MAX_TASKS) | ||
|
|
||
|
|
||
| client = httpx.AsyncClient() |
There was a problem hiding this comment.
suggestion (bug_risk): Creating the async HTTP client at import time can lead to lifecycle issues.
Creating httpx.AsyncClient() at import time means any import of this module allocates a client and connection pool, even if main() is never called, and makes cleanup harder to guarantee if errors occur before client.aclose().
Prefer constructing and closing the client within main() (or an async context manager), for example:
async def main():
async with httpx.AsyncClient() as client:
... # pass client down or accept it as a parameterThis keeps side effects out of imports and ties the client lifetime to main() execution.
Suggested implementation:
import asyncio
import json
import re
from pathlib import Path
import httpx
from lxml import html
BASE_URL = "https://corefork.telegram.org/"
MAX_TASKS = 10
sem = asyncio.Semaphore(MAX_TASKS)
ROOT_DIR = Path.cwd().absolute()To fully implement the lifecycle-safe client handling you described, you will also need to:
- Locate the
async def main(...):(or equivalent entrypoint) incompiler/api/scrape_docs.pyand change it to create the client in a context manager, e.g.:
async def main(...):
async with httpx.AsyncClient() as client:
await run_scrape(client, ...)-
Update functions that currently rely on a global
client(e.g. ones doingawait client.get(...)) to instead acceptclient: httpx.AsyncClientas a parameter, and pass theclientobject through frommain(). -
Remove any remaining references to a global
clientvariable in this module to ensure there is no accidental use of a stale or unclosed client.
There was a problem hiding this comment.
Code Review
This is a substantial pull request that introduces support for many new Telegram API features and significantly improves the codebase. The migration from synchronous pathlib to asynchronous anyio.AsyncPath for file operations is a great enhancement for an async library, preventing I/O blocking. The addition of a new script to scrape API documentation is a valuable tooling improvement. Furthermore, making core objects unhashable and adding explicit __all__ exports hardens the library and provides a more stable public API. My review includes a few suggestions for improving robustness and code clarity in the new scraping script and other areas.
| is_uploaded_file = is_bytes_io or ( | ||
| isinstance(media.media, str) and await AsyncPath(media.media).is_file() | ||
| ) |
There was a problem hiding this comment.
This condition for checking if the media is an uploaded file is a bit complex and is repeated in other methods like edit_message_media. To improve code clarity and reduce duplication (Don't Repeat Yourself), consider extracting this logic into a private async helper function.
For example:
async def _is_uploaded_media(self, media: Any) -> bool:
return isinstance(media, io.BytesIO) or (
isinstance(media, str) and await AsyncPath(media).is_file()
)You could then simplify this line to is_uploaded_file = await self._is_uploaded_media(media.media).
| async def _parse( | ||
| client, | ||
| star_gift: raw.types.StarGift, | ||
| star_gift: raw.base.StarGift, | ||
| ) -> Gift: | ||
| doc = star_gift.sticker | ||
| attributes = {type(i): i for i in doc.attributes} | ||
|
|
||
| return Gift( | ||
| id=star_gift.id, | ||
| sticker=await types.Sticker._parse(client, doc, attributes), | ||
| star_count=star_gift.stars, | ||
| default_sell_star_count=star_gift.convert_stars, | ||
| remaining_count=getattr(star_gift, "availability_remains", None), | ||
| total_count=getattr(star_gift, "availability_total", None), | ||
| first_send_date=utils.timestamp_to_datetime( | ||
| getattr(star_gift, "first_sale_date", None), | ||
| ), | ||
| last_send_date=utils.timestamp_to_datetime( | ||
| getattr(star_gift, "last_sale_date", None), | ||
| ), | ||
| is_limited=getattr(star_gift, "limited", None), | ||
| is_sold_out=getattr(star_gift, "sold_out", None), | ||
| client=client, | ||
| ) | ||
| if isinstance(star_gift, raw.types.StarGift): | ||
| doc = star_gift.sticker | ||
| attributes = {type(i): i for i in doc.attributes} | ||
|
|
||
| return Gift( | ||
| id=star_gift.id, | ||
| sticker=await types.Sticker._parse(client, doc, attributes), | ||
| star_count=star_gift.stars, | ||
| default_sell_star_count=star_gift.convert_stars, | ||
| remaining_count=getattr(star_gift, "availability_remains", None), | ||
| total_count=getattr(star_gift, "availability_total", None), | ||
| first_send_date=utils.timestamp_to_datetime( | ||
| getattr(star_gift, "first_sale_date", None), | ||
| ), | ||
| last_send_date=utils.timestamp_to_datetime( | ||
| getattr(star_gift, "last_sale_date", None), | ||
| ), | ||
| is_limited=getattr(star_gift, "limited", None), | ||
| is_sold_out=getattr(star_gift, "sold_out", None), | ||
| is_birthday=getattr(star_gift, "birthday", None), | ||
| can_upgrade=getattr(star_gift, "can_upgrade", None), | ||
| require_premium=getattr(star_gift, "require_premium", None), | ||
| is_limited_per_user=getattr(star_gift, "limited_per_user", None), | ||
| is_peer_color_available=getattr( | ||
| star_gift, | ||
| "peer_color_available", | ||
| None, | ||
| ), | ||
| is_auction=getattr(star_gift, "auction", None), | ||
| availability_resale=getattr(star_gift, "availability_resale", None), | ||
| upgrade_stars=getattr(star_gift, "upgrade_stars", None), | ||
| resell_min_stars=getattr(star_gift, "resell_min_stars", None), | ||
| title=getattr(star_gift, "title", None), | ||
| released_by=utils.get_raw_peer_id(star_gift.released_by) | ||
| if getattr(star_gift, "released_by", None) | ||
| else None, | ||
| per_user_total=getattr(star_gift, "per_user_total", None), | ||
| per_user_remains=getattr(star_gift, "per_user_remains", None), | ||
| locked_until_date=utils.timestamp_to_datetime( | ||
| getattr(star_gift, "locked_until_date", None), | ||
| ), | ||
| auction_slug=getattr(star_gift, "auction_slug", None), | ||
| gifts_per_round=getattr(star_gift, "gifts_per_round", None), | ||
| auction_start_date=utils.timestamp_to_datetime( | ||
| getattr(star_gift, "auction_start_date", None), | ||
| ), | ||
| upgrade_variants=getattr(star_gift, "upgrade_variants", None), | ||
| is_unique=False, | ||
| client=client, | ||
| ) | ||
| if isinstance(star_gift, raw.types.StarGiftUnique): | ||
| return Gift( | ||
| id=star_gift.id, | ||
| gift_id=star_gift.gift_id, | ||
| title=star_gift.title, | ||
| slug=star_gift.slug, | ||
| num=star_gift.num, | ||
| owner_id=utils.get_raw_peer_id(star_gift.owner_id) | ||
| if star_gift.owner_id | ||
| else None, | ||
| owner_name=star_gift.owner_name, | ||
| owner_address=star_gift.owner_address, | ||
| total_count=star_gift.availability_total, | ||
| gift_address=star_gift.gift_address, | ||
| released_by=utils.get_raw_peer_id(star_gift.released_by) | ||
| if star_gift.released_by | ||
| else None, | ||
| value_amount=star_gift.value_amount, | ||
| value_currency=star_gift.value_currency, | ||
| value_usd_amount=star_gift.value_usd_amount, | ||
| require_premium=star_gift.require_premium, | ||
| craft_chance_permille=star_gift.craft_chance_permille, | ||
| is_unique=True, | ||
| client=client, | ||
| ) | ||
| return None |
There was a problem hiding this comment.
This _parse method has become quite complex as it handles two different raw types (StarGift and StarGiftUnique). To improve readability and adhere to the Single Responsibility Principle, consider refactoring it. You could create two separate private helper methods, one for raw.types.StarGift and another for raw.types.StarGiftUnique. The main _parse method would then simply act as a dispatcher based on the input type.
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: 5hojib <[email protected]>
Summary by Sourcery
Add support for new Telegram API features including suggested posts, fact-check metadata, advanced gifts, button styles, and paid/quick-reply messaging options, while updating filesystem operations to use async paths and improving compiler and CI tooling.
New Features:
Enhancements:
Build:
CI:
Documentation:
Tests:
Chores: