Discord.py Snipe command - python

Im trying to make a command where the bot "snipes" the last deleted message. this is my current code:
snipe_message_content = None
snipe_message_author = None
#client.event
async def on_message_delete(message):
snipe_message_author.remove(None)
snipe_message_content.remove(None)
snipe_message_content.append(message.content)
snipe_message_author.append(message.author.id)
await asyncio.sleep(str(60))
snipe_message_author.remove(message.author.id)
snipe_message_content.remove(message.content)
#client.command()
async def snipe(message):
if snipe_message_content==None:
await message.channel.send("Theres nothing to snipe.")
else:
embed = discord.Embed(description=f"{snipe_message_content}")
embed.set_footer(text=f"Asked by {message.author.name}#{message.author.discriminator}", icon_url=message.author.avatar_url)
embed.set_author(name= f"<#{snipe_message_author}>")
await message.channel.send(embed=embed)
return
the await message.channel.send("Theres nothing to snipe.") part works perfectly fine, but the rest wont work. Can anyone help?

Well your on_message_delete() function is just not working.
First of all, your variables snipe_message_author and snipe_message_content are of the type None, but the methods remove and append are part of the type list, so you'd have to declare lists
snipe_message_content = []
snipe_message_author = []
in order for them to work.
Still, you wouldn't have to do this anyway. Just give your current variables a new value:
snipe_message_content = None
snipe_message_author = None
#client.event
async def on_message_delete(message):
global snipe_message_content
global snipe_message_author
# Variables outside a function have to be declared as global in order to be changed
snipe_message_content = message.content
snipe_message_author = message.author.id
await asyncio.sleep(60)
snipe_message_author = None
snipe_message_content = None
Also, do not convert 60 to a string. time.sleep and asyncio.sleep both need an integer in order to work (side note: if you wanted 60 to be a string, just write "60" in quotation marks).
At last, be aware of the following case: If a message x gets deleted, but 50 seconds after that, a new message y gets deleted, snipe_message_author and snipe_message_content would be assigned to the new message y. But 10 seconds later, the function executed by message x would set the value of snipe_message_author and snipe_message_content to None.
Therefore, after await asyncio.sleep(60), check whether your message is still the same as before:
snipe_message_content = None
snipe_message_author = None
snipe_message_id = None
#client.event
async def on_message_delete(message):
global snipe_message_content
global snipe_message_author
global snipe_message_id
snipe_message_content = message.content
snipe_message_author = message.author.id
snipe_message_id = message.id
await asyncio.sleep(60)
if message.id == snipe_message_id:
snipe_message_author = None
snipe_message_content = None
snipe_message_id = None

Your command works now probably, but there's a problem. If I delete a message in my server, and you run the command in your server, you'll probably see the message.
What you should do is make the snipe_message_author and snipe_message_content variables dictionaries.
This is how the event should be:
snipe_message_author = {}
snipe_message_content = {}
#client.event
async def on_message_delete(message):
snipe_message_author[message.channel.id] = message.author
snipe_message_content[message.channel.id] = message.content
await sleep(60)
del snipe_message_author[message.channel.id]
del snipe_message_content[message.channel.id]
#client.command(name = 'snipe')
async def snipe(ctx):
channel = ctx.channel
try: #This piece of code is run if the bot finds anything in the dictionary
em = discord.Embed(name = f"Last deleted message in #{channel.name}", description = snipe_message_content[channel.id])
em.set_footer(text = f"This message was sent by {snipe_message_author[channel.id]}")
await ctx.send(embed = em)
except KeyError: #This piece of code is run if the bot doesn't find anything in the dictionary
await ctx.send(f"There are no recently deleted messages in #{channel.name}")
#If the bot sends the embed, but it's empty, it simply means that the deleted message was either a media file or another embed.
To summarize it, here's what this fixes for you:
Doesn't show deleted messages from other servers
Doesn't show deleted messages from other channels
Deleted message in one server won't replace the deleted message in another server
Hope this helped :)

Related

