diff --git a/.gitignore b/.gitignore index b6e4761..55c4656 100644 --- a/.gitignore +++ b/.gitignore @@ -127,3 +127,6 @@ dmypy.json # Pyre type checker .pyre/ +*.session +*.session-journal +config.json5 \ No newline at end of file diff --git a/README.md b/README.md index 79c1b01..a34a019 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,34 @@ -# Telegram-To-Discord-Bot -Telegram-To-Discord-Bot +# Telegram-To-Discord +Forward message from the specified Telegram channel to Discord Webhooks with all the media + +### Requirements +1. Python 3.6+ +2. Telegram APPID and HASH (can be created from here https://core.telegram.org/api/obtaining_api_id) +3. Have a Telegram account with valid phone number +4. Discord webhooks for the channels you to forward to + +### Installing and Setup + +1. Clone this repository `git clone https://github.com/abdellahaski/Telegram-To-Discord-Bot.git`. +2. Open your choice of console (or Anaconda console) and navigate to cloned folder `cd Telegram-To-Discord-Bot.git`. +3. Run Command: `pip3 install -r requirements.txt`. +4. Rename `example.env` and `example.config.json5` to `.env` and `config.json5` respectively +5. Fill out the .env and config.json5 files +6. Run the bot by this command: `python3 main.py` + +#### Filling `.env` file +* Add your Telegram `api_id` and `api_hash` to the `.env` file | Read more [here](https://core.telegram.org/api/obtaining_api_id) +* Specify an existing temporary directory to the `DLLOC` variable (e.g.: C:/tmp or /tmp) +* Specify the text to append to each of the forwarded messages in the `TEXT_TO_PREPEND` variable (it can be a ping (`@everyone`, `@here`, or `@role`) and it can be a text or emojies like `:point_right:`) + + +#### Filling `config.json5` file +The ``config.json5` file contains a JSON array (list) of Telegram channel you want to forward messages from all along with specific configuration for each TG channel: + +* `TGchannelID` : Telegram ID of the channel that you want to forward from (you can get it by forwarding any message from that channel to [@jsondumpbot](https://t.me/jsondumpbot)) don't forget to remove the first part (-100) from the ID +* `senderAvatarUrl` : The avatar URL that will be shown on Discord sender profile (if it's empty it will be pulled automatically from the Telegram channel profile picture) +* `senderName` : The sender name that will be shown on Discord sender profile (if it's empty it will default to the Telegram channel name) +* `DiscordWebhooks` : Contains a list of the Discord webhooks that you want messages to be forwarded to +``` +You can have as many TG channels as you want forwarding message to as many as Discord webhooks you want +``` diff --git a/example.config.json5 b/example.config.json5 new file mode 100644 index 0000000..54907e5 --- /dev/null +++ b/example.config.json5 @@ -0,0 +1,21 @@ +[ + { + "TGchannelID":"1594095970", //Telegram channel 1 + "senderAvatarUrl":"https://i.imgur.com/j8ixDXF.jpg", // Sender Avatar that will be visible on Discord (if it's not provided it will be pulled automatically from Telegram) + "senderName":"", // Sender name that will be visible on Discord (if not provided it will defautl to the Telegram channel name) + "discordWebhooks": + [// Discord webhooks to forward to from this TG channel + "https://discord.com/api/webhooks/123", + "https://discord.com/api/webhooks/456" + ] + }, + { + "TGchannelID":"1786421862", //telegram channel 2 + "senderAvatarUrl":"https://i.imgur.com/8EpbciV.png",// Sender Avatar that will be visible on Discord (if it's not provided it will be pulled automatically from Telegram) + "senderName":"",// Sender name that will be visible on Discord (if not provided it will defautl to the Telegram channel name) + "discordWebhooks": + [ // Discord webhooks to forward to from this TG channel + "https://discord.com/api/webhooks/678" + ] + } +] \ No newline at end of file diff --git a/example.env b/example.env new file mode 100644 index 0000000..8f66be0 --- /dev/null +++ b/example.env @@ -0,0 +1,7 @@ +#APPID AND HASH https://core.telegram.org/api/obtaining_api_id +APPID = "123" +APIHASH = "bcdddexample123123" # +APINAME = "Telegram To Discord" # Any string will do +DLLOC = 'C:\tmp' # Temp storage for media, if able use a location in memory (unix) + +TEXT_TO_PREPEND="@here \n" #Text to prepend to every message, it can be a ping (@everyone, @here, or @role) and it can be a text or emojies like ":point_right:" diff --git a/main.py b/main.py new file mode 100644 index 0000000..4f1c162 --- /dev/null +++ b/main.py @@ -0,0 +1,173 @@ +from telethon import TelegramClient, events +import aiohttp +import nextcord +import textwrap +import os +import requests +import json +import json5 +import random +import validators +from dotenv import load_dotenv + +load_dotenv() + +path = os.path.join(os.path.dirname(__file__), 'config.json5') +with open(path) as fp: + config = json5.load(fp) + +#print(json5.dumps(config)) +appid = os.environ.get("APPID") +apihash = os.environ.get("APIHASH") +apiname = os.environ.get("APINAME") +dlloc = os.environ.get("DLLOC") +text_to_prepend=os.environ.get("TEXT_TO_PREPEND") + +channels_avatars={} + +#if input_channels_entities is not None: +# input_channels_entities = list(map(int, input_channels_entities.split(','))) +input_channels_entities=[] +for channel in config: + input_channels_entities.append(int(channel["TGchannelID"])) + + + +async def imgurimg(mediafile): # Uploads image to imgur + url = "https://api.imgur.com/3/upload" + + payload = { + 'type': 'file'} + files = [ + ('image', open(mediafile, 'rb')) + ] + headers = { + 'Authorization': str(random.randint(1,10000000000)) + } + response = requests.request("POST", url, headers=headers, data = payload, files = files) + return(json.loads(response.text)) + +async def imgur(mediafile): # Uploads video to imgur + url = "https://api.imgur.com/3/upload" + + payload = {'album': 'ALBUMID', + 'type': 'file', + 'disable_audio': '0'} + files = [ + ('video', open(mediafile,'rb')) + ] + headers = { + 'Authorization': str(random.randint(1,10000000000)) + } + response = requests.request("POST", url, headers=headers, data = payload, files = files) + return(json.loads(response.text)) + +def start(): + client = TelegramClient(apiname, + appid, + apihash) + client.start() + print('Started') + print('Listenning to the following Telegram Channels: '+str(input_channels_entities)) + + @client.on(events.NewMessage(chats=input_channels_entities)) + async def handler(event): + channelID=str(event.chat.id) + for channel in config: + if str(channel["TGchannelID"]) == channelID: + senderAvatarUrl=channel["senderAvatarUrl"] + senderName=channel["senderName"] + DiscordWebhooks=channel["discordWebhooks"] + + if DiscordWebhooks is None: + print('channel (id:'+channelID+') not configured correctly, please check the config.json5 file') + return + + if not senderName or senderName is None or senderName=='': + senderName=event.chat.title + + if(senderAvatarUrl is not None and validators.url(senderAvatarUrl)): + channels_avatars[channelID]=senderAvatarUrl + channelAvatarUrl=senderAvatarUrl + else: + #Checking if the channel avatar is already in the avatar list + if(channelID in channels_avatars): + channelAvatarUrl=channels_avatars[channelID] + else: # if not we download it and upload it to imgur (since discord accept only avartar urls) + channel = await client.get_entity(event.chat.id) + channelAvatar = await client.download_profile_photo(channel,dlloc, download_big=False) + channelAvatarUrl=await imgurimg(channelAvatar) + os.remove(channelAvatar) + channelAvatarUrl = channelAvatarUrl['data']['link'] + channels_avatars[channelID]=channelAvatarUrl # we store it on the channels avatars array so we can use it another time without reuploading to imgur + + msg = event.message.message + #Looking for href urls in the text message and appending them to the message + try: + for entity in event.message.entities: + if ('MessageEntityTextUrl' in type(entity).__name__): + msg +=f"\n\n{entity.url}" + except: + print("no url captured, forwording message") + + if event.message.media is not None: + + if('MessageMediaWebPage' in type(event.message.media).__name__):# directly send message if the media attached is a webpage embed + for webhookUrl in DiscordWebhooks: + await send_to_webhook(msg,senderName,channelAvatarUrl,webhookUrl) + else: + dur = event.message.file.duration # Get duration + if dur is None: + dur=1 # Set duration to 1 if media has no duration ex. photo + # If duration is greater than 60 seconds or file size is greater than 200MB + if dur>60 or event.message.file.size > 209715201: # Duration greater than 60s send link to media + print('Media too long!') + msg +=f"\n\nLink to Video: https://t.me/c/{event.chat.id}/{event.message.id}" + for webhookUrl in DiscordWebhooks: + await send_to_webhook(msg,senderName,channelAvatarUrl,webhookUrl) + return + else: # Duration less than 60s send media + path = await event.message.download_media(dlloc) + for webhookUrl in DiscordWebhooks: + await pic(path,msg,senderName,channelAvatarUrl,webhookUrl) + + os.remove(path) + else: # No media text message + for webhookUrl in DiscordWebhooks: + await send_to_webhook(msg,senderName,channelAvatarUrl,webhookUrl) + + client.run_until_disconnected() + +async def pic(filem,message,username,channelAvatarUrl,webhookUrl): # Send media to webhook + if(text_to_prepend is not None): + message=text_to_prepend+message + async with aiohttp.ClientSession() as session: + print('Sending w media') + webhook = nextcord.Webhook.from_url(webhookUrl, session=session) + try: # Try sending to discord + f = nextcord.File(filem) + await webhook.send(file=f,username=username,avatar_url=channelAvatarUrl) + except: # If it fails upload to imgur + print('File too big, uploading to imgur') + try: + image = await imgur(filem) # Upload to imgur + #print(image) + image = image['data']['link'] + print(f'Imgur: {image}') + await webhook.send(content=image,username=username,avatar_url=channelAvatarUrl) # Send imgur link to discord + except Exception as ee: + print(f'Error {ee.args}') + for line in textwrap.wrap(message, 2000, replace_whitespace=False): # Send message to discord + await webhook.send(content=line,username=username,avatar_url=channelAvatarUrl) + +async def send_to_webhook(message,username,channelAvatarUrl,webhookUrl): # Send message to webhook + if(text_to_prepend is not None): + message=text_to_prepend+message + async with aiohttp.ClientSession() as session: + print('Sending w/o media') + webhook = nextcord.Webhook.from_url(webhookUrl, session=session) + for line in textwrap.wrap(message, 2000, replace_whitespace=False): # Send message to discord + await webhook.send(content=line,username=username,avatar_url=channelAvatarUrl) + +if __name__ == "__main__": + start() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b714ba4 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,8 @@ +aiohttp==3.8.1 +json5==0.9.10 +nextcord==2.1.0 +nextcord_ext_menus==1.5.4 +python-dotenv==0.20.0 +requests==2.28.1 +Telethon==1.24.0 +validators==0.20.0