From c5e957bfd1bf45b66f52370e030afa564dfe2e26 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sat, 7 Mar 2026 19:01:53 +0000 Subject: [PATCH 1/4] feat: add --chat-id and --alias CLI parameters - Add `--chat-id ` flag to override the default chat id. - Add `--alias ` flag to use predefined chat ids from the `[aliases]` config section. - Support negative chat IDs by correctly casting them to integers. - Update `README.md` with usage and configuration examples. Co-authored-by: scartill <1055918+scartill@users.noreply.github.com> --- README.md | 16 ++++++++++++++++ telegram_send/telegram_send.py | 34 +++++++++++++++++++++++++--------- 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index da8b4d4..a07c1fc 100644 --- a/README.md +++ b/README.md @@ -61,6 +61,22 @@ The `--pre` flag formats messages as fixed-width text: telegram-send --pre "monospace" ``` +To send a message to a specific chat id without changing the configuration: +``` shell +telegram-send --chat-id -1001234567890 "Hello" +``` + +To send a message to a chat id by alias (the mapping should be in the configuration): +``` shell +telegram-send --alias channel "Hello" +``` +To use an alias, you must define an `[aliases]` block in the configuration file (`~/.config/telegram-send.conf` by default), mapping the alias to a specific chat ID: +```ini +[aliases] +channel = -1001234567890 +group = -1009876543210 +``` + To send a message without link previews: ``` shell telegram-send --disable-web-page-preview "https://github.com/rahiel/telegram-send" diff --git a/telegram_send/telegram_send.py b/telegram_send/telegram_send.py index 009c652..058deec 100644 --- a/telegram_send/telegram_send.py +++ b/telegram_send/telegram_send.py @@ -93,6 +93,8 @@ async def run(): parser.add_argument("--timeout", help="Set the read timeout for network operations. (in seconds)", type=float, default=30., action="store") parser.add_argument("--version", action="version", version=f"%(prog)s {__version__}") + parser.add_argument("--chat-id", help="Target chat id to send messages to, bypassing the configured chat id", dest="chat_id") + parser.add_argument("--alias", help="Target chat alias to send messages to, bypassing the configured chat id. The mapping is stored in the configuration.", dest="alias") args = parser.parse_args() conf : list[str | None] @@ -130,7 +132,7 @@ async def run(): args.message = [message] + args.message try: - await delete(args.delete, conf=conf[0]) + await delete(args.delete, conf=conf[0], chat_id=args.chat_id, alias=args.alias) message_ids = [] for c in conf: message_ids += await send( @@ -148,7 +150,9 @@ async def run(): audios=args.audio, captions=args.caption, locations=args.location, - timeout=args.timeout + timeout=args.timeout, + chat_id=args.chat_id, + alias=args.alias ) if args.showids and message_ids: smessage_ids = [str(m) for m in message_ids] @@ -170,7 +174,7 @@ async def run(): async def send(*, messages=None, files=None, images=None, stickers=None, animations=None, videos=None, audios=None, captions=None, locations=None, conf=None, parse_mode=None, pre=False, silent=False, - disable_web_page_preview=False, timeout=30): + disable_web_page_preview=False, timeout=30, chat_id=None, alias=None): """Send data over Telegram. All arguments are optional. Always use this function with explicit keyword arguments. So @@ -206,8 +210,10 @@ async def send(*, - silent (bool): Send silently without sound. - disable_web_page_preview (bool): Disables web page previews for all links in the messages. - timeout (int|float): The read timeout for network connections in seconds. + - chat_id (str): Target chat id to send messages to, bypassing the configured chat id. + - alias (str): Target chat alias to send messages to, bypassing the configured chat id. The mapping is stored in the configuration. """ - settings = get_config_settings(conf) + settings = get_config_settings(conf, override_chat_id=chat_id, override_alias=alias) token = settings.token chat_id = settings.chat_id reply_to_message_id = settings.reply_to_message_id @@ -323,7 +329,7 @@ def make_captions(items, captions): return message_ids -async def delete(message_ids, conf=None, timeout=30): +async def delete(message_ids, conf=None, timeout=30, chat_id=None, alias=None): """Delete messages that have been sent before over Telegram. Restrictions given by Telegram API apply. Note that Telegram restricts this to messages which have been sent during the last 48 hours. @@ -335,8 +341,10 @@ async def delete(message_ids, conf=None, timeout=30): - conf (str): Path of configuration file to use. Will use the default config if not specified. `~` expands to user's home directory. - timeout (int|float): The read timeout for network connections in seconds. + - chat_id (str): Target chat id to delete messages from, bypassing the configured chat id. + - alias (str): Target chat alias to delete messages from. """ - settings = get_config_settings(conf) + settings = get_config_settings(conf, override_chat_id=chat_id, override_alias=alias) token = settings.token chat_id = settings.chat_id bot = telegram.Bot(token) @@ -557,7 +565,7 @@ class Settings(NamedTuple): reply_to_message_id: int | str | None -def get_config_settings(conf=None) -> Settings: +def get_config_settings(conf=None, override_chat_id=None, override_alias=None) -> Settings: conf = expanduser(conf) if conf else get_config_path() config = configparser.ConfigParser() if not config.read(conf) or not config.has_section("telegram"): @@ -568,10 +576,18 @@ def get_config_settings(conf=None) -> Settings: raise ConfigError(f"Missing options in config: {', '.join(missing_options)}") token = config.get("telegram", "token") - chat = config.get("telegram", "chat_id") + if override_chat_id: + chat = override_chat_id + elif override_alias: + if config.has_section("aliases") and config.has_option("aliases", override_alias): + chat = config.get("aliases", override_alias) + else: + raise ConfigError(f"Alias '{override_alias}' not found in config: {conf}") + else: + chat = config.get("telegram", "chat_id") reply = config.get("telegram", "reply_to_message_id", fallback=None) - chat_id = int(chat) if chat.isdigit() else chat + chat_id = int(chat) if str(chat).lstrip('-').isdigit() else chat reply_to_message_id = int(reply) if reply and reply.isdigit() else reply return Settings(token=token, chat_id=chat_id, reply_to_message_id=reply_to_message_id) From 3118f15fe8038210071b710a2f8ffafd7b755204 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 01:28:25 +0000 Subject: [PATCH 2/4] feat: use TELEGRAM_BOT_TOKEN environment variable Allow configuring and running telegram-send using the TELEGRAM_BOT_TOKEN environment variable to avoid storing the bot token in a plain-text configuration file. Co-authored-by: scartill <1055918+scartill@users.noreply.github.com> --- telegram_send/telegram_send.py | 66 +++++++++++++++++++++++----------- 1 file changed, 46 insertions(+), 20 deletions(-) diff --git a/telegram_send/telegram_send.py b/telegram_send/telegram_send.py index 058deec..d1c96d0 100644 --- a/telegram_send/telegram_send.py +++ b/telegram_send/telegram_send.py @@ -17,6 +17,7 @@ import argparse import asyncio import configparser +import os import re import sys from copy import deepcopy @@ -373,14 +374,16 @@ async def configure(conf=None, channel=False, group=False, fm_integration=False) contact_url = "https://telegram.me/" root_topic_message = None - print(f"Talk with the {markup('BotFather', 'cyan')} on Telegram ({contact_url}BotFather), " - "create a bot and insert the token") - try: - token = input(markup(prompt, "magenta")).strip() - except UnicodeEncodeError: - # some users can only display ASCII - prompt = "> " - token = input(markup(prompt, "magenta")).strip() + token = os.environ.get("TELEGRAM_BOT_TOKEN") + if not token: + print(f"Talk with the {markup('BotFather', 'cyan')} on Telegram ({contact_url}BotFather), " + "create a bot and insert the token") + try: + token = input(markup(prompt, "magenta")).strip() + except UnicodeEncodeError: + # some users can only display ASCII + prompt = "> " + token = input(markup(prompt, "magenta")).strip() try: bot = telegram.Bot(token) @@ -487,10 +490,13 @@ def get_root_topic_message(message: telegram.Message): config = configparser.ConfigParser() + config_data = {"chat_id": chat_id} + if not os.environ.get("TELEGRAM_BOT_TOKEN"): + config_data["TOKEN"] = token if root_topic_message is not None and isinstance(root_topic_message, telegram.Message): - config["telegram"] = {"TOKEN": token, "chat_id": chat_id, "reply_to_message_id": root_topic_message.message_id} - else: - config["telegram"] = {"TOKEN": token, "chat_id": chat_id} + config_data["reply_to_message_id"] = root_topic_message.message_id + + config["telegram"] = config_data conf_dir = dirname(conf) if conf_dir: makedirs(conf_dir, exist_ok=True) @@ -568,24 +574,44 @@ class Settings(NamedTuple): def get_config_settings(conf=None, override_chat_id=None, override_alias=None) -> Settings: conf = expanduser(conf) if conf else get_config_path() config = configparser.ConfigParser() - if not config.read(conf) or not config.has_section("telegram"): - raise ConfigError(f"Config not found: {conf}") - missing_options = set(["token", "chat_id"]) - set(config.options("telegram")) - if len(missing_options) > 0: - raise ConfigError(f"Missing options in config: {', '.join(missing_options)}") + # Allow reading token from env instead of config file + env_token = os.environ.get("TELEGRAM_BOT_TOKEN") + + has_config = config.read(conf) and config.has_section("telegram") + + if not has_config: + if not env_token: + raise ConfigError(f"Config not found: {conf}") + + required_options = {"chat_id"} + if not env_token: + required_options.add("token") + + if has_config: + missing_options = set(required_options) - set(config.options("telegram")) + if len(missing_options) > 0: + raise ConfigError(f"Missing options in config: {', '.join(missing_options)}") + + if has_config and not env_token: + token = config.get("telegram", "token") + else: + token = env_token - token = config.get("telegram", "token") if override_chat_id: chat = override_chat_id elif override_alias: - if config.has_section("aliases") and config.has_option("aliases", override_alias): + if has_config and config.has_section("aliases") and config.has_option("aliases", override_alias): chat = config.get("aliases", override_alias) else: raise ConfigError(f"Alias '{override_alias}' not found in config: {conf}") else: - chat = config.get("telegram", "chat_id") - reply = config.get("telegram", "reply_to_message_id", fallback=None) + if has_config and config.has_option("telegram", "chat_id"): + chat = config.get("telegram", "chat_id") + else: + raise ConfigError(f"Missing options in config: chat_id") + + reply = config.get("telegram", "reply_to_message_id", fallback=None) if has_config else None chat_id = int(chat) if str(chat).lstrip('-').isdigit() else chat reply_to_message_id = int(reply) if reply and reply.isdigit() else reply From 477d04a351d266a5a6342e162b107fa69ced86a9 Mon Sep 17 00:00:00 2001 From: "google-labs-jules[bot]" <161369871+google-labs-jules[bot]@users.noreply.github.com> Date: Sun, 8 Mar 2026 01:42:40 +0000 Subject: [PATCH 3/4] feat: use TELEGRAM_BOT_TOKEN environment variable Allow configuring and running telegram-send using the TELEGRAM_BOT_TOKEN environment variable to avoid storing the bot token in a plain-text configuration file. Also amend README.md to document the new usage. Co-authored-by: scartill <1055918+scartill@users.noreply.github.com> --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index a07c1fc..73987b0 100644 --- a/README.md +++ b/README.md @@ -77,6 +77,12 @@ channel = -1001234567890 group = -1009876543210 ``` +To configure and use the bot token securely without storing it in a plain-text configuration file, you can set the `TELEGRAM_BOT_TOKEN` environment variable. If this is present, telegram-send will use it during `telegram-send --configure` and skip prompting you for the token. The token will not be saved into the configuration file. +``` shell +export TELEGRAM_BOT_TOKEN="123456789:ABCDEFGHIJKLMNOPQRSTUVWXYZ123456789" +telegram-send --configure +``` + To send a message without link previews: ``` shell telegram-send --disable-web-page-preview "https://github.com/rahiel/telegram-send" From 1bb593d59418c4950293ab38f8c8f1c43651a972 Mon Sep 17 00:00:00 2001 From: Boris Resnick Date: Sun, 8 Mar 2026 10:22:33 +0200 Subject: [PATCH 4/4] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 73987b0..d3b096f 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ To send a message to a chat id by alias (the mapping should be in the configurat ``` shell telegram-send --alias channel "Hello" ``` -To use an alias, you must define an `[aliases]` block in the configuration file (`~/.config/telegram-send.conf` by default), mapping the alias to a specific chat ID: +To use an alias, you must define an `[aliases]` block in the configuration file (`~/.config/telegram-send.conf` by default, or `%LOCALAPPDATA%\telegram-send\telegram-send.conf` on Windows), mapping the alias to a specific chat ID: ```ini [aliases] channel = -1001234567890