Not able to break from loop while running a Telegram Bot - python

I am creating a Telegram Bot using pyTelegramBotAPI that sends real-time updates of ongoing cricket matches. I want to break the loop whenever the user enters the "/stop" command. I've looked up various sources and also tried several methods to achieve the same but all in vain. The loop continues to iterate. The closest I've reached is by exiting the program by raising an error. Also, while inside the loop, the getUpdates method always returns an empty list. I've also written an issue for the same on GitHub.
def loop(match_url):
prev_info = ""
flag = 1
#continuously fetch data
while flag:
response = requests.get(match_url)
info = response.json()['score']
#display only when the score updates
if str(info) != prev_info:
prev_info = str(info)
send_msg(info)
else:
pass
send_msg(info)
#this handler needs to be fixed
#bot.message_handler(commands=['stop', 'end'])
def stop(message):
#code to break the loop
flag = 0
return
Since this was not working, I willingly used this wrong method:
while flag:
response = requests.get(match_url)
info = response.json()['score']
if str(info) != prev_info:
prev_info = str(info)
send_msg(info)
else:
pass
send_msg(info)
#bot.message_handler(commands=['stop', 'end'])
def stop(message):
bot.polling.abort = True #an arbitrary function that raises error and exits the program
Here's the whole code. I've also added my GitHub link of this code:
import requests, json, telebot
token = <TOKEN>
bot = telebot.TeleBot(token)
#parsing data from cricapi.com
def live_matches():
#here I'm using the KEY obtained from cricapi.com
curr_matches_url = "https://cricapi.com/api/cricket?apikey=<KEY>"
curr_matches = requests.get(curr_matches_url)
match_data = curr_matches.json()['data']
global unique_id_arr, score_arr
unique_id_arr, score_arr = [], []
match_details = ""
for i in match_data:
unique_id_arr.append(i["unique_id"])
for i in range(len(match_data)):
score_arr.append(match_data[i]["title"])
score_arr[i] += "\n"
match_details += str(i+1) + ". "
match_details += score_arr[i]
send_msg(match_details)
def send_msg(msg):
url2 = 'https://api.telegram.org/bot'+token+'/sendMessage'
data = {'chat_id': chat_id, 'text': msg}
requests.post(url2, data).json()
#bot.message_handler(commands=['start', 'help'])
def send_welcome(message):
bot.reply_to(message, "Howdy, how are you doing?")
global chat_id
chat_id = message.chat.id
msg = bot.reply_to(message, "Welcome to test project\nEnter the match number whose updates you want to receive")
live_matches()
bot.register_next_step_handler(msg, fetch_score)
def fetch_score(message):
chat_id = message.chat.id
match_no = message.text
#checking if the number entered is present in the displayed list
if not match_no.isdigit():
msg = bot.reply_to(message, 'Error1!\nSelect a no. from the above list only')
return bot.register_next_step_handler(msg, fetch_score)
elif 1 <= int(match_no) <= len(score_arr):
unique_id = unique_id_arr[int(match_no)-1]
global match_url
#get the data of the desired match
match_url = "https://cricapi.com/api/cricketScore?unique_id="+unique_id+"&apikey=<KEY>"
loop(match_url)
else:
msg = bot.reply_to(message, "Error2!\nSelect a no. from the above list only")
return bot.register_next_step_handler(msg, fetch_score)
def loop(match_url):
prev_info = ""
flag = 1
#continuously fetch data
while flag:
response = requests.get(match_url)
info = response.json()['score']
#display only when the score updates
if str(info) != prev_info:
prev_info = str(info)
send_msg(info)
else:
pass
send_msg(info)
#this handler needs to be fixed
#bot.message_handler(commands=['stop', 'end'])
def stop(message):
#an arbitrary function that raises error and then exits
bot.polling.abort = True
bot.polling()
"""
#currently not using
def receive_msg():
url1 = 'https://api.telegram.org/bot'+token+'/getUpdates'
response = requests.get(url1)
text = response.json()['result']
if len(text) > 0:
user_msg = text[-1]['message']['text']
return user_msg
return text
"""

You are using telebot(pyTelegramBotAPI) package in the wrong way:
Why did you create your own function send_msg where there is already send_message method in telebot exists?
You are redeclaring your "stop" handler in the loop, which is wrong!
My suggestion to you is to learn how to use the pyTelegramBotAPI properly!
Here is a demonstration code, that solves your problem:
import telebot
from time import sleep
bot = telebot.TeleBot(BOT_TOKEN)
flag = 1
#bot.message_handler(commands=['loop'])
def loop(msg):
while flag:
bot.send_message(msg.chat.id, "ping")
sleep(1)
#bot.message_handler(commands=['stop', 'end'])
def stop(msg):
global flag
flag = 0
bot.send_message(msg.chat.id, "stopped")
bot.polling(none_stop=True)
Explanation:
Declared flag as a global variable and set it to 1
"loop" handler for starting the loop that sends you "ping" message every second
"stop" handler that changes flag to 0, which terminates your running loop

