Send Discord Message from a looped function - python-3.x

So I've run into a wall trying to get this code to work.
I'm writing a fairly simple Discord bot that does two things.
The main part of the bot is to run an API request when the bot initializes to get a base value of a return. I then have a function that I want to run every X seconds, that checks the value again, and if it changes, send a message to a Discord Channel and then update the variable to the new value, so that the loop can report when it changes again.
The second part, which is not as critical but I still want to include it, is that a user can send a bot command to report the current value of the return.
Problem #1: Arises when I start the loop without the code to send the result via discord message. If i don't include the line to send the discord message in the loop code, the loop runs and prints out the results to the console (for test purposes). However, when the loop is running, I can no longer get the bot to respond to the command.
Problem #2: If I include the code to send the discord message, the whole thing immediately fails and gives me the following error:
File "bot.py", line 67, in <module>
asyncio.run(update_status())
File "/usr/lib/python3.7/asyncio/runners.py", line 43, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.7/asyncio/base_events.py", line 584, in run_until_complete
return future.result()
File "bot.py", line 63, in update_status
await channel.send(f'Server status is still: {updatestatus}.')
File "/usr/local/lib/python3.7/dist-packages/discord/abc.py", line 905, in send
nonce=nonce, allowed_mentions=allowed_mentions)
File "/usr/local/lib/python3.7/dist-packages/discord/http.py", line 185, in request
async with self.__session.request(method, url, **kwargs) as r:
File "/usr/local/lib/python3.7/dist-packages/aiohttp/client.py", line 1012, in __aenter__
self._resp = await self._coro
File "/usr/local/lib/python3.7/dist-packages/aiohttp/client.py", line 357, in _request
raise RuntimeError('Session is closed')
RuntimeError: Session is closed
Problem #3: More of a minor nuisance, but if I set my code up so that it works in Problem #1, the loop does not start automatically, but only after I press Ctrl+C (note: I am writing the code on a Raspi 4 via PuTTY.
Any thoughts?
Full Code:
# bot.py
import os
import requests
import discord
import json
import asyncio
from dotenv import load_dotenv
from discord.ext import commands
from discord.ext import tasks
from discord.utils import get
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
CLIENTID = os.getenv('CLIENT_ID')
CLIENTSECRET = os.getenv('CLIENT_SECRET')
bot = commands.Bot(command_prefix='+')
#bot.event
async def on_ready():
print(f'{bot.user.name} has connected to Discord!')
channel = bot.get_channel(795162312112865280)
await channel.send('MurkyBot has connected')
def create_access_token(client_id, client_secret, region = 'us'):
data = { 'grant_type': 'client_credentials' }
response = requests.post('https://%s.battle.net/oauth/token' % region, data=data, auth=(client_id, client_secret))
return response.json()
tokenresponse = create_access_token(CLIENTID, CLIENTSECRET)
accesstoken = tokenresponse["access_token"]
print(accesstoken)
initialrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
initialrequest = initialrequest.json()
initialstatus = initialrequest['status']['type']
print(f'Initial status: {initialstatus}')
#bot.command(name='status', help='Gets the current server status')
async def manual_status(ctx):
manualrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
manualrequest = manualrequest.json()
manualstatus = manualrequest['status']['type']
channel = bot.get_channel(795162312112865280)
await ctx.send(f'Current world server status is: {manualstatus}')
bot.run(TOKEN)
async def update_status():
while True:
global initialstatus
channel = bot.get_channel(795162312112865280)
updaterequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
updaterequest = updaterequest.json()
updatestatus = updaterequest['status']['type']
if updatestatus != initialstatus:
await channel.send(f'Server status has changed to: {updatestatus}!')
initialstatus = updatestatus
print('Status Change')
await asyncio.sleep(5)
else:
await channel.send(f'Server status is still: {updatestatus}.')
print('No Change')
await asyncio.sleep(5)
asyncio.run(update_status())

