Asyncio and await issue (Discord bot) - python

Alright, so, I made a relatively small function for my bot to post messages from an API until the process at that API is complete and changes from one type of JSON code to another (that is, showing a different thing)
Gives me the following: RuntimeWarning: coroutine 'discord_status_request' was never awaited
And the code is as follows:
#client.command(name='addip')
async def discord_add_ip(ip):
monitors.add(ip)
await client.say('Added IP: {}'.format(ip))
print(monitors)
if len(monitors) < 2:
discord_status_request()
print('Initiating monitoring process.')
##asyncio.coroutine
async def discord_status_request():
global old_response
global enablemonitoring
for i in monitors:
if enablemonitoring == True:
while True:
loop_break = False
response = requests.get(url_input.format(i)).json()
status_text = str("-" + "\n" + "Start year: " + str(response['start_year'])) + \
str('\n' + 'Start time: ' + str(
response['start_time'])) + \
str('\n' + 'Percentage: ' + str(response['percent'])) + \
str('\n' + 'Current year: ' + str(
response['current_year'])) + \
str('\n' + 'Scheme №: ' + str(
response['scheme_number'])) + \
str('\n' + 'Stop year: ' + str(response['stop_year']))
new_response = response.get('percent')
if new_response == old_response:
print('Comparison: nothing new.')
time.sleep(10)
else:
old_response = new_response
print('Comparison success!')
try:
client.say(status_text)
except KeyError:
client.say("Finished calculating. :white_check_mark:")
if len(monitors) == 0:
loop_break = True
monitors.remove(i)
print('Taking a break.')
time.sleep(10)
if loop_break:
break
I've looked the error up and found the following: https://xinhuang.github.io/posts/2017-07-31-common-mistakes-using-python3-asyncio.html
So I added this:
task = loop.create_task(discord_status_request())
loop.run_until_complete(task) #(I imported AbstractEventLoop as loop)
But, you guessed it, create_task requires coro and run_until_complete requires future. I need coro and future. So what the hell are those? Can't understand.

This is going to change depending on what version of python you are running.
but if your going to use the #asyncio.coroutine
then you wont need to use the async keyword just do
#asyncio.coroutine
def discord_status_request():
yield from function()
you would also use the yield from keywords so you may want to look up yield from with async.coroutine
other wise you should just do
async def discord_status_request():
await function()
which is the way as of python 3.5
async def syntactically defines a function as being a coroutine, although it cannot contain any form of yield expression; only return and await are allowed for returning a value from the coroutine.
I think this is what your asking

Related

Python Dictionary-Bot Questions

I was making a discode dictionary bot. The function I want is for the user to enter a word and the meaning of the word through a command to the bot.
So what's been conceived!It's input (word) (meaning), but the problem is that the bot separates words and their meanings by spaces, so if the meaning gets longer, only the sentence before the space is saved as meaning.
For example, if you type !input anarchism is a~~,
When you print it out, it only outputs up to anarchism.
I think it's because there's a space between the word anarchism and is. Please look at my code and answer me how to fix it.
import discord
from discord.ext import commands
from discord.utils import get
bot = commands.Bot(command_prefix='!',intents=discord.Intents.all())
front_command = "!"
user_input = ""
user_output = ""
check_number = 0
#bot.event
async def on_ready():
print('Logging in..: ')
print(bot.user.name)
print('connection was succesful')
await bot.change_presence(status=discord.Status.online, activity=discord.Game("Testing"))
#bot.command()
async def input(ctx, arg1, arg2):
global check_number
temp_f = open("user.txt","r")
datafile = temp_f.readlines()
if len(datafile) == 0:
temp_f.close()
temp_f = open("user.txt","w")
temp_f.write(ctx.author.name + " " + str(arg1) + " " + str(arg2) +"\n")
await ctx.send("Registration completed!")
else:
for i in range(len(datafile)):
if arg1 in datafile[i]:
temp_f.close()
temp_f = open("user.txt","w")
datafile[i] = ctx.author.name + " " + str(arg1) + " " + str(arg2) +"\n"
temp_f.write('\n'.join(datafile))
await ctx.send("There is aleready "+str(arg1)+" related input, so it is changed to input now!")
check_number = 1
break
if check_number == 0:
temp_f = open("user.txt","w")
temp_f.write(ctx.author.name + " " + str(arg1) + " " + str(arg2) +"\n")
await ctx.send("Registration completed!")
check_number = 0
temp_f.close()
#bot.command()
async def output(ctx, arg1):
global check_number
temp_f = open("user.txt","r")
datafile = temp_f.readlines()
if len(datafile) == 0:
await ctx.channel.send("Nothing's registered yet! :)")
else:
for i in range(len(datafile)):
if arg1 in datafile[i]:
info = datafile[i].split()
msg = await ctx.send(str(info[2]))
await msg.reply(str(info[0]) + " " + "wrote this!")
check_number = 1
break
if check_number == 0:
await ctx.channel.send("Nothing's registered yet! :)")
check_number = 0
bot.run('Token')
I hope I understood your question correctly.
You can add a * before your last argument to get everything the User has written, like this:
#bot.command()
async def input(ctx, arg1, *, arg2):
You could also circumvent it by having the user surround the input in quotes, in your example it would be: !input anarchism "your definition here", but the method above is more user-friendly.