Trouble defining a bot.get_channel() with Pycord when calling from a list - Printing "None"

I'm simply attempting to call this channel ID from a list after it is assigned by the command user. All of the print() commands that I do yield the proper ID including print(a_string) which comes RIGHT before the bot.get_channel call.
I cannot fathom why, but for some reason it consistantly returns "None" no matter how I try to assign the variable.
The part of the code in question is inside the class "EmbedConfirmButton" and the variable in question is "esendChannel", assigned by the slash command at the bottom of the code block.
This is a lot of code, but I wanted to be sure to include the entire thing for reference just in case
eEdit = ["blank"]
esendChannel = ["blank"]
eTitle = ["blank"]
eDescription = ["blank"]
channel = []
class EmbedSend(discord.ui.Modal):
def __init__(self, *args, **kwargs) -> None:
super().__init__(*args, **kwargs)
#self.title = kwargs.get("title")
#self.description = kwargs.get("description")
self.add_item(discord.ui.InputText(label="Title", value = eTitle[0]))
self.add_item(discord.ui.InputText(label="Message Content", style=discord.InputTextStyle.long, value = eDescription[0]))
async def callback(self, interaction: discord.Interaction):
eTitle[0] = self.children[0].value
eDescription[0] = self.children[1].value
esend = discord.Embed(title=''.join(eTitle), description=''.join(eDescription))
eEdit[0] = [esend]
print(eEdit)
await interaction.response.send_message(embed = esend, view = EmbedConfirmButton(), ephemeral=True)
class EmbedConfirmButton(discord.ui.View):
def __init__(self):
super().__init__(timeout=None)
#discord.ui.button(label="Send", custom_id="button-embededit", style=discord.ButtonStyle.blurple)
async def first_button_callback(self, button, interaction):
finalembed = discord.Embed(title=eTitle, description=eDescription)
finalembed.set_image(url="")
s = [str(integer) for integer in esendChannel]
a_string = "".join(s)
print(a_string)
await bot.get_channel(a_string).send(embed = finalembed)
await interaction.response.send_message("test", ephemeral = True)
#discord.ui.button(label="Edit", custom_id="button-embededit2", style=discord.ButtonStyle.blurple)
async def second_button_callback(self, button, interaction):
modal = EmbedSend(title=eTitle[0])
await interaction.response.send_modal(modal)
await interaction.followup.send("test")
#discord.ui.button(label="Cancel", custom_id="button-embedcancel", style=discord.ButtonStyle.blurple)
async def third_button_callback(self, button, interaction):
await interaction.response.send_message("Canceled Interaction", ephemeral = True)
return
#bot.slash_command()
#option("channel")
async def embedsend(ctx, channel:discord.TextChannel):
modal = EmbedSend(title = "Embed Create")
await ctx.send_modal(modal)
await ctx.respond("Success", ephemeral = True)
esendChannel[0] = str(channel.id)
print(esendChannel)
I've tried assigning the variable in several different ways to the point I've completely no clue why it would be returning "None" only when passed to bot.get_channel()
Are you saying that bot.get_channel is returning None when calling it with a_string?
Have you tried using await bot.fetch_channel(a_string) instead? It's possible that that channel isn't in the cache so get_channel isn't returning anything.

Multiple wait_for messages discord.py