.run is a blocking call, meaning that everything stops and waits for it to complete. Therefore, the second part can only run after you ctrl+c, ending the script however.
You may want to check out discord.ext.tasks, it will have the intended functionality.
For what you're trying to do, here would be the updated code using discord.ext.tasks:
from dotenv import load_dotenv
from discord.ext import commands
from discord.ext import tasks
from discord.utils import get
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
CLIENTID = os.getenv('CLIENT_ID')
CLIENTSECRET = os.getenv('CLIENT_SECRET')
bot = commands.Bot(command_prefix='+')
#tasks.loop(seconds=60.0)
async def update_status():
while True:
global initialstatus
channel = bot.get_channel(795162312112865280)
updaterequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
updaterequest = updaterequest.json()
updatestatus = updaterequest['status']['type']
if updatestatus != initialstatus:
await channel.send(f'Server status has changed to: {updatestatus}!')
initialstatus = updatestatus
print('Status Change')
await asyncio.sleep(5)
else:
await channel.send(f'Server status is still: {updatestatus}.')
print('No Change')
await asyncio.sleep(5)
#bot.event
async def on_ready():
update_status.start()
print(f'{bot.user.name} has connected to Discord!')
channel = bot.get_channel(795162312112865280)
await channel.send('MurkyBot has connected')
def create_access_token(client_id, client_secret, region = 'us'):
data = { 'grant_type': 'client_credentials' }
response = requests.post('https://%s.battle.net/oauth/token' % region, data=data, auth=(client_id, client_secret))
return response.json()
tokenresponse = create_access_token(CLIENTID, CLIENTSECRET)
accesstoken = tokenresponse["access_token"]
print(accesstoken)
initialrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
initialrequest = initialrequest.json()
initialstatus = initialrequest['status']['type']
print(f'Initial status: {initialstatus}')
#bot.command(name='status', help='Gets the current server status')
async def manual_status(ctx):
manualrequest = requests.get(f'https://us.api.blizzard.com/data/wow/connected-realm/154?namespace=dynamic-us&locale=en_US&access_token={accesstoken}')
manualrequest = manualrequest.json()
manualstatus = manualrequest['status']['type']
channel = bot.get_channel(795162312112865280)
await ctx.send(f'Current world server status is: {manualstatus}')
bot.run(TOKEN)

Related

Discord bot isn't playing audio when commanded

