Send a message to a specific ID python-telegram-bot - python

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

Related

Download public group messages without authentiction - Telethon

Is there, at the moment, any way to download messages from a public group without phone authentication? While using a bot, I get an error:
Exception has occurred: BotMethodInvalidError
The API access for bot users is restricted. The method you tried to invoke cannot be executed as a bot (caused by GetHistoryRequest)
I'd like to automate the process of getting new messages so I'm not able to authenticate every time the session starts.
Is there any workaround?
The bot can't get the message history. Bots work with updates. You can receive new messages while your bot is connected
#bot.on(events.NewMessage)
async def echo_all(event):
print(event.message)

Start telegram bot in separate thread

I have a process that should accept requests from two different sources. It is not important, what those requests are, consider them simple string messages for instance. Requests can come from two sources and are filed into a PriorityQueue. The main process handles the requests in the queue. One of the two sources for requests is a telegram bot created by the python-telegram-bot package. Every source needs to run its "event" loop to provide requests. Thus, I want to launch them in separate threads.
The (pseudo) code to show the intention would read as follows:
queue = PriorityQueue()
handler = RequestHandler(queue)
telegramRequester = TelegramRequester(queue)
anotherRequester = SomeOtherSourceRequester(queue)
telegramRequester.start() # launches telegram bot polling/idle loop
anotherRequester.start() # launches the loop of another request source
handler.handleRequestsLoop() # launches the loop that handles incoming requests
The telegram bot and corresponding requester look something like this:
class Bot:
def __init__(self):
self._updater = telegram.ext.Updater("my api token", use_context=True)
def run(self):
self._updater.start_polling(drop_pending_updates=True)
self._updater.idle()
def otherFunctions(self):
# like registering commands, command handlers etc.
# I've got my bot working and tested as I want it.
class TelegramRequester:
def __init__(self, queue:RequestQueue) -> None:
self._queue:RequestQueue = requestQueue
self._bot = Bot()
self._thread:threading.Thread = threading.Thread(target=self._bot.run)
def start(self):
if not self._thread.is_alive():
self._thread.start()
However, when running this, I receive the following error messsage:
File "...\myscript.py", line 83, in run
self._updater.idle()
File "...\env\lib\site-packages\telegram\ext\updater.py", line 885, in idle
signal(sig, self._signal_handler)
File "C:\Program Files\aaaProgrammieren\Python\Python3_9\lib\signal.py", line 47, in signal
handler = _signal.signal(_enum_to_int(signalnum), _enum_to_int(handler))
ValueError: signal only works in main thread of the main interpreter
It's the first time I use the telegram api and that I have multiple threads running in parallel. Plus, I have no experience in "web/network/etc." programming. Is it that simple? You shall not run a telegram bot in a separate thread! Or is there something very simple I am missing that would make a construct like mine possible?
Just some general hints on the usage of PTB (v13.x) in this context:
Updater.idle() is intended to keep the main thread alive - nothing else. This is because Updater.start_polling starts a few background threads that do the actual work but don't prevent the main thread from ending. If you have multiple things going on in the main thread, you'll probably have a custom "keep alive" and "shutdown" logic, so you'll likely not need Updater.idle() at all. Instead, you can just call Updater.stop() when you want it to shut down.
Updater.idle() allows you to customize which signals it sets. You can pass an empty list to not set any signal handlers.
Dislaimer: I'm currently the maintainer of PTB

How to download a file that was sent to my bot?

#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")

Basic Python Echo Bot not addressable through Bot Framework Emulator (Post 500) after linking to Azure QnA Maker

I have followed the official instructions (Docs) on how to create a python echo bot with the SDKv4 and as soon as the described init function is added my bot ceases to work. The echo bot by itself, without the connection to the Azure QnA Maker works perfectly within the Bot Framework Emulator.
The Error displayed in the Bot Framework Emulator:
POST500directline/conversations//activities. Also it doesn't display the welcoming message any more and when I send a message the bot apparently does not receive that message (send failed, retry is displayed below the message).
This problem occurs after adding the init function from the guide to the MyBot class from the bot.py file. The function reads:
def __init__(self, config: Config):
self.qna_maker = QnAMaker(
QnAMakerEndpoint(
knowledge_base_id=config["QNA_KNOWLEDGEBASE_ID"],
endpoint_key=config["QNA_ENDPOINT_KEY"],
host=config["QNA_ENDPOINT_HOST"],
)
)
When following the guide, I had to move the bot instance creation to the very bottom of the app.py file, below:
APP = web.Application(middlewares=[aiohttp_error_middleware])
APP.router.add_post("/api/messages", messages)
if __name__ == "__main__": (...)`
as the code otherwise doesn't run: BOT = MyBot(APP.config) causes: NameError: name 'APP' is not defined
Also I get the problem in the app.py file: No name 'DefaultConfig' in module 'config' - even though config.py exists and is obviously used as the ports change when I change them in the config file.
Aside from that I followed the guide precisely. I would be very thankful for any help or resource recommendations, over the past two days I've tried to everything I could find online. Thank you!

How to avoid slack command timeout error?

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`

Categories

Resources