How to run Streamlit and Telegram Bot (python-telegram-bot) simultaneously? - python

I'm trying to run a Streamlit webpage and a python-telegram-bot instance together, because I need to call Telegram Bot functions from the Webpage.
Below is my code:
import streamlit as st
from telegram import Update
from telegram.ext import Updater, CallbackContext, MessageHandler, Filters
def echo(update: Update, context: CallbackContext) -> None:
update.effective_chat.send_message(text="Hi")
def run_website():
st.set_page_config(page_title="My Webpage", page_icon=":tada:", layout="wide")
def run_bot() -> None:
updater = Updater('5222690292:AAHpTsw3ynWX0WA-2WonGErslqmoFjLHkvA')
dispatcher = updater.dispatcher
# Add handlers
echo_handler = MessageHandler(Filters.all, echo)
dispatcher.add_handler(echo_handler)
# Start Bot
updater.start_polling()
updater.idle()
if __name__ == '__main__':
run_website()
run_bot()
When starting the script with streamlit run test.py, I run into the following error:
ValueError: signal only works in main thread of the main interpreter
I then tried removing updater.idle(), but got the following error:
telegram.error.Conflict: Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
I also tried adding #st.cache to my run_bot() function to no avail (same error as above).

Related

I'm creating a Slackbot in Python and want to repeat the message until a reaction is added to that message. What am I doing wrong?

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.

Trouble deploying telegram bot on Render

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.

How to spin-up a background process that doesn't close after a response is served in FastAPI

I'm writing a web hook server to receive updates when my git repository is pushed.
Upon receiving the POST request from GitHub, I execute several commands like git pull, mvn install which take a very long time.
But the web hook request sent by GitHub timeouts after 10 seconds.
My code:
import logging
import os
from fastapi import FastAPI
app = FastAPI()
logger = logging.getLogger("uvicorn")
def exec_cmd(command):
out = os.system(command)
logger.info(str(out))
#app.post('/')
def func():
logger.info("WebHook received")
exec_cmd("git pull")
exec_cmd("mvn clean install")
exec_cmd("killall java")
return {}
if __name__ == "__main__":
import uvicorn
exec_cmd("git pull")
uvicorn.run("main:app", debug=False, reload=False, host="0.0.0.0")
Therefore I want to run the long running tasks in the background, and respond to GitHub's request as soon as possible.
How can I do this?
(If I make the exec_cmd() function async, when the request returns, the exec_cmd() function doesn't run till completion. )
They are called BackgroundTasks in FastAPI and can be used by adding a BackgroundTasks type to your view function signature.
The example given in the documentation can be further adapter to your needs:
from fastapi import BackgroundTasks, FastAPI
app = FastAPI()
def process_repository(email: str, message=""):
exec_cmd("git pull")
exec_cmd("mvn clean install")
exec_cmd("killall java")
#app.post("/")
async def update_repository(background_tasks: BackgroundTasks):
background_tasks.add_task(process_repository)
return {"message": "Repository update has begun"}
Since you don't check the results this should work for your use case.

Get User Input after keyboard inline keyboard selection Telegram Bot Python

I have an issue with my telegram bot. I wrote a bot in which the user needs to make a choice with an Inline Keyboard. If he selects 'Cerca un prodotto' the bot should wait for user input and print it. But if I start the bot then it prints the text 'FU' in update.message.reply_text('HELLO FU', reply_markup=reply_markup_main) row instead of printing user input. This is my code:
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters
from telegram.ext import CommandHandler, CallbackQueryHandler, CallbackContext
from telegram import InlineKeyboardButton, InlineKeyboardMarkup, ReplyKeyboardMarkup
from telegram import Update
import telegram
def start(update, context):
kb_main = [
[InlineKeyboardButton("Cerca le migliori offerte", callback_data='trovaofferte')],
[InlineKeyboardButton("Cerca un prodotto", callback_data='cercaprod')]
]
reply_markup_main = InlineKeyboardMarkup(kb_main)
update.message.reply_text('HELLO FU', reply_markup=reply_markup_main)
def kb(update: Update, _: CallbackContext) -> None:
query = update.callback_query
query.answer()
#===> IF USER CHOICE IS 'cercaprod' THEN BOT SHOULD WAIT FOR USER INPUT<====
if query.data == 'cercaprod':
print(query.message.text)
def main():
update= Updater('1655760090:AAFKyOR-i7wLW1zIAH9-air0EtqIDQJrCwk', use_context=True)
disp=update.dispatcher
disp.add_handler(CommandHandler("start", start))
disp.add_handler(MessageHandler(Filters.text & ~Filters.command, kb))
update.dispatcher.add_handler(CallbackQueryHandler(kb))
update.start_polling()
update.idle()
if __name__=='__main__':
main()
A have a few remarks:
You should revoke your bot token with #Botfather. Otherwise anyone could use it, because you posted it
you use the callback kb in both a MessageHandler and a CallbackQueryHandler, but call update.callback_query.answer(). However, if the update is a message (in the MessageHandler case, update.callback_query will be None and you'll get an exception.
Now to the actual problem:
When the button is pressed, the only update you get is the CallbackQuery and query.message is the message that the button is attached to - in your case the message with text HELLO FU. If you want the user to enter some text after pressing the button, that will come in a new update. So you'd have to use a MessageHandler to catch that. I guess that's what you tried by setting up a MessageHandler with kb as callback, but as mentioned above that will only raise an error, which you currently won't see because you have no logging set up and no error handler.
For this kind of setup, I strongly recommend to use ConversationHandler, which is perfect for setups where multiple user inputs are needed. There's also a neat example here.

Background task in Flask + Gunicorn without Celery

I want to send a telegram notification when the user performs a specific task in my flask application. I'm using python-telegram-bot to handle telegram. Here's the simplified code:
#app.route('/route')
def foo():
# do some stuff...
# if stuff is completed successfully - send the notification
app.telegram_notifier.send_notification(some_data)
return ''
I'm using messagequeue from python-telegram-bot to avoid flood limits. As you might have expected, that's not working and I'm getting the following error:
telegram.ext.messagequeue.DelayQueueError: Could not process callback in stopped thread
I tried to launch it in a separate daemon thread but I also ended up with that error.
This functionality is used only once in the entire application so I want things to be simple and don't want to install more dependencies like Celery.
Is there a way to achieve this using threads or some other simple way?
EDIT (more code)
Here's simplified implementation of the telegram bot:
from telegram import Bot, ParseMode
from telegram.ext import messagequeue as mq
class Notifier(Bot):
def __init__(self):
super(Notifier, self).__init__('my token')
# Message queue setup
self._is_messages_queued_default = True
self._msg_queue = mq.MessageQueue(all_burst_limit=3, all_time_limit_ms=3500)
self.chat_id = 'my chat ID'
#mq.queuedmessage
def _send_message(self, *args, **kwargs):
return super(Notifier, self).send_message(*args, **kwargs)
def send_notification(self, data: str):
msg = f'Notification content: <b>{data}</b>'
self._send_message(self.chat_id, msg, ParseMode.HTML)
In the app factory method:
from notifier import Notifier
def init_app():
app = Flask(__name__)
app.telegram_notifier = Notifier()
# some other init stuff...
return app
The thing with threads I tried:
#app.route('/route')
def foo():
# do some stuff...
# First method
t = Thread(target=app.telegram_notifier.send_notification, args=('some notification data',), daemon=True)
t.start()
# Second method
t = Thread(target=app.telegram_notifier.send_notification, args=('some notification data',))
t.start()
t.join()
return ''

Categories