What I'm trying to do: This is a similar situation to a question I previously asked, I am making a multiplayer game. I wanted to get multiple user inputs at the same time, basically the situation in another question I had found (multiple wait_for_messages wanted discord.py).
My Problem: I need to send messages to a different set of people each time. Basically, one game may have 3 players, while another might have 5 players. The original aim was also to send a message simultaneously to a user's dms and have them answer the input from there. I am unsure how to define the amount of wait_fors I need in my code.
Code attempt 1:
# inside a cog
#commands.command()
async def bagpie(self, ctx, *members: discord.Member):
members = list(members)
if ctx.author not in members:
members.append(ctx.author)
if len(members) > 3:
return
# making checks
def check1(msg):
return msg.author == members[0]
def check2(msg):
return msg.author == members[1]
def check3(msg):
return msg.author == members[2]
# waiting for multiple messages with the checks
ret = await asyncio.gather(
await self.bot.wait_for("message", timeout=10, check=check1),
await self.bot.wait_for("message", timeout=10, check=check2),
await self.bot.wait_for("message", timeout=10, check=check3),
return_exceptions = True
)
# Test for errors
ret = [r if not isinstance(r, Exception) else None for r in ret]
msg1, msg2, msg3 = ret # *ret would cause an error to occur
# setup embed to send final product
fin_string = ""
count = 0
msg_list = [msg1, msg2, msg3]
for member in members:
fin_string += f"{member} says: {msg_list[count]}"
count += 1
embed = discord.Embed(title="This is what you said!", description=fin_string)
await ctx.send(embed=embed)
Result: As you can see, despite there being inputs given, the bot does not respond and instead reverts to the timeout.
Code Attempt 2:
# couldn't get this to work in a cog, so this one is not in a cog
# checks if it's a dm and if author is given member
def is_dm_check(member):
def inner_check(msg):
return msg.author.id==member.id and msg.guild is None
return inner_check
# sends message to member and waits for the message
async def get_input(member):
await member.send("Please provide an input:")
return client.wait_for('message', check=is_dm_check(member), timeout=75)
#client.command()
async def play_game(ctx, members: commands.Greedy[discord.Member]=None):
if not members:
await ctx.send("No Players")
return
if ctx.author not in members:
members.append(ctx.author)
responses = asyncio.gather(*map(get_input, members))
for member, response in zip(members, responses):
await ctx.send(f"{member.mention}: {response.content}")
Result: Messages were supposedly sent simultaneously, however it still throws an error.

discord.py Passing context and self to function in class

I am trying to create a bot, which uses discord.py's VoiceClient.
The problem is, when you name a variable in a function it doesn't get passed on to the other functions. So the variable voicebot doesn't get passed on to the other functions (which are for making the bot leave or talk)
intents = discord.Intents.all()
bot = commands.Bot(command_prefix=commands.when_mentioned_or("~"),intents=intents, owner_id=587573816360960022)
...Other functions...
#bot.command(name='join', help='Joins the voice channel you are in')
async def JoinVoiceofAuthor(ctx):
vchannel = ctx.message.author.voice
if vchannel != None:
if "voicebot" not in globals():
global voicebot
voicebot = await vchannel.channel.connect()
else:
if voicebot.is_connected():
return
if vchannel != None:
voicebot = await vchannel.channel.connect()
await ctx.send('Joining you, '+str(ctx.message.author.mention)+', in voice channel "'+str(vchannel.channel)+'"')
else:
await ctx.send('Sorry,'+str(ctx.message.author.mention)+', you are not in a voice channel')
...Other functions...
I tried making voicebot a global variable, but found out, that you can't change global varibales from a function.
So my next idea was to make the bot a class:
class TheBot(commands.Bot):
def __init__(self):
intents = discord.Intents.all()
super().__init__(intents=intents, command_prefix='~', owner_id=587573816360960022)
members = inspect.getmembers(self)
for name, member in members:
if isinstance(member, commands.Command):
if member.parent is None:
self.add_command(member)
...Other functions...
#commands.command(name='join', help='Joins the voice channel you are in')
async def JoinVoiceofAuthor(self, ctx):
vchannel = ctx.message.author.voice
if vchannel != None:
if "voicebot" not in globals():
self.voicebot = await vchannel.channel.connect()
else:
if self.voicebot.is_connected():
return
if vchannel != None:
self.voicebot = await vchannel.channel.connect()
await ctx.send('Joining you, '+str(ctx.message.author.mention)+', in voice channel "'+str(vchannel.channel)+'"')
else:
await ctx.send('Sorry,'+str(ctx.message.author.mention)+', you are not in a voice channel')
...Other functions...
But that also didn't work, because the Context was also passed on to self and not to ctx
Does someone have a solution for this?
As Parasol Kirby said, you need to use the global keyword. To adapt this to your case we need to make some changes.
To make the voicebot variable global just add global voicebot at the top of the command. That way you can use this variable from every function.
Then to check if the bot is already connected to a voice channel, we do not need to use the voicebot variable. You can create a function that returns True if the bot is in a voice channel like this:
def is_connected(ctx):
voice_client = discord.utils.get(ctx.bot.voice_clients, guild=ctx.guild)
return voice_client and voice_client.is_connected()
Now to check if the bot is not connected to a voice channel from within the command, just use the is_connected function and "pass in" the context.
if not is_connected(ctx):
So the full code looks like this:
def is_connected(ctx):
voice_client = discord.utils.get(ctx.bot.voice_clients, guild=ctx.guild)
return voice_client and voice_client.is_connected()
#bot.command(name='join', help='Joins the voice channel you are in')
async def JoinVoiceofAuthor(ctx):
global voicebot
vchannel = ctx.message.author.voice
if vchannel != None:
if not is_connected(ctx):
voicebot = await vchannel.channel.connect()
await ctx.send('Joining you, '+str(ctx.message.author.mention)+', in voice channel "'+str(vchannel.channel)+'"')
If you want to change a global variable from a function, you must use the global keyword from inside the function:
x = 50
def add_to_x(num):
global x
x += num
print(x)
add_to_x(10)
print(x)
#The Output is this:
50
60
And an implementation in discord.py would be:
x = 50
#bot.command()
async def add_to_x(ctx,amount):
global x
print(x)
x += int(amount)
print(x)

