Discord.py Storing and Outputting Records/Warnings - python

So currently I am trying to create a blacklist (basically a warn command) command where server owners can use the command to publish bans they dealt with. I tried to rewrite my original warn command but the problem with that is the output isn't how I wanted it to be and if I did want to fix it, I had to rewrite a majority of the code which is why I wanted to make a new fresh command for it.
So the problem I am facing with making the command is taking in multiple arguments.
Eg: (Process of blacklisting)
User: >blacklist (user/id)
BOT: Gamertag?
User: *Sends an Xbox username*
BOT: Banned from?
User: *Sends server name*
BOT: Reason for the ban?
User: *Sends ban reason*
This is an example of the conversation the bot and user would be going through. The main thing is being able to save the user's responses and send everything as an embed.
I currently have this:
#commands.command()
async def blacklist(self, ctx):
embed = discord.Embed(title = "Blacklist Report")
#Then something here to collect responses
Note: I can create the embed just fine but just need help collecting responses from the user. Any help would be amazing!

I decided to send you 2 functions I always use to help you out
def check_message(author: discord.User, channel: discord.TextChannel, forbid: bool = False):
def check(message: discord.Message):
if forbid and message.channel.id == channel.id and message.author.id != author.id and message.author.id != bot.user.id:
embed = discord.Embed(title='**ERROR**', description=f'Only {author.mention} can send message here', colour=discord.Colour.red())
asyncio.ensure_future(message.delete())
asyncio.ensure_future(channel.send(embed=embed, delete_after=20))
return message.author.id == author.id and channel.id == message.channel.id
return check
async def get_answer(ctx: discord.TextChannel, user: discord.User, embed: discord.Embed, timeout: Optional[int] = None, delete_after: bool = False) -> Optional[str]:
message: discord.Message = await ctx.send(embed=embed)
try:
respond: discord.Message = await bot.wait_for('message', check=check_message(user, ctx), timeout=timeout)
if delete_after:
await try_delete(message)
await try_delete(respond)
return respond.content
except asyncio.TimeoutError:
if delete_after:
await try_delete(message)
return None
You don't need to even call check_message, You just need to await the get_answer and get what you want as a string
Here is an example:
#commands.command('example')
async def example(self, ctx):
embed = discord.Embed(title='What is your name?')
response = await get_answer(ctx.channel, ctx.author, embed)
print(f'His name was {response}') # or any other thing you want to use it for
The timeout allows you to close the process after some seconds without response and continue the code, In that case you will get None as the output of this function.
The delete_after allows you to remove both the question message and the response one after timeout or reciving a response to keep chat clear.

Related

How do i code an afk command that comes out the way dyno runs

I need help making a afk command for my discord server. When the afk command is triggered, my bot doesn't respond with a reasoning when you ping the person whos afk. Also, when you return from being afk and type, the bot doesn't send a message saying "(user) is no longer afk". Please help me and tell me what i'm doing wrong and how can I fix this?
afkdict = {User: "their reason"} # somewhere in the code
#bot.command("afk")
async def afk(ctx, reason=None):
afkdict[ctx.user] = reason
await ctx.send("You are now afk. Beware of the real world!")
#bot.event
async def on_message(message):
afkdict = {user: "their reason"}
# some other checks here
for user, reason in afkdict.items():
if user in message.mentions:
if reason is None:
reason = ""
embed = discord.Embed(title=f"{user} is AFK", color=0xFF0000, description=reason[:2500])
await message.reply()
I was expecting this to work, the way dyno works. When i ran the command i got a message back saying user has no context. I dont know what to do anymore.
I think there's a couple of issues. Firstly, you are redefining afkdict in your on_message function it doesn't matter that you're adding users to it in the afk command. Secondly, when you're doing await message.reply(), you're not actually sending the created embed along with it.
I've resolved those problems and changed the logic slightly. Instead of iterating over the users in the afk_dict and checking if they're mentioned, we're iterating over the mentions and seeing if they're in the afk_dict. I'm also using user.id rather user objects as keys.
# defined somewhere
afk_dict = {}
#bot.command()
async def afk(ctx, reason=None):
afk_dict[ctx.user.id] = reason
await ctx.send("You are now afk. Beware of the real world!")
#bot.event
async def on_message(message):
# do whatever else you're doing here
for user in message.mentions:
if user.id not in afk_dict:
continue
# mentioned used is "afk"
reason = afk_dict[user.id] or ""
embed = discord.Embed(title=f"{user.mention} is AFK", color=0xFF0000, description=reason[:2500])
await message.reply(embed=embed)
It looks like you are missing some pieces in your code. Here is an updated version of the code:
afkdict = {}
#bot.command("afk")
async def afk(ctx, reason=None):
user = ctx.message.author
afkdict[user] = reason
await ctx.send(f"You are now AFK. {'Reason: ' + reason if reason else ''}")
#bot.event
async def on_message(message):
for user, reason in afkdict.items():
if user in message.mentions:
if reason is None:
reason = ""
embed = discord.Embed(title=f"{user} is AFK", color=0xFF0000, description=reason[:2500])
await message.channel.send(embed=embed)
if message.author in afkdict:
afkdict.pop(message.author)
await message.channel.send(f"{message.author} is no longer AFK")
In this code, the afk command will add the user who runs the command to the afkdict dictionary along with the reason for being AFK. The on_message event handler will then check if any of the mentioned users are in the afkdict and if so, it will send an embed with the AFK status and reason. Finally, if the author of the message is in the afkdict, it will remove them from the dictionary and send a message indicating that they are no longer AFK.

