Discord connection lost Pycord - python-3.x

I'm working on a chat-revive bot that waits for a message (for an hour) before sending an embed with a chat revive ping. It does this correctly with a small waiting time. i.e if I put 5 - 10 minutes (300 - 600 seconds) on the wait_for section. But anything higher than about 30 minutes and the bot doesn't respond after the time is up. I've been told it's most likely from a reconnection and it cleared the task. I was wondering if anyone knew how to either keep the socket alive long enough to finish the said task or how to resume from where it left off after it disconnects then reconnects
#bot.event
async def on_message(ctx):
if ctx.channel.id == 991806371815243836:
pass
else:
return
if ctx.author.id != bot.user.id:
pass
if not ctx.author.bot:
pass
print("yes")
else:
return
#everything above is just for specifications
guild = bot.get_guild(920057625255747674)
chn = guild.get_channel(991806371815243836)
times = [10, 5, 15]
async def wait():
await bot.wait_for("message", timeout=3600) # Waits an hour for someone to send message
try:
loop = asyncio.get_event_loop()
task = loop.create_task(wait())
shields = shield(task)
shields.cancel()
await wait()
except asyncio.TimeoutError: # If returned with a timeout error, it would send the chat revive embed
async with chn.typing():
await asyncio.sleep(random.choice(times))
embed = discord.Embed(title="Come Chat!",
description="Aether is quiet,, it must be asleep :o Let's wake it up! Come talk to us!",
color=0xBEE0FA)
await chn.send("<#&982394594727718938>", embed=embed)
bot.run(token)

Related

Message repeating even after explicitly checking for history in discord

I'm trying to make a discord bot and I've added functionality of the tasks repeating after 24 hours using tasks.loop after at an interval of 24 hours( 64800 seconds). Since the code is run on repl.it, I've used uptime robot to continuously send a ping after 5 minutes to make it running always in the repl.it.
My issue is that even after checking historyin the channel the message is getting send again as you can see in the below image. ( by checking here I mean the explanation which is the explanation that comes with the API). I'm using NASA api here.
Code snippet
def get_APOD():
image = requests.get("https://api.nasa.gov/planetary/apod?api_key="+apod)
json_data = json.loads(image.text)
description = json_data["url"]
explanation = "'" + json_data["explanation"]+ "'"
title = json_data["title"]
return description,explanation,title
#client.event
async def on_ready():
print('We have logged in as {0.user}'.format(client))
send_message.start()
#tasks.loop(seconds=64800.0)
async def send_message():
channel = client.get_channel(int(apod_channel))
messages = await channel.history(limit=1).flatten()
description,explanation,title = get_APOD()
if messages[0].content == explanation:
print("Its the same")
else:
await channel.send(description)
await channel.send(title)
await channel.send(explanation)
. Here you can see the messages repeating twice. It shouldn't by the code I've written here. Since it check for the history in the channel.
What is the issue here? Appreciate any help.
EDIT: When I restart the repl.it IDE the code will run alright and print "It's the same" as it should if the message is already sent but otherwise it's failing.

Pause/Delay sending of new batch of users from swarm

