From aac6919c3161ef9485ab5f3e866b9964a3e51891 Mon Sep 17 00:00:00 2001 From: Mohammad Mahdi Samei <9259samei@gmail.com> Date: Thu, 22 Aug 2024 20:42:30 +0330 Subject: [PATCH 1/4] feat: add sending message option and command to anyone for better experience and automation flow --- README.rst | 6 ++++++ setup.py | 5 +++-- telegram_upload/management.py | 39 ++++++++++++++++++++++++++++++++++- 3 files changed, 47 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 1c7fefef..3cb246eb 100644 --- a/README.rst +++ b/README.rst @@ -73,6 +73,11 @@ downloaded until the last text message. $ telegram-download +You can also **send messages** only to your saved messages or to a specific chat: +.. code-block:: console + + $ telegram-message "Hello, world!" + `Read the documentation `_ for more info about the options availables. @@ -85,6 +90,7 @@ a **terminal 🪄 wizard**. It even **supports mouse**! $ telegram-upload --interactive # Interactive upload $ telegram-download --interactive # Interactive download + $ telegram-message --interactive # Interactive message `More info in the documentation `_ diff --git a/setup.py b/setup.py index 45948b60..a1d4d5c7 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ MODULE = 'telegram_upload' REQUIREMENT_FILE = 'requirements.txt' STATUS_LEVEL = 5 # 1:Planning 2:Pre-Alpha 3:Alpha 4:Beta 5:Production/Stable 6:Mature 7:Inactive -KEYWORDS = ['telegram-upload', 'telegram', 'upload', 'video'] +KEYWORDS = ['telegram-upload', 'telegram', 'upload', 'video', 'messenger', 'file', 'bot'] LICENSE = 'MIT license' CLASSIFIERS = [ # https://github.com/github/choosealicense.com/tree/gh-pages/_licenses @@ -40,7 +40,7 @@ # 'ios' # 'android' ] -PYTHON_VERSIONS = ['3.7-3.9', '3.10', '3.11'] +PYTHON_VERSIONS = ['3.7-3.9', '3.10', '3.11', '3.12'] def read_requirement_file(path): @@ -140,6 +140,7 @@ def get_platform_classifiers(platform): "console_scripts": [ "telegram-upload = telegram_upload.management:upload_cli", "telegram-download = telegram_upload.management:download_cli", + "telegram-message = telegram_upload.management:message_cli", ], }, diff --git a/telegram_upload/management.py b/telegram_upload/management.py index fa397aae..024f29da 100644 --- a/telegram_upload/management.py +++ b/telegram_upload/management.py @@ -231,15 +231,52 @@ def download(from_, config, delete_on_success, proxy, split_files, interactive): client.download_files(from_, download_files, delete_on_success) +@click.command() +@click.argument('messages', nargs=-1) +@click.option('--to', default=None, help='Phone number, username, invite link or "me" (saved messages). ' + 'By default "me".') +@click.option('--config', default=None, help='Configuration file to use. By default "{}".'.format(CONFIG_FILE)) +@click.option('-pm', '--parse_mode', default=None, + help='Parse mode for the message. Options: "text", "markdown", "html". By default "text" or None.') +@click.option('-p', '--proxy', default=None, + help='Use an http proxy, socks4, socks5 or mtproxy. For example socks5://user:pass@1.2.3.4:8080 ' + 'for socks5 and mtproxy://secret@1.2.3.4:443 for mtproxy.') +@click.option('-i', '--interactive', is_flag=True, + help='Use interactive mode.') +def message(messages, to, config, forward, proxy, interactive): + """Send messages to a chat or user.""" + client = TelegramManagerClient(config or default_config(), proxy=proxy) + client.start() + if not messages: + click.echo('No messages to send. Should be at least one message within quotation marks.') + return + if parse_mode: + parse_mode = parse_mode.lower() + if parse_mode not in ('text', 'markdown', 'html'): + click.echo('Invalid parse mode. Options: "text", "markdown", "html".') + parse_mode = None + else: + parse_mode = None + if interactive and not to: + click.echo('Select the recipient dialog of the messages:') + click.echo('[SPACE] Select dialog [ENTER] Next step') + to = async_to_sync(interactive_select_dialog(client)) + elif to is None: + to = 'me' + if isinstance(to, str) and to.lstrip("-+").isdigit(): + to = int(to) + client.send_messages(to, messages, parse_mode) + upload_cli = catch(upload) download_cli = catch(download) +message_cli = catch(message) if __name__ == '__main__': import sys import re sys.argv[0] = re.sub(r'(-script\.pyw|\.exe)?$', '', sys.argv[0]) - commands = {'upload': upload_cli, 'download': download_cli} + commands = {'upload': upload_cli, 'download': download_cli, 'message': message_cli} if len(sys.argv) < 2: sys.stderr.write('A command is required. Available commands: {}\n'.format( ', '.join(commands) From 70ee4e33a97fb08d990a5f17874f95bc9a6015d2 Mon Sep 17 00:00:00 2001 From: Mohammad Mahdi Samei <9259samei@gmail.com> Date: Thu, 22 Aug 2024 22:35:32 +0330 Subject: [PATCH 2/4] feat: Add TelegramMessageClient class for sending messages and forwarding them This commit adds a new class called TelegramMessageClient to the telegram_upload/client/telegram_message_client.py file. The class extends the TelegramClient class from the telethon library and provides methods for sending messages and forwarding them to multiple destinations. The TelegramMessageClient class includes the following methods: - forward_to: Forwards a message to multiple destinations. - send_one_message: Sends a single message to an entity with optional retries and parse mode. - send_messages: Sends multiple messages to an entity with optional parse mode and forwarding. This new functionality improves the experience and automation flow for sending messages using the Telegram API. --- .../client/telegram_message_client.py | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 telegram_upload/client/telegram_message_client.py diff --git a/telegram_upload/client/telegram_message_client.py b/telegram_upload/client/telegram_message_client.py new file mode 100644 index 00000000..42bbc21a --- /dev/null +++ b/telegram_upload/client/telegram_message_client.py @@ -0,0 +1,53 @@ +import time +from typing import Iterable, Optional + +import click +from telethon import TelegramClient +from telethon.errors import RPCError, FloodWaitError, InvalidBufferError +from enum import Enum + +RETRIES = 3 + +class ParsMode(str, Enum): + HTML = 'html' + MARKDOWN = 'markdown' + MD = 'md' + + +class TelegramMessageClient(TelegramClient): + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + + def forward_to(self, message, destinations): + for destination in destinations: + self.forward_messages(destination, [message]) + + def send_one_message(self, entity, text_message: str, retries=RETRIES, parse_mode: Optional[ParsMode] = None): + message = None + try: + message = self.send_message(entity, text_message, parse_mode=parse_mode) + except FloodWaitError as e: + click.echo(f'{e}. Waiting for {e.seconds} seconds.', err=True) + time.sleep(e.seconds) + message = self.send_one_message(entity, text_message, retries=retries, parse_mode=parse_mode) + except RPCError as e: + if retries > 0: + click.echo(f'The message "{text_message}" could not be sent: {e}. Retrying...', err=True) + message = self.send_one_message(entity, text_message, retries=retries - 1, parse_mode=parse_mode) + else: + click.echo(f'The message "{text_message}" could not be sent: {e}. It will not be retried.', err=True) + return message + + def send_messages(self, entity, text_messages: Iterable[str], parse_mode: Optional[ParsMode] = None, forward=()): + messages = [] + for text_msg in text_messages: + try: + message = self.send_one_message(entity, text_msg, parse_mode = parse_mode) + finally: + pass + if message is None: + click.echo('Failed to send message "{}"'.format(text_msg), err=True) + if message: + self.forward_to(message, forward) + messages.append(message) + return messages \ No newline at end of file From b7c1b2c6f830a1745d71ca777a42d1f994e3e868 Mon Sep 17 00:00:00 2001 From: Mohammad Mahdi Samei <9259samei@gmail.com> Date: Thu, 22 Aug 2024 22:35:50 +0330 Subject: [PATCH 3/4] feat: Add TelegramMessageClient to TelegramManagerClient This commit adds the TelegramMessageClient class to the TelegramManagerClient, allowing for sending messages and forwarding them. This enhances the functionality and automation flow of the application. Refactor the TelegramManagerClient class to inherit from TelegramUploadClient, TelegramDownloadClient, and TelegramMessageClient. --- telegram_upload/client/telegram_manager_client.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/telegram_upload/client/telegram_manager_client.py b/telegram_upload/client/telegram_manager_client.py index 77ce25de..80b121e3 100644 --- a/telegram_upload/client/telegram_manager_client.py +++ b/telegram_upload/client/telegram_manager_client.py @@ -15,6 +15,7 @@ from telegram_upload.client.telegram_download_client import TelegramDownloadClient from telegram_upload.client.telegram_upload_client import TelegramUploadClient +from telegram_upload.client.telegram_message_client import TelegramMessageClient from telegram_upload.config import SESSION_FILE from telegram_upload.exceptions import TelegramProxyError, InvalidApiFileError @@ -82,7 +83,7 @@ def parse_proxy_string(proxy: Union[str, None]): proxy_parsed.username, proxy_parsed.password) -class TelegramManagerClient(TelegramUploadClient, TelegramDownloadClient): +class TelegramManagerClient(TelegramUploadClient, TelegramDownloadClient, TelegramMessageClient): def __init__(self, config_file, proxy=None, **kwargs): with open(config_file) as f: config = json.load(f) From 6ee29906cea1ae7b3587d0a05249a121d169e412 Mon Sep 17 00:00:00 2001 From: Mohammad Mahdi Samei <9259samei@gmail.com> Date: Thu, 22 Aug 2024 22:36:16 +0330 Subject: [PATCH 4/4] feat: Add option to forward messages to a chat or user Add the option to forward messages to a chat or user by specifying the alias, id, username, mobile, or id. This option can be used multiple times. Refactor the `message` function in `telegram_upload/management.py` to include the new `forward` option. The function now accepts the `forward` option as a command-line argument and passes it to the `send_messages` method of the `TelegramManagerClient` class. This commit also includes a minor fix to handle the `parse_mode` option when it is set to 'text'. --- telegram_upload/management.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/telegram_upload/management.py b/telegram_upload/management.py index 024f29da..e2d4e4c4 100644 --- a/telegram_upload/management.py +++ b/telegram_upload/management.py @@ -238,12 +238,14 @@ def download(from_, config, delete_on_success, proxy, split_files, interactive): @click.option('--config', default=None, help='Configuration file to use. By default "{}".'.format(CONFIG_FILE)) @click.option('-pm', '--parse_mode', default=None, help='Parse mode for the message. Options: "text", "markdown", "html". By default "text" or None.') +@click.option('-f', '--forward', multiple=True, help='Forward the file to a chat (alias or id) or user (username, ' + 'mobile or id). This option can be used multiple times.') @click.option('-p', '--proxy', default=None, help='Use an http proxy, socks4, socks5 or mtproxy. For example socks5://user:pass@1.2.3.4:8080 ' 'for socks5 and mtproxy://secret@1.2.3.4:443 for mtproxy.') @click.option('-i', '--interactive', is_flag=True, help='Use interactive mode.') -def message(messages, to, config, forward, proxy, interactive): +def message(messages, to, config, parse_mode, proxy, interactive, forward): """Send messages to a chat or user.""" client = TelegramManagerClient(config or default_config(), proxy=proxy) client.start() @@ -255,6 +257,8 @@ def message(messages, to, config, forward, proxy, interactive): if parse_mode not in ('text', 'markdown', 'html'): click.echo('Invalid parse mode. Options: "text", "markdown", "html".') parse_mode = None + if parse_mode == 'text': + parse_mode = None else: parse_mode = None if interactive and not to: @@ -265,7 +269,7 @@ def message(messages, to, config, forward, proxy, interactive): to = 'me' if isinstance(to, str) and to.lstrip("-+").isdigit(): to = int(to) - client.send_messages(to, messages, parse_mode) + client.send_messages(to, messages, parse_mode, forward) upload_cli = catch(upload) download_cli = catch(download)