how to automatically delete channels with a discord bot - python

I'm doing a python bot for discord. it create an delete channel according to player instructions.
I want to create a garbage collector that test all the server.channels and delete the outdated one.
I do :
async def waitTimer():
while True:
await asyncio.sleep(10)
regex = re.compile(r"[0-9]*_[a-z0-9]*-[0-9]*") #nom des channels de raid
for cCurrent in client.get_all_channels():
if regex.match(cCurrent.name):
numRaid = int(cCurrent.name[0])
cRaidCurrent = cRaids[numRaid]
date = datetime.datetime.now()
print (cRaidCurrent.raid.fin.timestamp())
print (date.timestamp())
if cRaidCurrent.raid.fin < date:
if cRaidCurrent.retirerRaid():
cId = cRaidCurrent.com.id
await removeFromListe(cRaidCurrent)
await client.delete_channel(client.get_channel(cId))
cCurrent = 0
Sometimes it pass and sometimes I get this error :
for cCurrent in client.get_all_channels():
File "/Library/Frameworks/Python.framework/Versions/3.6/lib/python3.6/site-packages/discord/client.py", line 581,
in get_all_channels
for channel in server.channels:
RuntimeError: dictionary changed size during iteration
If I understand it clearly the client.get_all_channels is a dictionary and I can't remove the channels during the iteration ... So the question is what other possibilities do I have to remove those channel ?

Thanks Patrick Haugh for the answer that work perfectly.
In the end the deleting operation needs to be done in 2 times. Here is the code if anyone needs it :
for cCurrent in client.get_all_channels():
if regex.match(cCurrent.name):
numRaid = getNumChannel(cCurrent.name)
cRaidCurrent = cRaids[numRaid]
now = datetime.datetime.now()
if cRaidCurrent.raid.fin < now:
toDelete.append(cRaidCurrent)
for cRaidCurrent in toDelete:
cId = cRaidCurrent.id
cRaidCurrent.retirerRaid()
await removeCRaid(cRaidCurrent)
del cRaids[cId]

Related

discord.py updating a document in mongodb

I'm trying to make a command that allows the user to update a reason on a warn to something else and I don't know how to update documents in mongodb
check = list(warndb.warn_logs.find({"case_id": caseid}))
for w in check:
reason = w.get('reason')
caseid = w.get('case_id')
change = {{"reason": reason}, {"$set": {"reason": update}}}
w.update(change, upsert=False)
embed = discord.Embed(color=embedcolor, title="Successfully updated reason", description=f"Updated reason for case {caseid}")
embed.add_field(name="**Before:**", value=f"{reason}", inline=False)
embed.add_field(name="**Updated To:**", value=f"{update}", inline=False)
embed.timestamp = datetime.datetime.utcnow()
await ctx.send(embed=embed)
return
Here's the main code that I have to update documents but I'm getting a couple of errors and I don't know how to update documents. please help
So please provide full trace back but here is the code for update your mongo also check documention : Mongo doc.
In a cog it could look like this - arg would be the case id eg "!update_warn 123" then the arg would be 123 and also the case id
cluster = MongoClient('Your mongo link')
collection_name = cluster["Database Name"]["Collection Name"]
#commands.command()
async def update_warn(self,ctx,arg):
check = collection_name.find_one({"case_id": arg})
if check is None:
await ctx.reply("No Case was found!")
else:
reason = check['reason']
caseid = check['case_id']
update = "Your Updated Reason"
collection_name.update_one({'case_id':arg},{'$set':{'reason': update}})
embed = discord.Embed(color=embedcolor, title="Successfully updated reason", description=f"Updated reason for case {caseid}")
embed.add_field(name="**Before:**", value=f"{reason}", inline=False)
embed.add_field(name="**Updated To:**", value=f"{update}", inline=False)
embed.timestamp = datetime.datetime.utcnow()
await ctx.send(embed=embed)