I have a test case where I need to spawn 1000 websocket connections and sustain a conversation over them through a Locust task (It has a prefedined send/receive process for the websocket connections). I can successfully do it by the following setup in locust:
Max Number of Users: 1000
Hatch rate: 1000
However, this setup opens up 1000 connection every second. Even if i lower down the hatch rate, it will come to a time where it will continue to spawn 1000 websocket connections per second. Is there a way to spawn 1000 users instantly and halt/delay the swarm in sending new 1000 connections for quite some time?
I am trying to test if a my server can handle 1000 users sending and receiving messages from my server through a websocket connection. I have tried multiprocessing approach in python but I'm having a hard time to spawn connections as fast as I can with Locust.
class UserBehavior(TaskSet):
statements = [
"Do you like coffee?",
"What's your favorite book?",
"Do you invest in crypto?",
"Who will host the Superbowl next year?",
"Have you listened to the new Adele?",
"Coldplay released a new album",
"I watched the premiere of Succession season 3 last night",
"Who is your favorite team in the NBA?",
"I want to buy the new Travis Scott x Jordan shoes",
"I want a Lamborghini Urus",
"Have you been to the Philippines?",
"Did you sign up for a Netflix account?"
]
def on_start(self):
pass
def on_quit(self):
pass
#task
def send_convo(self):
end = False
ws_url = "ws://xx.xx.xx.xx:8080/websocket"
self.ws = create_connection(ws_url)
body = json.dumps({"text": "start blender"})
self.ws.send(body)
while True:
#print("Waiting for response..")
response = self.ws.recv()
if response != None:
if "Sorry, this world closed" in response:
end = True
break
if not end:
body = json.dumps({"text": "begin"})
self.ws.send(body)
while True:
#print("Waiting for response..")
response = self.ws.recv()
if response != None:
# print("[BOT]: ", response)
if "Sorry, this world closed" in response:
end = True
self.ws.close()
break
if not end:
body = json.dumps({"text": random.choice(self.statements)})
start_at = time.time()
self.ws.send(body)
while True:
response = self.ws.recv()
if response != None:
if "Sorry, this world closed" not in response:
response_time = int((time.time() - start_at)*1000)
print(f"[BOT]Response: {response}")
response_length = len(response)
events.request_success.fire(
request_type='Websocker Recv',
name='test/ws/echo',
response_time=response_time,
response_length=response_length,
)
else:
end = True
self.ws.close()
break
if not end:
body = json.dumps({"text": "[DONE]"})
self.ws.send(body)
while True:
response = self.ws.recv()
if response != None:
if "Sorry, this world closed" in response:
end = True
self.ws.close()
break
if not end:
time.sleep(1)
body = json.dumps({"text": "EXIT"})
self.ws.send(body)
time.sleep(1)
self.ws.close()
class WebsiteUser(HttpUser):
tasks = [UserBehavior]
wait_time = constant(2)
host = "ws://xx.xx.xx.xx:8080/websocket"
For this particular test, I set the maximum users to 1 and the hatch rate to 1 and clearly, locust keeps on sending 1 request per second as seen on the following responsees:
[BOT]Response: {"text": "No, I don't have a netflix account. I do have a Hulu account, though.", "quick_replies": null}
enter code here
[BOT]Response: {"text": "I have not, but I would love to go. I have always wanted to visit the Philippines.", "quick_replies": null
[BOT]Response: {"text": "No, I don't have a netflix account. I do have a Hulu account, though.", "quick_replies": null}
[BOT]Response: {"text": "I think it's going to be New Orleans. _POTENTIALLY_UNSAFE__", "quick_replies": null}
My expectation is after I set the maximum user to 1, and a hatch rate of 1, there would instantly be 1 websocket connection sending a random message, and receiving 1 main response from the websocket server. but what's happening is it keeps on repeating the task per second until i explicitly hit the stop button on the locust dashboard.
I would debug your logic. Put more print statements in each if block at various places and between each block. When dealing with a long list of decisions, it's easy to get things tripped up.
In this case, you are only wanting to sleep in a very specific situation but it's not happening. Most likely you're setting end = True when you're not expecting it so you're not sleeping and are immediately going to get a new user.
EDIT:
Reviewing your question and issue description again, it sounds like you expect Locust to send a single request and then never send another one. That's not how Locust works. Locust will run your task code for a user. When it's done, that user goes away and it waits for a certain amount of time (looks like you have it set to 2 seconds) and then it spawns another user and starts the task over again. The idea is it will try to keep a near constant number of users you tell it to. It will not only run 1000 users and then end the test, by default.
If you want to keep all 1000 users running, you need to make them continue to execute code. For example, you could put everything in your task in another while loop with another way to break out and end. That way even after making your socket connection and sending the single message you expect, the user will stay alive in the loop and won't end because it ran out of things to do. Doing it this way requires a lot more work and coordination but is possible. There may be other questions on SO about different approaches if this isn't exactly what you're looking for.

How can you reset or cancel asyncio.sleep events?

I am trying to set up a bot in discord that works on timers. One of them will work if one person types in the '!challenge' command, in which the bot will wait for 60 seconds to see if anyone types the '!accept' command in response. If it does not, it states 'Challenge was ignored. Resetting.' Or something along those lines.
Another timer actually runs during the game itself, and is an hour long. However, the hour resets after a command has been put in. If the games is idle for an hour (One player DCs or quits) the bot resets the game itself.
I had this working with threading:
# Initiate a challenge to the room. Opponent is whoever uses the !accept command. This command should be
# unavailable for use the moment someone !accepts, to ensure no one trolls during a fight.
if message[:10] == "!challenge":
msg, opponent, pOneInfo, new_game, bTimer, playerOne = message_10_challenge(channel, charFolder, message, unspoiledArena, character, self.game)
if opponent is not "":
self.opponent = opponent
if pOneInfo is not None:
self.pOneInfo = pOneInfo
self.pOneTotalHP = self.pOneInfo['thp']
self.pOneCurrentHP = self.pOneInfo['thp']
self.pOneLevel = self.pOneInfo['level']
if new_game != 0:
self.game = new_game
if bTimer is True:
timeout = 60
self.timer = Timer(timeout, self.challengeTimeOut)
self.timer.start()
if playerOne is not "":
self.playerOne = playerOne
super().MSG(channel, msg)
and:
# Response to use to accept a challenge.
if message == "!accept":
msg, pTwoInfo, new_game, playerTwo, bTimer, bGameTimer, new_oppenent, token = message_accept(channel, charFolder, unspoiledArena, character, self.game, self.opponent, self.pOneInfo)
if not charFile.is_file():
super().MSG(channel, "You don't even have a character made to fight.")
else:
if new_game is not None:
self.game = new_game
if pTwoInfo is not None:
self.pTwoInfo = pTwoInfo
self.pTwoTotalHP = self.pTwoInfo['thp']
self.pTwoCurrentHP = self.pTwoInfo['thp']
self.pTwoLevel = self.pTwoInfo['level']
if bTimer:
self.timer.cancel()
if bGameTimer:
gametimeout = 3600
self.gameTimer = Timer(gametimeout, self.combatTimeOut)
self.gameTimer.start()
if new_oppenent is not None:
self.opponent = new_oppenent
if playerTwo is not None:
self.playerTwo = playerTwo
if token is not None:
self.token = token
for msg_item in msg:
super().MSG(channel, msg_item)
with functions:
def challengeTimeOut(self):
super().MSG(unspoiledArena, "Challenge was not accepted. Challenge reset.")
self.game = 0
def combatTimeOut(self):
super().PRI('Unspoiled Desire', "!reset")
The above is an example of the same game, but on a different chat platform, with threading to handle the timers. But threading and discord.py aren't friends I guess. So I am trying to get the above code to work with discord.py, which seems to use asyncio.
The thought was to use asyncio.sleep() in the
if bTimer is true:
await asyncio.sleep(60)
self.game = 0
await ctx.send("Challenge was not accepted. Challenge reset.")
And this works...but it doesn't stop the timer, so even if someone !accepts, thus changing bTimer to False, which would cancel the timer:
if bTimer:
self.timer.cancel()
it's still going to say "Challenge was not accepted. Challenge reset."
The same problem will occure with bGameTimer if I try:
if bGameTimer:
await asyncio.sleep(3600)
await ctx.send("!reset")
the game will be hardwired to reset after 1 hours time, no matter if the game is done or not. Rather than resetting the 1 hour timer after every turn, and ONLY resetting if a full hour has passed in which no commands are made.
Is there a way to easily cancel or reset sleep cycles?
The asyncio code equivalent to your threading code would be:
...
if bTimer:
self.timer = asyncio.create_task(self.challengeTimeout())
...
async def challengeTimeout(self):
await asyncio.sleep(60)
self.game = 0
await ctx.send("Challenge was not accepted. Challenge reset.")
asyncio.create_task() creates a light-weight "task" object roughly equivalent to a thread. It runs "in parallel" to the your other coroutines, and you can cancel it by invoking its cancel() method:
if bTimer:
self.timer.cancel()

How to "temporary ban" someone with discord.py?

i am making a management discord bot with discord.py, so i realized that i need to add a command to temp ban someone for some time, this ban can be by roles or by kicking the member out of the channel and then banning him, but i don't know how to do that. Can someone help me?
After a lot of trial and error I finally got it! Given bellow is a discord.py bot with a command to temporarily ban a user and can be used for multiple users
ban_list = []
day_list = []
server_list = []
#This is a background process
async def countdown():
await client.wait_until_ready()
while not client.is_closed:
await asyncio.sleep(1)
day_list[:] = [x - 1 for x in day_list]
for day in day_list:
if day <= 0:
try:
await client.unban(server_list[day_list.index(day)], ban_list[day_list.index(day)])
except:
print('Error! User already unbanned!')
del ban_list[day_list.index(day)]
del server_list[day_list.index(day)]
del day_list[day_list.index(day)]
#Command starts here
#client.command(pass_context = True)
async def ban(ctx,member:discord.Member, days = 1):
if str(ctx.message.author.id) == '<You ID goes here>':
try:
await client.ban(member, delete_message_days=0)
await client.say('User banned for **' + str(days) + ' day(s)**')
ban_list.append(member)
day_list.append(days * 24 * 60 * 60)
server_list.append(ctx.message.server)
except:
await client.say('Error! User not active')
else:
await client.say('You do not have permission to ban users!')
client.loop.create_task(countdown())
I tested this program by banning three users for distinct amounts of time and it worked like a charm. Please note that the time may not be too accurate. The greater the time you choose, the greater the error.
For some reason users that are offline cannot be banned by a Bot.
The bot has to be online full time for this to work... If you reboot the bot or the bot crashes all lists get cleared.
It depends on what you mean by "temporary ban".
Do you want the user actually kicked out and banned from the server for a certain period of time, or do you want the user to be temporarily restricted from certain permissions such as chatting?
I recommend the latter and using the Discord rewrite branch of the API which is new and improved.
Restrict a member via role assignment and unrestrict after x seconds:
#bot.command()
async def restrict(ctx, member:discord.Member, duration: int):
role = discord.utils.get(ctx.guild.roles, name="Restricted")
await member.add_roles(role)
await asyncio.sleep(duration)
await member.remove_roles(role)
Ban a user and unban after x seconds:
#bot.command()
async def ban(ctx, user:discord.User, duration: int):
await ctx.guild.ban(user)
await asyncio.sleep(duration)
await ctx.guild.unban(user)
Keep in mind, if your bot crashes or goes offline for whatever reason while it's in the process of sleeping to unban a user, the bot will not unban the user after it comes back up, so something to consider using may be a database and storing the end time of the ban. You can then query all saved dates during bot startup to figure out how long to sleep for. Also, you will have to get their User object rather than Member object as they are not a part of the guild anymore.

Why is this queue not working properly?

The following queue is not working properly somehow. Is there any obvious mistake I have made? Basically every incoming SMS message is put onto the queue, tries to send it and if it successful deletes from the queue. If its unsuccessful it sleeps for 2 seconds and tries sending it again.
# initialize queue
queue = queue.Queue()
def messagePump():
while True:
item = queue.get()
if item is not None:
status = sendText(item)
if status == 'SUCCEEDED':
queue.task_done()
else:
time.sleep(2)
def sendText(item):
response = getClient().send_message(item)
response = response['messages'][0]
if response['status'] == '0':
return 'SUCCEEDED'
else:
return 'FAILED'
#app.route('/webhooks/inbound-sms', methods=['POST'])
def delivery_receipt():
data = dict(request.form) or dict(request.args)
senderNumber = data['msisdn'][0]
incomingMessage = data['text'][0]
# came from customer service operator
if (senderNumber == customerServiceNumber):
try:
split = incomingMessage.split(';')
# get recipient phone number
recipient = split[0]
# get message content
message = split[1]
# check if target number is 10 digit long and there is a message
if (len(message) > 0):
# for confirmation send beginning string only
successText = 'Message successfully sent to: '+recipient+' with text: '+message[:7]
queue.put({'from': virtualNumber, 'to': recipient, 'text': message})
The above is running on a Flask server. So invoking messagePump:
thread = threading.Thread(target=messagePump)
thread.start()
The common in such cases is that Thread has completed execution before item started to be presented in the queue, please call thread.daemon = True before running thread.start().
Another thing which may happen here is that Thread was terminated due to exception. Make sure the messagePump handle all possible exceptions.
That topic regarding tracing exceptions on threads may be useful for you:
Catch a thread's exception in the caller thread in Python

Resources