Returning a message in a channel issue - python-3.x

Hi I'm trying to return a message when the user uses the command !add in channels other than the channels in the check. This is how I'm checking for the channels the command should be used in:
#commands.check(lambda ctx: ctx.channel.id in [555913791615926302, 567769278351409174])
Here is how I'm trying to do this and having issues with:
if not ctx.channel.id:
await ctx.send("You can only use this command in botroom.")
return
and this is how I'd be using in the code:
#commands.command(pass_context=True)
#commands.check(lambda ctx: ctx.channel.id in [555913791615926302, 567769278351409174])
async def add(self, ctx, *, rolename):
author = ctx.message.author
role_dict = {
"members":557212810468392970,
"ps4":568761643916328960,
"lol":559792606364565505,
"pc":568725587322208287,
"nintendo switch":558649595102625795,
"ze/zir":569170061592494083}
if not ctx.channel.id:
await ctx.send("You can only use this command in botroom.")
return
role_id = role_dict.get(rolename.lower())
if not role_id:
message = 'I cannot find the role **{}**.'
embed = discord.Embed(description=message.format(rolename))
await ctx.send(embed=embed)
return
role = discord.utils.get(ctx.message.guild.roles, id = role_id)
if role in author.roles:
message = 'It looks like you already have the role **{}**.'
embed = discord.Embed(description=message.format(role.name))
await ctx.send(embed=embed)
else:
await author.add_roles(role)
message = '{} added the role **{}**.'.format(author.display_name, role.name)
embed = discord.Embed(description=message.format(author.display_name, role.name), colour=0x56e011)
await ctx.send(embed=embed)

If the check fails, your coroutine will never be called. Instead, an error is raised, that you can then handle by defining an error handler for your command.
While you're at it, you can make that check look much nicer at the same time
def in_channel_with_id(*ids):
def predicate(ctx):
return ctx.channel.id in ids
return commands.check(predicate)
#commands.command()
#in_channel_with_id(555913791615926302, 567769278351409174)
async def add(self, ctx, *, rolename):
...
#add.error
async def add_error(ctx, error):
if isinstance(error, commands.CheckFailure):
await ctx.send("You can only use this command in botroom.")
else:
raise error

Related

D.py/rewrite - Confessions System