Related

Check for button interaction while awaiting for message (Nextcord)

I'm making a discord slash command that I need for another projext that once interacted with starts looking for images sent by the user in the chat and saves them. It all works fine, but when I interact with the button to stop looking for images the loop has already checked event.is_set() != True at 2 lines after #HELP HERE and is awaiting for a message, so even if the loop is closed I get one more iteration. Is there a way to stop this code:
check = lambda msg: interaction.user.id == msg.author.id
message = await bot.wait_for("message", check=check)
from running even if it has already started awaiting?
This is the full code without the #bot.slash_command() decorator:
async def auto(interaction:Interaction):
await interaction.response.defer()
event = asyncio.Event()
embed = Embed(title="", color=0x2f3136)
#Make a screenshot with Win+Shift+S and press Ctrl+V on the chat to send it.
embed.add_field(name = "", value="Make a screenshot with `Win+Shift+S` and press `Ctrl+V` on the chat to send it.", inline= False)
stop_button = Button(label="Stop", style = ButtonStyle.red)
async def stopb_callback(interaction):
event.set()
stop_button.callback = stopb_callback
view = View()
view.add_item(stop_button)
message_to_delete = await interaction.followup.send(embed=embed, view = view)
#HELP HERE
while event.is_set() != True:
print("Event:",event.is_set())
if event.is_set() != True:
check = lambda msg: interaction.user.id == msg.author.id
message = await bot.wait_for("message", check=check)
if len(message.attachments) > 0:
print(message.attachments[0].url)
url = message.attachments[0].url
#save image
r = requests.get(url, stream=True)
imageName = str(uuid.uuid4()) + '.png'
with open(imageName, 'wb') as out_file:
shutil.copyfileobj(r.raw, out_file)
else:
embed1 = Embed(title="", color=0x2f3136)
embed1.add_field(name = "", value="You have to send a screenshot to start.", inline= False)
message1 = await interaction.followup.send(embed=embed1)
all_messages.append(message1)
I also tried assigning a variable like stop = False before the loop and assigning it the value True in the stopb_callback function but it doesn't seem to work I'm not sure why that's why I used asyncio.Event().
Whoa, that's a doozy of a problem you've got there! But don't worry, I'm here to help.
So, you're trying to stop a loop that's in the middle of waiting for a message, and you're finding that it still gets one more iteration even after you've closed the loop.
Well, one way you could solve this is by using asyncio.Event(). The way this works is that you can set the event, which then cancels the wait_for(). However, you've already tried this and it's not working for you.
What about trying a different approach? Maybe you could create a flag variable, let's call it stop, that you can set to True when the stop button is interacted with. Then, in the loop, you can check the value of the stop variable before each iteration and break out of the loop if it's set to True.
Here's what the code would look like:
async def auto(interaction:Interaction):
await interaction.response.defer()
stop = False
embed = Embed(title="", color=0x2f3136)
#Make a screenshot with Win+Shift+S and press Ctrl+V on the chat to send it.
embed.add_field(name = "", value="Make a screenshot with `Win+Shift+S` and press `Ctrl+V` on the chat to send it.", inline= False)
stop_button = Button(label="Stop", style = ButtonStyle.red)
async def stopb_callback(interaction):
nonlocal stop
stop = True
stop_button.callback = stopb_callback
view = View()
view.add_item(stop_button)
message_to_delete = await interaction.followup.send(embed=embed, view = view)
#HELP HERE
while not stop:
print("Event:",event.is_set())
check = lambda msg: interaction.user.id == msg.author.id
message = await bot.wait_for("message", check=check)
if len(message.attachments) > 0:
print(message.attachments[0].url)
url = message.attachments[0].url
#save image
r = requests.get(url, stream=True)
imageName = str(uuid.uuid4()) + '.png'
with open(imageName, 'wb') as out_file:
shutil.copyfileobj(r.raw, out_file)
else:
embed1 = Embed(title="", color=0x2f3136)
embed1.add_field(name = "", value="You have to send a screenshot to start.", inline= False)
message1 = await interaction.followup.send(embed=embed1)
all_messages.append(message1)
Try this out and see if it solves your problem! If not, let me know and I'll see if there's another solution. Good luck!
Looks like I found a solution:
message = await bot.wait_for("message", check=check)
this wait_for function when running continuously check for 2 things:
the event, in this case "message" and for the check, in this case:
check = lambda msg: interaction.user.id == msg.author.id
so, by adding to the add function an and statement that looks like this:
check = lambda msg: interaction.user.id == msg.author.id and not stop
the function while waiting for the message checks that the I pressed the stop button, and so the check is no more equal to True, but now it's equal to False, so stops searching for that message.

