I've created echo chat telegram bot with python-telegram-bot. It'll echo everything that I typed in with time. But the problem is It always echoes same time string since the bot start.
from telegram.ext import Updater, CommandHandler, MessageHandler, Filters, Dispatcher
import logging
import datetime
logging.basicConfig(format='%(levelname)s - %(message)s',
level=logging.DEBUG)
logger = logging.getLogger(__name__)
updater = None
t = datetime.now()
dt_string = time.strftime("%b/%d/%Y %H:%M:%S", t)
sitepath=""
filename="output.txt"
def updatetime():
t = datetime.now()
dt_string = time.strftime("%b/%d/%Y %H:%M:%S", t)
def repeater(update, context):
updatetime()
update.message.reply_text("Done: " + update.message.text + "\n"+ dt_string)
def start_bot():
global updater
updater = Updater(
'##Token##', use_context=True)
dispatcher = updater.dispatcher
dispatcher.add_handler(MessageHandler(Filters.text, repeater))
updater.start_polling()
updater.idle()
start_bot()
Expected result is
Done: This is a message
Feb/05/2021 15:13:34
Done: 10 Second have passed
Feb/05/2021 15:13:44
Done: 10 Second have passed
Feb/05/2021 15:13:54
But this is the actual result
Done: This is a message
Feb/05/2021 15:13:34
Done: 10 Second have passed
Feb/05/2021 15:13:34
Done: 10 Second have passed
Feb/05/2021 15:13:34
You need to add global keyword in the methods to make sure you use the global variable dt_string else it will create a local variable and you would not be updating the global variable.
def updatetime():
global dt_string
t = datetime.now()
dt_string = time.strftime("%b/%d/%Y %H:%M:%S", t)
You need to do the same for all methods and all variables.
Note that the use of global is not recommended so you should try to refactor your code to avoid use of global variables.
The solution by Krishna Chaurasia is right about everything: the use of global variables and the not recommended use of global variables.
A simpler solution is to remove def updatetime() entirely and insert the dt_string on repeater function, like this:
def repeater(update, context):
## dt_string will update everytime you call repeater
## datetime.now() it's not necessary
dt_string = time.strftime("%b/%d/%Y %H:%M:%S")
update.message.reply_text("Done: " + update.message.text + "\n"+ dt_string)
This will make you code short and easier to read.
Related
I'm struggling trying to prevent a Django view from being executed more than once within an hour period. In other words, if the function runs at 15:00, all future requests for all users should be ignored until 17:00 when it's allowed to run once more again.
Tried with a timer, but it does get reset every time the view is called. Maybe someone can point me in the right direction? Thanks!!!
import threading as th
def hello():
print("hello, world")
def webhook(request):
tm = th.Timer(3600, hello)
if request.method == 'POST' and not tm.is_alive():
tm.start()
code_to.ecexute()
return HttpResponse("Webhook received!")
Ultimately, this is what I did and it seems to work fine. I actually need it to run no more than once a day, hence the conditional below.
Thanks for all the suggestions!!!
def webhook2 (request):
today = datetime.now().date()
with open('timestamp.txt') as f:
tstamp = f.read()
last_run = datetime.strptime(tstamp, '%Y-%m-%d')
last_run_date = datetime.date(last_run)
print ("last run: " + str(last_run_date))
if last_run_date < today:
file = open("timestamp.txt" ,"w")
file.write(str(today))
file.close()
if request.method == 'POST':
msg = str(request.body)
final_msg=msg[2:-1]
print("Data received from Webhook is: ", request.body)
# creates a google calendar event
function_logic()
return HttpResponse("Webhook received! Event added to calendar")
else:
print ("we already have a record for today")
return HttpResponse("Not adding a record. We already have one for today.")
Your timer is being reset everytime because it is inside a function that is execute everytime when request is being made.
You should try to set the timer globally, such as outside your function. ( be aware when your script will re-run, timer will be reset again ).
import threading as th
def hello():
print("hello, world")
tm = None
def webhook(request):
# check here if timer is dead then process the request.
if timer_is_dead || tm is None:
# accessing global value and setting it for first time
if tm is None:
global tm
tm = th.Timer(3600, hello)
tm.start()
if request.method == 'POST' and not tm.is_alive():
code_to.ecexute()
# start timer again for next hour
return HttpResponse("Webhook received!")
else:
return HttResponse("Not Allowed")
Edit: Handling first request and then starting timer
I am making a discord bot that will grab a json using requests from time to time, and then send the relevant information to a specific channel.
I have the following classes:
Helper, which is the discord bot itself, that runs async from the start, inside an asyncio.gather;
tasker that controls the interval which calls the class that will do the requests. It runs in a different thread so it doesn't stop the async Helper while it waits
getInfo that does the requests, store the info and should talk with Helper
I am having 2 problems right now:
While the tasker is on a different thread, every time I try to talk with Helper via getInfo it gives me the errors RuntimeError: no running event loop and RuntimeWarning: coroutine 'getInfo.discordmsg' was never awaited
If I dont run it on a different thread, however, it does work on the TestStatus: 1 but it makes Helper get stuck and stop running with TestStatus: 2
Anyway, here is the code
import requests
import asyncio
import discord
from discord.ext import commands, tasks
from datetime import datetime, timedelta
import threading
class Helper(discord.Client):
async def on_ready(self):
global discordbot, taskervar
servername = 'ServerName'
discordbot = self
self.servidores = dict()
self.canais = dict()
for i in range(len(self.guilds)):
self.servidores[self.guilds[i].name] = {}
self.servidores[self.guilds[i].name]['guild']=self.guilds[i]
servidor = self.guilds[i]
for k in range(len(servidor.channels)):
canal = servidor.channels[k]
self.canais[str(canal.name)] = canal
if 'bottalk' not in self.canais.keys():
newchan = await self.servidores[self.guilds[i].name]['guild'].create_text_channel('bottalk')
self.canais[str(newchan.name)] = newchan
self.servidores[self.guilds[i].name]['canais'] = self.canais
self.bottalk = self.get_channel(self.servidores[servername]['canais']['bottalk'].id)
await self.msg("Bot online: " + converteHora(datetime.now(),True))
print(f'{self.user} has connected to Discord!')
taskervar.startprocess()
async def msg(self, msg):
await self.bottalk.send(msg)
async def on_message(self, message):
if message.author == self.user:
return
else:
print(message)
class tasker:
def __init__(self):
global discordbot, taskervar
print('Tasker start')
taskervar = self
self.waiter = threading.Event()
self.lastupdate = datetime.now()
self.nextupdate = datetime.now()
self.thread = threading.Thread(target=self.requests)
def startprocess(self):
if not self.thread.is_alive():
self.waiter = threading.Event()
self.interval = 60*5
self.thread = threading.Thread(target=self.requests)
self.thread.start()
def requests(self):
while not self.waiter.is_set():
getInfo()
self.lastupdate = datetime.now()
self.nextupdate = datetime.now()+timedelta(seconds=self.interval)
self.waiter.wait(self.interval)
def stopprocess(self):
self.waiter.set()
class getInfo:
def __init__(self):
global discordbot, taskervar
self.requests()
async def discordmsg(self,msg):
await discordbot.msg(msg)
def requests(self):
jsondata = {"TestStatus": 1}
if jsondata['TestStatus'] == 1:
print('here')
asyncio.create_task(self.discordmsg("SOMETHING WENT WRONG"))
taskervar.stopprocess()
return
elif jsondata['TestStatus'] == 2:
print('test')
hora = converteHora(datetime.now(),True)
asyncio.create_task(self.discordmsg(str("Everything is fine but not now: " + hora )))
print('test2')
def converteHora(dateUTC, current=False):
if current:
response = (dateUTC.strftime("%d/%m/%Y, %H:%M:%S"))
else:
response = (dateutil.parser.isoparse(dateUTC)-timedelta(hours=3)).strftime("%d/%m/%Y, %H:%M:%S")
return response
async def main():
TOKEN = 'TOKEN GOES HERE'
tasker()
await asyncio.gather(
await Helper().start(TOKEN)
)
if __name__ == '__main__':
asyncio.run(main())
Your primary problem is you don't give your secondary thread access to the asyncio event loop. You can't just await and/or create_task a coroutine on a global object (One of many reasons to avoid using global objects in the first place). Here is how you could modify your code to accomplish that:
class tasker:
def __init__(self):
# ...
self.loop = asyncio.get_running_loop()
# ...
class getInfo:
#...
def requests(self):
# replace the create_tasks calls with this.
asyncio.run_coroutine_threadsafe(self.discordmsg, taskervar.loop)
This uses your global variables because I don't want to rewrite your entire program, but I still strongly recommend avoiding them and considering a re-write yourself.
All that being said, I suspect you will still have this bug:
If I dont run it on a different thread, however, it does work on the TestStatus: 1 but it makes Helper get stuck and stop running with TestStatus: 2
I can't tell what would cause this issue and I'm running into trouble reproducing this on my machine. Your code is pretty hard to read and is missing some details for reproducibility. I would imagine that is part of the reason why you didn't get an answer in the first place. I'm sure you're aware of this article but might be worth a re-visit for better practices in sharing code. https://stackoverflow.com/help/minimal-reproducible-example
I have tried to work with a while loop to keep my script running, I use time to track the time it takes to run. Simpler code it's something like this:
import time
import datetime
class Test:
start = time.time()
dateStart = datetime.datetime.now()
def end(self):
time.sleep(10)
print(time.time() - self.start)
print(datetime.datetime.now() - self.dateStart)
while True:
test = Test()
test.end()
and also tried with:
import time
import datetime
class Test:
start = time.time()
dateStart = datetime.datetime.now()
def end(self):
time.sleep(10)
print(time.time() - self.start)
print(datetime.datetime.now() - self.dateStart)
def runClass(classtest):
test = classtest()
test.end()
while True:
runClass(Test)
But the timers are not set to 0 after each reset, they accumulate. The rest of the values I have in my big class do reset to the initial value they have after the class is created, but not dates. Is there something I'm missing about how python manage time and classes? I'm starting to use classes more, so maybe I'm missing something about how they are define in memory?
What you are missing is how classes are defined and instances created.
Your code:
class Test:
start = time.time()
dateStart = datetime.datetime.now()
executes exactly once when class Test is defined.
What you meant to write was this:
class Test:
def __init__(self)
self.start = time.time()
self.dateStart = datetime.datetime.now()
...
The __init__() method is executed every time test = Test() executes which is what you are after.
btw, I think that your line: test = classtest() should be test = Test()
I want to create a discord bot that sends 2 messages every day each at a specific time. The following code will make the messages enter a loop and will send a message every 5 seconds for example. How can I set a specific time to send the messages every day, for example, message 1 at 6 pm and message 2 at 10 am.
I found this code Here, but did not find what I want.
import discord
from discord.ext import commands, tasks
bot = commands.Bot("$")
channel_id = #Any ID
#Message 1
#tasks.loop(seconds=5)
async def called_once_a_day():
message_channel = bot.get_channel(channel id)
await message_channel.send("test 1")
#called_once_a_day.before_loop
async def before():
await bot.wait_until_ready()
print("Finished waiting")
#Message 2
#tasks.loop(seconds=10)
async def called_once_a_day2():
message_channel = bot.get_channel(channel id)
await message_channel.send("test 2")
#called_once_a_day2.before_loop
async def before():
await bot.wait_until_ready()
print("Finished waiting")
called_once_a_day.start()
called_once_a_day2.start()
bot.run('token')
Here's a simple implementation - Every day, it sleeps until the target time and then sends your message
from discord.ext import commands
from datetime import datetime, time, timedelta
import asyncio
bot = commands.Bot(command_prefix="$")
WHEN = time(18, 0, 0) # 6:00 PM
channel_id = 1 # Put your channel id here
async def called_once_a_day(): # Fired every day
await bot.wait_until_ready() # Make sure your guild cache is ready so the channel can be found via get_channel
channel = bot.get_channel(channel_id) # Note: It's more efficient to do bot.get_guild(guild_id).get_channel(channel_id) as there's less looping involved, but just get_channel still works fine
await channel.send("your message here")
async def background_task():
now = datetime.utcnow()
if now.time() > WHEN: # Make sure loop doesn't start after {WHEN} as then it will send immediately the first time as negative seconds will make the sleep yield instantly
tomorrow = datetime.combine(now.date() + timedelta(days=1), time(0))
seconds = (tomorrow - now).total_seconds() # Seconds until tomorrow (midnight)
await asyncio.sleep(seconds) # Sleep until tomorrow and then the loop will start
while True:
now = datetime.utcnow() # You can do now() or a specific timezone if that matters, but I'll leave it with utcnow
target_time = datetime.combine(now.date(), WHEN) # 6:00 PM today (In UTC)
seconds_until_target = (target_time - now).total_seconds()
await asyncio.sleep(seconds_until_target) # Sleep until we hit the target time
await called_once_a_day() # Call the helper function that sends the message
tomorrow = datetime.combine(now.date() + timedelta(days=1), time(0))
seconds = (tomorrow - now).total_seconds() # Seconds until tomorrow (midnight)
await asyncio.sleep(seconds) # Sleep until tomorrow and then the loop will start a new iteration
if __name__ == "__main__":
bot.loop.create_task(background_task())
bot.run('token')
You have to make a before_loop so that the first time it runs it will be on time after that just make the loop every 24 hours. Here is an example.
import asyncio
import datetime as dt
#bot.event
async def on_ready():
print("Logged in as")
print(bot.user.name)
print("------")
msg1.start()
# Message 1
#tasks.loop(hours=24)
async def msg1():
message_channel = bot.get_channel(705524214270132367)
await message_channel.send("test 1")
#msg1.before_loop
async def before_msg1():
for _ in range(60*60*24): # loop the whole day
if dt.datetime.now().hour == 10+12: # 24 hour format
print('It is time')
return
await asyncio.sleep(1)# wait a second before looping again. You can make it more
I created a [Discord-bot] That sends a message at a particular time and until that time comes it will be in a sleep. the code i created is small and easy to understand
Code:
import discord
from datetime import datetime
client = discord.Client()
token = "" #enter your bot's token and it should be a string
channel_id = #enter your channel id and it should be a integer
def time_module():
print("time module in use")
while True:created
current_time = datetime.now().strftime("%H:%M")#hour %H min %M sec %S am:pm %p
if current_time == "00:00": # enter the time you wish
print("time module ended")
break
time_module()
#client.event
async def on_ready():
print("bot:user ready == {0.user}".format(client))
channel = client.get_channel(channel_id)
await channel.send("message")
client.run(token)
You will need to figure the time of now, and the time that you schedule, then every like 10 or fewer minutes you check if the scheduled time meets the now.
check this link to see the DateTime library for python https://docs.python.org/3/library/datetime.html#datetime.datetime.now
and check this link to see an example of how to use #tasks.loop (background task)
https://github.com/Rapptz/discord.py/blob/master/examples/background_task.py
to get the time of now using the dateTime library
now=datetime.datetime.now() #will store the time of when it is called
#if you want to put specific time:
remindMeAT =datetime.datetime(2021,5,4,17,24) #year,month,day,hour,min,sec
After version 1.1.0 is introduced, tasks module supports scheduling tasks at a specific time. All you need to do is give datetime object ( or list of datetime objects for multiple specified time ) as time argument in task decorator.
You can find detailed information in their API reference.
import datetime
from discord.ext import commands, tasks
utc = datetime.timezone.utc
# If no tzinfo is given then UTC is assumed.
times = [
datetime.time(hour=8, tzinfo=utc),
datetime.time(hour=12, minute=30, tzinfo=utc),
datetime.time(hour=16, minute=40, second=30, tzinfo=utc)
]
daily_announcement_time = datetime.time(hour=14, tzinfo=utc),
class MyCog(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.my_task.start()
def cog_unload(self):
self.my_task.cancel()
#tasks.loop(time=daily_announcement_time) # scheduling task runs daily
async def my_daily_task(self):
print("My daily task is running!")
#tasks.loop(time=times) # scheduling on multiple time instances
async def my_task(self):
print("My task is running!")
I'm playing a little bit with Python Telegram Bot, I want to pass to my handler an argument obtained with previous computation, e.g:
def my_handler(bot, update, param):
print(param)
def main():
res = some_function()
updater.dispatcher.add_handler(CommandHandler('cmd', my_handler))
How do I pass param to the handler?
On version 12 of python-telegram-bot, the arguments are located as a list in the attribute CallbackContext.args. Here is a generic example:
def my_handler(update, context):
print(context.args)
def main():
res = some_function()
updater.dispatcher.add_handler(CommandHandler('cmd', my_handler))
A simple example would be summing two integer numbers:
import logging
from config import tgtoken
from telegram.ext import Updater, CommandHandler
logging.basicConfig(format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
level=logging.INFO)
logger = logging.getLogger(__name__)
def error(update, context):
"""Log Errors caused by Updates."""
logger.warning('Update "%s" caused error "%s"', update, context.error)
def sum(update, context):
try:
number1 = int(context.args[0])
number2 = int(context.args[1])
result = number1+number2
update.message.reply_text('The sum is: '+str(result))
except (IndexError, ValueError):
update.message.reply_text('There are not enough numbers')
def main():
updater = Updater(tgtoken, use_context=True)
dp = updater.dispatcher
dp.add_handler(CommandHandler("sum", sum))
dp.add_error_handler(error)
updater.start_polling()
updater.idle()
if __name__ == '__main__':
main()
If you send /sum 1 2, your bot will answer The sum is: 3
If you mean you want to pass to the function called by the handler the argument that the user sends with the command you should add the pass_args=True parameter and it will returns arguments the user sent as a list.
So your code should be:
def my_handler(bot, update, args):
for arg in args:
print(arg)
def main():
res = some_function()
updater.dispatcher.add_handler(CommandHandler('cmd', my_handler, pass_args=True))
I didn't check it tho
If you instead are looking for a way to pass something you took from another handler to that handler related to the same user, that library has a nice parameter called "chat_data" and "user_data".