Getting user input while a timer is counting and updating the console

I have a count-up/count-down timer library, and I've written some demo code that kicks off an instance of its main class, and updates the console as it counts down. When the timer expires, it displays a message to that effect. The demo code is pretty rudimentary and non-interactive, however, and I would like to improve it.
I would like to add the ability to pause/resume/reset the timer by pressing keys on the keyboard. The user would press the spacebar to pause/resume, and another key (maybe "r") to reset the timer. The console would be continually updated and display the current "remaining" time, including freezing the time when the timer is paused.
What I am struggling with is what approach would be best to use here. I have some limited experience with threads, but no experience with async. For now, I am not interested in using a full blown TUI, either, even though that might give the most satisfying results...I am treating this as a learning experience.
My first inclination was to use threads, one each for the user input and the timer countdown / console update tasks; but how do I get messages from the console when it receives user input (e.g. "pause") to the other task so that the time pauses?
I want to do this in the most Pythonic way I can - any suggestions?
I ended up using async and learning a bit in the process. Here's the code. And BTW it is a lot lighter weight than my threaded verssion. My Macbook pro fans spin up to max speed when I run the threaded version. But I can barely hear them at all with the async version.
import sys
import asyncio
from count_timer import CountTimer
from blessed import Terminal
def count():
if counter.remaining > 10:
print(
term.bold
+ term.green
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ str(round(counter.remaining, 3))
)
elif counter.remaining > 5:
print(
term.bold
+ term.yellow2
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ str(round(counter.remaining, 3))
)
elif counter.remaining > 0:
print(
term.bold
+ term.red
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ str(round(counter.remaining, 3))
)
else:
print(
term.bold
+ term.magenta
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ "TIME'S UP!"
)
def kb_input():
if counter.remaining <= 0:
return
with term.cbreak():
key = term.inkey(timeout=0.01).lower()
if key:
if key == "q":
print(
term.bold
+ term.magenta
+ term.move_x(0)
+ term.move_up
+ term.clear_eol
+ "Quitting..."
)
sys.exit()
elif key == "r":
counter.reset(duration=float(duration))
counter.start()
elif key == " ":
counter.pause() if counter.running else counter.resume()
async def main():
global counter
global term
global duration
duration = input("Enter countdown timer duration: ")
counter = CountTimer(duration=float(duration))
counter.start()
term = Terminal()
def _run_executor_count():
count()
def _run_executor_kb_input():
kb_input()
while counter.remaining > 0:
await asyncio.get_event_loop().run_in_executor(None, _run_executor_count)
await asyncio.get_event_loop().run_in_executor(None, _run_executor_kb_input)
await asyncio.get_event_loop().run_in_executor(None, _run_executor_count)
def async_main_entry():
asyncio.get_event_loop().run_until_complete(main())
if __name__ == "__main__":
async_main_entry()

Issues with making a simple loading icon inside an async event loop

I'm making a bot for a discord server and have a function that takes a bit of time to run. I want to add a spinning loading icon next to the status message like this Doing something: <spinning icon>. It edits the original message to loop through these messages:
Doing something: \
Doing something: |
Doing something: /
Doing something: -
I tried using a separate thread to update the message like this:
async def loadingBar(ctx, message : discord.Message):
loadingMessage0 = "{0}: \\".format(message)
loadingMessage1 = "{0}: |".format(message)
loadingMessage2 = "{0}: /".format(message)
loadingMessage3 = "{0}: -".format(message)
index = 0
while True:
if(index == 0):
await message.edit(contents = loadingMessage0)
index = 1
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 2
elif(index == 2):
await message.edit(contents = loadingMessage2)
index = 3
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 0
farther down, the bot command that starts the process...
#bot.command()
async def downloadSong(ctx, url : str, songname : str):
#Other code that doesn't matter
message = await ctx.send("Downloading audio")
_thread = threading.Thread(target=asyncio.run, args=(loadingBar(ctx, message),))
_thread.start()
#Function that takes a while
#Some way to kill thread, never got this far
However, I get the error Task <Task pending coro=<loadingBar() running at bot.py:20> cb=[_run_until_complete_cb() at /Users/user/.pyenv/versions/3.7.3/lib/python3.7/asyncio/base_events.py:158]> got Future <Future pending> attached to a different loop. I'm new to async programming and the discord libraries; Is there a better way to do this and if not what am I doing wrong?
Firstly, you should add a delay between iterations inside the while loop, use asyncio.sleep for this.
Secondly - asyncio and threading doesn't really work together, there's also no point in using threading here since it defeats the whole purpose of asyncio, use asyncio.create_task to run the coroutine "in the background", you can asign it to a variable and then call the cancel method to stop the task.
import asyncio
async def loadingBar(ctx, message : discord.Message):
loadingMessage0 = "{0}: \\".format(message)
loadingMessage1 = "{0}: |".format(message)
loadingMessage2 = "{0}: /".format(message)
loadingMessage3 = "{0}: -".format(message)
index = 0
while True:
if(index == 0):
await message.edit(contents = loadingMessage0)
index = 1
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 2
elif(index == 2):
await message.edit(contents = loadingMessage2)
index = 3
elif(index == 1):
await message.edit(contents = loadingMessage1)
index = 0
await asyncio.sleep(1) # you can edit the message 5 times per 5 seconds
#bot.command()
async def downloadSong(ctx, url : str, songname : str):
message = await ctx.send("Downloading audio")
task = asyncio.create_task(loadingBar(ctx, message)) # starting the coroutine "in the background"
# Function that takes a while
task.cancel() # stopping the background task

