Discord.py bot not running client.loop or tasks in on_ready - loops

I've made a python bot for a small project I am doing. Basically all it does is read a specific set of cells in a google sheet, draw a nice little chart and save it as an image, and then send it to a user in their DMs with their discord id.
However, I've struck a bit of an issue. The bot keeps going offline after 3 minutes or so. No biggie, I'll just change it's status to something funny out of a list every 2 mins, I thought. But, no matter if I do it using a client.loop event or tasks from discord.ext, the loop won't run.
The only times when they do run is when the discord bot throws an error (like losing connection when my wifi decides to have a mind of its own), or when the client.loop event regarding the cells that are being watched is fulfilled.
I have scoured the internet looking for the dumb thing I've done wrong, but can't for the love of me find out why. Can anyone help me? The code is below, with some changes as to not reveal tokens or anything.
import gspread
from oauth2client.service_account import ServiceAccountCredentials
from discord.ext import tasks
import time
import matplotlib.pyplot as plt
import requests
import json
import discord
import random
import asyncio
# Set up the credentials to access the Google Sheets API
scope = ['https://spreadsheets.google.com/feeds', 'https://www.googleapis.com/auth/drive']
credentials = ServiceAccountCredentials.from_json_keyfile_name(r"json location", scope)
# Authenticate with Google and open the desired sheet
gc = gspread.authorize(credentials)
sheet = gc.open("Spreadsheet name").worksheet('Sheet6')
# Get the initial value of the cell
cell_value = sheet.acell('B2').value
#init some variables
parties = [(20, -16, "royalblue", "BIU"), (-10, -33, "navy", "DXC"), (-10, 9, "gold", "ALP")]
status = [
'you make questionable political choices.',
'you lie on your political compass test.',
'you destroy your political career.',
'you spam Neutral/Unsure.',
'you bank on trickle down economics',
'you expand the government',
'you storm the Capitol',
'you claim you did not have sexual relations with that woman',
'you take secularism as more of a suggestion',
'you change the definition of morally ambiguous'
]
#define some defs
def political_compass(economics, social):
#this thing really is superfluous now but I cba to remove it
return (economics, social)
def plot_result(result, parties=None):
#plot things in a square graph and such
x, y = result
fig, ax = plt.subplots()
ax.scatter(x, y, color="black")
if parties:
for party in parties:
ax.scatter(party[0], party[1], color=party[2], label=party[3])
ax.set_title("TITLE")
ax.set_xlim(-50, 50)
ax.set_ylim(-50, 50)
ax.axhline(y=0, color="black")
ax.axvline(x=0, color="black")
ax.annotate("Economic Left/Right", (35, 0), (0, -5),
textcoords="offset points", ha="center", va="top")
ax.annotate("Progressive/Conservative", (0, 45), (-5, 0),
textcoords="offset points", ha="right", va="center")
if parties:
plt.legend(loc="upper right")
fig.savefig("political_compass.png")
client = discord.Client()
#tasks.loop(minutes=2)
async def status_change():
cur_status = random.choice(status)
print('Current status is: Watching '+cur_status)
await client.change_presence(status=discord.Status.online, activity=discord. Activity(type=discord.ActivityType.watching, name=cur_status))
async def sheet_check():
global cell_value
await client.wait_until_ready()
while not client.is_closed():
time.sleep(1)
current_value = sheet.acell('B2').value
if current_value != cell_value:
print("Cell value has changed from", cell_value, "to", current_value)
cell_value = current_value
# Grabbing info
discord_id = sheet.acell('C2').value
eco_val = float(sheet.acell('D2').value)
soc_val = float(sheet.acell('E2').value)
print(discord_id)
print(eco_val)
print(soc_val)
# make the graph
result = political_compass(eco_val, soc_val)
plot_result(result, parties)
# Upload the image to imgur
client_id = "ID I WON'T SHARE"
image = open("political_compass.png", "rb").read()
headers = {"Authorization": f"Client-ID {client_id}"}
response = requests.post("https://api.imgur.com/3/image", headers=headers, data={"image": image, "type": "file"})
if response.status_code == 200:
response_json = response.json()
print(f"Image uploaded successfully: {response_json['data']['link']}")
else:
print("Failed to upload image")
# Send with discord bot
user = user = await client.fetch_user(discord_id)
await user.send(f"Here is your Political Compass result: {response_json['data']['link']}")
await user.send(f"Additional message")
await user.send(f"other additional message")
t = time.localtime()
current_time = time.strftime("%H:%M:%S", t)
print("Message sent at " + current_time)
await asyncio.sleep(10)
#client.event
async def on_ready():
print(f'Logged in as {client.user}')
await client.change_presence(status=discord.Status.online, activity=discord. Activity(type=discord.ActivityType.watching, name='myself in the mirror.'))
channel = client.get_channel(CHANNEL ID)
await channel.send('Hello, World!')
print('He has awaken')
status_change.start()
client.loop.create_task(sheet_check())
client.run('TOKEN THAT SHOULDNT BE HERE BUT IN A ENV FILE I KNOW')
Thanks in advance for helping.
I tried calling status_change through client.loop and through discord.ext's tasks but neither has resulted in status_change being run on startup and then continuously.

