Downloading in real-time 1000s of Telegram chats with Python Telethon? - python

I have set up a script to download messages from thousands of Telegram supergroups (named chatlist). This works fine when I use a few groups, however, allowing 1000< groups seems to break it and no messages are collected at all. Is this the correct way to approach it, or is there a more efficient way? I don't think it's a network issue my end as I have GB internet
from telethon import TelegramClient, events, sync
client = TelegramClient('test', api_id, api_hash)
#client.on(events.NewMessage(chats=chatlist))
async def my_event_handler(event):
message = event.message.to_dict()
print(message['message'])
await client.start()
await client.run_until_disconnected()

A simple workaround is to remove the NewMessage event filter i.e.:
#client.on(events.NewMessage())
and filter messages inside the method yourself:
async def my_event_handler(event):
message = event.message.to_dict()
input_chat = await event.get_input_chat()
if input_chat in chatlist:
print(message['message'])
You didn't mention what's inside chatlist, so I assumed it's a list of InputChat objects.
Note that if your chat list is a list of #username strings, you'll soon reach Telegram username resolve limits. Always have InputChat or long ids in the chatlist.

Related

Automatically sending gifs in discord

I added a bot to Discord and added various functions in Python, but just as some work without any problems, the automatic sending of gifs at a specific time and on a specific channel doesn't work. After configuring everything and starting the bot, nothing happens, no error is displayed, and the console is also silent. What could be wrong? Below I'm sharing a part of the code responsible for this function, thank you in advance for your response.
import asyncio
import datetime
import discord
from discord.ext import commands
client = commands.Bot(command_prefix='.', intents=discord.Intents.all())
async def send_gif(channel):
print(f"Sending gif to channel {channel}")
gif_id = "Here's the gif id"
gif_url = f"Here's url"
await channel.send(gif_url)
#client.event
async def on_ready():
channel_id = "Here's the text channel ID"
channel = client.get_channel(channel_id)
await client.wait_until_ready()
while not client.is_closed():
now = datetime.datetime.now()
if now.hour == sample hour and now.minute == sample minutes:
await send_gif(channel)
await asyncio.sleep(60)
I would like the bot to send specific gifs at a specified time and on a specified channel, but I'm out of ideas. I tried using chatGPT, but it was unable to help. I also checked several times to make sure I was entering the correct gif IDs, channel IDs, and URLs, but it didn't help.
Check that you didn't miss any symbols on on_ready event.
Maybe the error is in
...
if now.hour == sample_hour and now.minute == sample_minutes:
...
Also, check that you defined sample_hour and sample_minutes variables before referring them in the code.
If still doesn't work try using the ext.tasks.loop decorator, if you don't know how to, take a look to the documentation
If it's not the thing causing the error, comment in this message and I will try to edit this message as fast as possible for solving doubts.

Forward message (album) telethon

I'm trying to forward a message with a group of photos. But they are redirected by separate messages in destination channel. How can I redirect with one message?
#client.on(events.NewMessage(incoming=True, chats=FROM))
async def handler_frw(event):
await client.forward_messages(CHANNEL_TO, event.message)
If I use "send_message" it send as one message.
But TO_NAME - doesn't work with id Channel, only name :(
And i need "Forward from" in destination channel.
#client.on(events.Album(chats=FROM))
async def handler_snd(event):
print("Have album.")
caps = []
for item in event.messages:
caps.append(item.message)
await client.send_message(TO_NAME, file=event.messages, message=caps)
If you want to forward the whole album, utilize the corresponding Album event instead of the New Message event. Telethon supposedly does the necessary hacks to collect the single messages together.
See the docu
from telethon import events
#events.register(events.Album)
async def albumHandler(self, event):
# Forwarding the album as a whole to some chat
event.forward_to(chat)

Discord.py Bot Takes too Long to Respond