How do you make it where when you react to a message it sends you a DM on discord.py

So i am making a discord bot for a roleplay server on roblox and it has a ;host {link} command and it automatically gives itself a thumbs up reaction, how would I make it where if a user clicks this thumbs up it will DM them the link that you put while typing ;host
here is the current code,
#bot.command(aliases=["start"])
#commands.has_role(964936610489724998)
async def host(ctx, *, link=None):
if link == None:
await ctx.reply("**`Plase Include a link for your Private Server for users to be able to join`**")
else:
await ctx.message.delete()
embed=discord.Embed(
title="A user is Hosting an event",
description=f"{ctx.author.mention} Is hosting a greenville server. Please react with the 👍 emoji if you want to join! Remember to follow the rules while in the server. A direct message will soon be sent to you with the link to join.",
color=0x0050ff
)
msg = await ctx.send("#everyone", embed=embed)
await msg.add_reaction("👍")
Try using wait_for().
Insert the following code at the end of the function definition:
def check(reaction, user):
return str(reaction.emoji) == '👍'
try:
reaction, user = await client.wait_for('reaction_add', timeout=60.0, check=check)
except asyncio.TimeoutError:
await channel.send('👎') # Replace this with what you want to happen when there is a timeout
else:
await channel.send('👍') # Replace this with what you want to happen normally
This code is modified from the discord.py docs.
You're going to have to save config for this, for example by using json or pickle.
This is a basic pickle implementation that saves the message across bot restarts:
def save_all():
# PURELY FOR DEMONSTRATION. You should save this in an autosave function every x minutes, IO is precious
# this saves the data into config.txt to be loaded in later
with open('config.txt', 'wb') as f:
pickle.dump(client.config, f)
print('saved')
#client.command(name='reaction')
async def reaction(ctx):
await ctx.message.delete()
msg = await ctx.send('this is a message to react to')
await msg.add_reaction('\U0001f44d')
client.config[msg.id] = {
'reaction': '\U0001f44d', # you can use custom emojis too
'send': 'You reacted, so I will send this message to you' # this is what you want to dm
}
save_all()
#client.event
async def on_reaction_add(reaction, user):
message = reaction.message
if message.id in client.config.keys() and reaction.emoji == client.config[message.id]['reaction']:
await user.send(client.config[message.id]['send'])
with open('config.txt', 'rb') as f:
client.config = pickle.load(f)

Discord.py : Forward message from one channel to another

I'm trying to make some sort of say command with my bot that has "//" as prefix.
When typing //say_next #some_channel in the admin commands channel, I want that when I send a second message, it is copied and sent to the #some_channel
Here is my code
#commands.command()
#commands.has_permissions(administrator=True)
async def say_next(self, ctx: Context, channel: discord.TextChannel):
if ctx.channel.id != get_from_config("admin_commands_channel_id"):
return
def message_check(m: discord.Message):
return m.author == ctx.author
try:
message_to_send: discord.Message = await self.bot.wait_for("message", check=message_check, timeout=15)
except asyncio.TimeoutError:
await ctx.send(content="Cancelled")
else:
await channel.send(content=message_to_send.content)
The problem is that this limits the forwarded message to only copy the original's message text.
What I want that forwarding to copy all the elements of the message, being an undefined number of embeds, a file or no file, etc ... but this cannot be done since the Messageable.send method cannot be used with a Message object, that is my message_to_send variable.
How can this forwarding be done, without having for each parameter of Messageable.send being an attribute of message_to_send so without something like this ?
content = message_to_send.content
embed = None if len(message_to_send.embeds) == 0 else message_to_send.embeds[0]
tts = message_to_send.tts
attachments = message_to_send.attachments
file = files = None
if len(attachments) > 1:
files = attachments
else:
file = attachments[0]
await channel.send(content=content, tts=tts, file=file, files=files, embed=embed)
The easiest approach would be the following:
#commands.command()
#commands.has_permissions(administrator=True)
async def say_next(self, ctx, channel: discord.TextChannel):
if ctx.channel.id != get_from_config("admin_commands_channel_id"):
return
def check(m):
return m.author == ctx.author
try:
msg = await self.bot.wait_for("message", check=check, timeout=15.0)
except asyncio.TimeoutError:
await ctx.send("Cancelled")
else:
await channel.send(msg.content, tts=msg.tts, files=[await attch.to_file() for attch in msg.attachments])
I haven't included the embeds as normal users cannot send embeds.
There's not really much that I can say about this code, the only thing would be the files kwarg, it's simply a bit of list comprehension to convert the discord.Attachment instances to discord.File instances. You could think it won't work if you send only one file (or zero), I thought so too, but apparently it works.

