Rebase onto upstream (a4d95fd) #12

Closed
wylab wants to merge 144 commits from rebase-onto-upstream into main
4 changed files with 2585 additions and 249 deletions
Showing only changes of commit 08add97bdb - Show all commits
Vendored Executable
BIN
View File
Binary file not shown.
+6 -178
View File
@@ -4,11 +4,9 @@ from __future__ import annotations
import asyncio
import re
from pathlib import Path
from loguru import logger
from telegram import BotCommand, Update
from telegram.ext import Application, CommandHandler, ContextTypes, MessageHandler, filters
from telegram.ext import Application, CommandHandler, MessageHandler, filters, ContextTypes
from telegram.request import HTTPXRequest
from nanobot.bus.events import OutboundMessage
@@ -16,10 +14,6 @@ from nanobot.bus.queue import MessageBus
from nanobot.channels.base import BaseChannel
from nanobot.config.schema import TelegramConfig
# Telegram API limits
REMOTE_MEDIA_SIZE_LIMIT = 100_000_000 # 100MB for remote URLs
TELEGRAM_PHOTO_SIZE_LIMIT = 10_000_000 # 10MB for photos
def _markdown_to_telegram_html(text: str) -> str:
"""
@@ -201,14 +195,10 @@ class TelegramChannel(BaseChannel):
return # Don't send to Telegram API
try:
# chat_id should be the Telegram chat ID (integer)
chat_id = int(msg.chat_id)
# Convert markdown to Telegram HTML
html_content = _markdown_to_telegram_html(msg.content)
# Handle media if present
if msg.media:
await self._send_with_media(chat_id, html_content, msg.media)
else:
# Text-only message
await self._app.bot.send_message(
chat_id=chat_id,
text=html_content,
@@ -221,173 +211,11 @@ class TelegramChannel(BaseChannel):
logger.warning(f"HTML parse failed, falling back to plain text: {e}")
try:
await self._app.bot.send_message(
chat_id=chat_id,
chat_id=int(msg.chat_id),
text=msg.content
)
except Exception as inner_e:
logger.error(f"Error sending Telegram message: {inner_e}")
async def _send_with_media(self, chat_id: int, caption: str, media_paths: list[str]) -> None:
"""
Send message with media attachments.
Args:
chat_id: Telegram chat ID
caption: Message caption
media_paths: List of file paths or URLs
"""
from telegram import InputMediaPhoto, InputMediaVideo
from nanobot.channels.telegram_media import (
MediaKind,
classify_media,
detect_mime,
fetch_media,
group_media_for_album,
optimize_image,
)
# Process each media item
processed_media: list[tuple[str, MediaKind, bytes]] = []
for path in media_paths:
try:
# Fetch remote URLs
if path.startswith(("http://", "https://")):
content, mime = await fetch_media(path, max_bytes=REMOTE_MEDIA_SIZE_LIMIT)
kind = classify_media(mime)
else:
# Local file
file_path = Path(path)
if not file_path.exists():
logger.warning(f"Media file not found: {path}")
continue
with open(file_path, "rb") as f:
content = f.read()
mime = detect_mime(path, content)
kind = classify_media(mime)
# Optimize images
if kind == MediaKind.IMAGE:
try:
content = optimize_image(path, max_bytes=6_000_000)
except Exception as e:
logger.warning(f"Image optimization failed: {e}")
# Check if original is under Telegram's limit
if len(content) <= TELEGRAM_PHOTO_SIZE_LIMIT:
logger.info("Using original image (under 10MB limit)")
else:
logger.error(f"Original image too large ({len(content)} bytes), skipping")
continue
processed_media.append((path, kind, content))
except Exception as e:
logger.error(f"Failed to process media {path}: {e}")
continue
if not processed_media:
# No media could be processed, send text only
await self._app.bot.send_message(
chat_id=chat_id,
text=caption,
parse_mode="HTML"
)
return
# Group media for album sending
media_items = [(path, kind) for path, kind, _ in processed_media]
grouping = group_media_for_album(media_items)
# Handle caption length (Telegram limit: 1024 chars)
if len(caption) > 1024:
# Send media without caption, then follow-up text
media_caption = None
followup_text = caption
else:
media_caption = caption
followup_text = None
# Send album if grouped
if grouping["album"]:
album_paths = grouping["album"]
album_media = []
for path, kind, content in processed_media:
if path not in album_paths:
continue
if kind == MediaKind.IMAGE:
media_obj = InputMediaPhoto(
media=content,
caption=media_caption if len(album_media) == 0 else None,
parse_mode="HTML" if media_caption else None
)
elif kind == MediaKind.VIDEO:
media_obj = InputMediaVideo(
media=content,
caption=media_caption if len(album_media) == 0 else None,
parse_mode="HTML" if media_caption else None
)
else:
continue # Skip non-album types
album_media.append(media_obj)
if album_media:
await self._app.bot.send_media_group(
chat_id=chat_id,
media=album_media
)
# Send separate media
first_separate = True
for path, kind, content in processed_media:
if path in grouping["album"]:
continue # Already sent in album
# Only first separate item gets caption
item_caption = media_caption if first_separate else None
first_separate = False
if kind == MediaKind.IMAGE:
await self._app.bot.send_photo(
chat_id=chat_id,
photo=content,
caption=item_caption,
parse_mode="HTML" if item_caption else None
)
elif kind == MediaKind.VIDEO:
await self._app.bot.send_video(
chat_id=chat_id,
video=content,
caption=item_caption,
parse_mode="HTML" if item_caption else None
)
elif kind == MediaKind.AUDIO:
await self._app.bot.send_audio(
chat_id=chat_id,
audio=content,
caption=item_caption,
parse_mode="HTML" if item_caption else None
)
elif kind == MediaKind.DOCUMENT:
await self._app.bot.send_document(
chat_id=chat_id,
document=content,
caption=item_caption,
parse_mode="HTML" if item_caption else None
)
# Send follow-up text if caption was too long
if followup_text:
await self._app.bot.send_message(
chat_id=chat_id,
text=followup_text,
parse_mode="HTML"
)
except Exception as e2:
logger.error(f"Error sending Telegram message: {e2}")
async def _on_start(self, update: Update, context: ContextTypes.DEFAULT_TYPE) -> None:
"""Handle /start command."""
-2
View File
@@ -38,8 +38,6 @@ dependencies = [
"qq-botpy>=1.0.0",
"python-socks[asyncio]>=2.4.0",
"prompt-toolkit>=3.0.0",
"pillow-heif>=0.10.0",
"python-magic>=0.4.27",
]
[project.optional-dependencies]
Generated
+2510
View File
File diff suppressed because it is too large Load Diff