Trying to send a message to a specific channel using Discord.py rewrite and it isn't working

I'm currently working on a discord bot and I'm trying to send a message to a specific channel using Discord.py rewrite once the user levels up, and I'm getting this error:
await channel.message.send(f"{message.author.mention} is now level {self.users[author_id]['level']}! congrats!")
AttributeError: 'NoneType' object has no attribute 'message'
Here is all the code:
import discord
from discord.ext import commands
import json
import asyncio
class Levels(commands.Cog):
#commands.Cog.listener()
async def on_message(self, message):
if message.author == self.bot.user:
return
author_id = str(message.author.id)
bot = commands.Bot(command_prefix='!')
if author_id not in self.users:
self.users[author_id] = {}
self.users[author_id]['level'] = 1
self.users[author_id]['exp'] = 0
self.users[author_id]['exp'] += 1
if author_id in self.users:
if self.lvl_up(author_id):
channel = bot.get_channel('636399538650742795')
await channel.message.send(f"{message.author.mention} is now level {self.users[author_id]['level']}! congrats!")
def __init__(self, bot):
self.bot = bot
with open(r"cogs\userdata.json", 'r') as f:
self.users = json.load(f)
self.bot.loop.create_task(self.save_users())
async def save_users(self):
await self.bot.wait_until_ready()
while not self.bot.is_closed():
with open(r"cogs\userdata.json", 'w') as f:
json.dump(self.users, f, indent=4)
await asyncio.sleep(5)
def lvl_up(self, author_id):
author_id = str(author_id)
current_xp = self.users[author_id]['exp']
current_lvl = self.users[author_id]['level']
if current_xp >= ((3 * (current_lvl ** 2)) / .5):
self.users[author_id]['level'] += 1
return True
else:
return False
I'm really not sure what the issue is here but if anyone knows the problem I would be very appreciative if you could let me know how I can correct this.
Thanks for reading I've been trying to figure this out for hours.
Edit: Still having the issue.
You get AttributeError because channel is None.
To fix it you need to remove quotes from channel id like this:
channel = bot.get_channel(636399538650742795)
This is described here: https://discordpy.readthedocs.io/en/latest/migrating.html#snowflakes-are-int
Also i see another error on the next line. The channel has no message attribute too. I think you need to fix it like this:
await channel.send(f"{message.author.mention} is now level {self.users[author_id]['level']}! congrats!")
I was able to send messages using this guide: https://discordpy.readthedocs.io/en/latest/faq.html#how-do-i-send-a-message-to-a-specific-channel
the code I used is:
channel = client.get_channel(12324234183172)
await channel.send('hello')
#bot.command()
async def lvl_up(member: discord.Member):
"""Send a level up message"""
channel = bot.get_channel(channel_id) # channel id should be an int
if not channel:
return
await channel.send(f"GG {member}, u lvled up") # Whatever msg u want to put
Try using that code for the channel and sending the message, then add ur logic. I'm new to Stack Overflow so idk if I formatted that code correctly
Not sure if this is solved (sorry I'm new to stack overflow) but using this made it work
#bot.command()
async def Hello(ctx):
channel = bot.get_channel(Insert Channel ID)
await channel.send('Hello')
Using this I didn't get the NoneType error.
you need to put it in an async function
so instead of
channel = bot.get_channel(<channel id>)
you should do
async def get_channel():
channel = bot.get_channel(<channel id>)
asyncio.run(get_channel())

Discord.py - Getting my bot to edit his response if the command is edited

I have a discord bot using the discord.py rewrite. One of my commands fetches the first Youtube video result for a given query. If a user deletes their message with the command, the bot deletes his response. That part works fine, but here it is for reference:
#bot.command()
async def yt(ctx):
ytquery = urllib.parse.urlencode({"search_query" : ctx.message.content[4:]})
html_cont = urllib.request.urlopen("http://youtube.com/results?"+ytquery)
ytresult = re.findall(r'href=\"\/watch\?v=(.{11})', html_cont.read().decode())
delcmd = await ctx.send("http://youtube.com/watch?v=" + ytresult[0])
deletelog[ctx.message] = delcmd
deletelog={}
#bot.event
async def on_message_delete(message):
if message in deletelog:
dellog = deletelog[message]
await dellog.delete()
del deletelog[message]
But I've seen other bots that can also EDIT the message if the command message is edited. My friends in my server have demanded I figure out how to make my bot do this. I figure it should be pretty simple, just piggyback off the "deletelog" I made, and if the youtube command in that log is edited, we edit the response.
This is my first time using the edit command, and I read the docs but I can't get it to work and am not sure what I'm screwing up:
#bot.event
async def on_message_edit(before, after):
print("test")
if before in deletelog:
print("test2")
ytquery = urllib.parse.urlencode({"search_query": after.message.content[4:]})
html_cont = urllib.request.urlopen("http://youtube.com/results?" + ytquery)
ytresult = re.findall(r'href=\"\/watch\?v=(.{11})', html_cont.read().decode())
delcmd = await before.edit(content=("http://youtube.com/watch?v=" + ytresult[0]))
deletelog[after] = delcmd
That second test print, "test2" is never firing. So my bot isn't even detecting the "before" message in the deletelog, even though it should, right? Sorry if this is a stupid question, I just am not sure where I'm messing up.
Use the id attribute of the messages instead of the Message object itself.
#bot.command()
async def yt(ctx):
ytquery = urllib.parse.urlencode({"search_query" : ctx.message.content[4:]})
html_cont = urllib.request.urlopen("http://youtube.com/results?"+ytquery)
ytresult = re.findall(r'href=\"\/watch\?v=(.{11})', html_cont.read().decode())
delcmd = await ctx.send("http://youtube.com/watch?v=" + ytresult[0])
deletelog[ctx.message.id] = delcmd
deletelog={}
#bot.event
async def on_message_delete(message):
if message.id in deletelog:
dellog = deletelog[message.id]
await dellog.delete()
del deletelog[message.id]
#bot.event
async def on_message_edit(before, after):
print("test")
if before.id in deletelog:
print("test2")
ytquery = urllib.parse.urlencode({"search_query": after.message.content[4:]})
html_cont = urllib.request.urlopen("http://youtube.com/results?" + ytquery)
ytresult = re.findall(r'href=\"\/watch\?v=(.{11})', html_cont.read().decode())
delcmd = await before.edit(content=("http://youtube.com/watch?v=" + ytresult[0]))
deletelog[after.id] = delcmd

Categories

Resources