mirror of
https://github.com/Mayuri-Chan/pyrofork.git
synced 2025-12-29 12:04:51 +00:00
Compare commits
14 commits
c09057c6f4
...
2de6d80905
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2de6d80905 | ||
|
|
e58354c98a | ||
|
|
a556504770 | ||
|
|
9105c1a9f3 | ||
|
|
984abd2008 | ||
|
|
d731e77305 | ||
|
|
c1816f2dbb | ||
|
|
1289e2a04a | ||
|
|
01cabc9e36 | ||
|
|
77ae91fb13 | ||
|
|
b1eb6cb54a | ||
|
|
9d46a9387a | ||
|
|
7faf2b45b5 | ||
|
|
5350450f6c |
18 changed files with 442 additions and 151 deletions
40
.github/workflows/python-publish.yml
vendored
40
.github/workflows/python-publish.yml
vendored
|
|
@ -1,40 +0,0 @@
|
|||
# This workflow will upload a Python Package using Twine when a release is created
|
||||
# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python#publishing-to-package-registries
|
||||
|
||||
# This workflow uses actions that are not certified by GitHub.
|
||||
# They are provided by a third-party and are governed by
|
||||
# separate terms of service, privacy policy, and support
|
||||
# documentation.
|
||||
|
||||
name: Upload Python Package
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*'
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
environment: release
|
||||
permissions:
|
||||
id-token: write
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- name: Set up Python
|
||||
uses: actions/setup-python@v5
|
||||
with:
|
||||
python-version: '3.10'
|
||||
- name: Install dependencies
|
||||
run: |
|
||||
python -m pip install --upgrade pip
|
||||
pip install -e '.[dev]'
|
||||
- name: Build package
|
||||
run: hatch build
|
||||
- name: Publish package
|
||||
uses: pypa/gh-action-pypi-publish@release/v1
|
||||
|
|
@ -435,7 +435,7 @@ updateStarsBalance#4e80a379 balance:StarsAmount = Update;
|
|||
updateBusinessBotCallbackQuery#1ea2fda7 flags:# query_id:long user_id:long connection_id:string message:Message reply_to_message:flags.2?Message chat_instance:long data:flags.0?bytes = Update;
|
||||
updateStarsRevenueStatus#a584b019 peer:Peer status:StarsRevenueStatus = Update;
|
||||
updateBotPurchasedPaidMedia#283bd312 user_id:long payload:string qts:int = Update;
|
||||
updatePaidReactionPrivacy#51ca7aec private:Bool = Update;
|
||||
updatePaidReactionPrivacy#8b725fce private:PaidReactionPrivacy = Update;
|
||||
|
||||
updates.state#a56c2a3e pts:int qts:int date:int seq:int unread_count:int = updates.State;
|
||||
|
||||
|
|
@ -1886,7 +1886,7 @@ starsGiveawayOption#94ce852a flags:# extended:flags.0?true default:flags.1?true
|
|||
starsGiveawayWinnersOption#54236209 flags:# default:flags.0?true users:int per_user_stars:long = StarsGiveawayWinnersOption;
|
||||
|
||||
starGift#2cc73c8 flags:# limited:flags.0?true sold_out:flags.1?true birthday:flags.2?true id:long sticker:Document stars:long availability_remains:flags.0?int availability_total:flags.0?int convert_stars:long first_sale_date:flags.1?int last_sale_date:flags.1?int upgrade_stars:flags.3?long = StarGift;
|
||||
starGiftUnique#f2fe7e4a flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int = StarGift;
|
||||
starGiftUnique#5c62d151 flags:# id:long title:string slug:string num:int owner_id:flags.0?Peer owner_name:flags.1?string owner_address:flags.2?string attributes:Vector<StarGiftAttribute> availability_issued:int availability_total:int gift_address:flags.3?string = StarGift;
|
||||
|
||||
payments.starGiftsNotModified#a388a368 = payments.StarGifts;
|
||||
payments.starGifts#901689ea hash:int gifts:Vector<StarGift> = payments.StarGifts;
|
||||
|
|
@ -1943,6 +1943,10 @@ inputSavedStarGiftChat#f101aa7f peer:InputPeer saved_id:long = InputSavedStarGif
|
|||
|
||||
payments.starGiftWithdrawalUrl#84aa3a9c url:string = payments.StarGiftWithdrawalUrl;
|
||||
|
||||
paidReactionPrivacyDefault#206ad49e = PaidReactionPrivacy;
|
||||
paidReactionPrivacyAnonymous#1f0c1ad9 = PaidReactionPrivacy;
|
||||
paidReactionPrivacyPeer#dc6cfcf0 peer:InputPeer = PaidReactionPrivacy;
|
||||
|
||||
---functions---
|
||||
|
||||
invokeAfterMsg#cb9f372d {X:Type} msg_id:long query:!X = X;
|
||||
|
|
@ -1955,6 +1959,7 @@ invokeWithTakeout#aca9fd2e {X:Type} takeout_id:long query:!X = X;
|
|||
invokeWithBusinessConnection#dd289f8e {X:Type} connection_id:string query:!X = X;
|
||||
invokeWithGooglePlayIntegrity#1df92984 {X:Type} nonce:string token:string query:!X = X;
|
||||
invokeWithApnsSecret#0dae54f8 {X:Type} nonce:string secret:string query:!X = X;
|
||||
invokeWithReCaptcha#adbb0f94 {X:Type} token:string query:!X = X;
|
||||
|
||||
auth.sendCode#a677244f phone_number:string api_id:int api_hash:string settings:CodeSettings = auth.SentCode;
|
||||
auth.signUp#aac7b717 flags:# no_joined_notifications:flags.0?true phone_number:string phone_code_hash:string first_name:string last_name:string = auth.Authorization;
|
||||
|
|
@ -2340,8 +2345,8 @@ messages.editFactCheck#589ee75 peer:InputPeer msg_id:int text:TextWithEntities =
|
|||
messages.deleteFactCheck#d1da940c peer:InputPeer msg_id:int = Updates;
|
||||
messages.getFactCheck#b9cdc5ee peer:InputPeer msg_id:Vector<int> = Vector<FactCheck>;
|
||||
messages.requestMainWebView#c9e01e7b flags:# compact:flags.7?true fullscreen:flags.8?true peer:InputPeer bot:InputUser start_param:flags.1?string theme_params:flags.0?DataJSON platform:string = WebViewResult;
|
||||
messages.sendPaidReaction#9dd6a67b flags:# peer:InputPeer msg_id:int count:int random_id:long private:flags.0?Bool = Updates;
|
||||
messages.togglePaidReactionPrivacy#849ad397 peer:InputPeer msg_id:int private:Bool = Bool;
|
||||
messages.sendPaidReaction#58bbcb50 flags:# peer:InputPeer msg_id:int count:int random_id:long private:flags.0?PaidReactionPrivacy = Updates;
|
||||
messages.togglePaidReactionPrivacy#435885b5 peer:InputPeer msg_id:int private:PaidReactionPrivacy = Bool;
|
||||
messages.getPaidReactionPrivacy#472455aa = Updates;
|
||||
messages.viewSponsoredMessage#673ad8f1 peer:InputPeer random_id:bytes = Bool;
|
||||
messages.clickSponsoredMessage#f093465 flags:# media:flags.0?true fullscreen:flags.1?true peer:InputPeer random_id:bytes = Bool;
|
||||
|
|
@ -2432,7 +2437,7 @@ channels.editLocation#58e63f6d channel:InputChannel geo_point:InputGeoPoint addr
|
|||
channels.toggleSlowMode#edd49ef0 channel:InputChannel seconds:int = Updates;
|
||||
channels.getInactiveChannels#11e831ee = messages.InactiveChats;
|
||||
channels.convertToGigagroup#b290c69 channel:InputChannel = Updates;
|
||||
channels.getSendAs#dc770ee peer:InputPeer = channels.SendAsPeers;
|
||||
channels.getSendAs#e785a43f flags:# for_paid_reactions:flags.0?true peer:InputPeer = channels.SendAsPeers;
|
||||
channels.deleteParticipantHistory#367544db channel:InputChannel participant:InputPeer = messages.AffectedHistory;
|
||||
channels.toggleJoinToSend#e4cb9580 channel:InputChannel enabled:Bool = Updates;
|
||||
channels.toggleJoinRequest#4c2985b6 channel:InputChannel enabled:Bool = Updates;
|
||||
|
|
@ -2657,4 +2662,4 @@ smsjobs.finishJob#4f1ebf24 flags:# job_id:string error:flags.0?string = Bool;
|
|||
|
||||
fragment.getCollectibleInfo#be1e85ba collectible:InputCollectible = fragment.CollectibleInfo;
|
||||
|
||||
// LAYER 198
|
||||
// LAYER 199
|
||||
|
|
|
|||
|
|
@ -223,6 +223,7 @@ def pyrogram_api():
|
|||
transcribe_audio
|
||||
translate_message_text
|
||||
start_bot
|
||||
delete_chat_history
|
||||
""",
|
||||
chats="""
|
||||
Chats
|
||||
|
|
@ -253,6 +254,7 @@ def pyrogram_api():
|
|||
get_folders
|
||||
get_forum_topics
|
||||
get_forum_topics_by_id
|
||||
get_forum_topics_count
|
||||
set_chat_username
|
||||
archive_chats
|
||||
unarchive_chats
|
||||
|
|
@ -553,6 +555,7 @@ def pyrogram_api():
|
|||
ForumTopicCreated
|
||||
ForumTopicEdited
|
||||
ForumTopicClosed
|
||||
ForumTopicDeleted
|
||||
ForumTopicReopened
|
||||
GeneralTopicHidden
|
||||
GeneralTopicUnhidden
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ from .get_dialogs_count import GetDialogsCount
|
|||
from .get_folders import GetFolders
|
||||
from .get_forum_topics import GetForumTopics
|
||||
from .get_forum_topics_by_id import GetForumTopicsByID
|
||||
from .get_forum_topics_count import GetForumTopicsCount
|
||||
from .get_send_as_chats import GetSendAsChats
|
||||
from .join_chat import JoinChat
|
||||
from .leave_chat import LeaveChat
|
||||
|
|
@ -98,6 +99,7 @@ class Chats(
|
|||
GetFolders,
|
||||
GetForumTopics,
|
||||
GetForumTopicsByID,
|
||||
GetForumTopicsCount,
|
||||
ArchiveChats,
|
||||
UnarchiveChats,
|
||||
CreateGroup,
|
||||
|
|
|
|||
|
|
@ -32,7 +32,10 @@ class GetForumTopics:
|
|||
async def get_forum_topics(
|
||||
self: "pyrogram.Client",
|
||||
chat_id: Union[int, str],
|
||||
limit: int = 0
|
||||
limit: int = 0,
|
||||
offset_date: int = 0,
|
||||
offset_id: int = 0,
|
||||
offset_topic: int = 0
|
||||
) -> Optional[AsyncGenerator["types.ForumTopic", None]]:
|
||||
"""Get one or more topic from a chat.
|
||||
|
||||
|
|
@ -46,6 +49,15 @@ class GetForumTopics:
|
|||
limit (``int``, *optional*):
|
||||
Limits the number of topics to be retrieved.
|
||||
|
||||
offset_date (``int``, *optional*):
|
||||
Date of the last message of the last found topic.
|
||||
|
||||
offset_id (``int``, *optional*):
|
||||
ID of the last message of the last found topic.
|
||||
|
||||
offset_topic (``int``, *optional*):
|
||||
ID of the last found topic.
|
||||
|
||||
Returns:
|
||||
``Generator``: On success, a generator yielding :obj:`~pyrogram.types.ForumTopic` objects is returned.
|
||||
|
||||
|
|
@ -62,7 +74,7 @@ class GetForumTopics:
|
|||
|
||||
peer = await self.resolve_peer(chat_id)
|
||||
|
||||
rpc = raw.functions.channels.GetForumTopics(channel=peer, offset_date=0, offset_id=0, offset_topic=0, limit=limit)
|
||||
rpc = raw.functions.channels.GetForumTopics(channel=peer, offset_date=offset_date, offset_id=offset_id, offset_topic=offset_topic, limit=limit)
|
||||
|
||||
r = await self.invoke(rpc, sleep_threshold=-1)
|
||||
|
||||
|
|
|
|||
63
pyrogram/methods/chats/get_forum_topics_count.py
Normal file
63
pyrogram/methods/chats/get_forum_topics_count.py
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
|
||||
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||
#
|
||||
# This file is part of Pyrofork.
|
||||
#
|
||||
# Pyrofork is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Pyrofork is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import logging
|
||||
from typing import Union, Optional, AsyncGenerator
|
||||
|
||||
import pyrogram
|
||||
from pyrogram import raw
|
||||
from pyrogram import types
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GetForumTopicsCount:
|
||||
async def get_forum_topics_count(
|
||||
self: "pyrogram.Client",
|
||||
chat_id: Union[int, str]
|
||||
) -> Optional[AsyncGenerator["types.ForumTopic", None]]:
|
||||
"""Get forum topics count from a chat.
|
||||
|
||||
.. include:: /_includes/usable-by/users.rst
|
||||
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
You can also use chat public link in form of *t.me/<username>* (str).
|
||||
|
||||
Returns:
|
||||
``int``: On success, the count of forum topics is returned.
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
# get all forum topics count
|
||||
app.get_forum_topics_count(chat_id)
|
||||
|
||||
Raises:
|
||||
ValueError: In case of invalid arguments.
|
||||
"""
|
||||
|
||||
peer = await self.resolve_peer(chat_id)
|
||||
|
||||
rpc = raw.functions.channels.GetForumTopics(channel=peer, offset_date=0, offset_id=0, offset_topic=0, limit=0)
|
||||
|
||||
r = await self.invoke(rpc, sleep_threshold=-1)
|
||||
|
||||
return r.count
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
|
||||
from .copy_media_group import CopyMediaGroup
|
||||
from .copy_message import CopyMessage
|
||||
from .delete_chat_history import DeleteChatHistory
|
||||
from .delete_messages import DeleteMessages
|
||||
from .delete_scheduled_messages import DeleteScheduledMessages
|
||||
from .download_media import DownloadMedia
|
||||
|
|
@ -78,6 +79,7 @@ from .transcribe_audio import TranscribeAudio
|
|||
from .translate_text import TranslateText
|
||||
|
||||
class Messages(
|
||||
DeleteChatHistory,
|
||||
DeleteMessages,
|
||||
DeleteScheduledMessages,
|
||||
EditMessageCaption,
|
||||
|
|
|
|||
102
pyrogram/methods/messages/delete_chat_history.py
Normal file
102
pyrogram/methods/messages/delete_chat_history.py
Normal file
|
|
@ -0,0 +1,102 @@
|
|||
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2017-present Dan <https://github.com/delivrance>
|
||||
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||
#
|
||||
# This file is part of Pyrofork.
|
||||
#
|
||||
# Pyrofork is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Pyrofork is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from datetime import datetime
|
||||
import logging
|
||||
from typing import Union
|
||||
|
||||
import pyrogram
|
||||
from pyrogram import raw
|
||||
from pyrogram import utils
|
||||
|
||||
log = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DeleteChatHistory:
|
||||
async def delete_chat_history(
|
||||
self: "pyrogram.Client",
|
||||
chat_id: Union[int, str],
|
||||
max_id: int = 0,
|
||||
revoke: bool = None,
|
||||
just_clear = None,
|
||||
min_date: datetime = None,
|
||||
max_date: datetime = None,
|
||||
) -> int:
|
||||
"""Delete the history of a chat.
|
||||
|
||||
.. include:: /_includes/usable-by/users.rst
|
||||
|
||||
Parameters:
|
||||
chat_id (``int`` | ``str``):
|
||||
Unique identifier (int) or username (str) of the target chat.
|
||||
|
||||
max_id (``int``, *optional*):
|
||||
Maximum ID of message to delete.
|
||||
|
||||
revoke (``bool``, *optional*):
|
||||
Deletes messages history for everyone.
|
||||
Required ``True`` if using in channel.
|
||||
|
||||
just_clear (``bool``, *optional*):
|
||||
If True, clear history for the current user, without actually removing chat.
|
||||
For private and simple group chats only.
|
||||
|
||||
min_date (:py:obj:`~datetime.datetime`, *optional*):
|
||||
Delete all messages newer than this time.
|
||||
For private and simple group chats only.
|
||||
|
||||
max_date (:py:obj:`~datetime.datetime`, *optional*):
|
||||
Delete all messages older than this time.
|
||||
For private and simple group chats only.
|
||||
|
||||
Returns:
|
||||
``int``: Amount of affected messages
|
||||
|
||||
Example:
|
||||
.. code-block:: python
|
||||
|
||||
# Delete all messages in channel
|
||||
await app.delete_chat_history(chat_id, revoke=True)
|
||||
"""
|
||||
peer = await self.resolve_peer(chat_id)
|
||||
|
||||
if isinstance(peer, raw.types.InputPeerChannel):
|
||||
r = await self.invoke(
|
||||
raw.functions.channels.DeleteHistory(
|
||||
channel=raw.types.InputChannel(
|
||||
channel_id=peer.channel_id,
|
||||
access_hash=peer.access_hash
|
||||
),
|
||||
max_id=max_id,
|
||||
for_everyone=revoke
|
||||
)
|
||||
)
|
||||
else:
|
||||
r = await self.invoke(
|
||||
raw.functions.messages.DeleteHistory(
|
||||
peer=peer,
|
||||
max_id=max_id,
|
||||
just_clear=just_clear,
|
||||
revoke=revoke,
|
||||
min_date=utils.datetime_to_timestamp(min_date),
|
||||
max_date=utils.datetime_to_timestamp(max_date)
|
||||
)
|
||||
)
|
||||
|
||||
return len(r.updates[0].messages) if isinstance(peer, raw.types.InputPeerChannel) else r.pts_count
|
||||
|
|
@ -23,10 +23,7 @@ from datetime import datetime
|
|||
from typing import Union, BinaryIO, List, Optional, Callable
|
||||
|
||||
import pyrogram
|
||||
from pyrogram import StopTransmission, enums
|
||||
from pyrogram import raw
|
||||
from pyrogram import types
|
||||
from pyrogram import utils
|
||||
from pyrogram import StopTransmission, enums, raw, types, utils
|
||||
from pyrogram.errors import FilePartMissing
|
||||
from pyrogram.file_id import FileType
|
||||
|
||||
|
|
@ -55,7 +52,7 @@ class SendVideo:
|
|||
reply_to_chat_id: Union[int, str] = None,
|
||||
quote_text: str = None,
|
||||
quote_entities: List["types.MessageEntity"] = None,
|
||||
cover: Optional[Union[str, "io.BytesIO"]] = None,
|
||||
cover: Union[str, BinaryIO] = None,
|
||||
start_timestamp: int = None,
|
||||
schedule_date: datetime = None,
|
||||
protect_content: bool = None,
|
||||
|
|
@ -161,8 +158,12 @@ class SendVideo:
|
|||
List of special entities that appear in quote_text, which can be specified instead of *parse_mode*.
|
||||
for reply_to_message only.
|
||||
|
||||
cover (``str`` | :obj:`io.BytesIO`, *optional*):
|
||||
Cover of the video; pass None to skip cover uploading.
|
||||
cover (``str`` | ``BinaryIO``, *optional*):
|
||||
Video cover.
|
||||
Pass a file_id as string to attach a photo that exists on the Telegram servers,
|
||||
pass a HTTP URL as a string for Telegram to get a video from the Internet,
|
||||
pass a file path as string to upload a new photo civer that exists on your local machine, or
|
||||
pass a binary file-like object with its attribute ".name" set for in-memory uploads.
|
||||
|
||||
start_timestamp (``int``, *optional*):
|
||||
Timestamp from which the video playing must start, in seconds.
|
||||
|
|
@ -224,6 +225,9 @@ class SendVideo:
|
|||
# Send self-destructing video
|
||||
await app.send_video("me", "video.mp4", ttl_seconds=10)
|
||||
|
||||
# Add video_cover to the video
|
||||
await app.send_video(channel_id, "video.mp4", video_cover="coverku.jpg")
|
||||
|
||||
# Keep track of the progress while uploading
|
||||
async def progress(current, total):
|
||||
print(f"{current * 100 / total:.1f}%")
|
||||
|
|
@ -231,6 +235,9 @@ class SendVideo:
|
|||
await app.send_video("me", "video.mp4", progress=progress)
|
||||
"""
|
||||
file = None
|
||||
vidcover_file = None
|
||||
vidcover_media = None
|
||||
peer = await self.resolve_peer(chat_id)
|
||||
|
||||
reply_to = await utils.get_reply_to(
|
||||
client=self,
|
||||
|
|
@ -245,6 +252,45 @@ class SendVideo:
|
|||
)
|
||||
|
||||
try:
|
||||
if cover is not None:
|
||||
if isinstance(cover, str):
|
||||
if os.path.isfile(cover):
|
||||
vidcover_media = await self.invoke(
|
||||
raw.functions.messages.UploadMedia(
|
||||
peer=peer,
|
||||
media=raw.types.InputMediaUploadedPhoto(
|
||||
file=await self.save_file(cover)
|
||||
)
|
||||
)
|
||||
)
|
||||
elif re.match("^https?://", cover):
|
||||
vidcover_media = await self.invoke(
|
||||
raw.functions.messages.UploadMedia(
|
||||
peer=peer,
|
||||
media=raw.types.InputMediaPhotoExternal(
|
||||
url=cover
|
||||
)
|
||||
)
|
||||
)
|
||||
else:
|
||||
vidcover_file = utils.get_input_media_from_file_id(cover, FileType.PHOTO).id
|
||||
else:
|
||||
vidcover_media = await self.invoke(
|
||||
raw.functions.messages.UploadMedia(
|
||||
peer=peer,
|
||||
media=raw.types.InputMediaUploadedPhoto(
|
||||
file=await self.save_file(cover)
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
if vidcover_media:
|
||||
vidcover_file = raw.types.InputPhoto(
|
||||
id=vidcover_media.photo.id,
|
||||
access_hash=vidcover_media.photo.access_hash,
|
||||
file_reference=vidcover_media.photo.file_reference
|
||||
)
|
||||
|
||||
if isinstance(video, str):
|
||||
if os.path.isfile(video):
|
||||
thumb = await self.save_file(thumb)
|
||||
|
|
@ -264,7 +310,7 @@ class SendVideo:
|
|||
),
|
||||
raw.types.DocumentAttributeFilename(file_name=file_name or os.path.basename(video))
|
||||
],
|
||||
video_cover=await self.save_file(cover) if cover else None,
|
||||
video_cover=vidcover_file,
|
||||
video_timestamp=start_timestamp
|
||||
)
|
||||
elif re.match("^https?://", video):
|
||||
|
|
@ -272,7 +318,7 @@ class SendVideo:
|
|||
url=video,
|
||||
ttl_seconds=ttl_seconds,
|
||||
spoiler=has_spoiler,
|
||||
video_cover=await self.save_file(cover) if cover else None,
|
||||
video_cover=vidcover_file,
|
||||
video_timestamp=start_timestamp
|
||||
)
|
||||
else:
|
||||
|
|
@ -296,14 +342,14 @@ class SendVideo:
|
|||
),
|
||||
raw.types.DocumentAttributeFilename(file_name=file_name or video.name)
|
||||
],
|
||||
video_cover=await self.save_file(cover) if cover else None,
|
||||
video_cover=vidcover_file,
|
||||
video_timestamp=start_timestamp
|
||||
)
|
||||
|
||||
while True:
|
||||
try:
|
||||
rpc = raw.functions.messages.SendMedia(
|
||||
peer=await self.resolve_peer(chat_id),
|
||||
peer=peer,
|
||||
media=media,
|
||||
silent=disable_notification or None,
|
||||
reply_to=reply_to,
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class GetChatGifts:
|
|||
limit: int = 0,
|
||||
offset: str = ""
|
||||
):
|
||||
"""Get user star gifts.
|
||||
"""Get all gifts owned by specified chat.
|
||||
|
||||
.. include:: /_includes/usable-by/users.rst
|
||||
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ class GetChatGiftsCount:
|
|||
exclude_limited: Optional[bool] = None,
|
||||
exclude_upgraded: Optional[bool] = None
|
||||
) -> int:
|
||||
"""Get the total count of star gifts of specified user.
|
||||
"""Get the total count of owned gifts of specified chat.
|
||||
|
||||
.. include:: /_includes/usable-by/users.rst
|
||||
|
||||
|
|
|
|||
|
|
@ -37,7 +37,7 @@ PRE_DELIM = "```"
|
|||
BLOCKQUOTE_DELIM = ">"
|
||||
BLOCKQUOTE_EXPANDABLE_DELIM = "**>"
|
||||
|
||||
MARKDOWN_RE = re.compile(r"({d})|(!?)\[(.+?)\]\((.+?)\)".format(
|
||||
MARKDOWN_RE = re.compile(r"({d})".format(
|
||||
d="|".join(
|
||||
["".join(i) for i in [
|
||||
[rf"\{j}" for j in i]
|
||||
|
|
@ -52,6 +52,7 @@ MARKDOWN_RE = re.compile(r"({d})|(!?)\[(.+?)\]\((.+?)\)".format(
|
|||
]
|
||||
]]
|
||||
)))
|
||||
URL_RE = re.compile(r"(!?)\[(.+?)\]\((.+?)\)")
|
||||
|
||||
OPENING_TAG = "<{}>"
|
||||
CLOSING_TAG = "</{}>"
|
||||
|
|
@ -118,7 +119,7 @@ class Markdown:
|
|||
|
||||
for i, match in enumerate(re.finditer(MARKDOWN_RE, text)):
|
||||
start, _ = match.span()
|
||||
delim, is_emoji, text_url, url = match.groups()
|
||||
delim = match.group(1)
|
||||
full = match.group(0)
|
||||
|
||||
if delim in FIXED_WIDTH_DELIMS:
|
||||
|
|
@ -127,16 +128,6 @@ class Markdown:
|
|||
if is_fixed_width and delim not in FIXED_WIDTH_DELIMS:
|
||||
continue
|
||||
|
||||
if not is_emoji and text_url:
|
||||
text = utils.replace_once(text, full, URL_MARKUP.format(url, text_url), start)
|
||||
continue
|
||||
|
||||
if is_emoji:
|
||||
emoji = text_url
|
||||
emoji_id = url.lstrip("tg://emoji?id=")
|
||||
text = utils.replace_once(text, full, EMOJI_MARKUP.format(emoji_id, emoji), start)
|
||||
continue
|
||||
|
||||
if delim == BOLD_DELIM:
|
||||
tag = "b"
|
||||
elif delim == ITALIC_DELIM:
|
||||
|
|
@ -169,6 +160,21 @@ class Markdown:
|
|||
|
||||
text = utils.replace_once(text, delim, tag, start)
|
||||
|
||||
for i, match in enumerate(re.finditer(URL_RE, text)):
|
||||
start, _ = match.span()
|
||||
is_emoji, text_url, url = match.groups()
|
||||
full = match.group(0)
|
||||
|
||||
if not is_emoji and text_url:
|
||||
text = utils.replace_once(text, full, URL_MARKUP.format(url, text_url), start)
|
||||
continue
|
||||
|
||||
if is_emoji:
|
||||
emoji = text_url
|
||||
emoji_id = url.lstrip("tg://emoji?id=")
|
||||
text = utils.replace_once(text, full, EMOJI_MARKUP.format(emoji_id, emoji), start)
|
||||
continue
|
||||
|
||||
for placeholder, code_section in placeholders.items():
|
||||
text = text.replace(placeholder, code_section)
|
||||
|
||||
|
|
@ -176,78 +182,84 @@ class Markdown:
|
|||
|
||||
@staticmethod
|
||||
def unparse(text: str, entities: list):
|
||||
"""
|
||||
Performs the reverse operation to .parse(), effectively returning
|
||||
markdown-like syntax given a normal text and its MessageEntity's.
|
||||
|
||||
:param text: the text to be reconverted into markdown.
|
||||
:param entities: list of MessageEntity's applied to the text.
|
||||
:return: a markdown-like text representing the combination of both inputs.
|
||||
"""
|
||||
delimiters = {
|
||||
MessageEntityType.BOLD: BOLD_DELIM,
|
||||
MessageEntityType.ITALIC: ITALIC_DELIM,
|
||||
MessageEntityType.UNDERLINE: UNDERLINE_DELIM,
|
||||
MessageEntityType.STRIKETHROUGH: STRIKE_DELIM,
|
||||
MessageEntityType.CODE: CODE_DELIM,
|
||||
MessageEntityType.PRE: PRE_DELIM,
|
||||
MessageEntityType.BLOCKQUOTE: BLOCKQUOTE_DELIM,
|
||||
MessageEntityType.EXPANDABLE_BLOCKQUOTE: BLOCKQUOTE_EXPANDABLE_DELIM,
|
||||
MessageEntityType.SPOILER: SPOILER_DELIM
|
||||
}
|
||||
|
||||
text = utils.add_surrogates(text)
|
||||
|
||||
entities_offsets = []
|
||||
|
||||
for entity in entities:
|
||||
entity_type = entity.type
|
||||
start = entity.offset
|
||||
end = start + entity.length
|
||||
|
||||
if entity_type == MessageEntityType.BOLD:
|
||||
start_tag = end_tag = BOLD_DELIM
|
||||
elif entity_type == MessageEntityType.ITALIC:
|
||||
start_tag = end_tag = ITALIC_DELIM
|
||||
elif entity_type == MessageEntityType.UNDERLINE:
|
||||
start_tag = end_tag = UNDERLINE_DELIM
|
||||
elif entity_type == MessageEntityType.STRIKETHROUGH:
|
||||
start_tag = end_tag = STRIKE_DELIM
|
||||
elif entity_type == MessageEntityType.CODE:
|
||||
start_tag = end_tag = CODE_DELIM
|
||||
elif entity_type == MessageEntityType.PRE:
|
||||
language = getattr(entity, "language", "") or ""
|
||||
start_tag = f"{PRE_DELIM}{language}\n"
|
||||
end_tag = f"\n{PRE_DELIM}"
|
||||
elif entity_type == MessageEntityType.BLOCKQUOTE:
|
||||
if entity.collapsed:
|
||||
start_tag = BLOCKQUOTE_EXPANDABLE_DELIM + " "
|
||||
insert_at = []
|
||||
for i, entity in enumerate(entities):
|
||||
s = entity.offset
|
||||
e = entity.offset + entity.length
|
||||
delimiter = delimiters.get(entity.type, None)
|
||||
if delimiter:
|
||||
if entity.type != MessageEntityType.BLOCKQUOTE and entity.type != MessageEntityType.EXPANDABLE_BLOCKQUOTE:
|
||||
open_delimiter = delimiter
|
||||
close_delimiter = delimiter
|
||||
if entity.type == MessageEntityType.PRE:
|
||||
close_delimiter = '\n' + delimiter
|
||||
if entity.language:
|
||||
open_delimiter += entity.language + '\n'
|
||||
else:
|
||||
open_delimiter += '\n'
|
||||
insert_at.append((s, i, open_delimiter))
|
||||
insert_at.append((e, -i, close_delimiter))
|
||||
else:
|
||||
start_tag = BLOCKQUOTE_DELIM + " "
|
||||
end_tag = ""
|
||||
blockquote_text = text[start:end]
|
||||
lines = blockquote_text.split("\n")
|
||||
last_length = 0
|
||||
for line in lines:
|
||||
if len(line) == 0 and last_length == end:
|
||||
continue
|
||||
start_offset = start+last_length
|
||||
last_length = last_length+len(line)
|
||||
end_offset = start_offset+last_length
|
||||
entities_offsets.append((start_tag, start_offset,))
|
||||
entities_offsets.append((end_tag, end_offset,))
|
||||
last_length = last_length+1
|
||||
continue
|
||||
elif entity_type == MessageEntityType.SPOILER:
|
||||
start_tag = end_tag = SPOILER_DELIM
|
||||
elif entity_type == MessageEntityType.TEXT_LINK:
|
||||
url = entity.url
|
||||
start_tag = "["
|
||||
end_tag = f"]({url})"
|
||||
elif entity_type == MessageEntityType.TEXT_MENTION:
|
||||
user = entity.user
|
||||
start_tag = "["
|
||||
end_tag = f"](tg://user?id={user.id})"
|
||||
elif entity_type == MessageEntityType.CUSTOM_EMOJI:
|
||||
emoji_id = entity.custom_emoji_id
|
||||
start_tag = ""
|
||||
# Handle multiline blockquotes
|
||||
text_subset = text[s:e]
|
||||
lines = text_subset.splitlines()
|
||||
for line_num, line in enumerate(lines):
|
||||
line_start = s + sum(len(l) + 1 for l in lines[:line_num])
|
||||
if entity.collapsed:
|
||||
insert_at.append((line_start, i, BLOCKQUOTE_EXPANDABLE_DELIM))
|
||||
else:
|
||||
insert_at.append((line_start, i, BLOCKQUOTE_DELIM))
|
||||
# No closing delimiter for blockquotes
|
||||
else:
|
||||
continue
|
||||
url = None
|
||||
is_emoji = False
|
||||
if entity.type == MessageEntityType.TEXT_LINK:
|
||||
url = entity.url
|
||||
elif entity.type == MessageEntityType.TEXT_MENTION:
|
||||
url = f'tg://user?id={entity.user.id}'
|
||||
elif entity.type == MessageEntityType.CUSTOM_EMOJI:
|
||||
url = f"tg://emoji?id={entity.custom_emoji_id}"
|
||||
is_emoji = True
|
||||
if url:
|
||||
if is_emoji:
|
||||
insert_at.append((s, i, ''))
|
||||
|
||||
entities_offsets.append((start_tag, start,))
|
||||
entities_offsets.append((end_tag, end,))
|
||||
insert_at.sort(key=lambda t: (t[0], t[1]))
|
||||
while insert_at:
|
||||
at, _, what = insert_at.pop()
|
||||
|
||||
entities_offsets = map(
|
||||
lambda x: x[1],
|
||||
sorted(
|
||||
enumerate(entities_offsets),
|
||||
key=lambda x: (x[1][1], x[0]),
|
||||
reverse=True
|
||||
)
|
||||
)
|
||||
# If we are in the middle of a surrogate nudge the position by -1.
|
||||
# Otherwise we would end up with malformed text and fail to encode.
|
||||
# For example of bad input: "Hi \ud83d\ude1c"
|
||||
# https://en.wikipedia.org/wiki/UTF-16#U+010000_to_U+10FFFF
|
||||
while utils.within_surrogate(text, at):
|
||||
at += 1
|
||||
|
||||
for entity, offset in entities_offsets:
|
||||
text = text[:offset] + entity + text[offset:]
|
||||
text = text[:at] + what + text[at:]
|
||||
|
||||
return utils.remove_surrogates(text)
|
||||
|
|
|
|||
|
|
@ -40,3 +40,16 @@ def remove_surrogates(text):
|
|||
|
||||
def replace_once(source: str, old: str, new: str, start: int):
|
||||
return source[:start] + source[start:].replace(old, new, 1)
|
||||
|
||||
def within_surrogate(text, index, *, length=None):
|
||||
"""
|
||||
`True` if ``index`` is within a surrogate (before and after it, not at!).
|
||||
"""
|
||||
if length is None:
|
||||
length = len(text)
|
||||
|
||||
return (
|
||||
1 < index < len(text) and # in bounds
|
||||
'\ud800' <= text[index - 1] <= '\udbff' and # previous is
|
||||
'\ud800' <= text[index] <= '\udfff' # current is
|
||||
)
|
||||
|
|
|
|||
|
|
@ -23,9 +23,7 @@ from functools import partial
|
|||
from typing import List, Match, Union, BinaryIO, Optional, Callable
|
||||
|
||||
import pyrogram
|
||||
from pyrogram import raw, enums
|
||||
from pyrogram import types
|
||||
from pyrogram import utils
|
||||
from pyrogram import enums, raw, types, utils
|
||||
from pyrogram.errors import ChannelPrivate, MessageIdsEmpty, PeerIdInvalid
|
||||
from pyrogram.parser import utils as parser_utils, Parser
|
||||
from ..object import Object
|
||||
|
|
@ -424,6 +422,9 @@ class Message(Object, Update):
|
|||
link (``str``, *property*):
|
||||
Generate a link to this message, only for groups and channels.
|
||||
|
||||
content (``str``, *property*):
|
||||
The text or caption content of the message.
|
||||
|
||||
scheduled (``bool``, *optional*):
|
||||
Message is a scheduled message and still in schedule.
|
||||
|
||||
|
|
@ -1112,7 +1113,7 @@ class Message(Object, Update):
|
|||
video_note = types.VideoNote._parse(client, doc, video_attributes)
|
||||
media_type = enums.MessageMediaType.VIDEO_NOTE
|
||||
else:
|
||||
video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds)
|
||||
video = types.Video._parse(client, doc, video_attributes, file_name, media.ttl_seconds, media.video_cover, media.video_timestamp)
|
||||
media_type = enums.MessageMediaType.VIDEO
|
||||
has_media_spoiler = media.spoiler
|
||||
|
||||
|
|
@ -1358,6 +1359,10 @@ class Message(Object, Update):
|
|||
else:
|
||||
return f"https://t.me/c/{utils.get_channel_id(self.chat.id)}/{self.id}"
|
||||
|
||||
@property
|
||||
def content(self) -> str:
|
||||
return self.text or self.caption or Str("").init([])
|
||||
|
||||
async def get_media_group(self) -> List["types.Message"]:
|
||||
"""Bound method *get_media_group* of :obj:`~pyrogram.types.Message`.
|
||||
|
||||
|
|
@ -3593,7 +3598,7 @@ class Message(Object, Update):
|
|||
quote_entities: List["types.MessageEntity"] = None,
|
||||
allow_paid_broadcast: bool = None,
|
||||
message_effect_id: int = None,
|
||||
cover: Optional[Union[str, "io.BytesIO"]] = None,
|
||||
cover: Union[str, BinaryIO] = None,
|
||||
start_timestamp: int = None,
|
||||
schedule_date: datetime = None,
|
||||
invert_media: bool = None,
|
||||
|
|
@ -3700,8 +3705,12 @@ class Message(Object, Update):
|
|||
allow_paid_broadcast (``bool``, *optional*):
|
||||
Pass True to allow the message to ignore regular broadcast limits for a small fee; for bots.
|
||||
|
||||
cover (``str`` | :obj:`io.BytesIO`, *optional*):
|
||||
Cover of the video; pass None to skip cover uploading.
|
||||
cover (``str`` | ``BinaryIO``, *optional*):
|
||||
Video cover.
|
||||
Pass a file_id as string to attach a photo that exists on the Telegram servers,
|
||||
pass a HTTP URL as a string for Telegram to get a video from the Internet,
|
||||
pass a file path as string to upload a new photo civer that exists on your local machine, or
|
||||
pass a binary file-like object with its attribute ".name" set for in-memory uploads.
|
||||
|
||||
start_timestamp (``int``, *optional*):
|
||||
Timestamp from which the video playing must start, in seconds.
|
||||
|
|
|
|||
|
|
@ -21,8 +21,7 @@ from datetime import datetime
|
|||
from typing import List
|
||||
|
||||
import pyrogram
|
||||
from pyrogram import raw, utils
|
||||
from pyrogram import types
|
||||
from pyrogram import raw, types, utils
|
||||
from pyrogram.file_id import FileId, FileType, FileUniqueId, FileUniqueType, ThumbnailSource
|
||||
from ..object import Object
|
||||
|
||||
|
|
@ -67,6 +66,12 @@ class Video(Object):
|
|||
|
||||
thumbs (List of :obj:`~pyrogram.types.Thumbnail`, *optional*):
|
||||
Video thumbnails.
|
||||
|
||||
cover (:obj:`~pyrogram.types.Photo`, *optional*):
|
||||
Video cover.
|
||||
|
||||
start_timestamp (``int``, *optional*):
|
||||
Video startpoint, in seconds.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
|
|
@ -84,7 +89,9 @@ class Video(Object):
|
|||
supports_streaming: bool = None,
|
||||
ttl_seconds: int = None,
|
||||
date: datetime = None,
|
||||
thumbs: List["types.Thumbnail"] = None
|
||||
thumbs: List["types.Thumbnail"] = None,
|
||||
cover: "types.Photo" = None,
|
||||
start_timestamp: int = None,
|
||||
):
|
||||
super().__init__(client)
|
||||
|
||||
|
|
@ -100,6 +107,8 @@ class Video(Object):
|
|||
self.ttl_seconds = ttl_seconds
|
||||
self.date = date
|
||||
self.thumbs = thumbs
|
||||
self.cover = cover
|
||||
self.start_timestamp = start_timestamp
|
||||
|
||||
@staticmethod
|
||||
def _parse(
|
||||
|
|
@ -107,7 +116,9 @@ class Video(Object):
|
|||
video: "raw.types.Document",
|
||||
video_attributes: "raw.types.DocumentAttributeVideo",
|
||||
file_name: str,
|
||||
ttl_seconds: int = None
|
||||
ttl_seconds: int = None,
|
||||
cover = None,
|
||||
start_timestamp: int = None
|
||||
) -> "Video":
|
||||
return Video(
|
||||
file_id=FileId(
|
||||
|
|
@ -131,5 +142,7 @@ class Video(Object):
|
|||
date=utils.timestamp_to_datetime(video.date),
|
||||
ttl_seconds=ttl_seconds,
|
||||
thumbs=types.Thumbnail._parse(client, video),
|
||||
cover=types.Photo._parse(client, cover),
|
||||
start_timestamp=start_timestamp,
|
||||
client=client
|
||||
)
|
||||
|
|
|
|||
|
|
@ -50,6 +50,7 @@ from .username import Username
|
|||
from .forum_topic import ForumTopic
|
||||
from .forum_topic_created import ForumTopicCreated
|
||||
from .forum_topic_closed import ForumTopicClosed
|
||||
from .forum_topic_deleted import ForumTopicDeleted
|
||||
from .forum_topic_reopened import ForumTopicReopened
|
||||
from .forum_topic_edited import ForumTopicEdited
|
||||
from .general_forum_topic_hidden import GeneralTopicHidden
|
||||
|
|
@ -88,6 +89,7 @@ __all__ = [
|
|||
"ForumTopic",
|
||||
"ForumTopicCreated",
|
||||
"ForumTopicClosed",
|
||||
"ForumTopicDeleted",
|
||||
"ForumTopicReopened",
|
||||
"ForumTopicEdited",
|
||||
"GeneralTopicHidden",
|
||||
|
|
|
|||
|
|
@ -123,7 +123,9 @@ class ForumTopic(Object):
|
|||
#self.draft = draft //todo
|
||||
|
||||
@staticmethod
|
||||
def _parse(forum_topic: "raw.types.forum_topic") -> "ForumTopic":
|
||||
def _parse(forum_topic: "raw.types.ForumTopic") -> "ForumTopic":
|
||||
if isinstance(forum_topic, raw.types.ForumTopicDeleted):
|
||||
return types.ForumTopicDeleted._parse(forum_topic)
|
||||
from_id = forum_topic.from_id
|
||||
if isinstance(from_id, raw.types.PeerChannel):
|
||||
peer = types.PeerChannel._parse(from_id)
|
||||
|
|
|
|||
45
pyrogram/types/user_and_chats/forum_topic_deleted.py
Normal file
45
pyrogram/types/user_and_chats/forum_topic_deleted.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Pyrofork - Telegram MTProto API Client Library for Python
|
||||
# Copyright (C) 2022-present Mayuri-Chan <https://github.com/Mayuri-Chan>
|
||||
#
|
||||
# This file is part of Pyrofork.
|
||||
#
|
||||
# Pyrofork is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU Lesser General Public License as published
|
||||
# by the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
#
|
||||
# Pyrofork is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU Lesser General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU Lesser General Public License
|
||||
# along with Pyrofork. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
from pyrogram import raw, types
|
||||
from typing import Union
|
||||
from ..object import Object
|
||||
|
||||
|
||||
class ForumTopicDeleted(Object):
|
||||
"""A deleted forum topic.
|
||||
|
||||
Parameters:
|
||||
id (``Integer``):
|
||||
Id of the topic
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
*,
|
||||
id: int
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.id = id
|
||||
|
||||
@staticmethod
|
||||
def _parse(forum_topic: "raw.types.ForumTopicDeleted") -> "ForumTopicDeleted":
|
||||
return ForumTopicDeleted(
|
||||
id=getattr(forum_topic,"id", None)
|
||||
)
|
||||
Loading…
Reference in a new issue