Trying to Loop Through functions. coroutine never awaited on 2nd loop

I'm pretty new to programming, but I've been working on this code and finally getting somewhere. I'm using telethon api to scan a TG group and then do something with the code.
My program has a main screen, where I take input then loop through functions before getting back to the main screen.
The first run works perfectly, but then when it gets back to main screen 2 things happen.
I get coroutine was never awaited.
RuntimeWarning: coroutine 'UpdateMethods._run_until_disconnected' was
never awaited
client.run_until_disconnected()
RuntimeWarning: coroutine 'AuthMethods._start' was never awaited
client.start()
It starts running the functions after it without another event happening
The actual code has more functions that actually do things besides print, but I left out those and just replaced with print to give an idea of what they're doing since those are working.
Any help is greatly appreciated. I apologize in advance if my code is very messy!
client = TelegramClient('anon', config['telegram_id'], config['telegram_hash'])
def mainScreen():
# Display Wallet / Buy Info
print('Wallets List')
print('------------')
for address in addresses:
print(address["wallet"] + ' | Amount to buy: '
+ str(address["amount"]) + ' | Current Balance : '
+ str(web3.fromWei(web3.eth.get_balance(address["wallet"]), 'ether')))
# Print Options
print('')
print('What do you want to do?')
print('-----------------------')
print('')
print('1) TG Scan')
print('')
input("Enter your answer: ")
scanTg1()
# Take input
def scanTg1():
print('')
if config['tgGroup'] == "":
group = input("Link to the TG: ")
else:
group = config['tgGroup']
print("Scanning : " + group)
#client.on(events.NewMessage(chats=group))
async def my_event_handler(event):
global handleNewMessage
if event.raw_text is not None and handleNewMessage == True:
if 'stealth' in event.raw_text.lower():
if '0x' in event.raw_text:
x = event.raw_text.split()
for string in x:
if '0x' in string:
start = string.index('0')
contract = Web3.toChecksumAddress(string[start:start+42].lower())
contractFound = await checkContract(contract)
if contractFound == True:
print("Contract Found: " + contract)
handleNewMessage = False
await buyTheToken(contract)
if contractFound == False:
if 't.me' in event.raw_text:
x = event.raw_text.split()
for string in x:
if 't.me' in string:
tgLink =x[x.index(string)]
print("Found " + tgLink)
await scanOldMessages(tgLink)
else:
print('*')
elif 't.me' in event.raw_text:
x = event.raw_text.split()
for string in x:
if 't.me' in string:
tgLink =x[x.index(string)]
print("Found " + tgLink)
await scanOldMessages(tgLink)
else:
print('*')
else:
print('*')
client.start()
client.run_until_disconnected()
async def buyTheToken(contract):
print("Bought the Token")
sellToken()
def sellToken():
print("Token sold")
mainScreen()

discord.py bot stops responding after I add a new block of code

