telegram forwarding method added
This commit is contained in:
1 parent
d4cc8eaf09
commit
2b26984698
5 files changed
+225
-30
No files matched your search
@@ -1,30 +1,45 @@
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
load_dotenv('credentials.env')
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix='!', intents=intents)
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f'✅ {bot.user} is online!')
|
||||
|
||||
async def main():
|
||||
async with bot:
|
||||
await bot.load_extension('cogs.general')
|
||||
await bot.load_extension('cogs.chance')
|
||||
await bot.load_extension('cogs.gifs')
|
||||
await bot.load_extension('cogs.copypasta')
|
||||
await bot.load_extension('cogs.sysinfo')
|
||||
await bot.load_extension('music.music')
|
||||
await bot.start(os.getenv('DISCORD_TOKEN'))
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
asyncio.run(main())
|
||||
import discord
|
||||
from discord.ext import commands
|
||||
from dotenv import load_dotenv
|
||||
import os
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
from tgbridge.forwarder import TelegramForwarder
|
||||
|
||||
load_dotenv('credentials.env')
|
||||
|
||||
intents = discord.Intents.default()
|
||||
intents.message_content = True
|
||||
|
||||
bot = commands.Bot(command_prefix='!', intents=intents)
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
print(f'✅ {bot.user} is online!')
|
||||
|
||||
async def main():
|
||||
# ── telegram forwarder ───────────────────────────────────────────────
|
||||
forwarder = TelegramForwarder(
|
||||
token=os.getenv('TELEGRAM_BOT_TOKEN'),
|
||||
group_id=int(os.getenv('TELEGRAM_GROUP_ID')),
|
||||
webhook_url=os.getenv('DISCORD_WEBHOOK_URL'),
|
||||
)
|
||||
|
||||
async with bot:
|
||||
await bot.load_extension('cogs.general')
|
||||
await bot.load_extension('cogs.chance')
|
||||
await bot.load_extension('cogs.gifs')
|
||||
await bot.load_extension('cogs.copypasta')
|
||||
await bot.load_extension('cogs.sysinfo')
|
||||
await bot.load_extension('music.music')
|
||||
|
||||
await forwarder.start()
|
||||
|
||||
try:
|
||||
await bot.start(os.getenv('DISCORD_TOKEN'))
|
||||
finally:
|
||||
await forwarder.stop()
|
||||
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
asyncio.run(main())␍
|
||||
@@ -0,0 +1 @@
|
||||
|
||||
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,179 @@
|
||||
# this is where telegram-to-discord forwarding lives
|
||||
import logging
|
||||
import io
|
||||
import json
|
||||
import aiohttp
|
||||
from telegram import Update
|
||||
from telegram.ext import Application, MessageHandler, filters, ContextTypes
|
||||
|
||||
logger = logging.getLogger('telegram.forwarder')
|
||||
|
||||
|
||||
class TelegramForwarder:
|
||||
"""Watches a Telegram group/channel and forwards messages to a Discord webhook."""
|
||||
|
||||
def __init__(self, token: str, group_id: int, webhook_url: str):
|
||||
self.token = token
|
||||
self.group_id = group_id
|
||||
self.webhook_url = webhook_url
|
||||
self._app: Application | None = None
|
||||
|
||||
async def start(self):
|
||||
"""Build the telegram application and start polling."""
|
||||
self._app = (
|
||||
Application.builder()
|
||||
.token(self.token)
|
||||
.build()
|
||||
)
|
||||
self._app.add_handler(MessageHandler(filters.ALL, self._on_message))
|
||||
|
||||
await self._app.initialize()
|
||||
await self._app.start()
|
||||
await self._app.updater.start_polling(drop_pending_updates=True)
|
||||
logger.info('✅ Telegram forwarder started — watching group %s', self.group_id)
|
||||
|
||||
async def stop(self):
|
||||
"""Graceful shutdown."""
|
||||
if self._app:
|
||||
await self._app.updater.stop()
|
||||
await self._app.stop()
|
||||
await self._app.shutdown()
|
||||
logger.info('Telegram forwarder stopped')
|
||||
|
||||
# ── internal helpers ─────────────────────────────────────────────────────
|
||||
|
||||
def _sender_name(self, update: Update) -> str:
|
||||
"""Extract a display name from the message sender."""
|
||||
user = update.effective_user
|
||||
if user:
|
||||
name = user.first_name or ''
|
||||
if user.last_name:
|
||||
name += f' {user.last_name}'
|
||||
return name.strip() or user.username or 'Unknown'
|
||||
# channel posts have no user — use chat title
|
||||
chat = update.effective_chat
|
||||
if chat and chat.title:
|
||||
return chat.title
|
||||
return 'Unknown'
|
||||
|
||||
def _forward_prefix(self, message) -> str:
|
||||
"""Build a prefix for forwarded messages."""
|
||||
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: str) -> tuple[bytes, str]:
|
||||
"""Download a file from Telegram, return (bytes, filename)."""
|
||||
tg_file = await self._app.bot.get_file(file_id)
|
||||
buf = io.BytesIO()
|
||||
await tg_file.download_to_memory(buf)
|
||||
buf.seek(0)
|
||||
filename = tg_file.file_path.split('/')[-1] if tg_file.file_path else 'file'
|
||||
return buf.read(), filename
|
||||
|
||||
async def _send_to_webhook(self, username: str, content: str = '',
|
||||
file_data: bytes | None = None,
|
||||
filename: str | None = None):
|
||||
"""POST a message (and optional file) to the Discord webhook."""
|
||||
async with aiohttp.ClientSession() as session:
|
||||
data = aiohttp.FormData()
|
||||
payload = {'username': username}
|
||||
if content:
|
||||
payload['content'] = content[:2000] # discord limit
|
||||
|
||||
if file_data and filename:
|
||||
data.add_field('payload_json', json.dumps(payload),
|
||||
content_type='application/json')
|
||||
data.add_field('file', file_data, filename=filename)
|
||||
else:
|
||||
# simple json post
|
||||
async with session.post(self.webhook_url, json=payload) as resp:
|
||||
if resp.status not in (200, 204):
|
||||
logger.error('Webhook POST failed: %s %s', resp.status, await resp.text())
|
||||
return
|
||||
|
||||
async with session.post(self.webhook_url, data=data) as resp:
|
||||
if resp.status not in (200, 204):
|
||||
logger.error('Webhook POST failed: %s %s', resp.status, await resp.text())
|
||||
|
||||
# ── main message handler ─────────────────────────────────────────────────
|
||||
|
||||
async def _on_message(self, update: Update, context: ContextTypes.DEFAULT_TYPE):
|
||||
"""Handle every incoming message from the watched group."""
|
||||
message = update.effective_message
|
||||
if not message:
|
||||
return
|
||||
|
||||
# only process messages from the configured group
|
||||
if update.effective_chat and update.effective_chat.id != self.group_id:
|
||||
return
|
||||
|
||||
sender = self._sender_name(update)
|
||||
prefix = self._forward_prefix(message)
|
||||
caption = message.caption or ''
|
||||
|
||||
try:
|
||||
# ── photo ────────────────────────────────────────────────────
|
||||
if message.photo:
|
||||
# largest resolution is last in the array
|
||||
photo = message.photo[-1]
|
||||
file_data, filename = await self._download_file(photo.file_id)
|
||||
text = f'{prefix}{caption}'.strip()
|
||||
await self._send_to_webhook(sender, text, file_data, filename)
|
||||
|
||||
# ── video ────────────────────────────────────────────────────
|
||||
elif message.video:
|
||||
file_data, filename = await self._download_file(message.video.file_id)
|
||||
text = f'{prefix}{caption}'.strip()
|
||||
await self._send_to_webhook(sender, text, file_data, filename)
|
||||
|
||||
# ── document (files, gifs sent as docs, etc) ─────────────────
|
||||
elif message.document:
|
||||
file_data, filename = await self._download_file(message.document.file_id)
|
||||
text = f'{prefix}{caption}'.strip()
|
||||
await self._send_to_webhook(sender, text, file_data, filename)
|
||||
|
||||
# ── animation (GIF) ──────────────────────────────────────────
|
||||
elif message.animation:
|
||||
file_data, filename = await self._download_file(message.animation.file_id)
|
||||
text = f'{prefix}{caption}'.strip()
|
||||
await self._send_to_webhook(sender, text, file_data, filename)
|
||||
|
||||
# ── sticker ──────────────────────────────────────────────────
|
||||
elif message.sticker:
|
||||
# stickers have no caption — send emoji or name
|
||||
sticker_text = message.sticker.emoji or '(sticker)'
|
||||
text = f'{prefix}{sticker_text}'
|
||||
# try to download the sticker image
|
||||
try:
|
||||
file_data, filename = await self._download_file(message.sticker.file_id)
|
||||
await self._send_to_webhook(sender, text, file_data, filename)
|
||||
except Exception:
|
||||
await self._send_to_webhook(sender, text)
|
||||
|
||||
# ── text ─────────────────────────────────────────────────────
|
||||
elif message.text:
|
||||
text = f'{prefix}{message.text}'
|
||||
await self._send_to_webhook(sender, text)
|
||||
|
||||
else:
|
||||
# voice, audio, contact, location, etc — just note it
|
||||
text = f'{prefix}*(unsupported message type)*'
|
||||
await self._send_to_webhook(sender, text)
|
||||
|
||||
except Exception:
|
||||
logger.exception('Failed to forward message from %s', sender)
|
||||
Reference in new issue
Block a user