I am trying to deploy this telegram bot on Render, but the deploy always fails (although on the first minutes after I receive the build successful log, it works perfectly).
However, minutes later the deploy fails and I keep getting this error log:
telebot.apihelper.ApiTelegramException: A request to the Telegram API was unsuccessful. Error code: 409. Description: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
Here is the "bot.py" script I coded. Suppressed some non important parts of the code. Also, all "src." modules were created by me for better code organization. There are no telebot methods used on those modules.
import pandas as pd
from datetime import datetime, date
import src.visualization.visualize as vz
import src.auxiliary.auxiliary as aux
import src.data.wrangling as w
import src.data.database as db
import os
import telebot
API = os.getenv("API_KEY")
bot = telebot.TeleBot(API)
#bot.message_handler(commands=["IBOVESPA", "SP500", "NASDAQ"])
def responder(mensagem):
# generic code and calculations suppressed
if condition:
# telebot method used
bot.send_photo(
mensagem.chat.id,
photo = photo
)
else:
# more generic code suppressed
# telebot method used
bot.send_message(
mensagem.chat.id,
aux.create_answer(assets)
)
# telebot method used
bot.send_photo(
mensagem.chat.id,
photo = photo
)
def verificar(mensagem):
return True
def calculating_msg(mensagem, date):
# telebot method used
bot.send_message(
mensagem.chat.id,
“generic answer”
)
#bot.message_handler(func=verificar)
def responder(mensagem):
text= (
“standard reply to any message”
)
# telebot method used
bot.reply_to(mensagem, text)
# telebot method used
bot.polling()
Already revoked an old api_key on telegram and used a fresh-generated one and nothing changes.
Edit: works perfectly running on local. Only receive this warning:
\src\visualization\visualize.py:34: UserWarning: Starting a Matplotlib GUI outside of the main thread will likely fail.
fig = plt.figure(figsize=(15, 8))
WARNING: QApplication was not created in the main() thread.
Related
as the title states, I'm writing a Slack Bot in Python and using NGROK to host it locally. I'm not super experienced with decorators, and I can get the bot posting messages in slack, however I can't seem to handle two events at once. For example, I want to handle a message and have the message keep repeating in slack until a thumbs up reaction is added to that message. The issue is I cannot figure out how to handle an event while another event is still running, please see the following code:
rom slack import WebClient
import os
import time
from pathlib import Path
from dotenv import load_dotenv
from flask import Flask
from slackeventsapi import SlackEventAdapter
env_path = Path('.') / '.env'
load_dotenv(dotenv_path=env_path)
app = Flask(__name__)
slack_event_adapter = SlackEventAdapter(
os.environ['SIGNING_SECRET'],'/slack/events',app)
client = WebClient(token=os.environ['SLACK_TOKEN'])
BOT_ID = client.api_call("auth.test")['user_id']
state = {}
#slack_event_adapter.on('message')
def handle_message(event_data):
message = event_data.get('event', {})
channel_id = message.get('channel')
user_id = message.get('user')
text = message.get('text')
messageid = message.get('ts')
state[messageid] = {"channel_id": channel_id, "user_id": user_id, "text": text}
if BOT_ID != user_id:
if text[0:12] == ":red_circle:":
time.sleep(5)
client.chat_postMessage(channel=channel_id, text=text)
if text[0:21] == ":large_yellow_circle:":
client.chat_postMessage(channel=channel_id, text="it's a yellow question!")
if text[0:14] == ":white_circle:":
client.chat_postMessage(channel=channel_id, text="it's a white question!")
#slack_event_adapter.on('reaction_added')
def reaction_added(event_data):
reaction = event_data.get('event',{})
emoji = reaction.get('reaction')
emoji_id = reaction.get('item',{}).get('ts')
emoji_channel_id = reaction.get('item',{}).get('channel')
client.chat_postMessage(channel=emoji_channel_id, text=emoji)
for message_id, message_data in state.items():
channel_id = message_data["channel_id"]
text = message_data["text"]
client.chat_postMessage(channel=channel_id, text=text)
print(message_id,message_data)
if __name__ == "__main__":
app.run(debug=True)
I can handle individual events, but I cannot handle them while another is running. Please help! :)
Flask is a synchronous web framework.
When it's running a view handler, it uses up a web worker thread. If you does something like time.sleep(...), that worker thread will still be occupied and unavailable to handle other requests until the sleep finishes.
There are a couple options you can do here.
You can use Bolt for Python, which is a Python Slack library that natively support asynchronous even processing. Instead of time.sleep(), you can do await asyncio.sleep(...), which returns the thread to the async loop, and allow the worker thread to process other events.
If you already have an existing slack application and don't want to rewrite your entire codebase to Bolt, then you'll need to handle the event processing yourself. You can do this by doing your work in an ThreadLoopExecutor, or by building your own async event Queue mechanism, or use Celery. Or if your slack bot has very low volume, you can probably just add more web workers, and hope for the best that you don't run out of workers.
Hi I'm trying to make a telegram bot that edits its same message multiple times like BotFather does, but every time that I try it gives me this error:
telegram.error.BadRequest: Message is not modified: specified new message content and reply markup are exactly the same as a current content and reply markup of the message
Here is the code, I tried to make it as clear as possible.
from telegram import InlineKeyboardButton, InlineKeyboardMarkup
from telegram.ext import *
from API import API_KEY
from bot_messages import *
updater = Updater(API_KEY, use_context=True)
dispatcher = updater.dispatcher
def start(update, context):
update.message.reply_text(WELCOME, reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Ciao", callback_data="ciao")]]))
def ciao(update, context):
update.callback_query.edit_message_text("Ciao", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Dona!", callback_data="donate")]]))
def donate(update, context):
update.callback_query.edit_message_text("Dona", reply_markup=InlineKeyboardMarkup([[InlineKeyboardButton("Dona", callback_data="dona")]]))
dispatcher.add_handler(CommandHandler("start", start))
dispatcher.add_handler(CommandHandler("restart", start))
dispatcher.add_handler(CallbackQueryHandler(ciao))
dispatcher.add_handler(CallbackQueryHandler(donate))
updater.start_polling()
updater.idle()
Also can you tell me the proper way of making inline query handlers? Because if it is giving me error it could perhaps mean that I'm not doing it quite right. Thanks in advance.
In your snippet only the first CallbackQueryHandler will ever handle updates - please see the docs of Dispatcher.add_handler for details on how dispatcher decides which handler gets to handle an update.
That's why your code is trying to update the message with the unchanged text and you get that error.
To fix that, you can e.g. use the pattern argument of CallbackQueryhandler.
Disclaimer: I'm currently the maintainer of python-telegram-bot.
This is a follow up question to this post.
I have a data warehouse table exposed via xxx.com\data API endpoint
I have been querying this table using the following code and parsing it into a dataframe as follows;
import requests
import json
import http.client
import pandas as pd
url = "xxx.com\data?q=Active%20%3D1%20and%20LATITUDE%20%3D%20%20%220.000000%22%20and%20LONGITUDE%20%3D%20%220.000000%22&pageSize =300"
payload = {}
headers = {'Authorization': access_token}
response = requests.request("GET", url, headers=headers, data = payload)
j=json.loads(response.text.encode('utf8'))
df = pd.json_normalize(j['DataSet'])
The warehouse table gets periodically updated and I am required to create a webhook to be listened to by the following Azure httptrigger;
import logging
import os
import json
import pandas as pd
import azure.functions as func
def main(req: func.HttpRequest) -> func.HttpResponse:
logging.info('Python HTTP trigger function processed a request.')
d={
'Date' :['2016-10-30','2016-10-30','2016-11-01','2016-10-30'],
'Time':['09:58:11', '10:05:34', '10:07:57', '11:15:32'],
'Transaction':[2,3,1,1]
}
df=pd.DataFrame(d, columns=['Date','Time','Transaction'])
output = df.to_csv (index_label="idx", encoding = "utf-8")
return func.HttpResponse(output)
When run,the httptrigger successfully listens to the following webhooker sender which I have created and am running locally on my disk.
import logging
import os
import json
import pandas as pd
data={'Lat': '0.000000',
'Long': '0.000000',
'Status': '1', 'Channel URL':"xxx.com\data"}
webhook_url="http://localhost:7071/api/HttpTrigger1"
r=requests.post(webhook_url, headers={'Content-Type':'application/json'}, data =json.dumps(l))
My question is;
How can I deploy the webhook sender to the cloud as an app so that every time "xxx.com\data" is updated with Lat==0,Long=00 and Status=1, a message is send to my webhook listener?
The app can either be Azure/Flask/postman or any other python based webhook builder.
A simple approach can be to wrap your sender code into a Timer Trigger Function which would poll your xxx.com\data at every x seconds (or whatever frequency you decide) and call your webhook (another http triggered function) if there is any change.
{
"name": "mytimer",
"type": "timerTrigger",
"direction": "in",
"schedule": "0 */5 * * * *"
}
import datetime
import logging
import os
import json
import pandas as pd
import azure.functions as func
def main(mytimer: func.TimerRequest) -> None:
utc_timestamp = datetime.datetime.utcnow().replace(
tzinfo=datetime.timezone.utc).isoformat()
if mytimer.past_due:
logging.warn('The timer is past due!')
# "xxx.com\data" polling in real scenario
l={'Lat': '0.000000',
'Long': '0.000000',
'Status': '1',
'Channel URL':"xxx.com\data"}
webhook_url="{function app base url}/api/HttpTrigger1"
r=requests.post(webhook_url, headers={'Content-Type':'application/json'}, data =json.dumps(l))
logging.info('Python timer trigger function ran at %s', utc_timestamp)
At the end of the day, you can deploy both your webhook function (http trigger) and the sender (timer trigger polling) into a Function app.
You can also think of getting rid of the webhook function altogether (to save one intermediate hop) and do your stuffs into the same timer triggered function.
You currently have some polling logic (under querying this table using the following code). If you want to move that to "Cloud" then create a TimerTrigger function, and put all your poller code in it.
If you want to leave that poller code untouched, but want to call some code in "cloud" whenever poller detects a change (updated with Lat==0,Long=00 and Status=1), then you can create an HTTPTrigger function and invoke it from poller whenever it detects the change.
Confusing part is this: How do you detect this change today? Where is the poller code hosted and how often is it executed?
If data in DB is changing then only ways you can execute "some code" whenever the data changes is:
poll the DB periodically, say every 1 minute and if tehre is a change execute "some code" OR
some feature of this DB allows you to configure a REST API (HTTP Webhook) that is called by the DB whenever there is a change. Implement a REST API (e.g. as an HttpTrigger function) and put that "some code" that you want executed inside it. Now whenever there is a change the DB calls your webhook/REST-API and "some code" is executed.
and the way to read it is to call a REST API (xxx.com/data?q=...) then the only ways you can detect
I am currently trying to deploy a cloud function triggered by Pub/Sub written in Python. Previously, we used loguru to log. I am now making the switch to the cloud logging. I thought it would be rather simple but am quite puzzled. Here is the code I deployed in a Cloud Function, just to try logging :
import base64
import logging
import google.cloud.logging as google_logging
def hello_pubsub(event, context):
client = google_logging.Client()
client.setup_logging()
logging.debug("Starting function")
logging.info("Hello")
logging.warning("warning ! ")
pubsub_message = base64.b64decode(event['data']).decode('utf-8')
logging.info(pubsub_message)
logging.error("Exit function")
I followed the documentation I could find on the subject (but the pages can show various methods, and are not very clear). Here is the result on the Logging interface :
This is the result on the "Global" logs. Two questions here : why aren't the debug logs not shown, even if I explicitely set the log level as "debug" in the interface ? And why the logs are shown 1, 2 or 3 times, randomly ?
Now I try to display the logs for my Cloud Function only :
This is getting worse, now the logs are displayed up to 5 times (and not even the same number of times than in the "Global" tab), the levels of informations are all wrong (logging.info results in 1 info line, 1 error line ; error and warning results in 2 errors lines...)
I imagine I must be doing something bad, but I can't see what, as what I am trying to do is fairly simple. Can somebody please help me ? Thanks !
EDIT : I did the mistake of putting the initialization of the client in the function, this explains that the logs were displayed more than once. One problem left is that the warnings are displayed as errors in the "Cloud Function" tabs, and displayed correctly in the "Global" tab. Do someone has an idea about this ?
Try moving your setup outside of the function:
import base64
import logging
import google.cloud.logging as google_logging
client = google_logging.Client()
client.setup_logging()
def hello_pubsub(event, context):
logging.debug("Starting function")
logging.info("Hello")
logging.warning("warning ! ")
pubsub_message = base64.b64decode(event['data']).decode('utf-8')
logging.info(pubsub_message)
logging.error("Exit function")
As-is, you're adding new handlers on every request per instance.
You should use the Integration with Python logging module¶
import logging
import base64
import google.cloud.logging # Don't conflict with standard logging
from google.cloud.logging.handlers import CloudLoggingHandler
client = google.cloud.logging.Client()
handler = CloudLoggingHandler(client)
cloud_logger = logging.getLogger('cloudLogger')
cloud_logger.setLevel(logging.INFO) # defaults to WARN
cloud_logger.addHandler(handler)
def hello_pubsub(event, context):
import logging
cloud_logger.debug("Starting function")
cloud_logger.info("Hello")
cloud_logger.warning("warning ! ")
pubsub_message = base64.b64decode(event['data']).decode('utf-8')
cloud_logger.info(pubsub_message)
cloud_logger.error("Exit function")
return 'OK', 200
Alright guys, so here's my problem.
I am in the process of developing a Slack app with a packaged bot that allows the user to play a game in Slack. I have successfully built the bot and packaged it with the app as per the API guidelines. Once I discovered the Interactive Messages feature, I decided to implement said feature for a more user-friendly take on the game.
The interactive messages feature allows you to post messages with buttons, which the user can click to invoke an action. My bot script, let's call it bot.py, prompts the user (using the Slack chat.postMessage function) with a message containing some buttons from which to choose. This script has one class (I know it should be more modular but all in good time), which opens a web-socket for communication via the Slack RTM API. As such, when the script runs, it is always "listening" for a command from a user in a channel directed as follows: #botname command. The portion of the script that invokes this "always listening" state looks like this:
#bot.py
...
if slack_client.rtm_connect():
print("MYBOT v1.0 connected and running!")
while True:
command, channel, user = self.parse_slack_output(slack_client.rtm_read())
if command and channel:
if channel not in self.channel_ids_to_name.keys():
#this (most likely) means that this channel is a PM with the bot
self.handle_private_message(command, user)
else:
self.handle_command(command, channel, user)
time.sleep(READ_WEBSOCKET_DELAY)
else:
print("Connection failed. Invalid Slack token or bot ID?")
That's all good and fine. Now, let's say the user has used a command to successfully create a game instance and has started playing. At a certain point, the user is prompted for a trump suit like so:
#bot.py
...
attachments =[{
"title":"Please select index for trump suit:",
"fallback":"Your interface does not support interactive messages.",
"callback_id":"prompt_trump_suit",
"attachment_type":"default",
"actions":
[{"name":"diamonds","text":":diamonds:","type":"button","value":"0"},
{"name":"clubs","text":":clubs:","type":"button","value":"1"},
{"name":"hearts","text":":hearts:","type":"button","value":"2"},
{"name":"spades","text":":spades:","type":"button","value":"3"}]
}]
slack.chat.post_message(
channel=player_id,
as_user=True,
attachments=attachments
)
The interactive message looks like this. The action of clicking on one of the buttons in this message sends a payload via an HTTP POST to a web server. My other script in the project, which we will call app.py, is a Flask script which successfully receives this POST request when the user clicks one of the buttons. The portion of the script that receives the POST request looks like this:
#app.py
...
# handles interactive button responses for mybot
#app.route('/actions', methods=['POST'])
def inbound():
payload = request.form.get('payload')
data = json.loads(payload)
token = data['token']
if token == SLACK_VERIFICATION_TOKEN:
print 'TOKEN is good!'
response_url = data['response_url']
channel_info = data['channel']
channel_id = channel_info['id']
user_info = data['user']
user_id = user_info['id']
user_name = user_info['name']
actions = data['actions'][0]
value = actions['value']
print 'User sending message: ',user_name
print "Value received: ",value
return Response(), 200
When the button is clicked, I get the expected output:
TOKEN is good!
User sending message: my_username
Value received: 3
So everything is successful up to this point. Now, what I want to do is to take that POST information and use it to invoke a function in my bot.py script that handles the trump suit selection. The problem is that if I were to invoke that function, let's call it handle_trump_suit_selection(), I would first have to instantiate a Bot() object in the app.py file, which of course would not work as desired because the function would be called with a new Bot() instance, and therefore would not be in the same state as the current game.
So how the heck can I get the POST information back to the desired Bot() instance in bot.py for further processing? I'm new to OOP in Python and especially new to Flask and the Slack API, so go easy on me ;).
Thanks in advance.
Great Success!
tl;dr:
Basically, the solution was to create a Celery task that instantiated the bot instance from the Flask app using the Slack Events API. You set the task to start after the desired input has been entered, promptly return the required Response(200) back to Slack, while in the meantime the bot script (which starts up the RTM API web-socket) launches in parallel.
The nitty gritty:
So, as stated above, it turns out that what was required was a queuing service of some sort. I ended up going with Celery for its relative ease at integrating with Heroku (where I host the Slack app) and its easy-to-follow documentation.
Developing your Slack app this way requires setting up and using the Slack Events API to receive the command ("play my_game" in this example) from the Slack channel the message was posted in. The Flask app (app.py) portion of the program listens for this event, and when the input matches up with what you're looking for, it launches the Celery task in parallel (in tasks.py, which instantiates a Bot() instance of bot.py in this example). :) Now the bot can listen and respond using both the Slack RTM API and the Slack Events API. This allows you to build rich applications/services within the Slack framework.
If you are looking to set up something similar, below are my project layout and the important code details. Feel free to use them as a template.
Project Layout:
project_name_folder
app_folder
static_folder
templates_folder
__init__.py
my_app.py
bot.py
tasks.py
Procfile
requirements.txt
__init__.py:
from celery import Celery
app = Celery('tasks')
import os
app.conf.update(BROKER_URL=os.environ['RABBITMQ_BIGWIG_URL']) # Heroku Celery broker
my_app.py:
from flask import Flask, request, Response, render_template
import app
from app import tasks
app = Flask(__name__)
#app.route('/events', methods=['POST'])
def events():
"""
Handles the inbound event of a post to the main Slack channel
"""
data = json.loads(request.data)
try:
for k, v in data['event'].iteritems():
ts = data['event']['ts']
channel = data['event']['channel']
user_id = data['event']['user']
team_id = data['team_id']
if 'play my_game' in str(v):
tasks.launch_bot.delay(user_id, channel, ts, team_id) # launch the bot in parallel
return Response(), 200
except Exception as e:
raise
bot.py:
from slackclient import SlackClient
class Bot():
def main():
# opening the Slack web-socket connection
READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose
if self.slack_client.rtm_connect():
while True:
command, channel, user, ts = self.parse_slack_output()
if command and channel:
if channel not in self.channel_ids_to_name.keys():
# this (most likely) means that this channel is a PM with the bot
self.handle_private_message(command, user, ts)
else:
self.handle_command(command, channel, user, ts)
time.sleep(READ_WEBSOCKET_DELAY)
tasks.py:
import bot
from bot import Bot
from app import app
#app.task
def launch_bot(user_id, channel, ts, team_id):
'''
Instantiates the necessary objects to play a game
Args:
[user_id] (str) The id of the user from which the command was sent
[channel] (str) The channel the command was posted in
[ts] (str) The timestamp of the command
'''
print "launch_bot(user_id,channel)"
app.control.purge()
bot = Bot()
bot.initialize(user_id, channel)
bot.main()
Procfile (if using Heroku):
web: gunicorn --pythonpath app my_app:app
worker: celery -A app.tasks worker -B --loglevel=DEBUG
Please let me know if you are having any issues. This took me a little while to figure out, and I would be happy to help you if you are banging your head on this one.