polished forwarding
This commit is contained in:
1 parent
b3a7612e6b
commit
793311b1e2
3 files changed
+112
-28
No files matched your search
@@ -6,6 +6,8 @@ import logging
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
from tgbridge.forwarder import TelegramForwarder
|
from tgbridge.forwarder import TelegramForwarder
|
||||||
|
from tgbridge.discord_to_telegram import DiscordToTelegramForwarder
|
||||||
|
|
||||||
from twitterbridge.rss import TwitterRSSForwarder
|
from twitterbridge.rss import TwitterRSSForwarder
|
||||||
|
|
||||||
load_dotenv('credentials.env')
|
load_dotenv('credentials.env')
|
||||||
@@ -21,6 +23,13 @@ if os.getenv('TELEGRAM_GROUP_ID') and os.getenv('DISCORD_WEBHOOK_URL'):
|
|||||||
if os.getenv('TELEGRAM_GROUP_ID_SHITPOST') and os.getenv('DISCORD_WEBHOOK_URL_SHITPOST'):
|
if os.getenv('TELEGRAM_GROUP_ID_SHITPOST') and os.getenv('DISCORD_WEBHOOK_URL_SHITPOST'):
|
||||||
forwarder.add_route(int(os.getenv('TELEGRAM_GROUP_ID_SHITPOST')), os.getenv('DISCORD_WEBHOOK_URL_SHITPOST'))
|
forwarder.add_route(int(os.getenv('TELEGRAM_GROUP_ID_SHITPOST')), os.getenv('DISCORD_WEBHOOK_URL_SHITPOST'))
|
||||||
|
|
||||||
|
# Discord → Telegram (reverse bridge)
|
||||||
|
d_to_tg = DiscordToTelegramForwarder(bot=bot, tg_token=os.getenv('TELEGRAM_BOT_TOKEN'))
|
||||||
|
if os.getenv('DISCORD_CHANNEL_ID') and os.getenv('TELEGRAM_GROUP_ID'):
|
||||||
|
d_to_tg.add_route(int(os.getenv('DISCORD_CHANNEL_ID')), int(os.getenv('TELEGRAM_GROUP_ID')))
|
||||||
|
if os.getenv('DISCORD_CHANNEL_ID_SHITPOST') and os.getenv('TELEGRAM_GROUP_ID_SHITPOST'):
|
||||||
|
d_to_tg.add_route(int(os.getenv('DISCORD_CHANNEL_ID_SHITPOST')), int(os.getenv('TELEGRAM_GROUP_ID_SHITPOST')))
|
||||||
|
|
||||||
twitter = TwitterRSSForwarder(
|
twitter = TwitterRSSForwarder(
|
||||||
rss_url=os.getenv('TWITTER_RSS_URL'),
|
rss_url=os.getenv('TWITTER_RSS_URL'),
|
||||||
webhook_url=os.getenv('TWITTER_DISCORD_WEBHOOK'),
|
webhook_url=os.getenv('TWITTER_DISCORD_WEBHOOK'),
|
||||||
@@ -34,6 +43,7 @@ async def on_ready():
|
|||||||
|
|
||||||
async def main():
|
async def main():
|
||||||
async with bot:
|
async with bot:
|
||||||
|
await bot.add_cog(d_to_tg)
|
||||||
await bot.load_extension('cogs.general')
|
await bot.load_extension('cogs.general')
|
||||||
await bot.load_extension('cogs.chance')
|
await bot.load_extension('cogs.chance')
|
||||||
await bot.load_extension('cogs.gifs')
|
await bot.load_extension('cogs.gifs')
|
||||||
|
|||||||
@@ -0,0 +1,96 @@
|
|||||||
|
# Discord → Telegram forwarding
|
||||||
|
import io
|
||||||
|
import discord
|
||||||
|
from discord.ext import commands
|
||||||
|
from telegram import Bot
|
||||||
|
from telegram.error import TelegramError
|
||||||
|
|
||||||
|
|
||||||
|
class DiscordToTelegramForwarder(commands.Cog):
|
||||||
|
"""
|
||||||
|
Listens for Discord messages in configured channels and forwards them to
|
||||||
|
the paired Telegram group.
|
||||||
|
|
||||||
|
Routes are added via add_route(discord_channel_id, telegram_chat_id).
|
||||||
|
Bot messages and webhook messages are silently ignored to prevent loops.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, bot: commands.Bot, tg_token: str):
|
||||||
|
self.bot = bot
|
||||||
|
self._tg_bot = Bot(token=tg_token)
|
||||||
|
# discord_channel_id (int) -> telegram_chat_id (int)
|
||||||
|
self.routes: dict[int, int] = {}
|
||||||
|
|
||||||
|
def add_route(self, discord_channel_id: int, telegram_chat_id: int):
|
||||||
|
self.routes[discord_channel_id] = telegram_chat_id
|
||||||
|
|
||||||
|
# -- helpers --------------------------------------------------------------
|
||||||
|
|
||||||
|
def _format_header(self, message: discord.Message) -> str:
|
||||||
|
"""Return a bold sender label: *Username* (Discord)"""
|
||||||
|
name = message.author.display_name
|
||||||
|
return f"*{discord.utils.escape_markdown(name)}* (Discord):"
|
||||||
|
|
||||||
|
async def _send_text(self, chat_id: int, text: str):
|
||||||
|
try:
|
||||||
|
await self._tg_bot.send_message(
|
||||||
|
chat_id=chat_id,
|
||||||
|
text=text,
|
||||||
|
parse_mode='Markdown',
|
||||||
|
)
|
||||||
|
except TelegramError as e:
|
||||||
|
print(f'[D->TG] send_message failed: {e}')
|
||||||
|
|
||||||
|
async def _send_attachment(self, chat_id: int, attachment: discord.Attachment, caption: str):
|
||||||
|
"""Download a Discord attachment and upload it to Telegram."""
|
||||||
|
try:
|
||||||
|
file_bytes = await attachment.read()
|
||||||
|
buf = io.BytesIO(file_bytes)
|
||||||
|
buf.name = attachment.filename
|
||||||
|
|
||||||
|
mime = attachment.content_type or ''
|
||||||
|
|
||||||
|
if mime.startswith('image/') and not mime.startswith('image/gif'):
|
||||||
|
await self._tg_bot.send_photo(chat_id=chat_id, photo=buf, caption=caption, parse_mode='Markdown')
|
||||||
|
elif mime.startswith('video/') or mime == 'image/gif':
|
||||||
|
await self._tg_bot.send_video(chat_id=chat_id, video=buf, caption=caption, parse_mode='Markdown')
|
||||||
|
elif mime.startswith('audio/'):
|
||||||
|
await self._tg_bot.send_audio(chat_id=chat_id, audio=buf, caption=caption, parse_mode='Markdown')
|
||||||
|
else:
|
||||||
|
await self._tg_bot.send_document(chat_id=chat_id, document=buf, caption=caption, parse_mode='Markdown')
|
||||||
|
|
||||||
|
except TelegramError as e:
|
||||||
|
print(f'[D->TG] send_attachment failed ({attachment.filename}): {e}')
|
||||||
|
except Exception as e:
|
||||||
|
print(f'[D->TG] unexpected error for attachment {attachment.filename}: {e}')
|
||||||
|
|
||||||
|
# -- event handler --------------------------------------------------------
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_message(self, message: discord.Message):
|
||||||
|
# ignore bots (including ourselves) and webhook posts to prevent echo loops
|
||||||
|
if message.author.bot or message.webhook_id:
|
||||||
|
return
|
||||||
|
|
||||||
|
chat_id = self.routes.get(message.channel.id)
|
||||||
|
if chat_id is None:
|
||||||
|
return
|
||||||
|
|
||||||
|
header = self._format_header(message)
|
||||||
|
body = message.content or ''
|
||||||
|
|
||||||
|
# text portion
|
||||||
|
if body or not message.attachments:
|
||||||
|
full_text = f"{header}\n{body}".strip()
|
||||||
|
await self._send_text(chat_id, full_text)
|
||||||
|
|
||||||
|
# attachments
|
||||||
|
for i, attachment in enumerate(message.attachments):
|
||||||
|
# first attachment gets the header as caption; rest are bare
|
||||||
|
if i == 0:
|
||||||
|
caption = header if body else f"{header}\n{body}".strip()
|
||||||
|
else:
|
||||||
|
caption = ''
|
||||||
|
await self._send_attachment(chat_id, attachment, caption)
|
||||||
|
|
||||||
|
print(f'[D->TG] {message.author} (channel {message.channel.id}) -> tg chat {chat_id}')
|
||||||
+6
-28
@@ -48,28 +48,7 @@ class TelegramForwarder:
|
|||||||
return chat.title
|
return chat.title
|
||||||
return 'unknown'
|
return 'unknown'
|
||||||
|
|
||||||
def _forward_prefix(self, message):
|
|
||||||
if message.forward_origin:
|
|
||||||
origin = message.forward_origin
|
|
||||||
|
|
||||||
# user forward
|
|
||||||
if hasattr(origin, 'sender_user') and origin.sender_user:
|
|
||||||
u = origin.sender_user
|
|
||||||
name = u.first_name or ''
|
|
||||||
if u.last_name:
|
|
||||||
name += f' {u.last_name}'
|
|
||||||
return f'↩️ Forwarded from **{name.strip() or u.username or "unknown"}**:\n'
|
|
||||||
|
|
||||||
# channel forward
|
|
||||||
if hasattr(origin, 'chat') and origin.chat:
|
|
||||||
return f'↩️ Forwarded from **{origin.chat.title or "unknown channel"}**:\n'
|
|
||||||
|
|
||||||
# hidden user
|
|
||||||
if hasattr(origin, 'sender_user_name') and origin.sender_user_name:
|
|
||||||
return f'↩️ Forwarded from **{origin.sender_user_name}**:\n'
|
|
||||||
|
|
||||||
return '↩️ Forwarded:\n'
|
|
||||||
return ''
|
|
||||||
|
|
||||||
async def _download_file(self, file_id):
|
async def _download_file(self, file_id):
|
||||||
tg_file = await self._app.bot.get_file(file_id)
|
tg_file = await self._app.bot.get_file(file_id)
|
||||||
@@ -111,7 +90,6 @@ class TelegramForwarder:
|
|||||||
|
|
||||||
webhook_url = self.routes[chat_id]
|
webhook_url = self.routes[chat_id]
|
||||||
sender = self._sender_name(update)
|
sender = self._sender_name(update)
|
||||||
prefix = self._forward_prefix(message)
|
|
||||||
caption = message.caption or ''
|
caption = message.caption or ''
|
||||||
|
|
||||||
# Log incoming message media types for debugging
|
# Log incoming message media types for debugging
|
||||||
@@ -120,7 +98,7 @@ class TelegramForwarder:
|
|||||||
if message.photo:
|
if message.photo:
|
||||||
photo = message.photo[-1]
|
photo = message.photo[-1]
|
||||||
file_data, filename = await self._download_file(photo.file_id)
|
file_data, filename = await self._download_file(photo.file_id)
|
||||||
text = f'{prefix}{caption}'.strip()
|
text = caption.strip()
|
||||||
await self._send_to_webhook(webhook_url, sender, text, file_data, filename)
|
await self._send_to_webhook(webhook_url, sender, text, file_data, filename)
|
||||||
|
|
||||||
# video and document handling are now managed by the enhanced GIF block below
|
# video and document handling are now managed by the enhanced GIF block below
|
||||||
@@ -182,17 +160,17 @@ class TelegramForwarder:
|
|||||||
print(f'ffmpeg conversion skipped/failed: {e}', flush=True)
|
print(f'ffmpeg conversion skipped/failed: {e}', flush=True)
|
||||||
# <<< FFMPEG GIF CONVERSION END <<<
|
# <<< FFMPEG GIF CONVERSION END <<<
|
||||||
|
|
||||||
text = f'{prefix}{caption}'.strip()
|
text = caption.strip()
|
||||||
await self._send_to_webhook(webhook_url, sender, text, file_data, filename)
|
await self._send_to_webhook(webhook_url, sender, text, file_data, filename)
|
||||||
|
|
||||||
elif message.document:
|
elif message.document:
|
||||||
file_data, filename = await self._download_file(message.document.file_id)
|
file_data, filename = await self._download_file(message.document.file_id)
|
||||||
text = f'{prefix}{caption}'.strip()
|
text = caption.strip()
|
||||||
await self._send_to_webhook(webhook_url, sender, text, file_data, filename)
|
await self._send_to_webhook(webhook_url, sender, text, file_data, filename)
|
||||||
|
|
||||||
elif message.sticker:
|
elif message.sticker:
|
||||||
sticker_text = message.sticker.emoji or '(sticker)'
|
sticker_text = message.sticker.emoji or '(sticker)'
|
||||||
text = f'{prefix}{sticker_text}'
|
text = sticker_text
|
||||||
# >>> STICKER TO GIF CONVERSION START >>>
|
# >>> STICKER TO GIF CONVERSION START >>>
|
||||||
file_data, filename = await self._download_file(message.sticker.file_id)
|
file_data, filename = await self._download_file(message.sticker.file_id)
|
||||||
try:
|
try:
|
||||||
@@ -235,11 +213,11 @@ class TelegramForwarder:
|
|||||||
# <<< STICKER TO GIF CONVERSION END <<<
|
# <<< STICKER TO GIF CONVERSION END <<<
|
||||||
|
|
||||||
elif message.text:
|
elif message.text:
|
||||||
text = f'{prefix}{message.text}'
|
text = message.text
|
||||||
await self._send_to_webhook(webhook_url, sender, text)
|
await self._send_to_webhook(webhook_url, sender, text)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
text = f'{prefix}*(unsupported message type)*'
|
text = '*(unsupported message type)*'
|
||||||
await self._send_to_webhook(webhook_url, sender, text)
|
await self._send_to_webhook(webhook_url, sender, text)
|
||||||
|
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
|||||||
Reference in new issue
Block a user