Tweepy Stream client Termination on button click

ive been trying to create a stream client using tweepy to fetch new tweets under a user-given hashtag. I've managed to achieve this using the following code successfully.
import tweepy
class IDPrinter(tweepy.StreamingClient):
def on_tweet(self,tweet):
#now = datetime.now()
#current_time = now.strftime("%H:%M:%S")
#print("Current Time =", current_time)
print(f"{tweet.id} \n {tweet.created_at} \n {tweet.author_id} \n {tweet.text}")
#https://docs.tweepy.org/en/v3.4.0/streaming_how_to.html
def on_error(self, tweet_code):
if tweet_code == 420:
return False
def cleanUP(self,printer):
print("test")
rule_ids = []
rules = printer.get_rules()
if str(rules).find("id") == -1:
print(rules)
return
else:
for rule in rules.data:
rule_ids.append(rule.id)
if(len(rule_ids) > 0):
printer.delete_rules(rule_ids)
print("rules have been reset")
else:
print("no rules to delete")
def Caller(self,value,printer):
#print("test")
printer.add_rules(tweepy.StreamRule(f"#{value} lang:en -is:retweet"))
printer.filter(expansions="author_id", tweet_fields="created_at")
But I want the user to be able to stop the stream of new tweets whenever he wants to. Ive been testing the following code but I can't get the tweepy stream loop to terminate on button click. This is my current attempt but I can't understand why is not working.
def on_button_clicked(event):
global break_cicle
break_cicle = False
print("Button pressed: break_cicle:", break_cicle)
class IDPrinter(tweepy.StreamingClient):
def on_tweet(self,tweet):
print(f"{tweet.id} \n {tweet.created_at} \n {tweet.author_id} \n {tweet.text}")
#https://docs.tweepy.org/en/v3.4.0/streaming_how_to.html
def on_error(self, tweet_code):
if tweet_code == 420:
return False
def cleanUP(self,printer):
print("test")
rule_ids = []
rules = printer.get_rules()
if str(rules).find("id") == -1:
print(rules)
return
else:
for rule in rules.data:
rule_ids.append(rule.id)
if(len(rule_ids) > 0):
printer.delete_rules(rule_ids)
print("rules have been reset")
else:
print("no rules to delete")
def Call(self,value,printer):
while break_cicle:
button.on_click(on_button_clicked)
printer.add_rules(tweepy.StreamRule(f"#{value} lang:en -is:retweet"))
printer.filter(expansions="author_id", tweet_fields="created_at")
time.sleep(1)
printer = IDPrinter("bearer key")
printer.cleanUP(printer)
hashtag = input("Give a hashtag: ")
button = widgets.Button(description="STOP!")
output = widgets.Output()
display(button, output)
break_cicle = True
button.on_click(on_button_clicked)
threading.Thread(target=printer.Call(hashtag,printer)).start()
basically i tried to create a button which on event click will change the variable break_circle and terminate the while loop onside def call.

IRC Twitch somehow start ignoring messages