how to display reply from dm discord.py

this is my dm code
#client.command()
async def dm(ctx, user_id=None, *, args= None):
if user_id != None and args != None:
try:
target=await client.fetch_user(user_id)
await target.send(args)
await ctx.channel.send("'"+args+"'ได้ถูกส่งไปที่ : " + target.name)
except:
await ctx.channel.send("ไม่สารมารถ dm ได้")
else:
await ctx.channel.send("A user_id / or arguments were not included.")
i wanted to take the response or just like anything that random person dm the bot and display by print('') or ctx.channel.send how do i do that ? please help
I have solve my problem by doing
async def on_message(message):
if message.guild is None and not message.author.bot:
print(f'{message.author } :' + f'ID :{message.author.id} :' + message.content)
await client.process_commands(message)```
Your question is neither well researched, nor explainative to people trying to help you. And unfortunately its unreadable too. You shouldn't expect your question to be answered as noone wants to solve your problem when they have to decode the question by themselves. For future questions, please read How do I ask a good question.
Still, heres your answer:
# Events are not costum commands but specific actions, like on_message
# The on_message event doesnt pass a Context, but only a message object
# For the context, run client.get_context(message)
#client.event
async def on_message(message):
# Message is, like context, a class. This means you can get a variety of information from message,
# like the author name or id
# check that the bot ignores its own messages
if message.author.id != client.user.id:
# Check if the channel is a DM channel
if isinstance(message.channel, discord.channel.DMChannel):
answer = f"{message.author.name}: {message.content}"
# Provide the channelid of the channel you want the message to be sent to.
# You can do that by enabling Developer Mode in Settings -> Appearance
# Then you can right click on any channel or member to copy their id
channelid = 1111 # 18-digit-integer
channel = client.get_channel(channelid)
await channel.send(answer)

Is there a way to do a check for pinned messages, and only purge a certain members messages using discord.py?

I want to make a purge command similar to Dyne's, where you can input a user, and it doesn't purge pinned, only the user's messages (if you input a user). I've tried making a seperate check function, but it doesn't purge anything. I get no error, it just won't purge.
#commands.command()
#commands.has_permissions(manage_messages=True)
async def purge(self, ctx, user: discord.Member = None, num: int = 10000):
if user:
def check_func(user: discord.Member, message: discord.Message):
return not msg.pinned
return user.id
await ctx.message.delete()
await ctx.channel.purge(limit=num, check=check_func)
verycool = await ctx.send(f'{num} messages deleted.')
await verycool.delete()
else:
await ctx.message.delete()
await ctx.channel.purge(limit=num, check=lambda msg: not msg.pinned)
verycool = await ctx.send(f'{num} messages deleted.')
await verycool.delete()
I have manage messages permissions on the server. Does anyone know how to make the check_func work properly?
The changes I have made should solve your issue as well as deal with a few other issues.
#commands.command()
#commands.has_permissions(manage_messages=True)
async def purge(self, ctx, num: int = None, user: discord.Member = None):
if user:
check_func = lambda msg: msg.author == user and not msg.pinned
else:
check_func = lambda msg: not msg.pinned
await ctx.message.delete()
await ctx.channel.purge(limit=num, check=check_func)
await ctx.send(f'{num} messages deleted.', delete_after=5)
Just changing the function based on the arguments looks better and is more efficient as otherwise you have duplicated code. Furthermore, the message you send at the end was being deleted effectively immediately. channel.send has an argument delete_after which will automatically deletes a message after a given amount of seconds. There are a few other syntax issue I haven't mentioned such as the check function only taking one argument but you parsing two which I have also fixed. Technically PEP8 prohibits storing lambda's in variables but I think this could be excused.
Edit: you could do something like check if num == "*" and delete all messages if it does.

Categories