im new to python and discord.py, this is a repost with updated details and without the block of code that was hard to understand. I have not found an answer to this problem online so it is probably a small mistake on my end.
Problem
I am trying to program a discord.py bot for my server which can do some basic commands.
I wanted to add some functionality into the shell which could allow me to control the bot via python shell.
Before this, I had some basic commands which would work as expected (I do c!help and it responds with an embed message with help)
After I added the code for control via the console, the commands for discord stopped responding as intended.
Desired Behaviour: I type c!boop {user} in discord, The bot send a dm to said user and a logging message is sent in the logging channel. I do c!console in the python shell, my interactive menu comes up
What Happens: I type c!boop {user} in discord, I get nothing back. I do c!console in the python shell, my interactive menu comes up.
As I said, It worked before I added the new code for the Shell.
My Code
The code here has been shortened a lot for the MRE but if you think the full code would be necessary, just ask. Sorry if it still is long, I already removed 3/4 of it to only keep the parts relevant to my problem.
import discord
import time
client = discord.Client()
prefix = 'c!'
#client.event
async def on_message(message):
if message.author == client.user:
return
#This bit is the command for within discord
if message.content.startswith(prefix + "boop"):
#Getting the Victim's user id
victim = str(message.content)
victim = victim.replace(prefix + 'boop ', '')
victim = victim.replace('<#', '')
victim = victim.replace('!', '')
victim = victim.replace('>','')
#Booping the user
user = client.get_user(int(victim))
await message.channel.send("Booped " + user.name)
await user.send('**Boop!**')
t = time.localtime()
current_time = time.strftime("%H:%M:%S", t)
channel = client.get_channel(int(759798825161326593))
LogMsg = str('`' + current_time + '` ' + message.author.name + ' used command in ' + str(message.channel) + ' `' + message.content + '`')
await channel.send(LogMsg)
#After I added this section, the above command stopped working
#client.event
async def on_ready():
print('Logged in as')
print(client.user.name)
print('USER ID: ' + str(client.user.id))
print('')
print('To Open the Console, type ' + prefix + 'console in the shell')
print('------')
console = str(prefix + 'console')
while 1 == 1:
ConsoleInput = input('')
if ConsoleInput == console:
while 1 == 1:
print('------')
print('Please Select a Module')
print('1 - Announce')
print('99 - Exit Console')
print('------')
ConsoleInput = int(input(''))
if ConsoleInput == 1:
print('------')
print('Module 1 Selected - Announce')
print("What's the id of the channel you want to announce in?")
Channel_id = int(input())
print("Embed? (1 for yes, 2 for no)")
YeNo = int(input())
if YeNo == 1:
print("What is the Title for the Embed message?")
EmbedTitle = str(input())
print("What is the Description for the Embed message?")
announcement = str(input())
print('Announcing')
channel = client.get_channel(Channel_id)
embed=discord.Embed(title=EmbedTitle, description=announcement, color=0xff40ff)
await channel.send(embed=embed)
print("Announced")
t = time.localtime()
current_time = time.strftime("%H:%M:%S", t)
channel = client.get_channel(int(759798825161326593))
await channel.send('`' + current_time + '` ' + 'Console User used command in Console ' '`' + str(Channel_id) + ' ' + EmbedTitle + ' ' + announcement + ' ' + str(YeNo) + '`')
elif YeNo == 2:
print("What is the announcement?")
announcement = str(input())
print("Announcing")
channel = client.get_channel(Channel_id)
await channel.send(announcement)
print("Announced")
t = time.localtime()
current_time = time.strftime("%H:%M:%S", t)
channel = client.get_channel(int(759798825161326593))
await channel.send('`' + current_time + '` ' + 'Console User used command in Console ' '`' + str(Channel_id) + ' ' + announcement + ' ' + str(YeNo) + '`')
elif ConsoleInput == 99:
print('------')
print('Exiting Console')
print('You can restart the console by typing ' + prefix + 'console in the shell')
print('------')
break
client.run(TOKEN GOES HERE)
Thanks in advance
Blocking code
It appears that the synchronous code in the on_ready event is what is hanging the bot; waiting for input from the console halts any other code from running, including reacting to commands.
The only way to fix this is to design your announcement command in a different way that doesn't involve using your console, such as having it be a command for your bot.
Sidenote about discord.Client()
Since you're using the lower-level API (discord.Client), you may find it more difficult to develop new commands for your bot. I would recommend using the bot commands framework (discord.ext.commands) that discord.py comes packaged with. The pitch is in the link so instead, here's an example using the framework with your boop command:
import time
import discord
from discord.ext import commands
prefix = 'c!'
TOKEN = ''
client = commands.Bot(command_prefix=prefix)
#client.event
async def on_ready():
print('Logged in as', client.user.name)
#client.command(name='boop')
async def client_boop(ctx, user: discord.User):
"""Boops a user.
Accepted inputs are: ID, mention, name#discrim, or name
Example: c!boop thegamecracks"""
await user.send('**Boop!**')
await ctx.channel.send("Booped " + user.name)
current_time = time.strftime("%H:%M:%S", time.localtime())
log_channel = client.get_channel(759798825161326593)
LogMsg = '`{}` {} used command in {} `{}`'.format(
current_time,
ctx.author.name,
ctx.channel.name,
ctx.message.content
)
await log_channel.send(LogMsg)
client.run(TOKEN)

Categories

Resources