Discord.py: client not working inside tasks? - python

I am trying to loop a task that would check a member's activity from a certain server every hour, and sending a message on a specific channel if they are still on the same activity. Here is the part of code I need help with:
import discord
from discord.ext import commands, tasks
from keep_alive import keep_alive
intents = discord.Intents.all()
client = commands.Bot(command_prefix="?", intents=intents)
#tasks.loop(seconds = 10, count = 1)
async def checkactiv():
guild = client.get_guild(int(####))
member = guild.get_member(int(####))
channel = client.get_channel(int(####))
if member.activity.name.lower() == "a certain game":
await channel.send("ignore this test message")
checkactiv.start()
I have tried this as a command and it works, but I want it to loop automatically every hour or so (I made it 10 seconds as a test). The error I get is saying that guild and channel are a NoneType, this wasn't the case when I was using as a command like this:
#client.command()
async def test(ctx):
guild = client.get_guild(int(####))
member = guild.get_member(int(####))
await ctx.channel.purge(limit = 1)
if member.activity.name.lower() == "a certain game":
await ctx.send("still playing")
I am not entirely sure what the problem is, but I am assuming that client cannot be called under a task? It obviously works just fine under #client.command, but not within a task loop. Any ideas on how I can fix this? Does it have something to do with my client being commands.Bot rather than discord.Client? I have commands on the Discord bot that is not displayed here so I can't try changing my client.
Any help is appreciated!

client.get_x gets the object from the internal cache, when you start the task the cache is not fully loaded yet, you can use the client.wait_until_ready method to wait till the cache is ready:
#tasks.loop(seconds = 10, count = 1)
async def checkactiv():
guild = client.get_guild(int(####))
member = guild.get_member(int(####))
channel = client.get_channel(int(####))
#checkactiv.before_loop # it's called before the actual task runs
async def before_checkactiv():
await client.wait_until_ready()

Related

Slash commands not syncing to specific guilds in discord.py

So I have a discord bot which overall functions correctly but the command sync behaves very strangely.
I've read that having the full global sync run in 'on_ready' is a bad idea and can get my bot rate limited, and that the alternative is to have a /sync function which exists only on a test server and will run the full tree.sync(). I've tried to implement this but for some reason I cannot get the /sync function to appear on my test server, and even worse for some reason my full global sync seems to be running anyway.
To test I have two different guilds, one of which is the main test guild that will be used for bot administration. Here's the relevant snippet of code:
# -- setup --
# create client
intents = discord.Intents.default()
intents.message_content = True
client = discord.Client(intents=intents)
global_synced = False
tree = app_commands.CommandTree(client)
# -- events --
#client.event
async def on_ready():
# sync commands
await client.wait_until_ready()
if(not global_synced):
for g in guilds:
# Before I added this loop and following if statement I was getting a "403 Forbidden"
# I guess the error was because the secondary guild doesn't have access to the
# discord Object of the main guild? IDK
if(g.id == TEST_GUILD_ID):
await tree.sync(guild=discord.Object(id=TEST_GUILD_ID))
global_synced = True
# -- commands --
#tree.command(name = "run_bot", description="Runs the bot")
async def self(interaction: discord.Interaction):
# this function is available to all guilds and runs the bot
return
#tree.command(name = "sync", description="Syncs the bot commands", guild=discord.Object(id=TEST_GUILD_ID))
async def self(interaction: discord.Interaction):
# this function is supposed to be available to only the main test server
await client.wait_until_ready()
await tree.sync()
return
So here are my issues:
"/sync" is not appearing in my main test guild
"/run_bot" is appearing on my secondary test guild even though I explicitly said not to sync all?
I'm at a loss. I'm getting no errors and I've pored over the documentation but can't find an answer. Does it have something to do with asynchronous code (my ongoing nemesis)? Please help!
You missed out the copy global commands to the guild.
self.tree.copy_global_to(guild=guild)
await self.tree.sync(guild=guild)

how to run a command in tasks.loop discord.py bot

so im trying to get a command to run every 5 minutes on my dsicrod.py bot and need ctx to get guild members and certain details like that so I need it in a bot.command, but I cant have that properly do that without tasks.loop(minutes=5) so I tried getting it to send the command with tasks.loop but it wouldn't work so I went to the pythin discord and got help they got me to this point
#bot.command(pass_context=True)
async def update_member_count(ctx):
await ctx.send(ctx.guild.member_count)
channel = discord.utils.get(ctx.guild.channels, id=829355122122424330)
await channel.edit(name = f'Member Count: {ctx.guild.member_count}')
#tasks.loop(minutes=5)
async def update_member_count2(ctx):
await update_member_count(ctx)
and It still gives errors saying that ctx arg is missing in update_member_count2. pls help
You can create your loop-function in another way. Your approach is a bit unclear to me.
Try out the following:
async def update_member_count(ctx):
while True:
await ctx.send(ctx.guild.member_count)
channel = discord.utils.get(ctx.guild.channels, id=YourID)
await channel.edit(name=f'Member Count: {ctx.guild.member_count}')
await asyncio.sleep(TimeInSeconds)
#bot.command()
async def updatem(ctx):
bot.loop.create_task(update_member_count(ctx)) # Create loop/task
await ctx.send("Loop started, changed member count.") # Optional
(Or you simply create the task in your on_ready event, depends on you)
What did we do?
Created a loop out of a function (async def update_member_count(ctx):)
Exectued updatem one time to activate the loop
Changed your actual command to a "function"
You could go with #Dominik's answer but its near impossible to stop a running While loop. Going ahead with discord.py tasks.
If you want to start the task with a command:
async def start(ctx):
update_member_count2.start(ctx)
If you want to start it when the bot starts, you have to slightly modify the code
#tasks.loop(minutes=5)
async def update_members():
guild = bot.get_guild(guildID)
channel = guild.get_channel(channelID)
#update channel, don't sleep
# inside init or on_ready
if not update_members.is_running():
update_members.start()
Use this in a on_member_join event
#bot.event
async def on_member_join(member):
channel = discord.utils.get(member.guild.channels, id=962732335386746910)
await channel.edit(name = f'⚫┃・Members: {member.guild.member_count}')

For discord python, How do I print a message every x seconds with task?

E.g I'm trying to write hello in general chat every 10 seconds. How do I do that? I'm using the task feature.
channel = client.get_channel('channel id')
#tasks.loop(seconds=10)
async def sendmessage():
await channel.send("hello")
You can Start the task with `.start()
channel = client.get_channel('channel id')
#tasks.loop(seconds=10)
async def sendmessage():
await channel.send("hello")
sendmessage.start()
Make sure your "channel id" is an integer, and your code should work as is, assuming you have sendmessage.start() somewhere.
An extra note: you probably want to put your get_channel inside your task. get_channel gets the channel object from the internal cache, so if the cache expires your channel object will too.
You might want to try moving channel into the function, like this:
import discord
import datetime
from discord.ext import commands, tasks
TOKEN = 'token'
client = discord.Client()
#tasks.loop(seconds=10)
async def sendmessage():
channel = client.get_channel('channel id')
await channel.send("hello")
sendmessage.start()
client.run(TOKEN)
The get_channel function only takes integer as input.
import discord
from discord.ext import commands, tasks
bot = commands.Bot('!') # ! = PREFIX
#tasks.loop(seconds=10)
async def sendmessage():
channel = bot.get_channel(123456789) #Channel ID
await channel.send("hello")
sendmessage.start()
bot.run("TOKEN")
You need to put sendmessage.start on your on_ready event.
Example:
#client.event
async def on_ready():
sendmessage.start()

How can I delete entire messages in a text channel in Discord with discord python library

I used a code from a asked question in stack overflow but the functions used in that seems to be changed.
Code :
from discord.ext.commands import Bot
.
.
#client.command(pass_context = True)
async def clear(ctx, number):
mgs = [] #Empty list to put all the messages in the log
number = int(number) #Converting the amount of messages to delete to an integer
async for x in client(ctx.message.channel, limit = number):
mgs.append(x)
await client.delete_messages(mgs)
Error:
async for x in client(ctx.message.channel, limit = number):
TypeError: 'Bot' object is not callable
and
discord.ext.commands.errors.CommandInvokeError: Command raised an exception: TypeError: 'Bot' object is not callable
It looks like you are using an older version of discord.py
If you want to clear messages in a channel in the latest version, it is very simple. You simply have to use the 'discord.TextChannel.purge()' method.
#client.command()
async def clear(ctx, amount=None):
if amount is None:
await ctx.channel.purge(limit=5)
elif amount == "all":
await ctx.channel.purge()
else:
await ctx.channel.purge(limit=int(amount))
A difference you may notice is that you no longer have to use 'pass_context' in the 'client.command()' decorator.
Also, at the top of your code, you shouldn't import Bot directly, rather replace that from statement with
from discord.ext import commands
and instantiate your client with
client = commands.Bot(command_prefix="!")
A really easy way to do it in the new versions of discord.py would probably look something like this:
#client.event
async def on_message(message):
if '-clear all' in message.content and message.author.permissions_in(message.channel).manage_messages:
deleted = await message.channel.purge(limit=10000, check=is_not_pinned)
await message.channel.send('All messages deleted.'.format(deleted))
await client.process_commands(message)
You could also use a regular command for this so you won't need await client.process_commands(message) but the function you are after will still be await message.channel.purge(limit=amount, check=is_not_pinned) That way only people with the manage messages permission will be able to use this command and it will not deleted pinned messages.
You can set the amount to an incredible high number so it will just delete close to, if not everything. I only tried it for around 300 and it worked perfectly (may take some time though).
A simpler way to do the clear command is this:
First of all replace from discord.ext.commands import Bot with from discord.ext import commands
Then replace
#client.command(pass_context = True)
async def clear(ctx, number):
mgs = [] #Empty list to put all the messages in the log
number = int(number) #Converting the amount of messages to delete to an integer
async for x in client(ctx.message.channel, limit = number):
mgs.append(x)
await client.delete_messages(mgs)
with
#client.command()
#commands.has_permissions(manage_messages=True)
async def clear(ctx, amount=1):
await ctx.channel.purge(limit=amount)
The 1 in amount is just the default value and you can change it to anything you want

Python Discord.py Bot Interval Message Send

I have been trying to create a bot for Discord using the discord.py library however when I am running the program it is not sending the message as expected. It is a simple bot that is suppose to send a message every 10 minutes to a channel. I am not getting any error messages in the command line and can't seem to see any obvious errors? Any help would be greatly appreciated.
import asyncio
client = discord.Client()
async def my_background_task():
await client.wait_until_ready()
counter = 0
channel = discord.Object(id='my channel ID goes here')
while not client.is_closed:
counter += 1
await message.channel.send("TEST")
await asyncio.sleep(5) # task runs every 60 seconds
#client.event
async def on_ready():
print('Logged in as')
print(client.user.name)
print(client.user.id)
print('------')
client.loop.create_task(my_background_task())
client.run('My token goes here')
Although creating a task could work, I wouldn't recommend it when there are better options. Dpy's command.ext addon has a task based system built in directly, so let's look into using that instead.
import discord
from discord.ext import tasks
client = discord.Client()
#tasks.loop(minutes=10)
async def my_background_task():
"""A background task that gets invoked every 10 minutes."""
channel = client.get_channel(754904403710050375) # Get the channel, the id has to be an int
await channel.send('TEST!')
#my_background_task.before_loop
async def my_background_task_before_loop():
await client.wait_until_ready()
my_background_task.start()
client.run('Your token goes here')
The time until which the above loop will run is dependent upon human psychology, laws of energy and cosmos.
That is:
• You get bored of it
• The power goes down and your script stops working
• The universe explodes
Read more about it here:
https://discordpy.readthedocs.io/en/latest/ext/tasks/
If the channel ID is a string, It should be an integer.
Turn this:
channel = discord.Object(id='my channel ID goes here')
into:
channel = discord.Object(id=101029321892839) # an example ID
If that doesn't solve your problem, try setting 'message.channel.send' to 'channel.send' and client.is_closed should be
client.is_closed() # put brackets
Instead of client.loop.create_task(my_background_task()), just put the background function in the on_ready event. And take out await client.wait_until_ready() and just put the function in the on_ready event as well.
I have also changed the client variable, taken out unnecessary code, and added some modules.
import asyncio, discord
from discord.ext import commands
client = commands.Bot(command_prefix = ".") # If you don't want a prefix just take out the parameter.
async def my_background_task():
channel = discord.Object(id='my channel ID goes here')
while True:
await channel.send("TEST")
await asyncio.sleep(5) # task runs every 60 seconds
#client.event
async def on_ready():
print('Logged in as')
print(client.user.name)
print(client.user.id)
print('------')
my_background_task()
client.run('My token goes here')
Also in future when posting a question, please tag the python version as I don't know which version it is and then this and other answers could be wrong.

Categories