loop attribute cannot be acessed in non-async contexts with discord.py

When I try to run this code
`
import json
import os
import random
from pprint import pprint
import aiohttp
import discord
import requests
from discord.ext import commands
from dotenv import load_dotenv
from mojang import api
# Functions
# Sends a Get request to a given url
def get_info(call):
r = requests.get(call)
return r.json()
# Get the sum of coins in the bazaar
def get_bazaar_buy_order_value(bazaar_data):
sum_coins = 0
price_increase_threshold = 2
buy_order_values = []
# For every product
for item_name, item_data in bazaar_data.get("products", {}).items():
item_sum_coins = 0
# For every buy order
for idx, buy_order in enumerate(item_data.get("buy_summary", [])):
# If its the best price
if(idx == 0):
item_expected_value = buy_order.get("pricePerUnit", 0)
item_sum_coins += buy_order.get("amount", 0) * buy_order.get("pricePerUnit", 0)
# If its not the best price, check for reasonable price
else:
if(buy_order.get("pricePerUnit", 0) < (item_expected_value * price_increase_threshold)):
item_sum_coins += buy_order.get("amount", 0) * buy_order.get("pricePerUnit", 0)
buy_order_values.append((item_name, item_sum_coins))
sum_coins += item_sum_coins
sort_bazaar_buy_orders_by_value(buy_order_values)
return sum_coins
# Sorts and displays a list of buy order items by total value
def sort_bazaar_buy_orders_by_value(buy_order_values):
# Sort items by values
buy_order_values.sort(key = lambda x: -x[1])
# Display items and values
for (item_name, item_sum_coins) in buy_order_values:
print(f"{item_name.ljust(30, ' ')} | {round(item_sum_coins):,}")
return
# Returns Bazaar data
def get_bazaar_data():
return get_info("https://api.hypixel.net/skyblock/bazaar")
# Returns a specific item from the Bazaar
def get_bazaar_item():
return
# Returns auction info from player uuid
def get_auctions_from_player(uuid):
return get_info(f"https://api.hypixel.net/skyblock/auction?key={API_KEY}&player={uuid}")
# Returns current mayor/election data
def get_election_data():
return get_info(f"https://api.hypixel.net/resources/skyblock/election")
# Returns a list of player profiles
def get_profiles_data():
return get_info(f"https://sky.shiiyu.moe/api/v2/profile/{example_uuid}")
# Returns player UUID when prompted with the name
async def get_uuid(name):
return get_info(f"https://sky.shiiyu.moe/api/v2/profile/{name}")
# Discord Functions / Vars
load_dotenv()
TOKEN = os.getenv('DISCORD_TOKEN')
GUILD = os.getenv('DISCORD_GUILD')
client = discord.Client(intents=discord.Intents.default())
intents = discord.Intents.all()
bot = commands.Bot(command_prefix='/',intents=intents)
# Hypixel Vars
Item = "Diamond"
API_FILE = open("API_KEY.json","r")
example_name = "4748"
example_uuid = "147ab344d3e54952b74a8b0fedee5534"
uuid_dashed = "147ab344-d3e5-4952-b74a-8b0fedee5534"
API_KEY = json.loads(API_FILE.read())["API_KEY"]
example_player_uuid = "147ab344d3e54952b74a8b0fedee5534"
auctions_player_url = f"https://api.hypixel.net/skyblock/auction?key={API_KEY}&player={example_player_uuid}"
# Commands
#bot.command(name='bazaar', description = "Gives a detailed readout of a certain item in the bazaar", brief = "Get data of an item in bazaar")
async def bazaar(ctx):
await ctx.send(get_bazaar_data())
await ctx.send(API_KEY)
#bot.command(name="bazaartotal", description = "Show the total amount of coins on the bazaar at any given point", brief = "Shows the amount of coins in the bazaar")
async def baztot(ctx):
await ctx.send(get_bazaar_buy_order_value(get_bazaar_data()))
#bot.command(name = "apikey", description = "Gives 4748's API key, make sure to remove me once publicly availible!", brief = "API Key")
async def key(ctx):
await ctx.send(API_KEY)
#bot.command(name = "profiles", description = 'Get a list of player profiles and data about them', brief = "List player profiles")
async def prof(ctx):
await ctx.send("Username to check?")
message = client.wait_for('message', check=lambda m: m.user == ctx.user)
username = str(message.content)
uuid = get_uuid(username)
pprint(uuid)
await ctx.send(uuid)
bot.run(TOKEN)
I get this error
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: 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
Anyone have a fix for this? The bot runs normally, but once I try to run /profiles it gives me that error. Also, other commands work fine, but when I try to access an api with a
Changed my code multiple times, putting the get_uuid command in async, and googling for a few hours. any help is appreciated!
Your Bot variable is called bot, but you're using client in your wait_for statement.
You've got both a discord.Client ("client") and a commands.Bot ("bot") instance. This doesn't make a whole lot of sense. If you only need Client features then use Client, if you want Bot features then use Bot. You can't use both at the same time.
Also, wait_for is a coroutine, so you should await it.
# Yours:
message = client.wait_for('message', check=lambda m: m.user == ctx.user)
# ^^^^^^^
# Missing await keyword & wrong bot variable
# Correct:
message = await bot.wait_for(...)
# ^^^^^ ^^^
Docs: https://discordpy.readthedocs.io/en/stable/api.html?highlight=wait_for#discord.Client.wait_for
PS requests is blocking and will make your whole bot freeze. Consider looking into an asynchronous http library like aiohttp.
try to do this:
message = await client.wait_for('message', check=lambda m: m.user == ctx.user)
I advise you to remove the 'name' from the slash command argument, it's better to just name the function and add an additional check for the channel 'm.channel == ctx.channel' to wait_for and split the file into several

Discord.py ... trying to find the users CURRENT status.. not status on change or thru discord.Member

Unless there is a way to do it through discord.Member with what im currently doing?
current code..
#tasks.loop(seconds = 5) # repeat after every 60 seconds
async def checkAFK():
global rTotalPlayers
global cTotalPlayers
with open('playerPop.json') as f:
playerPop = json.load(f)
msgChannel = await client.fetch_channel(variables['pickup'])
for i in list(cTotalPlayers):
cTotalPlayers[i] = cTotalPlayers[i] - 1
if(cTotalPlayers[i] < 0):
member = await client.fetch_user(i)
#print(member.activities)
print(i)
del rTotalPlayers[playerPop[str(i)][0]]
del cTotalPlayers[i]
await msgChannel.send("<#" + str(i) + "> has been removed from the pickup due to being AFK.")
print(cTotalPlayers)
PopulateTable()
await msgChannel.send("```" + msg + "```")
print(cTotalPlayers)
What im trying to do is loop through a dictionary of players and after 60 seconds.. (in this case 5 just for testing purposes) itll subtract one.. what i wanna do is when it gets to 0.. i want it to check whether or not they have a green or moon by their name.. if moon and less than 0, itll remove them from a "pickup game". I am trying to find this currently thru await client.fetch_user and the commented line is where im checking outputs.. anyone know how to do it via the approach im already taking? Thanks
Above is fixed.. Next problem is getting the status to show correctly.. always showing offline.. code below.. dev portal stuff: https://imgur.com/a/EA3deJT
intents = discord.Intents.all()
intents.members = True
intents.presences = True
#intents = discord.Intents(members = True, presences = True)
client = commands.Bot(command_prefix = ["!", "+", "-"], case_insensitive=True, intents= intents)
You can get their status by using Member.status
This returns a Status object where the property returns a True or False value.
status = user.status
if status.online or status.idle:
pass # Do something here

discord.py Changing file size to add custom emoji

What I'm trying to do: I have an on_message event for a 'global chat' command that I have created. This would send messages to any server that was in the json, currently supporting images, multiple lines of text, and custom emojis (as long as the bot shared a server with said emoji).
My problem: As you can see in the image above, one person, 'S o u p', has their profile picture replaced with a duck emoji. For context's sake, their avatar is not that of a duck emoji. However, if you were to look at the most recent message, the profile picture is shown. I believe that the problem lies in the size of the message.author's avatar. The PIL library may be a good solution, but I do not want to save the image.
Code I have tried:
avatar_bytes = await message.author.avatar_url.read()
guildem = client.get_guild(738285544911405107)
try:
em = await guildem.create_custom_emoji(name=f'{message.author.name}', image=avatar_bytes)
except:
em = '🦆'
The code above is the one I had used in the image example I had provided.
avatar = message.author.avatar_url_as(size=128) # resize avatar
avatar_bytes = await avatar.read() # then read as bytes?
Above code gives exact same results. Avatars are turned into duck emojis.
Others I have looked at:
How to crop an image to a shape for eg. circle using Pillow in Discord.py?: Messages did not send when using the code here. I assumed that using the PIL library would help reduce the file size significantly one way or the other without saving the image.
How to reduce the image file size using PIL: All the answers required saving the image, which I do not want to do. I tried tweaking the code a little bit, but caused all avatars to be turned into duck emojis.
If necessary, I will include the entire on_message event. However, this does not feel relevant nor necessary to include since the emojis are only a small portion of said event.
Edit: Here is the code to the entire on_message event.
#client.event
async def on_message(message):
if message.author == client.user: # stop bot from replying to self
return
else:
if not message.guild: # prevent errors when messaging in bot dm's
return
f = open("global text.json") # existing json
"""
{
"747061937673732097": 765059798131539988,
"724783642546536458": 818707151122989119,
"761524419003809832": 813963773295591485,
"755309786232258630": 760381389685784587,
"738285544911405107": 738285544911405110
}
"""
data = json.load(f)
for i in data:
if str(i) == str(message.guild.id):
if data[str(message.guild.id)] == message.channel.id:
avatar_bytes = await message.author.avatar_url.read()
guildem = client.get_guild(738285544911405107) # guild to upload emojis to
try:
em = await guildem.create_custom_emoji(name=f'{message.author.name}', image=avatar_bytes) # create emoji using avatar_bytes in guildem server
except:
em = '🦆'
### functions ###
def censor(text): # censor words for safety
banned = ["test"] # bad words were here, removed for stackoverflow reasons
text = text.split()
nearly = []
for word in text:
if any(item in word.lower() for item in banned):
word = word.lower()
replaced = word.replace(word, ('#'*(len(word))))
nearly.append(replaced)
else:
nearly.append(word)
message = ' '.join(nearly)
return message
def parag(string): # check for \n
length = [string]
if string in ["", " ", " ", " "]:
return "<:transparent:774136812813811713>"
final_list = []
if '\n' in string:
length = string.split('\n')
for item in length:
thing = censor(item)
final_list.append(thing)
return paragger(final_list)
def paragger(list: list): # returns original message with \n
message = ""
for item in list:
message += f"> {item}\n"
return message
msg = parag(message.content)
### attachment handling ###
embed_list = []
if message.attachments:
if len(message.attachments) == 1:
embed = discord.Embed(title=f"{message.author}'s file", color=0xc39ce6)
for file in message.attachments:
embed.set_image(url=file.url)
embed_list.append(embed)
else:
count = 1
for file in message.attachments:
embed = discord.Embed(title=f"{message.author}'s file {count}")
embed.set_image(url=file.url)
embed_list.append(embed)
for i in data:
if str(i) != str(message.guild.id):
channel = client.get_channel(data[str(i)])
try:
await channel.send(f"{em} {message.author} \n{msg}", allowed_mentions=allowed_mentions)
if len(embed_list) > 0:
for embed in embed_list:
await channel.send(embed=embed)
except:
await channel.send(f"{em} {message.author} \n> {em}", allowed_mentions=allowed_mentions) # only happens if message is larger than 2000 char
if em != '🦆':
await em.delete() # delete the custom emoji to make space for more emojis later on
break
await client.process_commands(message) # process, prevent commands breaking
Your issue is here:
em = await guildem.create_custom_emoji(name=f'{message.author.name}', image=avatar_bytes)
You see, discord.py is looking for an emoji name with alphanumeric characters or underscores. S o a p does not fit this requirement, hence the error you have gotten. The following fixes the issue by replacing said characters with an underscore.
avatar_bytes = await message.author.avatar_url_as(size=128).read() # size=128 makes the size smaller for emojis
name = message.author.name.replace('-', '_').replace(' ', '_') # Replace the erroring characters
try:
em = await guildem.create_custom_emoji(name=f'{name}', image=avatar_bytes)
except:
em = '🦆'
EDIT: After taking another look, the problem wasn't what I thought after all, the issue with the user named S o a p was that it had non alphanumeric characters, I've updated my answer to fix this.
You don't need to save image. This is how you send images to discord without saving it:
import io
import PIL
image = PIL.Image.new("RGB", (200, 200), (255, 255, 255))
bytes_io = io.BytesIO()
image.save(bytes_io, format="png")
bytes_io.seek(0)
file = discord.File(bytes_io, "white.png")
#now you can send this file to discord
await ctx.send(file=file)

Python Split string after 2000 characters

I'm working on a discord bot that can return the summary of Wikipedia articles. but there's an issue, some summaries are longer than 2000 characters, which exceeds discord's character limit. is there a way I can split my string into multiple messages?
The string I want to split is str(wikipedia.search(query)) (the entire thing is in an embed block):
embedVar = discord.Embed(title=str(query)
description=str(wikipedia.search(query)), color=0x9CAFBE)
await message.channel.send(embed=embedVar)
heres a solution:
article = "5j5rtOf8jMePXn7a350fOBKVHoAJ4A2sKqUERWxyc32..." # 4000 character string i used
chunklength = 2000
chunks = [article[i:i+chunklength ] for i in range(0, len(article), chunklength )]
print(len(chunks))
output
2
an extension on how you could use it:
for chunk in chunks:
embedVar = discord.Embed(title="article name",
description=chunk ,
color=0x9CAFBE)
await ctx.send(embed=embedVar)
To expand what Darina commented, splice the string before you post it on discord.
posted_string = str(wikipedia.search(query))[:2000]
embedVar = discord.Embed(title=str(query),
description=posted_string,
color=0x9CAFBE) await message.channel.send(embed=embedVar)
A 'string' is an array of characters. When you assign it to another variable by using [:2000], you're telling the interpreter to put all the characters from the beginning of the array up to, but not including, the 2000th character.
EDIT:
As Ironkey mentions in the comments, hardcoding values isn't viable since we don't know exactly how many characters an article has. Try this untested code instead:
wiki_string = str(wikipedia.search(query))
string_length = len(wiki_string)
if string_len < 2000:
embedVar = discord.Embed(title=str(query),
description=wiki_string,
color=0x9CAFBE)
await message.channel.send(embed=embedVar)
else:
max_index = 2000
index = 0
while index < (string_length - max_index):
posted_string = wiki_string[index:max_index]
embedVar = discord.Embed(title=str(query),
description=posted_string,
color=0x9CAFBE)
await message.channel.send(embed=embedVar)
index = index + max_index
posted_string = wiki_string[index-max_index:]
embedVar = discord.Embed(title=str(query),
description=wiki_string,
color=0x9CAFBE)
await message.channel.send(embed=embedVar)
If this doesn't work, please let me know where it failed. Thanks!

Categories

Resources