I am currently checking out the Python discord wrapper found here but it doesn't seem to work due to the above mentioned error. I tried calling the nest_asyncio.apply() before running the client.run() function but it doesn't seem to work either as mentioned in this other question. This question is possibly a duplicate but couldn't add a comment in the previous one.
I tried this:
nest_asyncio.apply()
client = discord.Client()
client.run(bot_token)
as well as this to no avail:
client = discord.Client()
nest_asyncio.apply(client.run(bot_token))
I've faced a similar, if not the same issue a few months ago, and what I found on a comment to a github issue ultimately led to the following:
class DiscordAccessor:
'''class to handle discord authentication and async loop handling
Attributes
----------
dc_coroutine
the coroutine to start the client
dc_thread
the thread to keep the coroutine alive
Methods
-------
start_loop
starts the async loop'''
dc_loop = asyncio.new_event_loop()
client = discord.Client(loop=dc_loop)
def __init__(self):
self.dc_coroutine = DiscordAccessor.client.start('YOUR_TOKEN')
self.dc_thread = threading.Thread(target=self.start_loop,
args=(self.dc_loop, self.dc_coroutine))
self.dc_thread.start()
def start_loop(self, loop, coro):
'''starts the async loop
Parameters
----------
loop
the asyncio loop
coro
the coroutine'''
loop.run_until_complete(coro)
This class wraps the discord client into it's own thread and event loop. You'd call your client something like:
dc = DiscordAccessor()
dc.client.whatever_you_want_to_call()
Related
So, this is my first post for help, so bear with me if I do something wrong. Basically, I'm getting the error above by trying to run 'await'. Ill start off with my code.
#main.py
t = threading.Thread(target=examplefunction, args=(msgcontent,channel ))
t.start()
I start off by calling the function with a thread. To sum it up this gathers information. Note that all the code isn't the full program.
#examplefunction.py
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
loop.run_until_complete(examplefunction2(content))
loop.close()
Then before the function ends, it calls upon another function to send the data, to avoid errors (just trust me here)
#examplefunction2.py
bot = discord.Client()
#bot.event
async def mcsending(content):
channel = content
print(channel)
await channel.send('here')
Then this is where the issue starts. 'Await' raises the error in the title.
EDIT:
I figured out my error if anyone is intrested.
#examplefunction.py
bot.loop.create_task(examplefunction2(content))
simply replaced 'loop.until_complete' with that
async def create_db_pool():
database_url = ''
client.pg_con = await asyncpg.create_pool(database_url, ssl="require")
so i have this function in my discord bot code but when i try to run this code using
client.loop.run_until_complete(create_db_pool())
i get the following error currently i am looking for a workaround for this or any way to solve it
AttributeError: loop attribute cannot be accessed in non-async contexts. Consider using either an asynchronous main function and passing it to asyncio.run or using asynchronous initialisation hooks such as Client.setup_hook
You must be using the master version of discord.py
It recently introduced breaking changes with asyncio, with one of them being this.
client.loop is no more accessible in a sync context. This gist explains what was the change and how to make a work around.
First way would be to introduce a setup_hook() function inside a commands.Bot subclass and use await create_db_pool() in there
class MyBot(commands.Bot):
def __init__(self, **kwargs):
super().__init__(**kwarg)
self.pg_conn: = None
async def create_db_pool(self): # making it a bound function in my example
database_url = ''
self.pg_con = await asyncpg.create_pool(database_url, ssl="require")
async def setup_hook(self):
await self.create_db_pool() # no need to use `loop.run_*` here, you are inside an async function
or you could also do this inside a main() function
async def main():
await create_db_pool() # again, no need to run with AbstractLoopEvent if you can await
await bot.start(TOKEN)
asyncio.run(main())
Are you running your bot in a synchronous context? The code should look something like:
async def on_message(ctx): ...
Also please show some code. But I think learning the asyncio module will help. Anyway, try this:
import asyncio
async def create_db_pool() -> None: ... # Your code
loop = asyncio.get_event_loop()
loop.run_until_complete(function_async())
loop.close()
This will not run your function asynchronously, but it doesn't seem like you wish to do so. But it should actually successfully run the function.
I am trying to run two different Discord Bots using a single python script using cogs. But when I try to run the 2nd bot it throws an ImportError even-though I didn't use that specific Library. The reaction roles bot works fine without the anti spam bot. Here's my code. FYI I am working inside a Virtual Env.
main.py
if __name__ == "__main__":
try:
reaction_role_bot = commands.Bot(command_prefix=config["reaction_role_bot"]["bot_prefix"], intents=discord.Intents.all())
reaction_slash = SlashCommand(reaction_role_bot, sync_commands=True)
reaction_role_bot.load_extension(f"cogs.{str(os.path.basename('cogs/reaction_roles.py')[:-3])}")
anti_spam_bot = commands.Bot(command_prefix=config["anti_spam_bot"]["bot_prefix"], intents=discord.Intents.default())
spam_slash = SlashCommand(anti_spam_bot, sync_commands=True)
anti_spam_bot.load_extension(f"cogs.{str(os.path.basename('cogs/anti_spam.py')[:-3])}")
event_loop = asyncio.get_event_loop()
event_loop.create_task(reaction_role_bot.run(config["reaction_role_bot"]["token"]))
event_loop.create_task(anti_spam_bot.run(config["anti_spam_bot"]["token"]))
event_loop.run_forever()
except Exception as e:
print(e)
anti_spam.py
import platform
import os
import discord
from discord.ext import commands
from antispam import AntiSpamHandler
from antispam.plugins import AntiSpamTracker, Options
class AntiSpamBot(commands.Cog):
def __init__(self, client):
self.client = client
# Initialize the AntiSpamHandler
self.client.handler = AntiSpamHandler(self.client, options=Options(no_punish=True))
# 3 Being how many 'punishment requests' before is_spamming returns True
self.client.tracker = AntiSpamTracker(self.client.handler, 3)
self.client.handler.register_extension(self.client.tracker)
#commands.Cog.listener()
async def on_ready(self):
print("---------------------------------")
print(f"Logged in as {str(self.client.user)}")
print(f"Discord.py API version: {discord.__version__}")
print(f"Python version: {platform.python_version()}")
print(f"Running on: {platform.system()} {platform.release()} ({os.name})")
await self.client.change_presence(status=discord.Status.idle, activity=discord.Game(name="Head of Security"))
print("---------------------------------\n")
# The code in this event is executed every time a valid commands catches an error
#commands.Cog.listener()
async def on_command_error(context, error):
raise error
#commands.Cog.listener()
async def on_message(self, message):
await self.client.handler.propagate(message)
if self.client.tracker.is_spamming(message):
await message.delete()
await message.channel.send(f"{message.author.mention} has been automatically kicked for spamming.")
await message.author.kick()
await self.client.process_commands(message)
def setup(client):
client.add_cog(AntiSpamBot(client))
Error
Extension 'cogs.anti_spam' raised an error: ImportError: cannot import name 'AsyncMock' from 'unittest.mock' (/Library/Frameworks/Python.framework/Versions/3.7/lib/python3.7/unittest/mock.py)
I've no experiences using cogs and its bit confusing me. Any kind of help would help me to sort this out! Thanks in advance!
I do not believe this is a cog registration issue. I believe this is an import error from some of the dependencies in your cog file. I googled your error and found something similar, I recommend checking it out here for some more information.
As a blanket statement, I would double check that you have mock installed, and that you're installing it on the version of Python that you think you're installing it on. It can get wonky if you have multiple python versions insealled.
Also, on an unrelated note:: It is best to avoid running multiple bot instances in one python file, but I can help you do it the best way possible.
For starters, you have to realize that Client.run is an abstraction of a couple of more lower level concepts.
There is Client.login which logs in the client and then Client.connect which actually runs the processing. These are coroutines.
asyncio provides the capability of putting things in the event loop for it to work whenever it has time to.
Something like this e.g.
loop = asyncio.get_event_loop()
async def foo():
await asyncio.sleep(10)
loop.close()
loop.create_task(foo())
loop.run_forever()
If we want to wait for something to happen from another coroutine, asyncio provides us with this functionality as well through the means of synchronisation via asyncio.Event. You can consider this as a boolean that you are waiting for:
e = asyncio.Event()
loop = asyncio.get_event_loop()
async def foo():
await e.wait()
print('we are done waiting...')
loop.stop()
async def bar():
await asyncio.sleep(20)
e.set()
loop.create_task(bar())
loop.create_task(foo())
loop.run_forever() # foo will stop this event loop when 'e' is set to true
loop.close()
Using this concept we can apply it to the discord bots themselves.
import asyncio
import discord
from collections import namedtuple
# First, we must attach an event signalling when the bot has been
# closed to the client itself so we know when to fully close the event loop.
Entry = namedtuple('Entry', 'client event')
entries = [
Entry(client=discord.Client(), event=asyncio.Event()),
Entry(client=discord.Client(), event=asyncio.Event())
]
# Then, we should login to all our clients and wrap the connect call
# so it knows when to do the actual full closure
loop = asyncio.get_event_loop()
async def login():
for e in entries:
await e.client.login()
async def wrapped_connect(entry):
try:
await entry.client.connect()
except Exception as e:
await entry.client.close()
print('We got an exception: ', e.__class__.__name__, e)
entry.event.set()
# actually check if we should close the event loop:
async def check_close():
futures = [e.event.wait() for e in entries]
await asyncio.wait(futures)
# here is when we actually login
loop.run_until_complete(login())
# now we connect to every client
for entry in entries:
loop.create_task(wrapped_connect(entry))
# now we're waiting for all the clients to close
loop.run_until_complete(check_close())
# finally, we close the event loop
loop.close()
I'm setting up a listener for Hyperledger Sawtooth events with a pyzmq dealer socket and the provided asyncio functionality. Currently futures are returned but only sometimes finished, even though messages are sent to the Socket.
Weirdly this works for the connection message (only when sleeping before it as shown below) but not for event messages. I implemented this already with JavaScript and it works without problems. It seems that the issue does not lie with Sawtooth but rather in pyzmq's implementation of asyncio functionality or in my code.
class EventListener:
def __init__(self):
...
ctx = Context.instance()
self._socket = ctx.socket(zmq.DEALER)
self._socket.connect("tcp://127.0.0.1:4004")
async def subscribe(self):
...
await self._socket.send_multipart([connection_msg])
async def receive(self):
# Necessary wait otherwise the future is never finished
await asyncio.sleep(0.1)
resp = await self._socket.recv_multipart()
handle_response(resp)
async def listen(self):
while True:
# here sleep is not helping
# await asyncio.sleep(0.1)
# follwing await is never finished
resp = await self._socket.recv_multipart()
handle_response(resp)
...
listener = listener.EventListener()
await asyncio.gather(
listener.receive(), listener.subscribe())
await asyncio.create_task(listener.listen())
...
Debugging shows that the returned Future object is never changed from a pending to a finished state. So, is my code incorrect, do I need to await messages differently or is it possible that something is wrong with pyzmq's asyncio functionality? Also, why do I need to sleep in receive(), isn't that why we have asyncio?
There are too many queries, this answer may not address all of them. Hope at least this will help others looking for a way to setup event listeners.
The Hyperledger Sawtooth python SDK provides option for clients to subscribe to the events. The SDK part of code that does what you're trying to do can be found at https://github.com/hyperledger/sawtooth-sdk-python/blob/master/sawtooth_sdk/messaging/stream.py
The example code to use the Hyperledger Sawtooth python SDK for event subscription can be found here https://github.com/danintel/sawtooth-cookiejar/blob/master/events/events_client.py
I have a server script running:
async def give_time(websocket,path):
while True:
await websocket.send(str(datetime.datetime.now()))
await asyncio.sleep(3)
start_server = websockets.serve(give_time, '192.168.1.32', 8765)
asyncio.get_event_loop().run_until_complete(start_server)
asyncio.get_event_loop().run_forever()
which is working fine, just sending the current time every 3 seconds.
I can receive that string from the client which runs this code:
async def hello(): #takes whatever text comes through the websocket and displays it in the socket_text label.
async with websockets.connect('ws://wilsons.lan:8765') as ws:
while True:
text = await ws.recv()
logger.info('message received through websocket:{}'.format(text))
socket_text.configure(text=text) #socket_text is a tkinter object
loop = asyncio.new_event_loop()
def socketstuff():
asyncio.set_event_loop(loop)
asyncio.get_event_loop().run_until_complete(hello())
t = threading.Thread(target=socketstuff,daemon=True)
It runs in a thread so that I can run tkinter.mainloop in the main thread. This is the first time I ever used threading so I may be getting it wrong, but it seems to work at present.
What I need to do, is to be able to send a message down the websocket based on a tkinter event - currently just clicking a button next to a text box, but eventually more complex things. The clicking part works fine.
I'm having a lot of trouble with the sending of the message. I've tried a lot of different things, with and without async and await, although that may have just been panic.
The main problem seems to be that I can't access ws from outside that hello() function. This makes sense as I'm using the with context manager. However, if I just use ws = websockets.connect('ws://host') then I get a websockets.py35.client.Connect object which wen I try to use the send (or indeed recv) methods, I get a object has no attribute 'send' error.
I hope this is enough info - will happily post anything else required!
It turns out that the best way of solving this is not to use threads.
This post helped me solve it. It shows that you can run, in a coroutine, one iteration of the tkinter mainloop:
async def do_gui(root,interval=0.05):
while True:
root.update()
await asyncio.sleep(interval)
However, the best way of getting a tkinter event to spawn a websocket message is to use asyncio.queue. Making the tkinter callback add an item to the queue using put_nowait() and having a coroutine which runs at the same time as do_gui which uses await queue.get() to get the message from the queue works for me.