I'm writing a telegram bot and I want to schedule it to send an automated message every day at a specific timing, cron style. I am using apscheduler to do it but I could not get the cron function to work. The interval scheduling works fine but it is not what I want.
I don't want to execute the .py file outside because I need the telegram bot to detect user's /start message.
So what I wanted to do was to detect user's /start message and then start the scheduler. Thereafter, the bot will send a message to the user everyday 8pm night.
Cron scheduling doesn't start and I don't know why. I suspect it is because it can't run within the main thread that I am running? Any advice would be helpful! Thank you.
import time
import telepot
import json
from telepot.loop import MessageLoop
from telepot.namedtuple import ReplyKeyboardMarkup # for custom keyboard
from telepot.delegate import pave_event_space, per_chat_id, create_open
from apscheduler.schedulers.blocking import BlockingScheduler
## Load token
TOKEN = 'TOKEN NUMBER'
# The main body
class main(telepot.helper.ChatHandler): # Implement continuous dialogue with user using DelegatorBot
global counter1
counter1 = 0
global counter2
counter2 = 0
def __init__(self, *args, **kwargs):
super(main, self).__init__(*args, **kwargs)
# Initialize and create empty dictionary for storing data.
self.indicator = '0'
self.data = {} # initialize an empty dictionary for storing data.
def on_chat_message(self, msg):
content_type, chat_type, chat_id = telepot.glance(msg) # this is very important.
# Debugging
print(content_type, chat_type, chat_id)
print(msg['text'])
global counter1
global counter2
scheduler = BackgroundScheduler()
#scheduler_cron = BlockingScheduler()
# Survey function
def survey():...
return
def repeat_message():
bot.sendMessage(chat_id, text='type /survey to repeat survey.')
print("Reminder sent.")
scheduler_cron.add_job(repeat_message, 'cron', day='*', week='*', day_of_week='*', hour=20, minute=00)
# Detect start messages.
while True:
if counter2 == 11: # If survey ends AKA 11 Qns done. Restart the counters.
counter1 = 0
counter2 = 0
# once bot starts, ask user to repeat survey at a specific time of the day.
if counter1 == 0: # If survey ends or at the very beginning of using the bot.
# Start message.
if msg['text'] == '/start': # /starts initiates bot. User gets greeted with a welcome message describing what the bot will do.
bot.sendMessage(chat_id,
text='Hello there.',
parse_mode='Markdown')
scheduler_cron.start()
print("Scheduler started.")
# Start survey after initializing the bot
elif msg['text'] == '/survey': # /survey initiates survey.
print("Survey started...")
#counter1 = 0
counter1 += 1
else:
bot.sendMessage(chat_id, text='I do not recognise your message.')
msg['text'] = '' # resets msg.
# User starts survey
if counter1 == 1: # if user types /survey, counter1 +=1 and when counter1 == 1, run survey function.
survey() # starts survey
counter2 += 1
break
bot = telepot.DelegatorBot(TOKEN, [pave_event_space()(per_chat_id(), create_open, main, timeout=60),])
MessageLoop(bot).run_as_thread() # Initiates the bot on telegram. Listens for user's response. If this is stopped, the entire bot stops.
print('Listening...')
while True:
time.sleep(1)
EDIT: I found out that apscheduler's cron does not work if I have another thread running in the background as stated in their documents:
BlockingScheduler: use when the scheduler is the only thing running in your process
BackgroundScheduler: use when you’re not using any of the frameworks below, and want the scheduler to run in the background inside your application
So it means I can't use apscheduler to make my bot work. Anyone know of any cron-like alternatives that allows me to schedule my telegram bot to fire message to users at specific timings of the day? Preferably, it has to be something that works with the telegram API.
Related
Hi i want to send message from bot in specific time (without message from me), for example every Saturday morning at 8:00am.
Here is my code:
import telebot
import config
from datetime import time, date, datetime
bot = telebot.TeleBot(config.bot_token)
chat_id=config.my_id
#bot.message_handler(commands=['start', 'help'])
def print_hi(message):
bot.send_message(message.chat.id, 'Hi!')
#bot.message_handler(func=lambda message: False) #cause there is no message
def saturday_message():
now = datetime.now()
if (now.date().weekday() == 5) and (now.time() == time(8,0)):
bot.send_message(chat_id, 'Wake up!')
bot.polling(none_stop=True)
But ofc that's not working.
Tried with
urlopen("https://api.telegram.org/bot" +bot_id+ "/sendMessage?chat_id=" +chat_id+ "&text="+msg)
but again no result. Have no idea what to do, help please with advice.
I had this same issue and I was able to solve it using the schedule library. I always find examples are the easiest way:
import schedule
import telebot
from threading import Thread
from time import sleep
TOKEN = "Some Token"
bot = telebot.TeleBot(TOKEN)
some_id = 12345 # This is our chat id.
def schedule_checker():
while True:
schedule.run_pending()
sleep(1)
def function_to_run():
return bot.send_message(some_id, "This is a message to send.")
if __name__ == "__main__":
# Create the job in schedule.
schedule.every().saturday.at("07:00").do(function_to_run)
# Spin up a thread to run the schedule check so it doesn't block your bot.
# This will take the function schedule_checker which will check every second
# to see if the scheduled job needs to be ran.
Thread(target=schedule_checker).start()
# And then of course, start your server.
server.run(host="0.0.0.0", port=int(os.environ.get('PORT', 5000)))
I hope you find this useful, solved the problem for me :).
You could manage the task with cron/at or similar.
Make a script, maybe called alarm_telegram.py.
#!/usr/bin/env python
import telebot
import config
bot = telebot.TeleBot(config.bot_token)
chat_id=config.my_id
bot.send_message(chat_id, 'Wake up!')
Then program in cron like this.
00 8 * * 6 /path/to/your/script/alarm_telegram.py
Happy Coding!!!
If you want your bot to both schedule a message and also get commands from typing something inside, you need to put Thread in a specific position (took me a while to understand how I can make both polling and threading to work at the same time).
By the way, I am using another library here, but it would also work nicely with schedule library.
import telebot
from apscheduler.schedulers.blocking import BlockingScheduler
from threading import Thread
def run_scheduled_task():
print("I am running")
bot.send_message(some_id, "This is a message to send.")
scheduler = BlockingScheduler(timezone="Europe/Berlin") # You need to add a timezone, otherwise it will give you a warning
scheduler.add_job(run_scheduled_task, "cron", hour=22) # Runs every day at 22:00
def schedule_checker():
while True:
scheduler.start()
#bot.message_handler(commands=['start', 'help'])
def print_hi(message):
bot.send_message(message.chat.id, 'Hi!')
Thread(target=schedule_checker).start() # Notice that you refer to schedule_checker function which starts the job
bot.polling() # Also notice that you need to include polling to allow your bot to get commands from you. But it should happen AFTER threading!
if message.content.startswith("*run"):
ticker = message.content[5:]
try:
df = exchange.fetch_ohlcv(ticker)
await message.channel.send('Read Command: will send message if long or short opportunity arises!')
valid = True
except:
await message.channel.send('Invalid ticker')
if valid == True:
bars = exchange.fetch_ohlcv(ticker, timeframe='5m', limit=210)
df = pd.DataFrame(bars, columns=['time','high', 'low', 'open','close', 'volume'])
close = df.iloc[-1]['close']
while valid == True:
if message.content == "*stop":
valid = False
trade = strat1(ticker)
The code above is for a discord bot that a friend and I are working on for a side project. My problem is in the last few lines of the code,
while valid == True:
if message.content == "*stop":
valid = False:
When a user types in *run (ticker) in discord, the code searches through data to see whether that ticker is valid or not and then based on the response, a loop will run. During that loop, I want the user to have the option to stop it without having to turn off the bot completely, but I can't seem to get it to register the *stop command. Every time I run it, it just moves past that command completely. So, if anyone knows how to fix this, please let me know.
Thank you for reading.
What you're trying to do is to interrupt a running execution. To do it in Python, you will need to use a multiprocessing algorithm.
You can try to use the multiprocessing module,
Firstly, design a master thread that listens for command in the discord server, which you already did.
Then you would wanna put the actual service, that needs to be run after the command, in a function
def thread_worker():
bars = exchange.fetch_ohlcv(ticker, timeframe='5m', limit=210)
df = pd.DataFrame(bars, columns=['time','high', 'low', 'open','close', 'volume'])
close = df.iloc[-1]['close']
trade = strat1(ticker)
Then when a command to start is received, you can initiate a new process and run the corresponding job
newProcess = multiprocessing.Process(target=thread_worker)
newProcess.start()
When your master thread receives the kill signal
newProcess.terminate()
I've made a bot that gets today football matches and if the user wants he can get a reminder 10 min before a selected match.
while current_time != new_hour:
now = datetime.now()
current_time = now.strftime("%H:%M")
#return notification
text_caps = "Your match starts in 10 minutes"
context.bot.send_message(chat_id=update.effective_chat.id, text=text_caps)
Obviously while the loop runs i can not use another command . I am new to programming how could i implement this so i still get the notification but while that runs i can use other commands?
Thank you!
Try to use an aiogram and you can make scheduled tasks with aiocron (store users who wants to get notification in database or in global dict)
You can schedule a job.
Let's say you have a CommandHandler("watch_match", watch_match) that listens for a /watch_match conmmand and 10 minutes later a message is supposed to arrive
def watch_match(update: Update, context: CallbackContext):
chat_id = update.effective_chat.id
ten_minutes = 60 * 10 # 10 minutes in seconds
context.job_queue.run_once(callback=send_match_info, when=ten_minutes, context=chat_id)
# Whatever you pass here as context is available in the job.context variable of the callback
def send_match_info(context: CallbackContext):
chat_id = context.job.context
context.bot.send_message(chat_id=chat_id, text="Yay")
A more detailed example in the official repository
And in the official documentation you can see the run_once function
Good afternoon!
My telegram bot is written in Python and is very simple. It includes thousands of lines of functions of the same type (example below) and ends with a line bot.infinity_polling(True)
#bot.message_handler(commands=['start'])
def com_start(m):
keyboard = types.ReplyKeyboardMarkup(resize_keyboard=True)
keyboard.add(*[types.KeyboardButton(name) for name in ['🇷🇺Русский']])
keyboard.add(*[types.KeyboardButton(name) for name in ['🇬🇧English']])
keyboard.add(*[types.KeyboardButton(name) for name in ['🇩🇪German']])
keyboard.add(*[types.KeyboardButton(name) for name in ['🇹🇷Turkish']])
msg = bot.send_message(m.chat.id, '👋🏼Привет!',
reply_markup=keyboard)
bot.register_next_step_handler(msg, starter)
def starter(m):
if m.text == '🇷🇺Русский':
second(m)
elif m.text == '🇬🇧English':
secondeng(m)
elif m.text == '🇩🇪German':
secondgerm(m)
elif m.text == '🇹🇷Turkish':
secondtur(m)
else:
msg = bot.send_message(m.chat.id, 'Используйте кнопки меню для общения с ботом OTON ⤵️')
com_start(m)
My bot works with gitlab, after each change in the code I commit, push it and Run Pipeline via CI/CD.
The problem is that the GetUpdates method (https://api.telegram.org/bot.../getUpdates) while the bot is running gives {"ok":true,"result":[]} and nothing more. But when I stop the bot and refresh the page - it gives me all that I need:
{"ok":true,"result":[{"update_id":57670007,
"message":{"message_id":10586,"from":{"id":435418164,"is_bot":false,"first_name":"Nika","last_name":"Fenina","username":"yanikailinet","language_code":"ru"},"chat":{"id":435418164,"first_name":"Nika","last_name":"Fenina","username":"yanikailinet","type":"private"},"date":1590065990,"text":"hello"}}
Can I solve this problem somehow? I need to get the information I need while the bot is running.
PS: no webhooks connected
You cannot make simultaneous getUpdates requests. It shows this error.
Conflict: terminated by other getUpdates request; make sure that only one bot instance is running
You can save the information of ["result"] inside the code only in some file.
Writing a chat bot that i think needs more then 1 thread. First Main thread reads constantly the chat. From time to time I need to check the moderators which i need to get from an URL((JSON)) and provide to the main thread so he can for example ban someone but not an moderator.
First Question: How do i make a thread run every 30 secs. I dont want to pause it. I want to run it, finish after getting the moderators list and then after some time run it again.
Code of Main File:
#Requests module, which gets JSON data from an url
#To get moderators in the chat.
def secondThread():
while(True):
getModsList()
def chatReading():
#Creates socket to the twitch irc chat
s = openSocket()
#Joins a specific chat room
joinRoom(s)
while(True):
#Revieves messages from irc
derp = s.recv(8192)
derp=derp.decode('utf-8')
temp = str.split(derp, "\n")
readbuffer = temp.pop()
#Loop which prints scanned thing from twitch irc chat
for line in temp:
#Time stamp
print(time.strftime('%X'), end=" : ")
#Pings server back
pingPong(s, line)
#This just displays the message recieved more readable User:Message
try:
print(getUser(line) + " typed :" + getMessage(line))
except (IndexError, UnicodeEncodeError):
print("Not an user")
def main():
thread_1 = threading.Thread(target=chatReading, args=[])
thread_2 = threading.Thread(target=secondThread, args=[])
thread_1.start()
thread_2.start()
if __name__ == '__main__':
main()
Second file with getModList
moderators = None
def getModsList():
gotList = False
while(gotList != True):
jsonList = requests.get(CHANNEL_GROUP)
#If the jsonList status_code == 200 that means that it got the list,
#every other number is bad.
statusFlag=jsonList.status_code
if(statusFlag == 200):
gotList = True
else:
print("Failed to get mods list, trying again")
continue
jsonList=jsonList.json()
#this gets the mods list
moderators = jsonList['chatters']['moderators']
time.sleep(10)
Second question would be more an advice. How do i plan the project? Its my first more serious project and I'm having trouble see how to plan it. I want to add later more funcionality to the bot like commands for user, mods, a raffle where a user by command can join only once, and after 5 minutes a winner is chosen. Thanks in advance.