Telegram Message Skill Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options. Prerequisites Authentication must be configured in . Run command to check status or get instructions: If not configured, follow these steps: 1. Get API credentials from https://my.telegram.org/auth 2. Clone telegram dl: https://github.com/glebis/telegram dl 3. Run and follow interactive prompts 4. Verify with Quick Start Run the script at with appropriate commands: Commands List Chats To see available Telegram chats: Options: - : Filter by substring match (case-insen…

, value)\n if rel_match:\n hours = int(rel_match.group(1) or 0)\n minutes = int(rel_match.group(2) or 0)\n return datetime.now(local_tz) + timedelta(hours=hours, minutes=minutes)\n\n # \"tomorrow HH:MM\"\n tom_match = re.match(r'^tomorrow\\s+(\\d{1,2}):(\\d{2})

Telegram Message Skill Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options. Prerequisites Authentication must be configured in . Run command to check status or get instructions: If not configured, follow these steps: 1. Get API credentials from https://my.telegram.org/auth 2. Clone telegram dl: https://github.com/glebis/telegram dl 3. Run and follow interactive prompts 4. Verify with Quick Start Run the script at with appropriate commands: Commands List Chats To see available Telegram chats: Options: - : Filter by substring match (case-insen…

, value, re.IGNORECASE)\n if tom_match:\n tomorrow = datetime.now(local_tz) + timedelta(days=1)\n return tomorrow.replace(hour=int(tom_match.group(1)), minute=int(tom_match.group(2)), second=0, microsecond=0)\n\n # ISO format: 2026-04-10T10:00 or 2026-04-10 10:00\n try:\n dt = datetime.fromisoformat(value)\n if dt.tzinfo is None:\n dt = dt.replace(tzinfo=local_tz)\n return dt\n except ValueError:\n pass\n\n raise ValueError(f\"Cannot parse schedule: '{value}'. Use ISO (2026-04-10T10:00), relative (+1h, +30m), or 'tomorrow HH:MM'\")\n\n\nasync def send_message(client: TelegramClient, chat_name: str, text: str,\n reply_to: Optional[int] = None,\n file_path: Optional[str] = None,\n parse_mode: Optional[str] = None,\n schedule: Optional['datetime'] = None) -> Dict:\n \"\"\"Send a message or file to a chat, optionally as a reply.\n\n Supports:\n - Chat names (fuzzy match in existing dialogs)\n - Usernames (@username or just username)\n - Phone numbers\n - Chat IDs (numeric)\n - File attachments (images, documents, videos)\n - parse_mode: 'html' for HTML formatting, None for plain text\n\n Safety: Groups/channels require explicit whitelist in config.json\n \"\"\"\n entity, resolved_name = await resolve_entity(client, chat_name)\n\n if entity is None:\n return {\"sent\": False, \"error\": f\"Chat '{chat_name}' not found\"}\n\n # Safety check: block group/channel sends unless whitelisted\n chat_type = get_chat_type(entity)\n if chat_type in [\"group\", \"channel\"]:\n config = load_config()\n allowed_groups = config.get(\"allowed_send_groups\", [])\n\n # Check if chat is whitelisted (by name or ID)\n entity_id = getattr(entity, 'id', None)\n if resolved_name not in allowed_groups and str(entity_id) not in allowed_groups:\n return {\n \"sent\": False,\n \"error\": f\"Sending to groups/channels requires whitelist. Add '{resolved_name}' or '{entity_id}' to allowed_send_groups in {CONFIG_FILE}\",\n \"chat_type\": chat_type,\n \"chat_name\": resolved_name,\n \"chat_id\": entity_id\n }\n\n try:\n # Send file if provided\n if file_path:\n import os\n if not os.path.exists(file_path):\n return {\"sent\": False, \"error\": f\"File not found: {file_path}\"}\n\n file_size = os.path.getsize(file_path)\n file_name = os.path.basename(file_path)\n\n msg = await client.send_file(\n entity,\n file_path,\n caption=text if text else None,\n reply_to=reply_to,\n parse_mode=parse_mode,\n schedule=schedule\n )\n return {\n \"sent\": True,\n \"chat\": resolved_name,\n \"message_id\": msg.id,\n \"reply_to\": reply_to,\n \"file\": {\n \"name\": file_name,\n \"size\": file_size,\n \"path\": file_path\n }\n }\n else:\n # Send text message\n msg = await client.send_message(\n entity,\n text,\n reply_to=reply_to,\n parse_mode=parse_mode,\n schedule=schedule\n )\n return {\n \"sent\": True,\n \"chat\": resolved_name,\n \"message_id\": msg.id,\n \"reply_to\": reply_to\n }\n except Exception as e:\n return {\"sent\": False, \"error\": str(e)}\n\n\nasync def pin_message(client: TelegramClient, chat_name: str, message_id: int,\n notify: bool = False) -> Dict:\n \"\"\"Pin a message in a chat.\n\n Args:\n chat_name: Chat name, @username, or ID\n message_id: Message ID to pin\n notify: Whether to notify members about the pin (default: False)\n\n Returns:\n Dict with pinned status and details\n \"\"\"\n entity, resolved_name = await resolve_entity(client, chat_name)\n\n if entity is None:\n return {\"pinned\": False, \"error\": f\"Chat '{chat_name}' not found\"}\n\n try:\n await client.pin_message(entity, message_id, notify=notify)\n return {\n \"pinned\": True,\n \"chat\": resolved_name,\n \"message_id\": message_id,\n \"notify\": notify\n }\n except Exception as e:\n return {\"pinned\": False, \"error\": str(e)}\n\n\nDEFAULT_ATTACHMENTS_DIR = Path.home() / 'Downloads' / 'telegram_attachments'\n\n\nasync def download_media(client: TelegramClient, chat_name: str,\n limit: int = 5, output_dir: Optional[str] = None,\n message_id: Optional[int] = None) -> List[Dict]:\n \"\"\"Download media attachments from a chat.\n\n Args:\n chat_name: Chat name, @username, or ID\n limit: Max number of attachments to download (default 5)\n output_dir: Output directory (default ~/Downloads/telegram_attachments)\n message_id: Specific message ID to download from (optional)\n \"\"\"\n import os\n\n # Set output directory\n if output_dir:\n out_path = Path(output_dir)\n else:\n out_path = DEFAULT_ATTACHMENTS_DIR\n\n out_path.mkdir(parents=True, exist_ok=True)\n\n entity, resolved_name = await resolve_entity(client, chat_name)\n if entity is None:\n return [{\"error\": f\"Chat '{chat_name}' not found\"}]\n\n downloaded = []\n\n if message_id:\n # Download from specific message\n msg = await client.get_messages(entity, ids=message_id)\n if msg and msg.media:\n try:\n file_path = await client.download_media(msg, str(out_path))\n if file_path:\n downloaded.append({\n \"message_id\": msg.id,\n \"chat\": resolved_name,\n \"file\": os.path.basename(file_path),\n \"path\": file_path,\n \"size\": os.path.getsize(file_path),\n \"date\": msg.date.isoformat() if msg.date else None\n })\n except Exception as e:\n downloaded.append({\"message_id\": message_id, \"error\": str(e)})\n else:\n downloaded.append({\"message_id\": message_id, \"error\": \"No media in message\"})\n else:\n # Download recent media\n count = 0\n async for msg in client.iter_messages(entity, limit=100):\n if msg.media and hasattr(msg.media, 'document') or hasattr(msg, 'photo') and msg.photo:\n try:\n file_path = await client.download_media(msg, str(out_path))\n if file_path:\n downloaded.append({\n \"message_id\": msg.id,\n \"chat\": resolved_name,\n \"file\": os.path.basename(file_path),\n \"path\": file_path,\n \"size\": os.path.getsize(file_path),\n \"date\": msg.date.isoformat() if msg.date else None\n })\n count += 1\n if count >= limit:\n break\n except Exception as e:\n downloaded.append({\"message_id\": msg.id, \"error\": str(e)})\n await asyncio.sleep(0.2) # Rate limiting\n\n return downloaded\n\n\nasync def fetch_unread(client: TelegramClient, chat_id: Optional[int] = None) -> List[Dict]:\n \"\"\"Fetch unread messages.\"\"\"\n messages = []\n dialogs = await client.get_dialogs()\n\n for d in dialogs:\n if chat_id and d.id != chat_id:\n continue\n if d.unread_count == 0:\n continue\n\n name = d.name or \"Unnamed\"\n chat_type = get_chat_type(d.entity)\n\n try:\n async for msg in client.iter_messages(d.entity, limit=d.unread_count):\n messages.append(format_message(msg, name, chat_type))\n await asyncio.sleep(0.1)\n except FloodWaitError as e:\n print(f\"Rate limited, waiting {e.seconds}s...\", file=sys.stderr)\n await asyncio.sleep(e.seconds)\n\n return messages\n\n\ndef format_output(messages: List[Dict], output_format: str = \"markdown\") -> str:\n \"\"\"Format messages for output.\"\"\"\n if output_format == \"json\":\n return json.dumps(messages, indent=2, ensure_ascii=False)\n\n # Markdown format\n lines = []\n current_chat = None\n\n for msg in messages:\n if msg[\"chat\"] != current_chat:\n current_chat = msg[\"chat\"]\n lines.append(f\"\\n## {current_chat} ({msg['chat_type']})\\n\")\n\n date_str = \"\"\n if msg[\"date\"]:\n dt = datetime.fromisoformat(msg[\"date\"])\n date_str = dt.strftime(\"%Y-%m-%d %H:%M\")\n\n sender = msg[\"sender\"]\n text = msg[\"text\"] or \"[media]\" if msg[\"has_media\"] else msg[\"text\"]\n\n if text:\n lines.append(f\"**{date_str}** - {sender}:\")\n lines.append(f\"> {text}\")\n\n # Add reactions if present\n if \"reactions\" in msg and msg[\"reactions\"]:\n reaction_str = \" \".join([\n f\"{r['emoji']} {r['count']}\" if 'emoji' in r\n else f\"[custom] {r['count']}\"\n for r in msg[\"reactions\"]\n ])\n lines.append(f\"> **Reactions:** {reaction_str}\")\n\n lines.append(\"\") # Empty line\n\n return \"\\n\".join(lines)\n\n\ndef append_to_daily(content: str):\n \"\"\"Append content to today's daily note.\"\"\"\n today = datetime.now().strftime(\"%Y%m%d\")\n daily_path = VAULT_PATH / \"Daily\" / f\"{today}.md\"\n\n if not daily_path.exists():\n print(f\"Creating daily note: {daily_path}\", file=sys.stderr)\n daily_path.parent.mkdir(parents=True, exist_ok=True)\n daily_path.write_text(f\"# {today}\\n\\n\")\n\n with open(daily_path, 'a') as f:\n f.write(f\"\\n## Telegram Messages\\n{content}\\n\")\n\n print(f\"Appended to {daily_path}\", file=sys.stderr)\n\n\ndef append_to_person(content: str, person_name: str):\n \"\"\"Append content to a person's note.\"\"\"\n person_path = VAULT_PATH / f\"{person_name}.md\"\n\n if not person_path.exists():\n print(f\"Creating person note: {person_path}\", file=sys.stderr)\n person_path.write_text(f\"# {person_name}\\n\\n\")\n\n timestamp = datetime.now().strftime(\"%Y-%m-%d %H:%M\")\n with open(person_path, 'a') as f:\n f.write(f\"\\n## Telegram ({timestamp})\\n{content}\\n\")\n\n print(f\"Appended to {person_path}\", file=sys.stderr)\n\n\nasync def save_to_file(client: TelegramClient, messages: List[Dict], output_path: str,\n with_media: bool = False, output_format: str = \"markdown\") -> Dict:\n \"\"\"Save messages to file, optionally downloading media.\n\n Args:\n client: Telegram client for media downloads\n messages: List of message dicts\n output_path: Path to output file\n with_media: Whether to download media files\n output_format: 'markdown' or 'json'\n\n Returns:\n Dict with save status and media download results\n \"\"\"\n import os\n\n output_file = Path(output_path)\n output_file.parent.mkdir(parents=True, exist_ok=True)\n\n # Format content\n if output_format == \"json\":\n content = json.dumps(messages, indent=2, ensure_ascii=False)\n else:\n content = format_output(messages, \"markdown\")\n\n # Write to file\n output_file.write_text(content, encoding='utf-8')\n\n result = {\n \"saved\": True,\n \"file\": str(output_file),\n \"message_count\": len(messages)\n }\n\n # Download media if requested\n if with_media and messages:\n media_dir = output_file.parent / \"media\"\n media_dir.mkdir(exist_ok=True)\n\n downloaded = []\n for msg in messages:\n if msg.get(\"has_media\") and msg.get(\"chat_id\") and msg.get(\"message_id\"):\n try:\n # Get the entity from chat_id\n entity = await client.get_entity(msg[\"chat_id\"])\n tg_msg = await client.get_messages(entity, ids=msg[\"message_id\"])\n if tg_msg and tg_msg.media:\n file_path = await client.download_media(tg_msg, str(media_dir))\n if file_path:\n downloaded.append({\n \"message_id\": msg[\"message_id\"],\n \"file\": os.path.basename(file_path),\n \"path\": file_path\n })\n except Exception as e:\n downloaded.append({\n \"message_id\": msg.get(\"message_id\"),\n \"error\": str(e)\n })\n await asyncio.sleep(0.2) # Rate limiting\n\n result[\"media\"] = downloaded\n result[\"media_dir\"] = str(media_dir)\n\n return result\n\n\nasync def fetch_thread_messages(client: TelegramClient, chat_id: int,\n thread_id: int, limit: int = 100) -> List[Dict]:\n \"\"\"Fetch messages from a specific forum thread.\"\"\"\n entity = await client.get_entity(chat_id)\n chat_type = get_chat_type(entity)\n name = getattr(entity, 'title', None) or getattr(entity, 'first_name', '') or \"Unknown\"\n\n messages = []\n async for msg in client.iter_messages(entity, reply_to=thread_id, limit=limit):\n messages.append(format_message(msg, name, chat_type))\n await asyncio.sleep(0.1) # Rate limiting\n\n return messages\n\n\n# ============================================================================\n# Publishing Functions\n# ============================================================================\n\ndef parse_draft_frontmatter(content: str) -> Tuple[Dict, str]:\n \"\"\"Parse frontmatter and body from draft content.\n\n Returns:\n Tuple of (frontmatter_dict, body_content)\n \"\"\"\n parts = content.split('---', 2)\n if len(parts) \u003c 3:\n return {}, content\n\n try:\n frontmatter = yaml.safe_load(parts[1])\n body = parts[2].strip()\n return frontmatter, body\n except Exception as e:\n raise ValueError(f\"Failed to parse frontmatter: {e}\")\n\n\ndef extract_media_references(frontmatter: Dict, body: str) -> List[str]:\n \"\"\"Extract media file references from frontmatter and body.\n\n Returns:\n List of media filenames (mp4, png, jpg, jpeg)\n \"\"\"\n media_files = []\n\n # Check frontmatter video field\n if 'video' in frontmatter and frontmatter['video']:\n media_files.append(frontmatter['video'])\n\n # Find wikilinks with media extensions\n wikilink_pattern = r'\\[\\[([^\\[\\]]+\\.(mp4|png|jpg|jpeg))(?:\\|[^\\]]+)?\\]\\]'\n matches = re.findall(wikilink_pattern, body, re.IGNORECASE)\n for match in matches:\n media_files.append(match[0])\n\n return media_files\n\n\ndef resolve_media_paths(filenames: List[str], vault_path: Path) -> List[Path]:\n \"\"\"Resolve media filenames to absolute paths.\n\n Searches in:\n 1. Channels/klodkot/attachments/\n 2. Sources/\n\n Returns:\n List of absolute paths\n\n Raises:\n FileNotFoundError if any file not found\n \"\"\"\n search_dirs = [\n vault_path / \"Channels\" / \"klodkot\" / \"attachments\",\n vault_path / \"Sources\"\n ]\n\n resolved = []\n for filename in filenames:\n found = False\n for search_dir in search_dirs:\n candidate = search_dir / filename\n if candidate.exists():\n resolved.append(candidate)\n found = True\n break\n\n if not found:\n raise FileNotFoundError(\n f\"Media file not found: {filename}. \"\n f\"Searched in: {', '.join(str(d) for d in search_dirs)}\"\n )\n\n return resolved\n\n\ndef strip_draft_header(body: str) -> str:\n \"\"\"Strip markdown header ending with '- Telegram Draft' or similar.\"\"\"\n lines = body.strip().split('\\n')\n\n if lines and lines[0].startswith('#'):\n first_line = lines[0]\n # Remove headers ending with common draft markers\n if any(marker in first_line.lower() for marker in ['telegram draft', 'draft', '— draft']):\n # Remove first line and any following empty lines\n lines = lines[1:]\n while lines and not lines[0].strip():\n lines.pop(0)\n return '\\n'.join(lines)\n\n return body\n\n\ndef strip_media_wikilinks(body: str) -> str:\n \"\"\"Strip media wikilinks from body text.\n\n Removes: ![[image.png]], ![[video.mp4|caption]], etc.\n These will be sent as Telegram media attachments.\n \"\"\"\n # Pattern matches ![[filename.ext]] or ![[filename.ext|caption]]\n # where ext is mp4, png, jpg, jpeg\n pattern = r'!\\[\\[([^\\[\\]]+\\.(mp4|png|jpg|jpeg))(?:\\|[^\\]]+)?\\]\\]\\n?'\n cleaned = re.sub(pattern, '', body, flags=re.IGNORECASE)\n\n # Remove multiple consecutive newlines that might result\n cleaned = re.sub(r'\\n{3,}', '\\n\\n', cleaned)\n\n return cleaned.strip()\n\n\ndef check_footer_exists(body: str) -> bool:\n \"\"\"Check if footer signature already exists in body.\"\"\"\n footer_patterns = [\n r'КЛОДКОТ',\n r't\\.me/klodkot'\n ]\n\n for pattern in footer_patterns:\n if re.search(pattern, body, re.IGNORECASE):\n return True\n\n return False\n\n\ndef append_footer(body: str) -> str:\n \"\"\"Append standard footer to body.\"\"\"\n footer = '\\n\\n**[КЛОДКОТ](https://t.me/klodkot)** — Claude Code и другие агенты: инструменты, кейсы, вдохновение'\n return body + footer\n\n\ndef convert_markdown_to_telegram_html(text: str) -> str:\n \"\"\"Convert markdown formatting to Telegram HTML.\n\n Conversions:\n - ## Header → \u003cb>Header\u003c/b>\n - **bold** → \u003cb>bold\u003c/b>\n - _italic_ → \u003ci>italic\u003c/i>\n - [text](url) → \u003ca href=\"url\">text\u003c/a>\n - * item → → item (bullet lists to arrows)\n \"\"\"\n # Convert headers to bold (must be done first, before other conversions)\n text = re.sub(r'^##\\s+(.+?)

