#dp.message_handler(content_types=types.ContentType.DOCUMENT)
async def scan_message(file: types.File):
print("downloading document")
file_path = file.file_path
destination = r"C:\users\aleks\PycharmProjects\pythonProject\file.pdf"
destination_file = bot.download_file(file_path, destination)
print("success")
I would like to be able to download a file (in this case it is pdf) that was sent to my bot by the user. But the problem is that the bot doesn't even recognize a file (when I send a file, it doesn't even print "downloading document")
TL;DR
I hope you're using aiogram v2.x.
Make sure that's the only handler for types.ContentType.DOCUMENT without filters and your bot can get needed updates, then:
#dp.message_handler(content_types=types.ContentType.DOCUMENT)
async def scan_message(message: types.Message):
print("downloading document")
destination = r"C:\users\aleks\PycharmProjects\pythonProject\file.pdf"
await message.document.download(destination)
print("success")
Detailed
Everything described below applies to stable aiogram v2.x.
aiogram.Dispatcher class parses raw telegram Update and sends it parsed and unpacked to corresponding handler, e.g. dp.message_handler will receive aiogram.types.Message for updates containing message, dp.callback_query_handler will receive aiogram.types.CallbackQuery for updates containing callback_query and so on. In your case you are expecting aiogram.types.File, which is wrong. Then dispatcher checks filters and calls corresponding handlers in registration order and stops dispatching if any handler was called.
Consider the following example:
# Some code omitted in favor of brievity
#dp.message_handler()
async def handle1(msg: types.Message):
print("handled 1")
#dp.message_handler()
async def handle2(msg: types.Message):
print("handled 2")
You send any text message then look at bot's console. There will only "handled 1" be printed because it was the first matching handler and only one handler is called.
Bots have so-called "Privacy mode" in group chats, so not every message comes to your bot in a group. This is not the case in private (direct) messages, so it's better to test your bot in private. You can read more about Privacy mode in official Bot API docs: https://core.telegram.org/bots#privacy-mode.
It's better and more readable to use shortcuts which are contained in each class, e.g. you can .reply() or .answer() to a aiogram.types.Message, which is a shortcut for aiogram.Bot.send_message(). Same with downloading file, you can just use .download() on aiogram.types.Document, aiogram.types.PhotoSize and so on. You can find more downloadable types by looking for classes that implement aiogram.types.mixins.Downloadable.
All .download() methods return destination where the file was saved. If you pass your own optional destination as the first argument to the method, it'll be useless to get it back as you already know it.
So, after these modifications, you'll get the code as in TL;DR section.
You did not specify version of aiogram. I guess it is 2.x.
All asynchronous function registered, as message handler must take the first positional parameter received message. If you like type hints you must to specify your function as
async def scan_message(message: types.Message):
for download your document you need just:
destination_file = await message.document.download(destination)
so correct handler for download document is:
async def scan_message(message: types.Message):
print("downloading document")
destination = r"C:\users\aleks\PycharmProjects\pythonProject\file.pdf"
destination_file = await message.document.download(destination)
print("success")
Related
I would like to send a message to the mantainer of the bot (specific ID) whenever it initializes. All the script was made with handlers, and I can't make it work with both methods of sending messages:
def main() -> None:
"""Start the bot."""
# Create the Application and pass it your bot's token.
application = Application.builder().token("TOKEN").build()
application.bot.send_message("SOME ID","Hello! I am working.") # Problematic line
# Handlers
application.add_handler(CommandHandler("command", command_function))
...
# on non command i.e message - echo the message on Telegram
application.add_handler(MessageHandler(filters.TEXT & ~filters.COMMAND, default_command))
# Run the bot until the user presses Ctrl-C
application.run_polling()
If I try to run the previous script, I get the following warning:
RuntimeWarning: coroutine 'ExtBot.send_message' was never awaited
application.bot.send_message("SOME ID","Hello! I am working.") # Problematic line
I can guess that the problem is between both methods of sending messages. But is there any way I can use both at the same time? (Or at least once, only when the bot initializes)
To run asyncio-code on startup, the Application(Builder) class provides the post_init hook. The documentation also includes an example of how to use this hook to call bot methods on startup.
Disclaimer: I'm currentyl the maintainer of python-telegram-bot
Im coding an bot which searches and sends files search command is already infuction but send command won't work here is my code appreciate any help
#bot.command()
async def search(ctx, *, file_name):
search_result = ''
from os import listdir
for file in listdir('ArkFiles/Dinos'):
if file_name in file.lower():
search_result = search_result+'\n'+file
if search_result == 'Search Results:':
await ctx.reply('Error: Not matched with any file.')
else:
embed = discord.Embed(title=f"Here is a list of files I found in the current Ark files that include '{file_name}':")
embed.add_field(name='** **', value= search_result, inline=False)
await ctx.reply(embed=embed)
#bot.command(pass_context=True)
async def sendachatina(ctx):
area=ctx.message.channel
await bot.send_file(area, "ArkFiles/Dinos/Achatina.zip", filename="Achatina.zip",content="Here you go:")
I don't want it that i have to do a command for every single file i want it that i have to input correctly and it automatically outputs the correct file.
I am not sure how to send the file by your style of coding but follow the below code it works for sure
await ctx.send(file=discord.File("ArkFiles/Dinos/Achatina.zip"),content="Here you go:")
This is the syntax of the send in discord.py. There is no option to change file there
await ctx.send(content=None, *, wait=False, username=None, avatar_url=None, tts=False, file=None, files=None, embed=None, embeds=None, allowed_mentions=None)
where
content (str) – The content of the message to send.
wait (bool) – Whether the server should wait before sending a response. This essentially means that the return type of this function changes from None to a WebhookMessage if set to True.
username (str) – The username to send with this message. If no username is provided then the default username for the webhook is used.
avatar_url (Union[str, Asset]) – The avatar URL to send with this message. If no avatar URL is provided then the default avatar for the webhook is used.
tts (bool) – Indicates if the message should be sent using text-to-speech.
file (File) – The file to upload. This cannot be mixed with files parameter.
files (List[File]) – A list of files to send with the content. This cannot be mixed with the file parameter.
embed (Embed) – The rich embed for the content to send. This cannot be mixed with embeds parameter.
embeds (List[Embed]) – A list of embeds to send with the content. Maximum of 10. This cannot be mixed with the embed parameter.
allowed_mentions (AllowedMentions) – Controls the mentions being processed in this message.
This is the documentation of discord.py if you want you can refer it
here
So today, all of a sudden, my Discord Selfbot stopped working. It has been running for weeks without any issue. All it does is monitoring the bot alerts from other channels and notify me if certain conditions are met.
Basically the problem is that when I print(message.content) I get empty string, and when I print(message.embeds) I get an empty list. This happens for any message that isn't sent by myself. Basically I can pull any message from any channel, but if it's not sent by me, I will see it empty. I can still print(message) and see its ID, author, etc., but can't retrieve the content/embeds.
I thought it was some sort of soft-ban from the Discord API (account didn't receive any warning and works normally), but then tryed to make a new account and got the same issue. I'm so confused and can't find out what's the cause of the problem... Unless they changed the API for everyone.
I've been playing with this for the past few weeks, and using tips and ideas all over the internet, I created a patched version of Discord.py for self-bots.
It seems that you need to do couple of things to get message.content & message.embeds working again:
Disable all Intents
Edit the IDENTIFY packet sent to Discord
My fork does all that, as well as obfuscate the user-agent and a few other things.
Check the README for things changed + credits.
Link: GitHub, PyPi
The answer to everyone's question:
On April 30th, 2021 discord made some change that broke receiving message content and embeds (and maybe more) on selfbots only. If you have this issue, then you're using a selfbot, which is against the discord TOS... it is also deprecated by discord.py since version 1.7 and won't receive support. You need to change to a real bot if you want support
Turns out actually it doesn't work for me too, I thought it worked
because I didn't test it before, and only used history or myself as the user.
A workaround is to use channel.history instead which returns messages that
support .content and similar.
With possibly something similar to this
#bot.event
async def on_message(message):
async for message in message.channel.history(limit=1):
print(message.content)
I also tried (badly) making discord.py always get it from history, and this seems to work for me. (For v1.x)
From cbbd51bf17bb19ea4835365e08266d06a27c1459 Mon Sep 17 00:00:00 2001
From: Example User <user#example.com>
Date: Fri, 7 May 2021 13:37:50 +0300
Subject: [PATCH] add workaround for on_message on self bots
Recently, now the messages that self bots get in events are broken.
In the case of on_message, .content and .embeds don't work.
However, this isn't the case if the message is retrieved with
channel.history()
This workarounds the issue for on_message by retrieving it from history
always
NOTE: I made the parse call async, didnt seem to break anything (yet)
---
discord/gateway.py | 6 +++++-
discord/state.py | 9 ++++++++-
2 files changed, 13 insertions(+), 2 deletions(-)
diff --git a/discord/gateway.py b/discord/gateway.py
index 210a8822..019693db 100644
--- a/discord/gateway.py
+++ b/discord/gateway.py
## -30,6 +30,7 ## import concurrent.futures
import json
import logging
import struct
+import inspect
import sys
import time
import threading
## -506,7 +507,10 ## class DiscordWebSocket:
except KeyError:
log.debug('Unknown event %s.', event)
else:
- func(data)
+ if inspect.iscoroutinefunction(func):
+ await func(data)
+ else:
+ func(data)
# remove the dispatched listeners
removed = []
diff --git a/discord/state.py b/discord/state.py
index da1212c1..7c713e39 100644
--- a/discord/state.py
+++ b/discord/state.py
## -485,9 +485,16 ## class ConnectionState:
def parse_resumed(self, data):
self.dispatch('resumed')
- def parse_message_create(self, data):
+ async def parse_message_create(self, data):
channel, _ = self._get_guild_channel(data)
message = Message(channel=channel, data=data, state=self)
+ # This is a workaround for messages not working on self bots
+ async for temp_msg in message.channel.history(limit=1):
+ if temp_msg.id != message.id:
+ log.warning("retrieved on_message from history is not correct one")
+ break
+
+ message = temp_msg
self.dispatch('message', message)
if self._messages is not None:
self._messages.append(message)
--
2.30.2
EDIT: see someone elses answer now instead
Edit the gateway.py source from the discord library and find the line:
if state._intents is not None:
payload['d']['intents'] = state._intents.value
and change it to:
if state._intents is not None and self._connection.is_bot == True:
payload['d']['intents'] = state._intents.value
Make sure to include "bot=False" option when calling the client.run. Having an intents option when sending the IDENTIFY payload causes user accounts to not receive message content, no matter what you set the intent value to. I hate doing this, but editing the library source to remove including the intents for non bot users is the only way I can find so you can use discord.py with a user account to view message content. The intents option is however required to be used for bot accounts.
I already tried search this in Google. But I couldn't find the answer. I want to bot send user's location. Here is the final version(It doesn't work):
markup_request = ReplyKeyboardMarkup(resize_keyboard=True).add(
KeyboardButton('Send your contact ☎️', request_contact=True)
).add(
KeyboardButton('Send your location 🗺️', request_location=True)
)
#dp.message_handler(commands=['location'])
async def message_for_start(message: types.Message):
await message.reply('Location', reply_markup=markup_request)
Firstly, check your imports.
Secondly, use other methods, instead of try <send_message> or . Reason is updating of aiogram library.
I am working with slack command (python code is running behind this), it works fine, but this gives error
This slash command experienced a problem: 'Timeout was reached' (error detail provided only to team owning command).
How to avoid this ?
According to the Slack slash command documentation, you need to respond within 3000ms (three seconds). If your command takes longer then you get the Timeout was reached error. Your code obviously won't stop running, but the user won't get any response to their command.
Three seconds is fine for a quick thing where your command has instant access to data, but might not be long enough if you're calling out to external APIs or doing something complicated. If you do need to take longer, then see the Delayed responses and multiple responses section of the documentation:
Validate the request is okay.
Return a 200 response immediately, maybe something along the lines of {'text': 'ok, got that'}
Go and perform the actual action you want to do.
In the original request, you get passed a unique response_url parameter. Make a POST request to that URL with your follow-up message:
Content-type needs to be application/json
With the body as a JSON-encoded message: {'text': 'all done :)'}
you can return ephemeral or in-channel responses, and add attachments the same as the immediate approach
According to the docs, "you can respond to a user commands up to 5 times within 30 minutes of the user's invocation".
After dealing with this issue myself and having my Flask app hosted on Heroku I found that the simplest solution was to use threading. I followed the example from here:
https://blog.miguelgrinberg.com/post/the-flask-mega-tutorial-part-xi-email-support
from threading import Thread
def backgroundworker(somedata,response_url):
# your task
payload = {"text":"your task is complete",
"username": "bot"}
requests.post(response_url,data=json.dumps(payload))
#app.route('/appmethodaddress',methods=['POST','GET'])
def receptionist():
response_url = request.form.get("response_url")
somedata = {}
thr = Thread(target=backgroundworker, args=[somedata,response_url])
thr.start()
return jsonify(message= "working on your request")
All the slow heavy work is performed by the backgroundworker() function. My slack command points to https://myappaddress.com/appmethodaddress where the receptionist() function takes the response_url of the received Slack message and passes it alongside any other optional data to the backgroundworker(). As the process is now split it simply returns the "working on your request" message to your Slack channel pretty much instantly and upon completion backgroundworker() sends the second message "your task is complete".
I too was facing this error frequently:
"Darn – that slash command didn't work (error message: Timeout was reached). Manage the command at slash-command"
I was writing a Slack slash-command "bot" on AWS Lambda that sometimes needed to perform slow operations (invoking other external APIs etc). The Lambda function would take greater than 3 seconds in some cases causing the Timeout was reached error from Slack.
I found #rcoup's excellent answer here and applied it in the context of AWS Lambda. The error doesn't appear any more.
I did this with two separate Lambda functions. One is a "dispatcher" or "receptionist" that greets the incoming Slack slash command with a "200 OK" and returns the simple "Ok, got that" type of message to the user. The other is the actual "worker" Lambda function that starts the long-ish operation asynchronously and posts the result of that operation to the Slack response_url later.
This is the dispatcher/receptionist Lambda function:
def lambda_handler(event, context):
req_body = event['body']
try:
retval = {}
# the param_map contains the 'response_url' that the worker will need to post back to later
param_map = _formparams_to_dict(req_body)
# command_list is a sequence of strings in the slash command such as "slashcommand weather pune"
command_list = param_map['text'].split('+')
# publish SNS message to delegate the actual work to worker lambda function
message = {
"param_map": param_map,
"command_list": command_list
}
sns_response = sns_client.publish(
TopicArn=MY_SNS_TOPIC_ARN,
Message=json.dumps({'default': json.dumps(message)}),
MessageStructure='json'
)
retval['text'] = "Ok, working on your slash command ..."
except Exception as e:
retval['text'] = '[ERROR] {}'.format(str(e))
return retval
def _formparams_to_dict(req_body):
""" Converts the incoming form_params from Slack into a dictionary. """
retval = {}
for val in req_body.split('&'):
k, v = val.split('=')
retval[k] = v
return retval
As you can see from the above, I didn't invoke the worker Lambda Function directly from the dispatcher (though this is possible). I chose to use AWS SNS to publish a message that the worker receives and processes.
Based on this StackOverflow answer, this is the better approach as it's non-blocking (asynchronous) and scalable. Also it was easier to use SNS to decouple the two functions in the context of AWS Lambda, direct invocation is trickier for this use-case.
Finally, here's how I consume the SNS event in my worker Lambda Function:
def lambda_handler(event, context):
message = json.loads(event['Records'][0]['Sns']['Message'])
param_map = message['param_map']
response_url = param_map['response_url']
command_list = message['command_list']
main_command = command_list[0].lower()
# process the command as you need to and finally post results to `response_url`