Related

How can I change the permissions of a specific role of a channel in discord.py

I'm trying to make a command that takes a channel name as input (or argument) and locks that channel but for only the students in the channel. Here's the code:
# bot.GUILD = bot.get_channel(guild_id) [In on_ready method in bot class]
#bot.command()
async def lock(ctx, channel_name):
user_dm = await bot.create_dm(ctx.author)
channels = await bot.GUILD.fetch_channels()
roles = await bot.GUILD.fetch_roles()
channels = list(filter(lambda c: c.name.lower() == channel_name.lower(), channels))
student_roles = list(filter(lambda r: r.name.endswith("students"), roles))
# print(channels, roles) [This works fine]
if channels:
for channel in channels:
for role in student_roles:
await channel.set_permissions(role, send_messages=False)
await user_dm.send(f"✅ {channel_name} has been locked!")
else:
await user_dm.send(f"❌ {channel_name} does not exist!")
I don't know why it's not sending any message to my dm. Because it's an if-else statement so at least I get a message from the bot whether positive or negative but that doesn't happen. And yes my bot has the necessary permissions. I really hope you can help me with this 😕🥺

How do I get a list of all members in a discord server, preferably those who aren't bot

I'm attempting checks if a username is in a valid username in the server. The check is done by the following code
intents = discord.Intents.all()
bot = commands.Bot(command_prefix='!logan',
intents=intents,
helpCommand=helpCommand)
Users = [# Need help getting users]
async def Fight(ctx, User):
if ctx.author.name != User:
if User in Users:
open_file = open(str(ctx.author.name), "w")
open_file.write(User + "\n300\nTurn")
open_file.close()
open_file = open(User, "w")
open_file.write(str(ctx.author.name) + "\n300\nNot")
open_file.close()
await ctx.author.send("You are now fighting " + User)
#await ctx.User.send("You are now fighting " + ctx.author.name)
else:
await ctx.send("User is not in this server")
else:
await ctx.send("You Cannot Fight Yourself")
I've tried googling, but I haven't found a valid answer
There are other ways to get your guild name, but here's a possible method to get it and get the member list from it.
In member (and user so), there is the ".bot" that is a boolean, 0 if not a bot.
listMembers = []
#bot.command(name="init")
async def FuncInit (ctx) :
global listMembers
guilde = ctx.message.guild
for member in guilde.members :
if not member.bot :
listMembers.append(member.name)

Delete messages from specifc user using Discord py

I wanted to create a clear command where if example .clear #user#0000 100 it will clear the last 100 messages #user#0000 sent. Here is my code:
#commands.command()
#commands.has_permissions(manage_messages=True)
async def clear(self, ctx, amount=1):
await ctx.channel.purge(limit=amount + 1)
#clear.error
async def clear_error(self, ctx, error):
if isinstance(error, commands.MissingPermissions):
await ctx.send('Sorry, you are missing the ``MANAGE_MESSAGES`` permission to use this command.')
This code i provided only deletes depeneding how many messages will be deleted in a channel. I want to make a code where the bot will delete messages from a specific user. If this is possible, please let me know. I will use the code as future reference. Much appreciated.
You can use check kwarg to purge messages from specific user:
from typing import Optional
#commands.command()
#commands.has_permissions(manage_messages=True)
async def clear(self, ctx, member: Optional[discord.Member], amount: int = 1):
def _check(message):
if member is None:
return True
else:
if message.author != member:
return False
_check.count += 1
return _check.count <= amount
_check.count = 0
await ctx.channel.purge(limit=amount + 1 if member is None else 1000, check=_check)

Random tips. Discord.py

I want my bot to send random tips out of a list of tips whenever a command is run.
Also, I want it to show the tips only sometimes.
What I mean to say is, for example, a user types the command hi then it should say ofcourse hi. But the tips should be shown only when the command is run a certain amount of times.
So if the command is run 5 times, then what I would like is that every 5 times my command is executed, one tip should be shown.
It is not like I have a command that sends random tips.
For example, if I have an unban command, it could send a tip every 5 times a command is run like, "You can also use kick and ban commands. Use -help for more info."
I hope you get what I am trying to say and this is all the information you need.
This is inspired by Dank Memer
Just store a counter somehow and use the random module to select a random tip
If using functions for commands:
import random
hi_counter = 0
hi_tips = [
"You can also use kick and ban commands. Use `-help` for more info.",
"a tip",
"another tip",
"yet another tip",
]
#bot.command('hi')
async def hi(ctx):
global hi_counter, hi_tips
await ctx.send('hi')
hi_counter += 1
if hi_counter % 5 == 0:
await asyncio.sleep(1)
await ctx.send(random.choice(hi_tips))
If you're using a Cog for commands:
import random
class hi(commands.Cog):
def __init__(self, bot):
self.bot = bot
self.counter = 0
self.tips = [
"You can also use kick and ban commands. Use `-help` for more info.",
"a tip",
"another tip",
"yet another tip",
]
#commands.command('hi')
async def hi(self, ctx):
await ctx.send('hi')
self.counter += 1
if self.counter % 5 == 0:
await asyncio.sleep(1)
await ctx.send(random.choice(self.tips))
def setup(bot):
bot.add_cog(hi(bot))
If you want to make a decorator for this, you can do that too:
import random
def tips(times: int, tips: List[str]):
counter = 0
def decorator(func):
nonlocal counter, tips
async def wrapper(*args, **kwargs):
nonlocal counter, tips
ctx = func(*args, **kwargs)
counter += 1
if counter % times == 0:
await asyncio.sleep(1)
await ctx.send(random.choice(tips))
return wrapper
return decorator
# Use it like:
#bot.command('hi')
#tips(5, [
"You can also use kick and ban commands. Use `-help` for more info.",
"a tip",
"another tip",
"yet another tip",
])
async def hi(ctx):
global hi_counter, hi_tips
await ctx.send('hi')
return ctx # MAKE SURE TO RETURN `ctx` OR THE DECORATOR WON'T WORK