I made a confessions system but there’s some things that are wrong with it. How would I make it so when users want to type, they don’t have to put in *confess and they can just type whatever they want without needing to use a command? And how do I make a mod logs channel to log the deleted confessions with the author name, etc.?
import discord
from discord.ext import commands
class Confess(commands.Cog):
def __init__(self, client: discord.ext.commands.Bot):
self.client = client
#commands.command()
async def confess(self, ctx: commands.Context, *, message: str):
channel = self.client.get_channel(806649868314869760)
await ctx.message.delete()
embed = discord.Embed(title="Success", description=f"I've received your confession and sent it to the <#806649874379964487> channel!")
embed.set_footer(text="Confessions")
await ctx.send(embed=embed, delete_after=10)
channel = self.client.get_channel(806649874379964487)
embed = discord.Embed(title="Confession", description=f"{message}")
embed.set_footer(text="All confessions are anonymous.")
await channel.send(embed=embed)
def setup(client):
client.add_cog(Confess(client))
For the first question
If you want to use a "command" without actually using a command you could make an on_message event, check the id of the channel (like a confessions channel) and if it matches then do the thing
Example:
#commands.Cog.listener()
async def on_message(message):
if message.channel.id == some_channel_id_here:
channel = self.client.get_channel(806649868314869760)
await message.delete()
embed = discord.Embed(title="Success", description=f"I've received your confession and sent it to the <#806649874379964487> channel!")
embed.set_footer(text="Confessions")
await message.channel.send(embed=embed, delete_after=10)
channel = self.client.get_channel(806649874379964487)
embed = discord.Embed(title="Confession", description=f"{message}")
embed.set_footer(text="All confessions are anonymous.")
await channel.send(embed=embed)
For the second question
You can use get_channel again to get the log channel and post in there. (If you mean't on how to check if someone deleted a message/confession, use on_message_delete)
Example:
#commands.command()
async def confess(self, ctx: commands.Context, *, message: str):
channel = self.client.get_channel(806649868314869760)
log_channel = self.client.get_channel(log_channel_id)
await ctx.message.delete()
embed = discord.Embed(title="Success", description=f"I've received your confession and sent it to the <#806649874379964487> channel!")
embed.set_footer(text="Confessions")
await ctx.send(embed=embed, delete_after=10)
channel = self.client.get_channel(806649874379964487)
embed = discord.Embed(title="Confession", description=f"{message}")
embed.set_footer(text="All confessions are anonymous.")
await channel.send(embed=embed)
await logchannel.send("User confessed something!")

Translation with reactions not working on python

I'm trying to do a bot translation using reactions. Although something is not working.
#commands.Cog.listener()
async def on_reaction_add(self, reaction, user):
if user == self.client:
return
if reaction.emoji == ":flag_us:":
text = reaction.message.id
translate_text = google_translator.translate(text, lang_tgt='en')
await self.client.send_message(translate_text.channel)
elif reaction.emoji == ":flag_cn:":
text = reaction.message.id
translate_text = google_translator.translate(text, lang_tgt='zh')
await self.client.send_message(translate_text.channel)
else:
return
No error returned and no action made
This is because reaction.emoji isn't a string, but is an object itself. You're probably looking for reaction.emoji.name.
Also, there are a few issues within the if/elif clauses that would prevent your code from running, even if the original issue was fixed.
reaction.message.id is an integer, so passing it to google_translator.translate() will result in an error.
The name of an emoji tends not to be the name you would enter in Discord. The best practice would be to put the unicode of the emoji.
To send a message to channel, you should use TextChannel.send()
#commands.Cog.listener()
async def on_raw_reaction_add(self, payload):
if payload.member == self.client:
return
if payload.emoji.name == u"\U0001f1fa\U0001f1f8":
message = await self.client.fetch_message(payload.message_id)
translate_text = google_translator.translate(message.content, lang_tgt='en')
channel = await self.client.fetch_channel(payload.channel_id)
await channel.send(translate_text)
elif payload.emoji.name == u"\U0001F1E8\U0001F1F3":
message = await self.client.fetch_message(payload.message_id)
translate_text = google_translator.translate(message.content, lang_tgt='zh')
channel = await self.client.fetch_channel(payload.channel_id)
await channel.send(translate_text)
This would work, but I would recommend taking all of the various calls outside of the if/elif clauses:
#commands.Cog.listener()
async def on_raw_reaction_add(self, payload):
if payload.member == self.client:
return
emoji_to_language = {
u"\U0001f1fa\U0001f1f8": "en",
u"\U0001F1E8\U0001F1F3": "zh"
}
lang = emoji_to_language.get(payload.emoji.name)
if lang is None:
break
message = await self.client.fetch_message(payload.message_id)
translate_text = google_translator.translate(message.content, lang_tgt=lang')
channel = await self.client.fetch_channel(payload.channel_id)
await channel.send(translate_text)

Why is discord.Client.wait_for is asking for event when it has one?

The problem i'm having is the code never getting through "0004" since it gets stuck on wait_for which wants additional positional argument: 'event', which should be the 'message' in bracket right next to it from what I've seen on examples from discord.py site & inside the code itself.
'''
class Test(commands.Cog):
def __init__(self, spreadsheeter):
self.spreadsheeter = spreadsheeter
#commands.command()
async def apply(self, ctx):
a_list = []
submit_channel = spreadsheeter.get_channel(718698172293316608)
channel = await ctx.author.create_dm()
def check(m):
return m.content is not None and m.channel == channel
for question in q_list:
print("0001")
sleep(.5)
print("0002")
await channel.send(question)
print("0003")
msg = await Client.wait_for('message', timeout=60, check=check)
print("0004")
a_list.append(msg.content)
print("0005")
submit_wait = True
print("0006")
while submit_wait:
print("0007")
await channel.send("End of questions 'submit' to finish.")
print("0008")
msg = await Client.wait_for("message", check=check)
print("0009")
if "submit" in msg.content.lower():
print("0010")
submit_wait =False
print("0011")
answers = "\n".join(f"{a}. {b}" for a, b in enumerate(a_list, 1))
print("0012")
submit_msg = f"Apllication from {msg.author} \nThe answers are:\n{answers}"
print("0013")
await submit_channel.send(submit_msg)
print("0014")
Error:
What I've unsuccessfully tried:
discord.client.wait_for('message', check=check)
Error: discord.client has no attribute 'wait_for'
Same for discord.member, ctx.member, ctx.client
Replacing "message" to 'message' (doesn't change anything)
Moving around the position of 'message'
Raises ton of other errors...
Giving the line bracket at start (self, "message", check=check)
Error: 'Test' object has no attribute 'loop'
#client.event instead of cog style commands.command
Error: Unresolved reference 'client'
Intent of the code:
Be called from DM as a command, then start a series of questions from q_list in that DM and then store them into a_list. Then after its done it should submit the a_list into as submit_msg into discord channel.
What it does so far?:
Asks first question from q_list
q_list = [
"Question one",
"Question two ha-ha-haaa",
"Question three ha-ha-haaa"
]
"""
After that it immediately after trying to await answer gives error.
2 Things at the beginning, you should only make a dm channel if there isn't any yet.
ctx.author.chreate_dm() doesn't return a channel, so instead of assigning the dm channel to channel you can check it's in the right channel by using ctx.author.dm_channel
#commands.command()
async def apply(self, ctx):
a_list = []
submit_channel = self.spreadsheeter.get_channel(718698172293316608)
if not ctx.author.dm_channel:
await ctx.author.create_dm()
def check(m):
return m.content is not None and m.channel == ctx.author.dm_channel
Instead of Client use spreadsheeter as that is your client
msg = await self.spreadsheeter.wait_for('message', timeout=60, check=check)
Figured it out, the thing being is it might work in cogs, but I think it won't or something is just missing...
The way i made it to work is to use it in
#Client.event (in my case #spreadsheeter.event)
Instead of ctx. I used payload.
"""
#spreadsheeter.event
async def on_raw_reaction_add(payload):
print("Hell-Yeah")
message_id = payload.message_id
if message_id == 739153263907176480:
guild_id = payload.guild_id
guild = discord.utils.find(lambda g : g.id == guild_id, spreadsheeter.guilds)
print (payload.emoji.name)
if payload.emoji.name == "No1":
role = discord.utils.find(lambda r: r.name == 'RAIDERS', guild.roles)
print(role)
if role in payload.member.roles:
print("You really want to leave?")
else:
print("Kick")
await payload.member.kick()
elif payload.emoji.name == "Yes1":
print("Yo, we got a potential raider over here..")
channel = await payload.member.create_dm()
await channel.send("This is still Work/Bot is under Development (Not actuall application yet).")
await channel.send("Whenever you are ready, please start replaying to the questions.")
print("0000")
submit_channel = spreadsheeter.get_channel(718698172293316608)
if not payload.member.dm_channel:
await payload.member.create_dm()
channel = payload.member.dm_channel
print(channel)
def check(m):
return m.content is not None and m.channel == channel
for question in q_list:
print("0001")
sleep(.5)
print("0002")
await channel.send(question)
print("0003")
msg = await spreadsheeter.wait_for(event='message', timeout=60, check=check)
print("0004")
a_list.append(msg.content)
print("0005")
submit_wait = True
print("0006")
while submit_wait:
print("0007")
await channel.send("End of questions 'submit' to finish.")
print("0008")
msg = await spreadsheeter.wait_for("message", timeout=60, check=check)
print("0009")
if "submit" in msg.content.lower():
print("0010")
submit_wait = False
print("0011")
answers = "\n".join(f"{a}. {b}" for a, b in enumerate(a_list, 1))
print("0012")
submit_msg = f"Apllication from {msg.author} \nThe answers are:\n{answers}"
print("0013")
await submit_channel.send(submit_msg)
print("0014")
elif payload.emoji.name == "Need_more_info":
print("Yo, we got a potential diplomat over here..")
channel = await payload.member.create_dm()
await channel.send("This is still Work/Bot is under Development (Not actuall application yet).")
await channel.send("Whenever you are ready, please start with \n_apply")
"""
It still doesn't have the handling for multiple users, but works fine with one at a time at this moment, which solves the original problem of wait_for not waiting for anything else then timeout to run out.

How do I get the discord bot to trigger commands on it's own message using discord.py?

Let's just say the user enters an invalid command, the bot suggests a command with (y/n)? If y, then the bot should trigger the suggested command.
I feel this can be achieved in 2 ways:
If the bot can trigger commands on its own message
If I can call the command from the other cogs
I can't seem to get either of em working.
Here is an example to help you guys help me better:
Let's just say this below code is from a cog called Joke.py:
#commands.command()
async def joke(self,ctx):
await ctx.send("A Joke")
And then there another cog "CommandsCorrection.py" that corrects wrong commands used by the user that are saved on data.json file:
#commands.Cog.listener()
async def on_message(self, message):
channel = message.channel
prefix = get_prefix(self,message)
if message.author.id == bot.id:
return
elif message.content.startswith(prefix):
withoutprefix = message.content.replace(prefix,"")
if withoutprefix in data:
return
else:
try:
rightCommand= get_close_matches(withoutprefix, data.keys())[0]
await message.channel.send(f"Did you mean {prefix}%s instead? Enter Y if yes, or N if no:" %rightCommand)
def check(m):
return m.content == "Y" or "N" and m.channel == channel
msg = await self.client.wait_for('message', check=check, timeout = 10.0)
msg.content = msg.content.lower()
if msg.content == "y":
await channel.send(f'{prefix}{rightCommand}')
elif msg.content == "n":
await channel.send('You said no.')
except asyncio.TimeoutError:
await channel.send('Timed Out')
except IndexError as error:
await channel.send("Command Not Found. Try !help for the list of commands and use '!' as prefix.")
in the above code await message.channel.send(f"Did you mean {prefix}%s instead? Enter Y if yes, or N if no:" %rightCommand) suggests the right command and await channel.send(f'{prefix}{rightCommand}') sends the right command.
So,for example:
user : !jok
bot : Did you mean !joke instead? Enter Y if yes, or N if no:
user : y
bot : !joke **I want to trigger the command when it sends this message by reading its own message or my just calling that command/function
How should I go about this?
One solution would be to separate the logic of your commands from the command callbacks and put it in it's own coroutines. Then you can call these coroutines freely from any of the command callbacks.
So you would turn code like this:
#bot.command()
async def my_command(ctx):
await ctx.send("Running command")
#bot.command()
async def other_command(ctx, arg):
if arg == "command":
await ctx.send("Running command")
Into something like this:
async def command_logic(ctx):
await ctx.send("Running command")
#bot.command()
async def my_command(ctx):
await command_logic(ctx)
#bot.command()
async def other_command(ctx, arg):
if arg == "command":
await command_logic(ctx)
#Patrick Haugh suggested this idea, he was on to something but wasn't really working but I got a way to get it working.
If you use the following method for a cog, you'll be able to read bot commands as well:
import discord
from discord.ext import commands
async def command_logic(self, ctx):
await ctx.send("Running command")
class Example(commands.Cog):
def __init__(self, client):
self.client = client
#commands.command()
async def my_command(self, ctx):
await command_logic(self,ctx)
#commands.Cog.listener()
async def on_message(self, message):
if message.content == '!command':
ctx = await self.client.get_context(message)
await command_logic(self,ctx)
def setup(client):
client.add_cog(Example(client))

Reaction Handling in Discord.py Rewrite Commands

Is there a way to capture a reaction from a command. I have made a command that deletes a channel but I would like to ask the user if they are sure using reactions. I would like to prevent others from reacting to this message (Only the Context Author should React).
So far what I have found is just to use the on_reaction_add() but this can not detect the user who sent the command. I would like to only update the command if the message author is the one who reacted to the message, anyone else, ignore it.
Update: I found that wait_for() does exactly what I want but the issue now is how do I check for if the wrong reaction is set? (i.e if I press the second reaction, delete the message)
if is_admin:
msg = await ctx.send('Clear: Are you sure you would like to purge this entire channel?')
emoji1 = u"\u2705"
emoji2 = u"\u274E"
await msg.add_reaction(emoji=emoji1)
await msg.add_reaction(emoji=emoji2)
def check(reaction, user):
return user == ctx.message.author and reaction.emoji == u"\u2705"
try:
reaction, user = await self.client.wait_for('reaction_add', timeout=10.0, check=check)
except asyncio.TimeoutError:
return await msg.delete()
else:
channel = ctx.message.channel
new_channel = await channel.clone(name=channel.name, reason=f'Clear Channel ran by {ctx.message.author.name}')
await new_channel.edit(position=channel.position)
await channel.delete(reason=f'Clear Channel ran by {ctx.message.author.name}')
await new_channel.send('Clear: Channel has now been cleared.', delete_after=7)
else:
await ctx.send(f"Sorry, you do not have access to this command.", delete_after=5)
Here's a function I use for generating check functions for wait_for:
from collections.abc import Sequence
def make_sequence(seq):
if seq is None:
return ()
if isinstance(seq, Sequence) and not isinstance(seq, str):
return seq
else:
return (seq,)
def reaction_check(message=None, emoji=None, author=None, ignore_bot=True):
message = make_sequence(message)
message = tuple(m.id for m in message)
emoji = make_sequence(emoji)
author = make_sequence(author)
def check(reaction, user):
if ignore_bot and user.bot:
return False
if message and reaction.message.id not in message:
return False
if emoji and reaction.emoji not in emoji:
return False
if author and user not in author:
return False
return True
return check
We can pass the message(s), user(s), and emoji(s) that we want to wait for, and it will automatically ignore everything else.
check = reaction_check(message=msg, author=ctx.author, emoji=(emoji1, emoji2))
try:
reaction, user = await self.client.wait_for('reaction_add', timeout=10.0, check=check)
if reaction.emoji == emoji1:
# emoji1 logic
elif reaction.emoji == emoji2:
# emoji2 logic
except TimeoutError:
# timeout logic

Resources