Telegram Message Skill Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options. Prerequisites Authentication must be configured in . Run command to check status or get instructions: If not configured, follow these steps: 1. Get API credentials from https://my.telegram.org/auth 2. Clone telegram dl: https://github.com/glebis/telegram dl 3. Run and follow interactive prompts 4. Verify with Quick Start Run the script at with appropriate commands: Commands List Chats To see available Telegram chats: Options: - : Filter by substring match (case-insen…

, r'\u003cb>\\1\u003c/b>', text, flags=re.MULTILINE)\n\n # Convert bullet lists to arrow format\n text = re.sub(r'^\\*\\s+(.+?)

Telegram Message Skill Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options. Prerequisites Authentication must be configured in . Run command to check status or get instructions: If not configured, follow these steps: 1. Get API credentials from https://my.telegram.org/auth 2. Clone telegram dl: https://github.com/glebis/telegram dl 3. Run and follow interactive prompts 4. Verify with Quick Start Run the script at with appropriate commands: Commands List Chats To see available Telegram chats: Options: - : Filter by substring match (case-insen…

, r'→ \\1', text, flags=re.MULTILINE)\n text = re.sub(r'^-\\s+(.+?)

Telegram Message Skill Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options. Prerequisites Authentication must be configured in . Run command to check status or get instructions: If not configured, follow these steps: 1. Get API credentials from https://my.telegram.org/auth 2. Clone telegram dl: https://github.com/glebis/telegram dl 3. Run and follow interactive prompts 4. Verify with Quick Start Run the script at with appropriate commands: Commands List Chats To see available Telegram chats: Options: - : Filter by substring match (case-insen…

, r'→ \\1', text, flags=re.MULTILINE)\n\n # Convert bold **text** to \u003cb>text\u003c/b>\n text = re.sub(r'\\*\\*(.+?)\\*\\*', r'\u003cb>\\1\u003c/b>', text)\n\n # Convert italic _text_ to \u003ci>text\u003c/i>\n text = re.sub(r'_(.+?)_', r'\u003ci>\\1\u003c/i>', text)\n\n # Convert markdown links [text](url) to \u003ca href=\"url\">text\u003c/a>\n text = re.sub(r'\\[([^\\]]+)\\]\\(([^\\)]+)\\)', r'\u003ca href=\"\\2\">\\1\u003c/a>', text)\n\n return text\n\n\ndef update_frontmatter(file_path: Path, message_id: int) -> None:\n \"\"\"Update draft frontmatter with publish metadata.\n\n Updates:\n - type: published\n - published_date: '[[YYYYMMDD]]'\n - telegram_message_id: {message_id}\n \"\"\"\n content = file_path.read_text(encoding='utf-8')\n frontmatter, body = parse_draft_frontmatter(content)\n\n # Update frontmatter\n frontmatter['type'] = 'published'\n frontmatter['published_date'] = f\"[[{datetime.now().strftime('%Y%m%d')}]]\"\n frontmatter['telegram_message_id'] = message_id\n\n # Reconstruct file\n yaml_str = yaml.dump(frontmatter, allow_unicode=True, default_flow_style=False)\n new_content = f\"---\\n{yaml_str}---\\n\\n{body}\"\n\n file_path.write_text(new_content, encoding='utf-8')\n\n\ndef extract_first_line(body: str) -> str:\n \"\"\"Extract first meaningful line from body for index description.\n\n Strips markdown formatting and truncates to 80 chars.\n \"\"\"\n lines = body.strip().split('\\n')\n\n for line in lines:\n line = line.strip()\n # Skip empty lines and markdown headers\n if not line or line.startswith('#'):\n continue\n\n # Strip markdown formatting\n line = re.sub(r'\\*\\*([^*]+)\\*\\*', r'\\1', line) # Bold\n line = re.sub(r'\\*([^*]+)\\*', r'\\1', line) # Italic\n line = re.sub(r'\\[([^\\]]+)\\]\\([^\\)]+\\)', r'\\1', line) # Links\n line = line.strip()\n\n if line:\n # Truncate if too long\n if len(line) > 80:\n line = line[:77] + '...'\n return line\n\n return \"New post\"\n\n\ndef update_channel_index(index_path: Path, filename: str, description: str) -> None:\n \"\"\"Update channel index with new published entry.\n\n Inserts at top of Published section.\n \"\"\"\n content = index_path.read_text(encoding='utf-8')\n lines = content.split('\\n')\n\n # Find the line with \"**Published**: `published/`\"\n insert_idx = None\n for i, line in enumerate(lines):\n if '**Published**:' in line and 'published/' in line:\n insert_idx = i + 1\n break\n\n if insert_idx is None:\n raise ValueError(\"Could not find **Published**: section in channel index\")\n\n # Extract filename without extension\n filename_no_ext = filename.replace('.md', '')\n\n # Create new entry\n new_entry = f\"- [[{filename_no_ext}]] — {description}\"\n\n # Insert at top\n lines.insert(insert_idx, new_entry)\n\n # Write back\n index_path.write_text('\\n'.join(lines), encoding='utf-8')\n\n\nasync def publish_draft(client: TelegramClient, draft_path: str, dry_run: bool) -> Dict:\n \"\"\"Publish a draft to Telegram channel.\n\n Workflow:\n 1. Parse draft frontmatter + body\n 2. Validate channel field\n 3. Extract and resolve media paths\n 4. Check/append footer if needed\n 5. Send to Telegram (or preview if dry_run)\n 6. Update frontmatter, move file, update index (only if send succeeded)\n\n Args:\n client: Telegram client\n draft_path: Path to draft file (can be relative to vault)\n dry_run: If True, preview only without sending\n\n Returns:\n Dict with publish status and details\n \"\"\"\n # Convert to absolute path\n draft_file = Path(draft_path)\n if not draft_file.is_absolute():\n draft_file = VAULT_PATH / draft_path\n\n if not draft_file.exists():\n return {\"published\": False, \"error\": f\"Draft file not found: {draft_path}\"}\n\n try:\n # Parse draft\n content = draft_file.read_text(encoding='utf-8')\n frontmatter, body = parse_draft_frontmatter(content)\n\n # Check if already published\n if frontmatter.get('telegram_message_id'):\n return {\n \"published\": False,\n \"error\": f\"Draft already published (message_id: {frontmatter['telegram_message_id']})\",\n \"already_published\": True,\n \"message_id\": frontmatter['telegram_message_id']\n }\n\n if frontmatter.get('type') == 'published':\n return {\n \"published\": False,\n \"error\": \"Draft type is already 'published'\",\n \"already_published\": True\n }\n\n # Validate channel\n channel = frontmatter.get('channel', '')\n # Handle both \"klodkot\" and \"[[klodkot (Telegram channel)]]\"\n if isinstance(channel, str):\n # Extract channel name from wikilink if present\n if '[[' in channel:\n channel_match = re.search(r'\\[\\[([^\\]|]+)', channel)\n if channel_match:\n channel_name = channel_match.group(1).split('(')[0].strip().lower()\n else:\n channel_name = ''\n else:\n channel_name = channel.lower()\n\n if 'klodkot' not in channel_name:\n return {\n \"published\": False,\n \"error\": f\"Invalid channel: {channel}. Expected 'klodkot'\"\n }\n else:\n return {\n \"published\": False,\n \"error\": f\"Invalid channel type: {type(channel)}. Expected string\"\n }\n\n # Strip draft header\n body = strip_draft_header(body)\n\n # Extract media references (before stripping them from body)\n media_filenames = extract_media_references(frontmatter, body)\n\n # Resolve media paths\n media_paths = []\n if media_filenames:\n try:\n media_paths = resolve_media_paths(media_filenames, VAULT_PATH)\n except FileNotFoundError as e:\n return {\"published\": False, \"error\": str(e)}\n\n # Strip media wikilinks from body (they'll be sent as attachments)\n body = strip_media_wikilinks(body)\n\n # Check and append footer if needed\n footer_exists = check_footer_exists(body)\n final_body = body if footer_exists else append_footer(body)\n\n # Convert markdown to Telegram HTML\n final_body = convert_markdown_to_telegram_html(final_body)\n\n # Prepare preview\n preview = {\n \"draft_file\": str(draft_file),\n \"channel\": channel,\n \"media_count\": len(media_paths),\n \"media_files\": [p.name for p in media_paths],\n \"footer_exists\": footer_exists,\n \"body_preview\": final_body[:200] + \"...\" if len(final_body) > 200 else final_body\n }\n\n if dry_run:\n preview[\"published\"] = False\n preview[\"dry_run\"] = True\n return preview\n\n # Send to Telegram\n entity, resolved_name = await resolve_entity(client, \"@klodkot\")\n if entity is None:\n return {\"published\": False, \"error\": \"Could not resolve @klodkot channel\"}\n\n try:\n if media_paths:\n # Send as album with caption\n msg = await client.send_file(\n entity,\n [str(p) for p in media_paths],\n caption=final_body,\n parse_mode='html'\n )\n # msg is a list when sending multiple files\n message_id = msg[0].id if isinstance(msg, list) else msg.id\n else:\n # Send text message\n msg = await client.send_message(entity, final_body, parse_mode='html')\n message_id = msg.id\n except Exception as e:\n return {\"published\": False, \"error\": f\"Failed to send to Telegram: {e}\"}\n\n # Post-publish operations (only if send succeeded)\n warnings = []\n\n try:\n # Update frontmatter\n update_frontmatter(draft_file, message_id)\n\n # Move file to published/\n published_dir = draft_file.parent.parent / \"published\"\n published_dir.mkdir(exist_ok=True)\n new_path = published_dir / draft_file.name\n draft_file.rename(new_path)\n\n # Update channel index\n index_path = VAULT_PATH / \"Channels\" / \"klodkot\" / \"klodkot.md\"\n description = extract_first_line(body)\n update_channel_index(index_path, draft_file.name, description)\n\n except Exception as e:\n warnings.append(f\"Post-publish error: {e}\")\n\n result = {\n \"published\": True,\n \"channel\": resolved_name,\n \"message_id\": message_id,\n \"media_count\": len(media_paths),\n \"moved_to\": str(new_path) if 'new_path' in locals() else None\n }\n\n if warnings:\n result[\"warnings\"] = warnings\n\n return result\n\n except Exception as e:\n return {\"published\": False, \"error\": f\"Unexpected error: {e}\"}\n\n\nasync def main():\n parser = argparse.ArgumentParser(description=\"Fetch Telegram messages\")\n subparsers = parser.add_subparsers(dest=\"command\", required=True)\n\n # List chats\n list_parser = subparsers.add_parser(\"list\", help=\"List available chats\")\n list_parser.add_argument(\"--limit\", type=int, default=30, help=\"Max chats to show\")\n list_parser.add_argument(\"--search\", help=\"Filter chats by name\")\n list_parser.add_argument(\"--exact\", action=\"store_true\", help=\"Require exact name match (case-insensitive)\")\n\n # Recent messages\n recent_parser = subparsers.add_parser(\"recent\", help=\"Fetch recent messages\")\n recent_parser.add_argument(\"--chat\", help=\"Chat name to filter\")\n recent_parser.add_argument(\"--chat-id\", type=int, help=\"Chat ID to filter\")\n recent_parser.add_argument(\"--limit\", type=int, default=50, help=\"Max messages\")\n recent_parser.add_argument(\"--days\", type=int, help=\"Only messages from last N days\")\n recent_parser.add_argument(\"--to-daily\", action=\"store_true\", help=\"Append to daily note\")\n recent_parser.add_argument(\"--to-person\", help=\"Append to person's note\")\n recent_parser.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n recent_parser.add_argument(\"--output\", \"-o\", help=\"Save to file (markdown) instead of stdout\")\n recent_parser.add_argument(\"--with-media\", action=\"store_true\", help=\"Download media to same folder as output file\")\n\n # Search messages\n search_parser = subparsers.add_parser(\"search\", help=\"Search messages\")\n search_parser.add_argument(\"query\", help=\"Search query\")\n search_parser.add_argument(\"--chat-id\", type=int, help=\"Limit to specific chat\")\n search_parser.add_argument(\"--limit\", type=int, default=50, help=\"Max results\")\n search_parser.add_argument(\"--to-daily\", action=\"store_true\", help=\"Append to daily note\")\n search_parser.add_argument(\"--to-person\", help=\"Append to person's note\")\n search_parser.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n search_parser.add_argument(\"--output\", \"-o\", help=\"Save to file (markdown) instead of stdout\")\n search_parser.add_argument(\"--with-media\", action=\"store_true\", help=\"Download media to same folder as output file\")\n\n # Unread messages\n unread_parser = subparsers.add_parser(\"unread\", help=\"Fetch unread messages\")\n unread_parser.add_argument(\"--chat-id\", type=int, help=\"Limit to specific chat\")\n unread_parser.add_argument(\"--to-daily\", action=\"store_true\", help=\"Append to daily note\")\n unread_parser.add_argument(\"--to-person\", help=\"Append to person's note\")\n unread_parser.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n unread_parser.add_argument(\"--output\", \"-o\", help=\"Save to file (markdown) instead of stdout\")\n unread_parser.add_argument(\"--with-media\", action=\"store_true\", help=\"Download media to same folder as output file\")\n\n # Send message\n send_parser = subparsers.add_parser(\"send\", help=\"Send a message or file\")\n send_parser.add_argument(\"--chat\", required=True, help=\"Chat name, @username, or ID\")\n send_parser.add_argument(\"--text\", help=\"Message text (or caption for files)\")\n send_parser.add_argument(\"--file\", help=\"File path to send (image, document, video)\")\n send_parser.add_argument(\"--reply-to\", type=int, help=\"Message ID to reply to\")\n send_parser.add_argument(\"--topic\", type=int, help=\"Forum topic ID to send to (for groups with topics)\")\n send_parser.add_argument(\"--markdown\", action=\"store_true\", help=\"Convert markdown formatting to Telegram HTML before sending\")\n send_parser.add_argument(\"--schedule\", help=\"Schedule message for future delivery (ISO format: 2026-04-10T10:00 or relative: +1h, +30m, tomorrow 10:00)\")\n\n # Pin message\n pin_parser = subparsers.add_parser(\"pin\", help=\"Pin a message in a chat\")\n pin_parser.add_argument(\"--chat\", required=True, help=\"Chat name, @username, or ID\")\n pin_parser.add_argument(\"--message-id\", type=int, required=True, help=\"Message ID to pin\")\n pin_parser.add_argument(\"--notify\", action=\"store_true\", help=\"Notify members about the pin\")\n\n # Download media\n download_parser = subparsers.add_parser(\"download\", help=\"Download media attachments\")\n download_parser.add_argument(\"--chat\", required=True, help=\"Chat name, @username, or ID\")\n download_parser.add_argument(\"--limit\", type=int, default=5, help=\"Max attachments to download (default 5)\")\n download_parser.add_argument(\"--output\", help=\"Output directory (default ~/Downloads/telegram_attachments)\")\n download_parser.add_argument(\"--message-id\", type=int, help=\"Download from specific message ID\")\n\n # Edit message\n edit_parser = subparsers.add_parser(\"edit\", help=\"Edit an existing message\")\n edit_parser.add_argument(\"--chat\", help=\"Chat name to filter\")\n edit_parser.add_argument(\"--chat-id\", type=int, help=\"Chat ID to filter\")\n edit_parser.add_argument(\"--message-id\", type=int, required=True, help=\"Message ID to edit\")\n edit_parser.add_argument(\"--text\", required=True, help=\"New message text\")\n\n # Setup/status\n setup_parser = subparsers.add_parser(\"setup\", help=\"Check status or get setup instructions\")\n setup_parser.add_argument(\"--status\", action=\"store_true\", help=\"Check configuration status\")\n\n # Thread messages\n thread_parser = subparsers.add_parser(\"thread\", help=\"Fetch messages from a forum thread\")\n thread_parser.add_argument(\"--chat-id\", type=int, required=True, help=\"Chat ID\")\n thread_parser.add_argument(\"--thread-id\", type=int, required=True, help=\"Thread/topic ID\")\n thread_parser.add_argument(\"--limit\", type=int, default=100, help=\"Max messages (default 100)\")\n thread_parser.add_argument(\"--to-daily\", action=\"store_true\", help=\"Append to daily note\")\n thread_parser.add_argument(\"--to-person\", help=\"Append to person's note\")\n thread_parser.add_argument(\"--json\", action=\"store_true\", help=\"Output as JSON\")\n thread_parser.add_argument(\"--output\", \"-o\", help=\"Save to file (markdown) instead of stdout\")\n\n # Publish draft\n publish_parser = subparsers.add_parser(\"publish\", help=\"Publish draft to channel\")\n publish_parser.add_argument(\"--draft\", required=True, help=\"Draft file path (relative to vault or absolute)\")\n publish_parser.add_argument(\"--dry-run\", action=\"store_true\", help=\"Preview only, don't send\")\n\n args = parser.parse_args()\n\n # Handle setup command before requiring authentication\n if args.command == \"setup\":\n result = get_status()\n print(json.dumps(result, indent=2))\n return\n\n client = await get_client()\n\n try:\n if args.command == \"list\":\n chats = await list_chats(client, limit=args.limit, search=args.search, exact=args.exact)\n print(json.dumps(chats, indent=2, ensure_ascii=False))\n\n elif args.command == \"recent\":\n messages = await fetch_recent(\n client,\n chat_id=args.chat_id,\n chat_name=args.chat,\n limit=args.limit,\n days=args.days\n )\n output_fmt = \"json\" if args.json else \"markdown\"\n\n if args.output:\n # Save to file instead of stdout\n result = await save_to_file(\n client, messages, args.output,\n with_media=args.with_media,\n output_format=output_fmt\n )\n print(json.dumps(result, indent=2))\n elif args.to_daily:\n output = format_output(messages, output_fmt)\n append_to_daily(output)\n elif args.to_person:\n output = format_output(messages, output_fmt)\n append_to_person(output, args.to_person)\n else:\n output = format_output(messages, output_fmt)\n print(output)\n\n elif args.command == \"search\":\n messages = await search_messages(\n client,\n query=args.query,\n chat_id=args.chat_id,\n limit=args.limit\n )\n output_fmt = \"json\" if args.json else \"markdown\"\n\n if args.output:\n result = await save_to_file(\n client, messages, args.output,\n with_media=args.with_media,\n output_format=output_fmt\n )\n print(json.dumps(result, indent=2))\n elif args.to_daily:\n output = format_output(messages, output_fmt)\n append_to_daily(output)\n elif args.to_person:\n output = format_output(messages, output_fmt)\n append_to_person(output, args.to_person)\n else:\n output = format_output(messages, output_fmt)\n print(output)\n\n elif args.command == \"unread\":\n messages = await fetch_unread(client, chat_id=args.chat_id)\n output_fmt = \"json\" if args.json else \"markdown\"\n\n if args.output:\n result = await save_to_file(\n client, messages, args.output,\n with_media=args.with_media,\n output_format=output_fmt\n )\n print(json.dumps(result, indent=2))\n elif args.to_daily:\n output = format_output(messages, output_fmt)\n append_to_daily(output)\n elif args.to_person:\n output = format_output(messages, output_fmt)\n append_to_person(output, args.to_person)\n else:\n output = format_output(messages, output_fmt)\n print(output)\n\n elif args.command == \"send\":\n if not args.text and not args.file:\n print(json.dumps({\"sent\": False, \"error\": \"Must provide --text or --file\"}))\n else:\n # --topic is an alias for --reply-to (forum topics use reply_to internally)\n reply_to = args.topic if args.topic else args.reply_to\n text = args.text or \"\"\n parse_mode = None\n if args.markdown and text:\n text = convert_markdown_to_telegram_html(text)\n parse_mode = 'html'\n schedule = None\n if args.schedule:\n try:\n schedule = parse_schedule(args.schedule)\n except ValueError as e:\n print(json.dumps({\"sent\": False, \"error\": str(e)}))\n return\n result = await send_message(\n client,\n chat_name=args.chat,\n text=text,\n reply_to=reply_to,\n file_path=args.file,\n parse_mode=parse_mode,\n schedule=schedule\n )\n if schedule and result.get(\"sent\"):\n result[\"scheduled_for\"] = schedule.isoformat()\n print(json.dumps(result, indent=2))\n\n elif args.command == \"pin\":\n result = await pin_message(\n client,\n chat_name=args.chat,\n message_id=args.message_id,\n notify=args.notify\n )\n print(json.dumps(result, indent=2))\n\n elif args.command == \"download\":\n results = await download_media(\n client,\n chat_name=args.chat,\n limit=args.limit,\n output_dir=args.output,\n message_id=args.message_id\n )\n print(json.dumps(results, indent=2))\n\n elif args.command == \"edit\":\n result = await edit_message(\n client,\n chat_id=args.chat_id,\n chat_name=args.chat,\n message_id=args.message_id,\n text=args.text\n )\n print(json.dumps(result, indent=2))\n\n elif args.command == \"thread\":\n messages = await fetch_thread_messages(\n client,\n chat_id=args.chat_id,\n thread_id=args.thread_id,\n limit=args.limit\n )\n output_fmt = \"json\" if args.json else \"markdown\"\n\n if args.output:\n result = await save_to_file(\n client, messages, args.output,\n with_media=False,\n output_format=output_fmt\n )\n print(json.dumps(result, indent=2))\n elif args.to_daily:\n output = format_output(messages, output_fmt)\n append_to_daily(output)\n elif args.to_person:\n output = format_output(messages, output_fmt)\n append_to_person(output, args.to_person)\n else:\n output = format_output(messages, output_fmt)\n print(output)\n\n elif args.command == \"publish\":\n result = await publish_draft(client, args.draft, args.dry_run)\n print(json.dumps(result, indent=2, ensure_ascii=False))\n if not result.get(\"published\", False) and not result.get(\"dry_run\", False):\n sys.exit(1)\n\n finally:\n await client.disconnect()\n\n\nif __name__ == \"__main__\":\n asyncio.run(main())\n","content_type":"text/x-python; charset=utf-8","language":"python","size":55533,"content_sha256":"ef73e5275e7223388a04bbfa453057f22455e8a30cf3fd078b55d29f8d4f6bd2"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Telegram Message Skill","type":"text"}]},{"type":"paragraph","content":[{"text":"Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Prerequisites","type":"text"}]},{"type":"paragraph","content":[{"text":"Authentication must be configured in ","type":"text"},{"text":"~/.telegram_dl/","type":"text","marks":[{"type":"code_inline"}]},{"text":". Run ","type":"text"},{"text":"setup","type":"text","marks":[{"type":"code_inline"}]},{"text":" command to check status or get instructions:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/telegram_fetch.py setup","type":"text"}]},{"type":"paragraph","content":[{"text":"If not configured, follow these steps:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Get API credentials from https://my.telegram.org/auth","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Clone telegram_dl: https://github.com/glebis/telegram_dl","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Run ","type":"text"},{"text":"python telegram_dl.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" and follow interactive prompts","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Verify with ","type":"text"},{"text":"python3 scripts/telegram_fetch.py setup","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Quick Start","type":"text"}]},{"type":"paragraph","content":[{"text":"Run the script at ","type":"text"},{"text":"scripts/telegram_fetch.py","type":"text","marks":[{"type":"code_inline"}]},{"text":" with appropriate commands:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# List available chats\npython3 scripts/telegram_fetch.py list\n\n# Get recent messages\npython3 scripts/telegram_fetch.py recent --limit 20\n\n# Search messages\npython3 scripts/telegram_fetch.py search \"meeting\"\n\n# Get unread messages\npython3 scripts/telegram_fetch.py unread","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Commands","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"List Chats","type":"text"}]},{"type":"paragraph","content":[{"text":"To see available Telegram chats:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/telegram_fetch.py list\npython3 scripts/telegram_fetch.py list --limit 50\npython3 scripts/telegram_fetch.py list --search \"AI\"\npython3 scripts/telegram_fetch.py list --search \"claude code глеб + саши\" --exact","type":"text"}]},{"type":"paragraph","content":[{"text":"Options:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--search \"text\"","type":"text","marks":[{"type":"code_inline"}]},{"text":": Filter by substring match (case-insensitive)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--exact","type":"text","marks":[{"type":"code_inline"}]},{"text":": Require exact name match instead of substring (use with --search)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--limit N","type":"text","marks":[{"type":"code_inline"}]},{"text":": Max chats to retrieve (default: 30, increase if chat not found)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Important:","type":"text","marks":[{"type":"strong"}]},{"text":" If you're looking for a specific chat by exact name and it's not found, increase ","type":"text"},{"text":"--limit","type":"text","marks":[{"type":"code_inline"}]},{"text":" to 100 or 200, as the chat may not be in the most recent 30.","type":"text"}]},{"type":"paragraph","content":[{"text":"Returns JSON with chat IDs, names, types, and unread counts.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Fetch Recent Messages","type":"text"}]},{"type":"paragraph","content":[{"text":"To get recent messages:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# From all chats (last 50 messages across top 10 chats)\npython3 scripts/telegram_fetch.py recent\n\n# From specific chat\npython3 scripts/telegram_fetch.py recent --chat \"Tool Building Ape\"\npython3 scripts/telegram_fetch.py recent --chat-id 123456789\n\n# With limits\npython3 scripts/telegram_fetch.py recent --limit 100\npython3 scripts/telegram_fetch.py recent --days 7","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Search Messages","type":"text"}]},{"type":"paragraph","content":[{"text":"To search message content:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Global search across all chats\npython3 scripts/telegram_fetch.py search \"project deadline\"\n\n# Search in specific chat\npython3 scripts/telegram_fetch.py search \"meeting\" --chat-id 123456789\n\n# Limit results\npython3 scripts/telegram_fetch.py search \"important\" --limit 20","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Fetch Unread Messages","type":"text"}]},{"type":"paragraph","content":[{"text":"To get only unread messages:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/telegram_fetch.py unread\npython3 scripts/telegram_fetch.py unread --chat-id 123456789","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Send Messages","type":"text"}]},{"type":"paragraph","content":[{"text":"To send a message to a chat:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Send to existing chat by name\npython3 scripts/telegram_fetch.py send --chat \"John Doe\" --text \"Hello!\"\n\n# Send to username (works even without prior conversation)\npython3 scripts/telegram_fetch.py send --chat \"@username\" --text \"Hello!\"\n\n# Reply to a specific message (use message ID from recent/search output)\npython3 scripts/telegram_fetch.py send --chat \"Tool Building Ape\" --text \"Thanks!\" --reply-to 12345\n\n# Send to a forum topic (for groups with topics enabled)\npython3 scripts/telegram_fetch.py send --chat \"Group Name\" --text \"Hello topic!\" --topic 12\n\n# Send with markdown formatting (converts **bold**, _italic_, [links](url) to Telegram HTML)\npython3 scripts/telegram_fetch.py send --chat \"@username\" --text \"**Bold** and _italic_ text\" --markdown","type":"text"}]},{"type":"paragraph","content":[{"text":"Formatting (","type":"text","marks":[{"type":"strong"}]},{"text":"--markdown","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" flag):","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Without ","type":"text"},{"text":"--markdown","type":"text","marks":[{"type":"code_inline"}]},{"text":": text is sent as-is (plain text, no formatting)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"With ","type":"text"},{"text":"--markdown","type":"text","marks":[{"type":"code_inline"}]},{"text":": converts markdown to Telegram HTML (","type":"text"},{"text":"**bold**","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> bold, ","type":"text"},{"text":"_italic_","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> italic, ","type":"text"},{"text":"[text](url)","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> link, ","type":"text"},{"text":"## Header","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> bold, ","type":"text"},{"text":"* item","type":"text","marks":[{"type":"code_inline"}]},{"text":" -> arrow list)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"IMPORTANT","type":"text","marks":[{"type":"strong"}]},{"text":": Always use ","type":"text"},{"text":"--markdown","type":"text","marks":[{"type":"code_inline"}]},{"text":" when sending draft content that contains markdown formatting","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"The ","type":"text"},{"text":"publish","type":"text","marks":[{"type":"code_inline"}]},{"text":" command handles markdown conversion automatically; the ","type":"text"},{"text":"send","type":"text","marks":[{"type":"code_inline"}]},{"text":" command does NOT unless ","type":"text"},{"text":"--markdown","type":"text","marks":[{"type":"code_inline"}]},{"text":" is specified","type":"text"}]}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Send Files","type":"text"}]},{"type":"paragraph","content":[{"text":"To send images, documents, or videos:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Send an image\npython3 scripts/telegram_fetch.py send --chat \"John Doe\" --file \"/path/to/image.jpg\"\n\n# Send document with caption\npython3 scripts/telegram_fetch.py send --chat \"@username\" --file \"report.pdf\" --text \"Here's the report\"\n\n# Reply with media\npython3 scripts/telegram_fetch.py send --chat \"Group\" --file \"screenshot.png\" --reply-to 12345","type":"text"}]},{"type":"paragraph","content":[{"text":"Chat resolution order:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"@username","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Resolves Telegram username directly","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Numeric ID - Resolves chat by Telegram ID","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Name match - Fuzzy search in existing dialogs","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Returns JSON with send status, resolved chat name, message ID, and file info (for media).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Edit Messages","type":"text"}]},{"type":"paragraph","content":[{"text":"To edit an existing message:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Edit a message by ID\npython3 scripts/telegram_fetch.py edit --chat \"@mentalhealthtech\" --message-id 76 --text \"Updated text\"\n\n# Edit in a group/channel\npython3 scripts/telegram_fetch.py edit --chat \"Mental health tech\" --message-id 123 --text \"Corrected content\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Note:","type":"text","marks":[{"type":"strong"}]},{"text":" You can only edit your own messages. Telegram formatting (","type":"text"},{"text":"bold","type":"text","marks":[{"type":"strong"}]},{"text":", etc.) is preserved.","type":"text"}]},{"type":"paragraph","content":[{"text":"Returns JSON with edit status and message ID.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Download Attachments","type":"text"}]},{"type":"paragraph","content":[{"text":"To download media files from a chat:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Download last 5 attachments from a chat (default)\npython3 scripts/telegram_fetch.py download --chat \"Tool Building Ape\"\n\n# Download last 10 attachments\npython3 scripts/telegram_fetch.py download --chat \"Project Group\" --limit 10\n\n# Download to custom directory\npython3 scripts/telegram_fetch.py download --chat \"@username\" --output \"/path/to/folder\"\n\n# Download from specific message\npython3 scripts/telegram_fetch.py download --chat \"John Doe\" --message-id 12345","type":"text"}]},{"type":"paragraph","content":[{"text":"Default output:","type":"text","marks":[{"type":"strong"}]},{"text":" ","type":"text"},{"text":"~/Downloads/telegram_attachments/","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"paragraph","content":[{"text":"Returns JSON with download results (file names, paths, sizes).","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Fetch Forum Thread Messages","type":"text"}]},{"type":"paragraph","content":[{"text":"To get messages from a specific forum thread (topics in groups):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Fetch from thread 174 in Claude Code Lab\npython3 scripts/telegram_fetch.py thread --chat-id -1003237581133 --thread-id 174\n\n# Fetch with custom limit\npython3 scripts/telegram_fetch.py thread --chat-id -1003237581133 --thread-id 174 --limit 50\n\n# Save to file\npython3 scripts/telegram_fetch.py thread --chat-id -1003237581133 --thread-id 174 -o ~/thread.md\n\n# Append to daily note\npython3 scripts/telegram_fetch.py thread --chat-id -1003237581133 --thread-id 174 --to-daily\n\n# JSON output\npython3 scripts/telegram_fetch.py thread --chat-id -1003237581133 --thread-id 174 --json","type":"text"}]},{"type":"paragraph","content":[{"text":"Messages are sorted newest first","type":"text","marks":[{"type":"strong"}]},{"text":" (reverse chronological order).","type":"text"}]},{"type":"paragraph","content":[{"text":"How to find thread ID:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Forum topic IDs appear in the thread URL: ","type":"text"},{"text":"https://t.me/c/CHAT_ID/THREAD_ID","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Use ","type":"text"},{"text":"recent","type":"text","marks":[{"type":"code_inline"}]},{"text":" command on the chat to see message IDs in threads","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Returns markdown or JSON with all messages from the specified thread.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Publish Draft to Channel","type":"text"}]},{"type":"paragraph","content":[{"text":"To publish a draft from the klodkot channel to Telegram:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Dry run (preview without sending)\npython3 scripts/telegram_fetch.py publish --draft \"Channels/klodkot/drafts/20260122-anthropic-consciousness-question.md\" --dry-run\n\n# Publish to channel\npython3 scripts/telegram_fetch.py publish --draft \"Channels/klodkot/drafts/20260122-anthropic-consciousness-question.md\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Workflow:","type":"text","marks":[{"type":"strong"}]}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Parses draft frontmatter and body","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validates channel field (must be \"klodkot\")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Extracts media references from frontmatter ","type":"text"},{"text":"video:","type":"text","marks":[{"type":"code_inline"}]},{"text":" field and wikilinks","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Resolves media paths in ","type":"text"},{"text":"Channels/klodkot/attachments/","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"Sources/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Strips draft headers (e.g., \"# Title - Telegram Draft\")","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Appends footer if not present: \"","type":"text"},{"text":"КЛОДКОТ","type":"text","marks":[{"type":"link","attrs":{"href":"https://t.me/klodkot","title":null}},{"type":"strong"}]},{"text":" — Claude Code и другие агенты: инструменты, кейсы, вдохновение\"","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Sends to @klodkot channel (multiple media as album)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Updates frontmatter with ","type":"text"},{"text":"published_date","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"telegram_message_id","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Moves file from ","type":"text"},{"text":"drafts/","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":"published/","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Updates channel index with new entry at top","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Media handling:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Frontmatter: ","type":"text"},{"text":"video: filename.mp4","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Wikilinks: ","type":"text"},{"text":"[[filename.mp4]]","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"[[image.png|alt text]]","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Multiple media sent as Telegram album","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Safety:","type":"text","marks":[{"type":"strong"}]}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--dry-run","type":"text","marks":[{"type":"code_inline"}]},{"text":" shows preview without sending","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Validates before sending","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Rollback on send failure (file not moved)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Warnings on post-publish errors (file sent but move/index update failed)","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"Returns:","type":"text","marks":[{"type":"strong"}]},{"text":" JSON with publish status, message ID, warnings (if any)","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Output Options","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Default (Markdown to stdout)","type":"text"}]},{"type":"paragraph","content":[{"text":"By default, outputs formatted markdown suitable for Claude to read and summarize.","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"JSON Format","type":"text"}]},{"type":"paragraph","content":[{"text":"Add ","type":"text"},{"text":"--json","type":"text","marks":[{"type":"code_inline"}]},{"text":" flag for structured data:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/telegram_fetch.py recent --json","type":"text"}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Append to Obsidian Daily Note","type":"text"}]},{"type":"paragraph","content":[{"text":"Add messages to today's daily note in the vault:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/telegram_fetch.py recent --to-daily\npython3 scripts/telegram_fetch.py search \"project\" --to-daily","type":"text"}]},{"type":"paragraph","content":[{"text":"Appends to ","type":"text"},{"text":"~/Brains/brain/Daily/YYYYMMDD.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Append to Person's Note","type":"text"}]},{"type":"paragraph","content":[{"text":"Add messages to a specific person's note:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"python3 scripts/telegram_fetch.py recent --chat \"John Doe\" --to-person \"John Doe\"","type":"text"}]},{"type":"paragraph","content":[{"text":"Creates or appends to ","type":"text"},{"text":"~/Brains/brain/{PersonName}.md","type":"text","marks":[{"type":"code_inline"}]}]},{"type":"heading","attrs":{"level":3},"content":[{"text":"Save to File (Token-Efficient)","type":"text"}]},{"type":"paragraph","content":[{"text":"Save messages directly to file without consuming context tokens:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"# Save 100 messages to markdown file\npython3 scripts/telegram_fetch.py recent --chat \"AGENCY: Community\" --limit 100 -o ~/chat_archive.md\n\n# Save with media files downloaded to same folder\npython3 scripts/telegram_fetch.py recent --chat \"Project Group\" --limit 50 -o ~/project/archive.md --with-media\n\n# Save search results to file\npython3 scripts/telegram_fetch.py search \"meeting\" -o ~/meetings.md","type":"text"}]},{"type":"paragraph","content":[{"text":"Returns JSON with save status (file path, message count, media download results) - minimal token usage.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Example User Requests","type":"text"}]},{"type":"paragraph","content":[{"text":"When user asks:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Show my recent Telegram messages\" -> ","type":"text"},{"text":"recent --limit 20","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"What Telegram messages did I get today?\" -> ","type":"text"},{"text":"recent --days 1","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Search Telegram for messages about the project\" -> ","type":"text"},{"text":"search \"project\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Get unread messages from Tool Building Ape\" -> ","type":"text"},{"text":"unread","type":"text","marks":[{"type":"code_inline"}]},{"text":" + filter output","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Add my Telegram messages to daily note\" -> ","type":"text"},{"text":"recent --to-daily","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"What chats do I have on Telegram?\" -> ","type":"text"},{"text":"list","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Find the exact chat named X\" -> ","type":"text"},{"text":"list --search \"X\" --exact --limit 200","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Send hello to John on Telegram\" -> ","type":"text"},{"text":"send --chat \"John\" --text \"Hello!\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Message @username on Telegram\" -> ","type":"text"},{"text":"send --chat \"@username\" --text \"...\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Reply to that message with thanks\" -> ","type":"text"},{"text":"send --chat \"...\" --text \"Thanks!\" --reply-to \u003cid>","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Send this image to John\" -> ","type":"text"},{"text":"send --chat \"John\" --file \"/path/to/image.jpg\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Send report.pdf with caption\" -> ","type":"text"},{"text":"send --chat \"...\" --file \"report.pdf\" --text \"Here's the report\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Send to topic 12 in Group\" -> ","type":"text"},{"text":"send --chat \"Group\" --text \"...\" --topic 12","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Download attachments from Tool Building Ape\" -> ","type":"text"},{"text":"download --chat \"Tool Building Ape\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Download last 10 files from Project Group\" -> ","type":"text"},{"text":"download --chat \"Project Group\" --limit 10","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Save last 100 messages from AGENCY to file\" -> ","type":"text"},{"text":"recent --chat \"AGENCY: Community\" --limit 100 -o ~/agency.md","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Archive chat with media\" -> ","type":"text"},{"text":"recent --chat \"Group\" -o ~/archive.md --with-media","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Edit that message\" -> ","type":"text"},{"text":"edit --chat \"...\" --message-id \u003cid> --text \"new text\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Fix the typo in message 123\" -> ","type":"text"},{"text":"edit --chat \"...\" --message-id 123 --text \"corrected text\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Is Telegram configured?\" -> ","type":"text"},{"text":"setup","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"How do I set up Telegram?\" -> ","type":"text"},{"text":"setup","type":"text","marks":[{"type":"code_inline"}]},{"text":" (returns instructions if not configured)","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Publish this draft to klodkot\" -> ","type":"text"},{"text":"publish --draft \"Channels/klodkot/drafts/...md\"","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"\"Preview this draft before publishing\" -> ","type":"text"},{"text":"publish --draft \"...\" --dry-run","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Rate Limiting","type":"text"}]},{"type":"paragraph","content":[{"text":"The script includes built-in rate limiting (0.1s between messages) and handles Telegram's FloodWaitError automatically with backoff.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Dependencies","type":"text"}]},{"type":"paragraph","content":[{"text":"Requires Python packages:","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"telethon","type":"text","marks":[{"type":"code_inline"}]},{"text":" - Telegram API client","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"pyyaml","type":"text","marks":[{"type":"code_inline"}]},{"text":" - YAML parsing for draft frontmatter","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Install with: ","type":"text"},{"text":"pip install telethon pyyaml","type":"text","marks":[{"type":"code_inline"}]}]}]},"metadata":{"date":"2026-06-05","name":"telegram","author":"@skillopedia","source":{"stars":237,"repo_name":"claude-skills","origin_url":"https://github.com/glebis/claude-skills/blob/HEAD/telegram/SKILL.md","repo_owner":"glebis","body_sha256":"80c97c89db0ebc90ae375a1f338da2d5677cb9cf1f7d3b6bc1944b193b812f47","cluster_key":"8e75def723d439be3bddef7f6349bed5311230704e3a7d076b55547c4dcbc753","clean_bundle":{"format":"clean-skill-bundle-v1","source":"glebis/claude-skills/telegram/SKILL.md","attachments":[{"id":"f5c7dca1-5b3a-5d0f-8401-65eda4026725","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/f5c7dca1-5b3a-5d0f-8401-65eda4026725/attachment.py","path":"scripts/bot_send.py","size":7802,"sha256":"b9272bc815caf8ae28645a418eae8d9a3c9ea983773061c260fc3c19dba5bf25","contentType":"text/x-python; charset=utf-8"},{"id":"1f826f90-0e90-5689-a79c-09a90764c521","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/1f826f90-0e90-5689-a79c-09a90764c521/attachment.py","path":"scripts/telegram_fetch.py","size":55533,"sha256":"ef73e5275e7223388a04bbfa453057f22455e8a30cf3fd078b55d29f8d4f6bd2","contentType":"text/x-python; charset=utf-8"}],"bundle_sha256":"502a514563e93e4ef5328e1f7bcbd0bfad27f7578149db58b89661270efd2acd","attachment_count":2,"text_attachments":2,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":0,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"telegram/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"productivity-workflow","category_label":"Productivity"},"exact_dupes_collapsed_into_this":0},"version":"v1","category":"productivity-workflow","import_tag":"clean-skills-v1","description":"This skill should be used when fetching, searching, downloading, sending, editing, or publishing messages on Telegram. Use for queries like \"show my Telegram messages\", \"search Telegram for...\", \"get unread messages\", \"send a message to...\", \"edit that message\", \"publish this draft to klodkot\", or \"add Telegram messages to my notes\"."}},"renderedAt":1782986641772}

Telegram Message Skill Fetch, search, download, send, and publish Telegram messages with flexible filtering and output options. Prerequisites Authentication must be configured in . Run command to check status or get instructions: If not configured, follow these steps: 1. Get API credentials from https://my.telegram.org/auth 2. Clone telegram dl: https://github.com/glebis/telegram dl 3. Run and follow interactive prompts 4. Verify with Quick Start Run the script at with appropriate commands: Commands List Chats To see available Telegram chats: Options: - : Filter by substring match (case-insen…