Sorry for the unclear question title. I don't know any other way to put it.
I made a command that says p!channel [channel_id] which basically makes a channel where my bot will respond with "e". I want the command to store the channel_id and guild_id into a json file called channel.json, and when a user sends a message, it will check if the message is in the channel_id channel, and if it is in the channel, will send "e". However, it's not responding and no error codes are showing up. Can someone help? Code is below:
def get_channel(client,message):
with open("channel.json", "r") as f:
e = json.load(f)
return e[str(message.guild.id)]
#client.command()
#commands.has_permissions()
async def channel(ctx, *, channelid):
with open("channel.json", "r") as f:
e = json.load(f)
e[str(ctx.guild.id)] = channelid
with open("channel.json", "w") as f:
json.dump(e,f)
await ctx.send(f"Successfully setup <#{channelid}>")
#client.event
async def on_message(message):
if message.channel.id == get_channel:
await message.channel.send('e')
There are several immediate problems that are keeping this from functioning.
You're only referencing get_channel, not calling it. The channel's ID isn't equal to the function itself, so the message is never sent. You want get_channel(client, message).
Your on_message event ensures that your command never gets called.
You attempt to use ctx.send() instead of ctx.channel.send().
Channel IDs are integers, but command arguments are always read in as strings. Without converting the argument to an integer, comparing it against a channel's ID will always return False.
In addition, there are several things you could improve:
The get_channel function doesn't ever use client, so you could alter your function definition to simply get_channel(message).
Furthermore, channel IDs are globally unique, so you don't need to save the guild ID in order to unambiguously identify a channel.
It would be more efficient not to read the whole file every time you need to check for an ID.
The has_permissions check doesn't check anything if you supply it no arguments, so in your code it does nothing.
You probably don't want your bot to respond to its own messages.
Here's an improved version that reads a saved file on startup, if one exists. It then keeps the IDs as a set in memory, and only opens the file when it needs to add a new ID.
from discord.ext import commands
import json
client = commands.Bot(command_prefix='p!')
try:
with open('channels.json') as f:
client.ids = set(json.load(f))
print("Loaded channels file")
except FileNotFoundError:
client.ids = set()
print("No channels file found")
#client.command()
async def channel(ctx, channel_id):
try:
channel_id = int(channel_id)
except ValueError:
await ctx.channel.send("Channel must be all digits")
return
if channel_id in client.ids:
await ctx.channel.send(f"Channel <#{channel_id}> is already set up.")
return
client.ids.add(channel_id)
with open('channels.json', 'w') as f:
json.dump(list(client.ids), f)
await ctx.channel.send(f"Successfully set up <#{channel_id}>")
#client.event
async def on_message(message):
if message.channel.id in client.ids and message.author != client.user:
await message.channel.send('e')
# Pass processing on to the bot's command(s)
await client.process_commands(message)
client.run(TOKEN)
Related
So I'm trying to make a command that adds the name of a song to an user. I just don't understand how I should do that. I tried looking on the dictionary documentations but I couldn't find anywhere how I could append a variable to a certain person. This is my current code altough I think it's completely wrong:
#commands.command()
async def quote(self, ctx):
await ctx.send("add your quote")
msg = await self.client.wait_for('message', check=lambda message: message.author == ctx.author)
quote = msg.content
with open('quotes.json', 'r') as f:
quotes = json.load(f)
quotes.append(quote)
with open('quotes.json', 'w') as f:
json.dump(quotes, f)
await ctx.send("quote added!")
You can use this with a dictionary and track them by ID. Be careful with this, as JSON does not allow you to use integers as keys to anything. Only strings are allowed.
#commands.command()
async def quote(self, ctx):
await ctx.send("add your quote")
msg = await self.client.wait_for('message', check=lambda message: message.author == ctx.author)
quote = msg.content
with open('quotes.json', 'r') as f:
quotes = json.load(f)
strid = str(msg.author.id) # this is necessary for json
if strid not in quotes.keys():
quotes[strid] = []
quotes[strid].append('My quote, or put whatever you need to add in here')
with open('quotes.json', 'w') as f:
json.dump(quotes, f)
await ctx.send("quote added!")
As a sidenote, it's a bad idea to open the file and close it multiple times. Instead, you can try a construct like this, and then you will be spared from opening the file so much:
client = commands.Bot(...)
with open('quotes.json', 'r'):
client.quotes_data = json.load(f)
#tasks.loop(minutes=3.0) # adjust this at your liking, or execute it manually
async def save_all():
with open('quotes.json', 'w'):
json.dump(client.quotes_data, f)
save_all.start()
If you're trying to make it so users can request multiple songs in a queue type fashion, I'd create a dictionary, make the key the user (the message's author)'s ID (ctx.author.id) and set the value to an empty list, then append to that list the user's requested song's name.
On the other hand, if you prefer one song per user, just set the value to the song for the user's ID's key.
This would typically use just casual key assignments for dictionaries.
An example of how this would work (assume this is inside your command):
songs = {};
# This code if you'd like multiple songs per user.
songs[ctx.author.id] = [];
songs[ctx.author.id].append("SONG VALUE HERE");
# This code if you'd like one.
songs[ctx.author.id] = "SONG VALUE HERE";
For more detail I would like to create a command for my bot that takes in messages stores it somewhere like in a new file or something like that and have a new command send a message from that new file. I am wondering if I should use json or make a .txt file.
My Code:
#bot.event
async def on_message(message):
if message.author.bot:
return
if message.content == "echo":
echomessage = (message.content)
await message.channel.send(f"{echomessage} has been added to a list of messages")
await bot.process_commands(message)
#bot.command()
async def echo(ctx):
await ctx.send(f"{ctx.author.mention} {echomessage[random.randint(0, len(echomessage) - 1)]}")
I know that I have a event I would like to make it a command at some point but if I cant then imma just keep it as it.
Thanks for anyone who takes a shot at helping me! I really appriciate it.
You can simply save it to a file and randomly pick one.
Using readlines, simply write each word to use into its own line, and a random one will be picked automatically.
#bot.command()
async def addword(ctx, *, word: str):
with open('words.txt', 'a') as f:
f.write(f'{word}\n')
#bot.command()
#commands.cooldown(5, 20.0, commands.BucketType.guild) # Highly recommended, io is precious. You can also keep this file permanently opened and store it in your client at startup.
async def echo(ctx):
with open('words.txt') as f:
await ctx.send(random.choice(f.readlines()))
I have tried different options, and this one was the one that I desired the most, but I cannot seem to get it working. Could I get some help? (Sorry I am very new to coding)
This is my code:
import discord
from discord.ext import commands
client = discord.Client()
bot = commands.Bot(command_prefix='-')
#bot.command()
async def speak(ctx, *, text):
if ctx.message.author.id == ID here:
message = ctx.message
await message.delete()
await ctx.send(f"{text}")
else:
await ctx.send('I need text!')
Thanks
Your else statement makes little sense here. It is best to set up the condition differently, that you want text once and if that does not happen, then there is an else argument.
I don't know if this is relevant either, but apparently it looks like you want only one person to be able to execute this command. For the owner there would be:
#commands.is_owner()
But if you want to make it refer to another person use:
#bot.command()
#commands.is_owner() # If you want to set this condition
async def say(ctx, *, text: str = None):
if ctx.message.author is not ID_You_Want:
await ctx.send("You are not allowed to use the command.")
return # Do not proceed
if text is None: # If just say is passed with no text
await ctx.send("Please insert a text.")
else:
await ctx.send(text) # Send the text
Optional: You can also delete the command that was send with ctx.message.delete()
Your command is not executing because you defined client and bot.
Simply remove client = discord.Client() and you will be fine.
What I'm trying to implement in my discord bot is a system that stops a running command coroutine as soon as another command is invoked by the same user, in an attempt to sort of keep a unique active session for each user.
So essentially I need to be able to access that coroutine object and stop it from within another coroutine.
What I've tried so far looks logic to me but I'm sure there is some misconception about how discord API handles coroutines. Here's a basic example:
active_sessions = {}
#bot.command()
async def first_command(ctx):
# Saving the coroutine in the dictionary with the user ID as key:
active_sessions[ctx.author.id] = ctx.command.callback
# do stuff
#bot.command()
async def second_command(ctx):
# Accessing the coroutine object and trying to stop it.
try:
coro = active_sessions[ctx.author.id]
coro.close()
except KeyError:
pass
which throws an AttributeError stating that function object has no attribute close.
But, as per discord.py docs, command.callback refers to a coroutine object that should have the close method. Also tried coro.cancel() with the same result.
I'm conceptually doing something wrong here but I'm not sure what exactly, and also I have no clue how to implement my idea correctly, so any help is very appreciated.
EDIT
So, since my goal might still be unclear, here is what I'm trying to accomplish.
I'm doing a discord bot that is sort of a game. Users can run several commands, many of which would wait for a certain amount of time for a reaction or message from the command author.
But this user might run another command without 'completing' the first one, and if they come back to the first, their reactions would still be accepted.
I'm trying to avoid this behaviour and keep the user focused on a 'single session'; whatever command they run, the execution of the previous one needs to be automatically stopped.
So how I'm implementing this, which comes from a wrong misconception of coroutines, is having a dictionary that associates each user with the loop of the command thy're currently running. If another loop starts with the other one still running, the 'old loop' would be stopped and the respective entry in the dictionary would be updated with the new loop:
active_sessions = {}
#bot.command()
async def first_command(ctx):
# In this first command I want to assign the current loop to a variable and
# add it to the active_sessions dictionary along with the ID of the command author.
# Then there's a loop that waits for user's reaction.
# Ignore the details such as the check function.
while True:
try:
reaction, user = await bot.wait_for("reaction_add",timeout=60,check=check)
# some if conditions here make the bot behave differently according
# to the specific reaction added
except asyncio.TimeoutError:
return
#bot.command()
async def second_command(ctx):
# As soon as this second command is invoked, I need to access the 'loop object'
# previously stored in the dictionary (if any) and stop it from here.
There are two ways to actually stop the wait_for from working.
Switching to a bot event.
import json
#bot.event()
async def on_reaction_add(reaction, user):
# use a config variable
with open('config.json', 'r') as f:
data = json.load(f)
if data['wait_for_reaction']:
# do stuff here
else:
return
#commands.command()
async def activate(ctx):
#activates the listenere
with open('config.json', 'r') as f:
data = json.load(f)
data['wait_for_reaction'] = True
with open('config.json', 'w') as f:
json.write(data, f, indent=4)
await ctx.send('activated listener')
#commands.command()
async def deactivate(ctx):
with open('config.json', 'r') as f:
data = json.load(f)
data['wait_for_reaction'] = False
with open('config.json', 'w') as f:
json.write(data, f, indent=4)
await ctx.send('activated listener')
You can also use a global variable or a .env file to load the config variable
Using a task
from discord.ext import tasks
#tasks.loop(seconds=0)
async def task(ctx):
try:
reaction, user = await bot.wait_for("reaction_add",timeout=60, check=check)
except asyncio.TimeoutError:
return
#commands.command():
async def activate(ctx):
try:
task.start(ctx)
except RuntimeError:
await ctx.send("task is already running")
finally:
await ctx.send('started task')
#commands.command()
async def deactivate(ctx):
task.stop()
await ctx.send('stopped task')
The problem is tasks is that you can only run one task at once, so you cannot use the command in multiple contexts. There is a workaround but I wouldn't recommend using it. Use tasks only if your bot is a personal one that only you run.
References:
tasks
Note:
If you are going to use a .env file to store your variable. Look into dotenv
So I want to make my bot allow specific messages, like "/verify" in a specifc channel, if someone sent a message other than "verify" the bot should delete the message, however only in a specific channel, I'm new to all of that but I made this code and it's not working
async def verify(ctx):
user = ctx.message.author
role = 'Member' #change the role here
try:
await user.add_roles(discord.utils.get(user.guild.roles, name=role))
await ctx.send('Welcome to our server! :white_check_mark:', delete_after=1)
except Exception as e:
await ctx.send('Cannot assign role. Error: ' + str(e))
if not msg.content == '/verify':
await client.delete_message(msg)
await ctx.message.delete()
any help would be much appreciated.
You can just write a simple check right at the beginning of the function.
For example,
#bot.command()
async def verify(ctx):
if ctx.channel.name != "verify":
return
# rest of the code
Also, you have not defined msg in your code. So that'll also raise an error
You have to specify what exactly your "msg" is that you want to delete.
So instead of msg.delete it should be enough to instead write:
if ctx.message.content == "verify":
await ctx.message.delete
Because the ctx/context is where you get the information about what kind of message it is, which channel it was posted in and so on.