I'm just trying to make a 'Twitch Plays' game. I used a tutorial for making Twitch Plays games. I need to re-run this code every five minutes as it ignores the messages. I'm making Threes game on Scratch. I checked for all the possible bugs and it works very well. This game is based on moving tiles that contain numbers on them. Any suggestions regarding the IRC twitch ignoring all messages will be highly appreciated.
The code is shown here below.
import socket
import pyautogui
import time
SERVER = "irc.twitch.tv"
PORT = 6667
PASS = "oauth:fz5kp09su0lz46vnos78sklvpjnu6l"
BOT = "TwitchBot"
CHANNEL = "olejika2016"
OWNER = "olejika2016"
message = ""
irc = socket.socket()
irc.connect((SERVER,PORT))
irc.send(( "PASS "+PASS+"\n"+
"NICK "+BOT+"\n"+
"JOIN #"+CHANNEL+"\n").encode())
def twitch():
def joinchat():
loading = True
while loading:
readbuffer_join = irc.recv(1024)
readbuffer_join = readbuffer_join.decode()
for line in readbuffer_join.split("\n")[0:-1]:
# print(line)
loading = loadingComplete(line)
def loadingComplete(line):
if ("End of /NAMES list" in line):
print("Bot has joined "+CHANNEL+"'s channel")
sendMessage(irc, "Chat Room Joined")
return False
else:
return True
def sendMessage(irc, message):
messageTemp = "PRIVMSG #" + CHANNEL + " :" + message
irc.send((messageTemp+"\n").encode())
def getUser(line):
separate = line.split(":", 2)
user = separate[1].split("!",1)[0]
return user
def getMessage(line):
global message
try:
message = (line.split(":",2))[2]
except:
message = ""
return message
joinchat()
while True:
try:
readbuffer = irc.recv(1024).decode()
except:
readbuffer = ""
for line in readbuffer.split("\r\n"):
if line == "":
continue
else:
# print("2;"+line)
user = getUser(line)
message = getMessage(line)
print(user+":"+message)
def writeUser():
time.sleep(0.1)
pyautogui.typewrite(user)
pyautogui.press('enter')
if message.lower() == "w":
pyautogui.press('up')
writeUser()
if message.lower() == "s":
pyautogui.press('down')
writeUser()
if message.lower() == "a":
pyautogui.press('left')
writeUser()
if message.lower() == "d":
pyautogui.press('right')
writeUser()
else:
pass
user = ''
message = ''
twitch()
for line in readbuffer.split("\r\n"):
if line == "":
continue
elif "PING" is line and Console(line):
msgg = "PONG tml.twitch.tv\r\n".encode()
irc.send(msgg)
print(msgg)
continue
else:
user = getUser(line)
message = getMessage(line)
print(user+":"+message)

how to take input from user on slack to proceed further?

Im working on chatbot where bot ask users name and then bot replies with Greeting + name. This works when I use this on terminal with input() but not able to figure out how to accept input from slack and use that input.
def start(request, channel):
response = ('\n\nHello!')
send_response(response, channel)
name = ('Please tell me your name.\n')
send_response(name, channel)
name = request
greet = "Hello" + name
send_response(greet, channel)
def send_response(response,channel):
slack_client.api_call("chat.postMessage", channel=channel, text=response, as_user=True)
def parse_slack_output(slack_rtm_output):
output_list = slack_rtm_output
if output_list and len(output_list) > 0:
for output in output_list:
if output and 'text' in output and AT_BOT in output['text']:
# return text after the # mention, whitespace removed
return output['text'].split(AT_BOT)[1].strip(), \
output['channel']
return None, None
if __name__ == "__main__":
READ_WEBSOCKET_DELAY = 1 # 1 second delay between reading from firehose
if slack_client.rtm_connect():
print ("connected and running!")
while True:
request, channel = parse_slack_output(slack_client.rtm_read())
if request and channel:
start(request, channel)
time.sleep(READ_WEBSOCKET_DELAY)
else:
print("Connection failed. Invalid Slack token or bot ID?")
As per slack doc, dialog.open() method is the way to achieve your requirement.
https://api.slack.com/dialogs.
EDIT:
And RASA NLU-CORE gives more option for conversational based ChatBots. http://rasa.com/docs/core/quickstart/
Slot Filling is what you need to look into to store the name or any other values and use it further in the conversation.

Python: print function hang when printing global list of objects