How would i get live updates when a user joins and leaves a discord voice channel

Is there a way too do a callback or a background task to see when a user joins and leaves a voicechannel? Currently when I cmd the bot I only am able to see the users that are currently in the voicechannel only. Sorry if it might not make sense.
import asyncio
import config
client = discord.Client()
#client.event
async def on_ready():
print("We have logged in as {0.user}".format(client))
#Voice channel
lobby_voicechannel = client.get_channel(708339994871463986)
#Text channel
txt_channel = client.get_channel(702908501533655203)
team_one = []
team_two = []
member_id = []
lobby_queue = lobby_voicechannel.members
for x in lobby_queue:
#change mention to name or nick for variations
member_id.append(x.mention)
player_num = len(member_id)
joined_user = str(member_id)
#check how many players in total for queue
if player_num == 5:
user_convert = tuple(member_id)
embed = discord.Embed(
title="**{}/10** players joined `{}`".format(player_num, lobby_voicechannel),
description="\n".join(user_convert),
color=0x00f2ff)
await txt_channel.send(delete_after=None, embed=embed)
else:
if player_num == 0:
embed = discord.Embed(
title="**{}/10** players joined `{}`".format(player_num, lobby_voicechannel),
description=f"***```No players in {lobby_voicechannel}```***",
color=0x00f2ff
)
await txt_channel.send(delete_after=None, embed=embed)
client.run(config.Token)```
You can use the on_voice_state_update event. This event gets triggered whenever a member joins/leaves or when a members state changes in a voice channel. For more info see link.
However you still need to check whether or not the member left/joined. This can be done through the before and after VoiceState objects received from the event: on_voice_state_update(member, before, after):.
Example:
#commands.Cog.listener()
async def on_voice_state_update(member, before, after):
if before.channel == None and after.channel != None:
state = 'joined'
else if before.channel != None and after.channel == None:
state = 'left'
else:
state = 'unchanged'
# Continue with some code

Resources