I'm trying to edit an embed by adding the user's name into the embed field. It works, however if there are more than one field it edits all of them. User can add and remove embeds using a command so I don't know what names these fields have.
I want it so that users can react by emojis and depending on which emoji they use, it adds their name to a field.
#bot.event
async def on_raw_reaction_add(payload):
msg = await bot.get_channel(payload.channel_id).fetch_message(payload.message_id)
if not msg.author.bot:
return
if not msg.embeds:
return
embed1 = msg.embeds[0]
embed_dict = embed1.to_dict()
for field in embed_dict["fields"]:
field["value"] += f" {payload.member.name},"
embed2 = discord.Embed.from_dict(embed_dict)
await msg.edit(embed=embed2)
You are iterating through every field, but only one of them needs to be changed.
You can check the PartialEmoji, which can be both standard or custom, used in the reaction through payload.emoji.
embed_dict = embed1.to_dict()
to_modify = {
discord.PartialEmoji.from_str('😂'): 0,
discord.PartialEmoji.from_str('😢'): 1
# etc
}
emoji_used = payload.emoji # returns PartialEmoji containing the emoji
number = to_modify[emoji_used] # select field to modify
embed_dict[number]["value"] += f" {payload.member.name},"
embed2 = discord.Embed.from_dict(embed_dict)
Related
So I'm still making a warning system but in the process I am having this issue with the embed showing the warning multiple times. Shown in the image provided.
https://i.stack.imgur.com/ks4Gm.png I'm not sure what could be causing this but I think it might be the for loop I made?
#client.hybrid_command(name = "warnings", with_app_command=True, description="View the warnings of a member", aliases=["punishments"])
async def warnings(ctx, member: discord.Member = None):
if member == None:
await ctx.reply("A Member is required")
else:
check = warndb.warn_logs.find_one({"user_id": member.id})
if check is None:
await ctx.reply("This user has no warns")
else:
reason = check["reason"]
moderator_id = check["moderator_id"]
embed = discord.Embed(color=embedcolor, title=f"{member.name}'s warnings")
for w in check:
embed.add_field(name=f"{reason}", value=f"<#{moderator_id}>", inline=False)
await ctx.send(embed=embed)
There is no error and it works fine it just shows the warning(s) multiple times
You query with find_one so there is no point in looping over check.
I would try to just remove the line for w in check:.
If it's suppose to be an array(change to find_all), maybe you manually adding data to the table when testing and it's displaying all previous warns you've added.
I figured it out finally. The issue is you need to turn the check = warndb.warn_logs.find_one({"user_id": member.id}) into a list by doing check = list(warndb.warn_logs.find_one({"user_id": member.id})). Once you do that you should use the .get() function to get the values of the keys
In this case:
for w in check:
reason = w.get('reason')
moderatorid = w.get('moderator_id')
guildid = w.get('guild_id')
caseid = w.get('_id')
embed.add_field(name=f"Case ID: {caseid}—Reason: {reason}", value=f"<#{moderatorid}>", inline=False)
So I was trying to make my own custom help command
main.py (get_prefix is another function I defined, but that shouldn't be the problem):
bot = commands.Bot(command_prefix = get_prefix, case_insensitive = True, intents = discord.Intents.all())
bot.remove_command("help") # remove the default help command to add a custom one
#bot.group(invoke_without_command = True) # custom help command
async def help(ctx):
await ctx.send(await help_embed(ctx.message))
customhelp.py:
import discord
import typing
async def create_help(cmd: str, desc: str, syntax: typing.List[str], prefix: str, examples: typing.List[str] = None, thumbnail_url: str = None, fields: typing.Dict[str, str] = None):
embed = discord.Embed(title = cmd, description = desc, color = discord.Color.random())
embed.set_thumbnail(url = thumbnail_url)
embed.add_field(name = "syntax", value = f"`{prefix}{syntax}`")
if examples is not None:
example_str = ""
for ex in examples:
example_str += f"\n`{prefix}{ex}`"
embed.add_field(name = "examples", value = example_str[1:])
if fields is not None:
for field in fields:
embed.add_field(name = field, value = fields[field])
return embed
async def help_embed(prefix: str):
return await create_help("help", f"This is a list of all commands. For more details of each command type `{prefix}help <command>`", ["help", "help <command>"], prefix, thumbnail_url = "https://i1.sndcdn.com/avatars-000378178157-omlzp3-t500x500.jpg")
but then when I tested it out on discord my bot sent this:
<discord.embeds.Embed object at 0x7f1552033820>
What is the cause of this problem? And how can I fix it?
Your issue is with how you are using the ctx.send() method, the definition of this method (from the docs) is
await send(content=None, *, tts=False, embed=None, file=None, files=None, delete_after=None, nonce=None, allowed_mentions=None, reference=None, mention_author=None)
As you can see, what you are currently passing to the command is only the content variable, which is typically what the bot will send, but you are passing to it a discord.embeds.Embed object (returned from the help_embed() and create_help functions. content is expecting to be a string, so that's why you see that as your output, it's just printing the object itself.
Now, what you are looking for is to send an embed, to do so, you will need to specify it in the send() command:
#bot.group(invoke_without_command = True) # custom help command
async def help(ctx):
await ctx.send(embed = await help_embed(ctx.message))
i made an avatar command for my bot but it doest work with mentioions, im trying to not use ' #client.command ' because it causes problems for the rest of the code
if message.content.startswith('+avatar'):
sender = message.author
name = sender.display_name
avatar_url = sender.avatar_url
embedVar = discord.Embed(title="**{}**".format(sender), description="Username: {}".format(name), color=0xec9e36)
embedVar.set_image(url = avatar_url)
await message.channel.send(embed=embedVar)
is what i have written and i cant find any ways of making it work, any kind of help would be appreciated!
I am guessing you are trying to do something like
+avatar #mention
A mention is basically a string marking that contains ID of the user.
import re
def is_mention(s):
'''Check if the string is a valid 'mention' string for Discord'''
try:
return bool(re.match(r'<#!?(\d+)>', s))
except Exception:
return None
def mention_to_id(s):
'''Extract user-id from the mention'''
if is_mention(s):
return int(re.sub(r'[<>!#]', '', s))
return None
You can first extract the mention string using mention_str = message.content.split()[1], and then call mention_to_id function to extract the ID from mention string.
After that, call get_user on your bot (client) object to get the user, which will return a User object which has avatar_url field.
To summarize,
if message.content.startswith('+avatar'):
message_parts = message.content.split()
if len(message_parts) > 1:
mention_str = message.content.split()[1]
if is_mention(mention_str):
user = client.get_user(mention_to_id(mention_str)
# do whatever with user.avatar_url
else:
# Warn about invalid mention
else:
# Whatever you want to do when there's no mention
References:
get_user: https://discordpy.readthedocs.io/en/latest/api.html#discord.Client.get_user
User: https://discordpy.readthedocs.io/en/latest/api.html#discord.User
I used message.mentions to find if someone is mentioned in the message and if mentioned i took the mentioned user's name and avatar_url else i took the sender's name and avatar_url
if message.content.startswith('+avatar'):
if( len(message.mentions) > 0 ): #checking if someone is mentioned
#mesage.mentions[0] is the first mentioned user in the message
name = message.mentions[0].display_name
avatar_url = message.mentions[0].avatar_url
else: #no one is mentioned
name = sender.display_name
avatar_url = sender.avatar_url
embedVar = discord.Embed(title="Username: {}".format(name), color=0xec9e36)
embedVar.set_image(url = avatar_url)
await message.channel.send(embed=embedVar)
So I have a command which allows you to create a channel and a role based on what the user gives. The problem arises when the channel name is over a word. For example:
>newrealm Space Invaders (emoji) (mention)
This command will raise an error because now it's assuming "Invaders" is the emoji.
Which is why I am now using something like this:
>newrealm Space-Invaders (emoji) (mention)
Now the problem with this command is that while creating the role, the name is Space-Invaders OP. Is there a way to remove the - in the role's name?
My code:
#commands.command()
#commands.has_permissions(manage_roles = True)
async def newrealm(self, ctx, realm, emoji, user: discord.Member):
author = ctx.message.author
guild = ctx.message.guild
channel = ctx.message.channel
color = discord.Colour(0x3498DB)
role = await guild.create_role(name= realm + " OP", color = color)
category = discord.utils.get(guild.categories, name = "Realm Channels List Test")
await category.create_text_channel(realm + "-" + emoji)
await ctx.send("Created Channel and Role!")
await user.add_roles(role)
Any suggestions or tip's would help a lot!
There are several options that would solve this problem:
encase the name in quotes so the function recognizes it as one argument.
receive the name last with a special syntax:
async def newrealm(self, ctx, emoji, user: discord.Member, *, realm):
The second method is more elegant but it is not in order, the first method is less elegant because of the quotes but it is in order, Choose your preference.
I'm using the discord.py library and want to purge inactive members by kicking them from the server. I am aware of the function discord.Guild.prune_members, however it does not provide all the functionality I need as I'd like to send the user a warning a few days before they are removed.
How can I best find when a user was last active so that I can calculate the number of days they have been inactive for? I have been searching the internet for several days and cannot find a solution.
As stated in the discord.py server, you can use a simple cache alongside a database to keep track of this. Let's run through a simple example on how we could do this.
from typing import TYPE_CHECKING, Dict, List
import discord
from discord.ext import commands
if TYPE_CHECKING:
import datetime
import asyncpg
# We set the type checking here to avoid errors, your bot instance
# shouldnt run into this issue and this shouldnt be needed.
class Bot(commands.Bot):
pool: asyncpg.Pool[asyncpg.Record]
else:
Bot = commands.Bot
# We're going to layout our data as so:
# CREATE TABLE prune (member_id BIGINT, guild_id BIGINT, last_login TIMESTAMP WITH TIMEZONE)
class Tracker(commands.Cog):
def __init__(self, bot: Bot) -> None:
self.bot: Bot = bot
self.presence_cache: Dict[int, Dict[int, datetime.datetime]] = {} # mapping of guild : {member_id: last_login}
async def cog_load(self) -> None:
# Let's load the info from our database table into our cache.
# This implements a postgres asyncpg pool
data = await self.bot.pool.fetch('SELECT * FROM prune') # type: ignore
for entry in data:
entry: asyncpg.Record
# Enter in the guild id and the member id
if (guild_id := entry['guild_id']) not in self.presence_cache:
self.presence_cache[guild_id] = {}
self.presence_cache[guild_id][entry['member_id']] = entry['last_login']
async def cog_unload(self) -> None:
# We need to dump back into our database here, so we can save our data.
async with self.bot.pool.acquire() as connection:
async with connection.transaction():
query = 'INSERT INTO prune (member_id, guild_id, last_login) VALUES ($1, $2, $3) ON CONFLICT (member_id, guild_id) DO UPDATE SET last_login = $3'
# Let's format our data so we can use executemany
# The formatted data should be a list of tuples, (member_id, guild_id, last_login)
formatted_data = [(member_id, guild_id, last_login) for guild_id, guild_data in self.presence_cache.items() for member_id, last_login in guild_data.items()]
await connection.executemany(query, formatted_data)
#commands.Cog.listener('on_presence_update')
async def on_presence_update(self, before: discord.Member, after: discord.Member) -> None:
if before.status == after.status: # The two statuses are the same, return
return
if not (before.status is discord.Status.offline and after.status is discord.Status.online): # The member did NOT come onine
return
# This means the member's last login was just now, let's add it to our cache to update
if (guild_id := after.guild.id) not in self.presence_cache:
self.presence_cache[guild_id] = {}
self.presence_cache[guild_id][after.id] = discord.utils.utcnow() # utc now (dpy is timezone aware in 2.0)
#commands.command(
name='get_prune',
brief='Get the members who will be pruned',
description='Get the members who will be pruned',
)
#commands.guild_only()
async def get_prune(self, ctx: commands.Context[Bot], days: int) -> None:
assert ctx.guild is not None
# Let's find the members who would be pruned
query = 'SELECT member_id FROM prune WHERE guild_id = $1 AND last_login < $2'
data = await self.bot.pool.fetch(query, ctx.guild.id, (discord.utils.utcnow() - datetime.timedelta(days=days)).replace(tzinfo=None))
# Now let's format an embed
embed = discord.Embed(
title=f'{len(data)} members will be pruned',
)
members: List[discord.Member] = []
for entry in data:
member = ctx.guild.get_member(entry['member_id']) or (await ctx.guild.fetch_member(entry['member_id']))
members.append(member)
embed.description = ', '.join(member.mention for member in members)
await ctx.send(embed=embed, allowed_mentions=discord.AllowedMentions.none())
In this example, we used the async cog_load and cog_unload functions to keep a cache up to date. To append to our cache we used on_presence_update to check when the member came online. I've also implemented a simple command to get the members who would be pruned after X days. It can be invoked as so:
[pr]get_prune 20
If you have any questions feel free to reach out on this thread :)