Goal:
I'm developing a discord bot which scans a url every 5 seconds or so, checks for a specified change on that webpage, and will send a message in the discord channel if that change occurs. I've done this by sending the url to the bot using an if statement in on_message. The url is then passed to a tasks.loop() function, where it is scanned and processed in another function for the change.
Problem:
I'd like to be able to send a message in the discord channel which quickly ends the process taking place in the tasks.loop(), so that I can pass it a different url to scan using the on_message function. In its current form, it works-- just very slowly. From the time the cancel trigger is sent, it takes around 3 minutes to send the verification message that the process has been cancelled. I need to make this 5 seconds or less. For what its worth, the bot is kept running using replit and uptime robot, but I am sure that the long response time is not related to the frequency the repl is awoken by uptime robot.
Code:
My code is much more complex and riddled with obscurely named variables, so here is a much simpler snippet of code with the same general structure.
client = discord.Client()
channel = client.get_channel(CHANNEL_ID)
#tasks.loop()
async def myloop(website, dataframe):
channel = client.get_channel(CHANNEL_ID)
try:
# iteratively scrape data from a website for
# a predefined change in the dataframe
if change = True:
await channel.send(notification)
except:
pass
#client.event
async def on_message(message):
channel = client.get_channel(CHANNEL_ID)
msg = message.content
if msg.startswith('track'):
website = msg[6:]
await channel.send('Now tracking '+str(website))
myloop(website,df)
if msg.starswith('stop'):
myloop.cancel()
await channel.send('Done tracking, awaiting orders.')
Attempted Solutions:
I have tried using some forms of threading, which I am very new to, and I haven't found a way to make it work any faster. Any suggestions or solutions would be greatly appreciated! I've been combing the web for help for quite some time now.
Looks like you could use client.loop.create_task to create asyncio task objects, and their cancel method to immediately cancel those asyncio tasks at the right time, e.g.
import asyncio
from replit import db
_task = None
async def myloop():
website = db['website']
dataframe = db['dataframe']
channel = client.get_channel(CHANNEL_ID)
while not client.is_closed():
await asyncio.sleep(5)
try:
# iteratively scrape data from a website for
# a predefined change in the dataframe
if change:
await channel.send(notification)
except:
pass
#client.event
async def on_message(message):
global _task # This gives the function access to the variable that was already created above.
msg = message.content
if msg.startswith('track'):
website = msg[6:]
await message.channel.send('Now tracking '+str(website))
db['website'] = website
db['dataframe'] = df
if _task is not None:
_task.cancel()
_task = client.loop.create_task(myloop())
if msg.startswith('stop'):
if _task is not None:
_task.cancel()
_task = None
await message.channel.send('Done tracking, awaiting orders.')
The argument create_task takes is a coroutine that takes no arguments, so the website URL and dataframe need to be accessible to the function a different way (I'm not sure which way you would prefer or would be best; using replit's db is just an example).
With this approach, you should be able to use track again to change which website is being monitored without using stop in between.
More details in the docs:
discord.Client.loop
loop.create_task
Task.cancel
asyncio.sleep
discord.Client.is_closed
Replit db

How to make discord.py bot send to the server it's called in?

had a question regarding discord.py.
I run two separate servers that my bot is on, my test server and my main server.
The problem is when I send a message in the test server, the bot then sends its message to the main server, and never sends it back to the server the command is being called in (only in functions).
For example:
if message.content == '!Hello':
await message.channel.send('Hello there!')
If I type the above in the test server, my bot will respond with "Hello there!" in the test server like it should. However, if I tried putting this code in a function and called it:
if message.content == "!Hello":
await hellomessage()
async def hellomessage():
channel = client.get_channel('Channel ID Here')
await channel.send('Hello there!')
The channel ID is obviously set to a specific server. So say I have ID '1234' as my main server and ID '1111' as my test server, regardless whether I call it in my test server or main server, that message is going to send to the main server because the ID is no different. My question is how do I make sure the "channel" attribute changes depending on the server it was called in. I want it so if I say !Hello in my test server, it doesn't send to the main server and only the test server.
Seems like a very trivial answer but I'm just struggling to find it, any help is appreciated!
You can check what guild a message has been sent from using the .guild attribute of a message.
Example:
# You can also have a separate coroutine for your main server
async def hello_test_message():
test_guild_channel = client.get_channel(ID_HERE)
await test_guild_channel.send("Hello there!")
#client.event
async def on_message(message):
if client.user == message.author:
return
if message.guild.id == TEST_GUILD_ID_HERE:
if message.content.lower() == "!hello": # .lower() for case insensitivity
await hello_test_message()
# Another condition for your main server + calling the other coroutine
This being said, I'm assuming that you're not hard-coding values in values for every possible channel etc. and if that is the case, and you just want the bot to respond in the original message's channel, you can use message.channel.send(....
The current method of doing things will result in quite a bit of duplicate code.
I'd also recommend having a look into the commands extension of discord instead of using on_message events for them.
References:
Bot example using command decorators
Message.guild
Guild.id
commands.command()
Message.channel
TextChannel.send()
Messageable.send() - Anything that inherits from abc.Messageable is able to have a message sent to it! So this includes stuff like users, text channels, context etc.
commands.Context - You'll use this a lot when dealing with commands!

How to get a message from Telegram groups by API?

I was looking for some way to listen and catch new messages provided by telegram groups.
I have not found libraries or API in order to do this in Python.
Someone having any suggestion?
Using Telethon
Replace channel_name with your telegram channel.
from telethon import TelegramClient, events, sync
# Remember to use your own values from my.telegram.org!
api_id = ...
api_hash = '...'
client = TelegramClient('anon', api_id, api_hash)
#client.on(events.NewMessage(chats='channel_name'))
async def my_event_handler(event):
print(event.raw_text)
client.start()
client.run_until_disconnected()
There are two ways to achieve your goal:
Method 1:
My suggested library for python: python-telegram-bot
Create a bot.
Add the bot to the desired group as administrator.
Listen to messages as you normally listen in bots.
Method 2:
My suggested library for python: Telethon
Join the desired group as a user (not a bot).
Create a simple client that listens to new messages.

Categories