I am using python 3.6
I am making a discord bot to play music.
Whenever I give it the 'play' command the discord app shows that the bot is producing audio but I don't hear any audio. I checked the settings and maximized all the audio-related settings.
When I go back to pycharm, I see this error in the 'run' tab:
Traceback (most recent call last):
File "/Volumes/Mahmoud-Disk/MyProfile/Library/Python/3.8/lib/python/site-packages/discord/ext/commands/core.py", line 229, in wrapped
ret = await coro(*args, **kwargs)
File "/Volumes/Mahmoud-Disk/MyProfile/Desktop/Discord Bot/Discord-Bot/main.py", line 72, in play
await ctx.send(embed = discord.Embed(
TypeError: __init__() got an unexpected keyword argument 'author'
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/Volumes/Mahmoud-Disk/MyProfile/Library/Python/3.8/lib/python/site-packages/discord/ext/commands/bot.py", line 1349, in invoke
await ctx.command.invoke(ctx)
File "/Volumes/Mahmoud-Disk/MyProfile/Library/Python/3.8/lib/python/site-packages/discord/ext/commands/core.py", line 1023, in invoke
await injected(*ctx.args, **ctx.kwargs) # type: ignore
File "/Volumes/Mahmoud-Disk/MyProfile/Library/Python/3.8/lib/python/site-packages/discord/ext/commands/core.py", line 238, in wrapped
raise CommandInvokeError(exc) from exc
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: __init__() got an unexpected keyword argument 'author'
Here is my code:
import discord
from discord.ext import commands
import wavelink
client = commands.Bot(command_prefix = ".", intents = discord.Intents.all())
class CustomPlayer (wavelink.Player):
def __init__(self):
super().__init__()
self.queue = wavelink.Queue()
#client.event
async def on_ready():
client.loop.create_task(connect_nodes()) #HTTPS and Websocket operations
async def connect_nodes(): #Helper function
await client.wait_until_ready()
await wavelink.NodePool.create_node(
bot = client ,
host = "127.0.0.1" ,
port = 2333 ,
password = "yk I can't "
)
#client.event
async def on_wavelink_node_ready(node = wavelink.Node):
print(f"Node: <{node.identifier}> is ready!")
#client.command()
async def connect(ctx):
vc = ctx.voice_client
try:
channel = ctx.author.voice.channel
except AttributeError:
return await ctx.send("Please join a channel to connect.")
if not vc:
await ctx.author.voice.channel.connect(cls = CustomPlayer())
else:
await ctx.send("The bot is already connected to a voice channel.")
#client.command()
async def disconnect(ctx):
vc = ctx.voice_client
if vc:
await vc.disconnect()
else:
await ctx.send("Bot is not connected to a voice channel.")
#client.command()
async def play(ctx, *, search: wavelink.YouTubeTrack):
vc = ctx.voice_client #represents a discord voice connection
if not vc:
custom_player = CustomPlayer()
vc: CustomPlayer = await ctx.author.voice.channel.connect(cls = custom_player)
if vc.is_playing():
vc.queue.put(item = search)
await ctx.send(embed = discord.Embed(
title = search.title,
url = search.uri,
author = ctx.author,
description = f"Queued {search.title} in {vc.channel}"
))
else:
await vc.play(search)
await ctx.send(embed = discord.Embed(
title = search.title,
url = search.uri,
author = ctx.author,
description = f"Playing {vc.source.title} in {vc.channel}"))
I have no idea what to do to fix this problem.
Simply, I removed the
author = ctx.author,
That are in the "play()" command both times

Discord.py : coroutine was never awaited

Can't seem to fix this code where I'm trying to get the name of the discord server from it's invite link.
import discord
from discord.ext import commands
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
async def get_invite_name(link):
name = await client.fetch_invite(link).guild.name
return name
print(get_invite_name('https://discord.com/invite/veefriends'))
Tried putting await infront of client.fetch_invite(link).guild.name but it didn't work. I don't understand async.
Tried what #matthew-barlowe suggested but it spit out more errors -
File "~/DLG/sandbox.py", line 14, in <module>
print(asyncio.run(get_invite_name('https://discord.com/invite/veefriends')))
File "/usr/lib/python3.10/asyncio/runners.py", line 44, in run
return loop.run_until_complete(main)
File "/usr/lib/python3.10/asyncio/base_events.py", line 646, in run_until_complete
return future.result()
File "~/DLG/sandbox.py", line 12, in get_invite_name
return await invite.guild.name
AttributeError: 'coroutine' object has no attribute 'guild'
sys:1: RuntimeWarning: coroutine 'Client.fetch_invite' was never awaited
You have to await the async wrapper function get_invite_name as well. Running it in asyncio.run(get_invite_name('https://discord.com/invite/veefriends')) will handle that in a non async setting. You will need to import asyncio as well.
import discord
import asyncio
from discord.ext import commands
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
async def get_invite_name(link):
response = await client.fetch_invite(link)
name = response.guild.name
return name
print(asyncio.run(get_invite_name('https://discord.com/invite/veefriends')))
If you were calling it in another async function then just await get_invite_name('https://discord.com/invite/veefriends') would be sufficient

Discord bot runs differently when called in-file and from outside file

I am trying to integrate a discord bot into an app such that when the user of the app does something, it calls a function that uploads something to discord. This means that the bot only has an on_ready function, and then a client.close() at the end. I wrapped the entire thing inside its own function for easy access from the main app file. Here's the code
def uploader(filePath):
homeDir = str(Path.home())
with open(f'{homeDir}/Library/Application Support/FileBreakerApp/userInfo.json', 'r') as json_file:
userInfo = json.load(json_file)
bot = discord.Client()
#bot.event
async def on_ready():
botChannel = await bot.fetch_channel(userInfo["botChannel"])
await botChannel.send("This confirms that uploader is running")
file = SplitFile(filePath)
for name in file.chunkNames:
fileObj = discord.File(
open(f'{homeDir}/Library/Application Support/FileBreakerApp/filePieces/{name}', 'rb'),
filename=name)
await botChannel.send(file=fileObj)
print("sent file")
await botChannel.send(f'successfully uploaded:{file.fullName}')
await bot.close()
bot.run('CENSORED')
What I want it to do is after it sends "sucessfully uploaded..." it will close the bot, so the code can move on from bot.run() and end the method. This worked perfectly when I called the outermost function within the file it was written in, but when I called it from the main file it threw the following error:
Traceback (most recent call last):
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/tkinter/__init__.py", line 1884, in __call__
return self.func(*args)
File "/Users/nathanwolf/Documents/coding/PycharmProjects/Discord-File-Breaker/Interface.py", line 62, in uploadFile
uploader(filename)
File "/Users/nathanwolf/Documents/coding/PycharmProjects/Discord-File-Breaker/Uploader.py", line 27, in uploader
bot.run('CENSORED')
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/site-packages/discord/client.py", line 635, in run
loop.add_signal_handler(signal.SIGINT, lambda: loop.stop())
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/unix_events.py", line 89, in add_signal_handler
self._check_closed()
File "/Library/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/base_events.py", line 510, in _check_closed
raise RuntimeError('Event loop is closed')
RuntimeError: Event loop is closed
I read some sources saying that it had to do with enabling server members intent, so I did that on the developer portal, to no avail. Few others seem to have this problem, and one source said it had to do with bot.close(). Given that, here is my current theory:
I run several others of this kind of function in the app (a bot nested in a function), all with the same token so I could run them through one bot user. By the time this runs, another function like it has been run already, with no issues. This suggests that when it closes the event loop, the next call of bot.run doesn't reopen it.
How can I reopen the bot's event loop at the beginning of this function?(bot.start(TOKEN) doesn't appear to work)
Thanks!
You essentially need to create a new event loop before running this. You can either do this in the function or better (imo) make this an async function and create an event loop in your main file.
def uploader(filePath):
import asyncio
homeDir = str(Path.home())
with open(f'{homeDir}/Library/Application Support/FileBreakerApp/userInfo.json', 'r') as json_file:
userInfo = json.load(json_file)
bot = discord.Client()
#bot.event
async def on_ready():
botChannel = await bot.fetch_channel(userInfo["botChannel"])
await botChannel.send("This confirms that uploader is running")
file = SplitFile(filePath)
for name in file.chunkNames:
fileObj = discord.File(
open(f'{homeDir}/Library/Application Support/FileBreakerApp/filePieces/{name}', 'rb'),
filename=name)
await botChannel.send(file=fileObj)
print("sent file")
await botChannel.send(f'successfully uploaded:{file.fullName}')
await bot.close()
loop = asyncio.new_event_loop()
loop.run_until_complete(bot.start('CENSORED')
loop.close()
OR
# main.py
import asyncio as aio
loop = aio.new_event_loop()
loop.run_until_complete(uploader("./YOUR/PATH/HERE"))
loop.close()
# your func
async def uploader(filePath):
homeDir = str(Path.home())
with open(f'{homeDir}/Library/Application Support/FileBreakerApp/userInfo.json', 'r') as json_file:
userInfo = json.load(json_file)
bot = discord.Client()
#bot.event
async def on_ready():
botChannel = await bot.fetch_channel(userInfo["botChannel"])
await botChannel.send("This confirms that uploader is running")
file = SplitFile(filePath)
for name in file.chunkNames:
fileObj = discord.File(
open(f'{homeDir}/Library/Application Support/FileBreakerApp/filePieces/{name}', 'rb'),
filename=name)
await botChannel.send(file=fileObj)
print("sent file")
await botChannel.send(f'successfully uploaded:{file.fullName}')
await bot.close()
await bot.start('CENSORED')

asyncpg + aiogram. cannot perform operation: another operation is in progress

How I can fix it? I played with it a lot and for a long time, but nothing came of it.
sql_db.py:
import asyncio
import asyncpg
LOG_PG = {"database": 'test_bot',
"user": 'misha',
"password": '1234',
"host": 'localhost'}
class Database:
SELECT_USER_LANG = "SELECT lang FROM lang_set WHERE user_id = $1 AND bot_token = $2"
def __init__(self, loop: asyncio.AbstractEventLoop):
self.pool = loop.run_until_complete(
asyncpg.create_pool(**LOG_PG)
)
async def get_lang(self, user_id, token):
search_d = [user_id, token]
res = await self.pool.fetchval(self.SELECT_USER_LANG, *search_d)
if res is None:
return "ru"
return res
I tried to insert this loop everywhere, run without it, multiple combinations in the code itself. But nothing changed. I do not know how to describe the problem in more detail
main.py:
from aiogram import Bot, Dispatcher
from aiogram.types import Message
import asyncio
from sql_db import Database
loop = asyncio.get_event_loop()
token = "TOKEN"
dp = Dispatcher()
bot = Bot(token=token, parse_mode="HTML")
db = Database(loop)
async def echo_msg(message: Message):
user_id = message.from_user.id
await message.send_copy(user_id)
await db.get_lang(user_id, token)
dp.message.register(callback=echo_msg)
if __name__ == '__main__':
dp.run_polling(bot, skip_updates=True)
error:
...
File "/home/mickey/Desktop/chat_admin/venv/lib/python3.8/site-packages/asyncpg/pool.py", line 867, in release
return await asyncio.shield(ch.release(timeout))
File "/home/mickey/Desktop/chat_admin/venv/lib/python3.8/site-packages/asyncpg/pool.py", line 224, in release
raise ex
File "/home/mickey/Desktop/chat_admin/venv/lib/python3.8/site-packages/asyncpg/pool.py", line 214, in release
await self._con.reset(timeout=budget)
File "/home/mickey/Desktop/chat_admin/venv/lib/python3.8/site-packages/asyncpg/connection.py", line 1367, in reset
await self.execute(reset_query, timeout=timeout)
File "/home/mickey/Desktop/chat_admin/venv/lib/python3.8/site-packages/asyncpg/connection.py", line 318, in execute
return await self._protocol.query(query, timeout)
File "asyncpg/protocol/protocol.pyx", line 323, in query
File "asyncpg/protocol/protocol.pyx", line 707, in asyncpg.protocol.protocol.BaseProtocol._check_state
asyncpg.exceptions._base.InterfaceError: cannot perform operation: another operation is in progress
Works through such a launch. It must be turned on through the aiogram. I do not know how to formulate, but I was lucky to understand the problem
...
data_ = {}
class Database:
def __init__(self, pool: asyncpg.create_pool):
self.pool = pool
async def get_lang(self, user_id, token):
search_d = [user_id, token]
async with self.pool.acquire() as conn:
res = await conn.fetchval(SELECT_USER_LANG, *search_d)
if res is None:
return "ru"
return res
async def create_pool():
pool = await asyncpg.create_pool(**LOG_PG)
data_["db"] = Database(pool)
async def echo_msg(message: Message):
user_id = message.from_user.id
await message.send_copy(user_id)
await data_["db"].get_lang(user_id, token)
dp.message.register(callback=echo_msg)
if __name__ == '__main__':
dp.startup.register(create_pool) # ANSWER
dp.run_polling(bot, skip_updates=True)

Discordpy welcome bot

So, i tried to make a bot that send embed to specific channel everytime user join my server.
The code is look like this
import discord
import asyncio
import datetime
from discord.ext import commands
intents = discord.Intents()
intents.members = True
intents.messages = True
intents.presences = True
bot = commands.Bot(command_prefix="a!", intents=intents)
#bot.event
async def on_ready():
print('Bot is ready.')
#bot.event
async def on_member_join(ctx, member):
embed = discord.Embed(colour=0x1abc9c, description=f"Welcome {member.name} to {member.guild.name}!")
embed.set_thumbnail(url=f"{member.avatar_url}")
embed.set_author(name=member.name, icon_url=member.avatar_url)
embed.timestamp = datetime.datetime.utcnow()
channel = guild.get_channel(816353040482566164)
await channel.send(embed=embed)
and i got an error
Ignoring exception in on_member_join
Traceback (most recent call last):
File "C:\Users\Piero\AppData\Roaming\Python\Python39\site-packages\discord\client.py", line 343, in _run_event
await coro(*args, **kwargs)
File "C:\Users\Piero\Documents\Discord\a-chan\achan_bot\main.py", line 24, in on_member_join
channel = guild.get_channel(816353040482566164)
NameError: name 'guild' is not defined
Anyone know what is wrong in my code?
First of all, looking at the discord.py documention, ctx is not passed to the on_member_join event reference. However, you can use the attributes of member which is passed in order to get the values which you need.
#bot.event
async def on_member_join(member):
embed = discord.Embed(
colour=0x1abc9c,
description=f"Welcome {member.name} to {member.guild.name}!"
)
embed.set_thumbnail(url=f"{member.avatar_url}")
embed.set_author(name=member.name, icon_url=member.avatar_url)
embed.timestamp = datetime.datetime.utcnow()
channel = member.guild.get_channel(816353040482566164)
await channel.send(embed=embed)
Interestingly enough, you did this perfectly for getting the guild name, but it seems you forgot to do the same when retrieving channel.
You did not define guild.
To define your guild you can do the following:
guild = bot.get_guild(GuildID)
It's the same method you used to define your channel, just for your guild now.
For further information you can have a look at the docs: https://discordpy.readthedocs.io/en/latest/api.html#discord.Client.get_guild
Also take into account that we do not have a paramter like ctx in an on_member_join event.
The event just has the parameter member in your case:
#bot.event
async def on_member_join(member): #Removed ctx

Resources