I'm currently writing a Python Telegram bot which is used to monitor Raspi IOs and send messages to a channel. So basically it has a function that will update a logging variable llog.
This function (logUpdate), as it's named, will remove entries that are more than 5 mins old. In it, I tried to check the content of the global variable. Upon printing, it just hangs.
This doesn't seem to block any other functionalities of the bot because I can still call out other bot commands.
I don't think it's the bot. It must be some kind of data access problems.
I attach some code snippet below:
#!usr/bin/python
##
### RF Security bot start script
##
##
### Imports
##
import telegram as tg
import telegram.ext as tgExt
import RPi.GPIO as gpio
import time
from datetime import datetime as dt
##
### Common variables
##
NULLSENSOR = 0
PRESSENSOR = 1
MAGSENSOR = 2
sensDict = {NULLSENSOR:"No sensor",
PRESSENSOR:"Pressure sensor",
MAGSENSOR:"Magnetic sensor"}
# Event class
class ev(object):
timestamp = 0
sType = NULLSENSOR
def __init__(self, ts=0, st=NULLSENSOR):
self.timestamp = ts
self.sType = st
def toString(self):
if(sType == PRESSENSOR):
return str("-> #"+timestamp.strftime('%c')+
": Pressure sensor triggered\n")
elif(sType == MAGSENSOR):
return str("-> #"+timestamp.strftime('%c')+
": Magnetic sensor triggered\n")
else:
return ""
# Report log
llog = [] # Data log
lmutex = True # Log mutex for writing
##
### Hardware configuration
##
# GPIO callbacks
def pressureCallback(channel):
global llog
global lmutex
global trigCntGlobal
global trigCntPress
ep = ev(ts=dt.now(), st=PRESSENSOR)
print("---> Pressure sensor triggered at "+
ep.timestamp.strftime("%c"))
rfSecuBot.sendMessage('#channel', "Pressure sensor "+
"triggered.")
while(not lmutex):
pass
lmutex = False
llog.insert(0, ep)
trigCntGlobal = trigCntGlobal + 1
trigCntPress = trigCntPress + 1
lmutex = True
def magneticCallback(channel):
global llog
global lmutex
global trigCntGlobal
global trigCntMag
global rfSecuBot
em = ev(ts=dt.now(), st=PRESSENSOR)
print("---> Magnetic sensor triggered at "+
em.timestamp.strftime("%c"))
rfSecuBot.sendMessage('#channel', "Magnetic sensor "+
"triggered.")
while(not lmutex):
pass
lmutex = False
llog.insert(0, em)
trigCntGlobal = trigCntGlobal + 1
trigCntMag = trigCntMag + 1
lmutex = True
# Periodic logging function
def logUpdate():
global llog
global lmutex
updTime = dt.now()
print("---> Updating log\n")
while(not lmutex):
pass
lmutex = False
for i in llog: ########### STUCK HERE
print(i.toString()) ###########
# Check log timestamps
for i in llog:
if((updTime - i.timestamp).total_seconds() > 300):
llog.remove(i)
for i in llog: ########### WAS STUCK HERE
print(i.toString()) ########### TOO
lmutex = True
print("---> Log updated\n")
# Formatting function
def logFormat():
global llog
global lmutex
logUpdate() # Asynchronous call to logUpdate to make sure
# that the log has been updated at the time
# of formatting
while(not lmutex):
pass
lmutex = False
flog = []
cnt = 0
for i in llog:
if(cnt < 10):
flog.append(i.toString())
cnt = cnt + 1
else:
break
lmutex = True
print("----> Formatted string:")
print(flog+"\n")
return flog
def listFormat():
global llog
global lmutex
logUpdate() # Asynchronous call to logUpdate to make sure
# that the log has been updated at the time
# of formatting
while(not lmutex):
pass
lmutex = False
flog = []
flog.append(" Sensors \n")
dLen = len(sensDict.keys())
if(dLen <= 1):
flog.append(sensDict.get(NULLSENSOR))
else:
sdItr = sensDict.iterkeys()
st = sdItr.next() # Had to add extra var
while(dLen > 1):
st = sdItr.next()
trigCnt = 0
for i in llog:
if(i.sType == st):
trigCnt = trigCnt + 1
if(trigCnt < 1):
pass
else:
flog.append("-> "+st+"\n")
flog.append(" No. of times tripped: "+
trigCnt+"\n")
lmutex = True
print("----> Formatted string:")
print(flog+"\n")
return flog
##
### Software configuration
##
def blist(bot, update):
print("--> List command received\n")
listString = "List of sensor trips in the last 5 minutes:\n"
listString = listString+listFormat()
print("> "+listString+"\n")
bot.sendMessage('#channel', listString)
def log(bot, update):
print("--> Log command received\n")
logString = "Log of last 10 occurrences:\n"
logString = logString+logFormat()
print("> "+logString+"\n")
bot.sendMessage('#channel', logString)
rfSecuBotUpd.start_polling(poll_interval=1.0,clean=True)
while True:
try:
time.sleep(1.1)
except KeyboardInterrupt:
print("\n--> Ctrl+C key hit\n")
gpio.cleanup()
rfSecuBotUpd.stop()
rfSecuBot = 0
quit()
break
## Callback registration and handlers are inserted afterwards
# Just in case...
print("--> Bot exiting\n")
gpio.cleanup()
rfSecuBotUpd.stop()
rfsecuBot = 0
print("\n\n\t *** EOF[] *** \t\n\n")
quit()
# EOF []
P.S. I think someone might suggest a 'class' version of this. Think it'll work?
In the toString function, I forgot to put self in front of the should-be members sType and timestamp:
def toString(self):
if(sType == PRESSENSOR):
return str("-> #"+timestamp.strftime('%c')+
": Pressure sensor triggered\n")
elif(sType == MAGSENSOR):
return str("-> #"+timestamp.strftime('%c')+
": Magnetic sensor triggered\n")
else:
return ""
Which is why the value returned was always an empty string.
Note to self: check your variables!!!
On that note, that kind of explained why it didn't seem to block the thread.

Categories

Resources