So here's the crux of the problem. I'm using the Discord Py API (1.7.3). I have 2 bots I run that use it, Red Bot and the one I made myself. I've come to notice one little discrepancy that I can't troubleshoot successfully. If I kill Red Bot, like either by shutting down the host computer or otherwise, it leaves Chat immediately. If I do the same to my bot, it lingers for minutes at a time. It's driving me up the wall and I can't figure out why. I have another friend with his own completely independent bot, with the same issue.
There are 5 other modules, but the below is main and the most likely to have either the problem or the place for the solution.
from events import Events
from macros import Macros
from voting import Voting
from miscellaneous import Miscellaneous
from persistent import *
# Fetches configuration information
comList = []
discordAuthcode = ""
with open(DAC_FILE, "r") as discordAuthfile:
discordAuthcode = discordAuthfile.read().strip()
# Sets Google's credentials
os.environ["GOOGLE_APPLICATION_CREDENTIALS"] = GAJ_FILE
# Creates basic bot object
help_overwrite = commands.DefaultHelpCommand(no_category="Help")
intents_overwrite = discord.Intents.default()
intents_overwrite.members = True
intents_overwrite.messages = True
bot = commands.Bot(command_prefix=COM_PRFX, description="Gestalt's Help Menu", help_command=help_overwrite, intents=intents_overwrite)
# Creates a task to iterate randomly through creative messages
#tasks.loop(minutes=random.randint(1,5))
async def random_presence():
choice = presenceList[random.randint(0, len(presenceList)-1)]
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name=choice))
# Creates a task to backup active nomination instances
#tasks.loop(minutes=5)
async def backup_nominations():
repickles(NBP_FILE, nominationMap)
# DEBUG: Executes on Interval, good for live-testing
##tasks.loop(seconds=3)
#async def backup_nominations():
# print(nominationMap)
# Bot Initialization
#bot.event
async def on_ready():
"""Initializes the bot's functions.
"""
# Feedback
print("{0.user} Version {1} has started.".format(bot, VERSION))
await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.listening, name="the beginning."))
# Starts Random Presence
random_presence.start()
# Starts backing up nominations list
backup_nominations.start()
# Adds Cogs to Bot
bot.add_cog(Events(bot))
bot.add_cog(Macros(bot))
bot.add_cog(Voting(bot))
bot.add_cog(Miscellenous(bot))
# Runs Bot
bot.run(discordAuthcode)
I've looked at Red Bot's handling of shutdown, and while it clearly puts a lot more effort into it, I didn't successfully transplant anything that worked. Could I please have some help?
Edit: I have also tried enabling logging, nothing is erroring out, the last entry is always it exiting the Event Loop.
Edit 2: I have replicated this problem on a barebones bot
Related
so, im trying to make a multiple status for my discord bot, but i cant find anything.
also im trying to make the status show how many times two commands have been used globally
this is what ive tried for multiple status
async def change_playing():
threading.Timer(10, change_playing).start()
await test_bot.change_presence(game=discord.Game(name='Currently on ' + str(len(client.servers)) +
' servers'))
threading.Timer(10, change_playing).start()
await test_bot.change_presence(game=discord.Game(name='Say shelp'))```
i found it, but thanks for the help
guilds = len(client.guilds)
users = len(client.users)
await client.wait_until_ready()
statuses = ["Shelp", f"im in {guilds} guilds| watching {users} users", "with knives"]
while True:
game = discord.Game(random.choice(statuses))
await client.change_presence(status=discord.Status, activity=game)
await asyncio.sleep(10)
client.loop.create_task(roll_presence())```
You could add this to your bot with the discord.ext.tasks decorator that was made for the very purpose of repeating an action every n seconds.
The docs for it are here.
You could do something like
import discord
from discord.ext import tasks
SAYHELP = discord.Game(name="Say shelp")
SERVER_COUNT = discord.Game(name=f'Currently on {len(client.servers)} servers'))
#tasks.loop(seconds=10)
async def change_playing():
if test_bot.activity is SAYHELP:
test_bot.change_presence(game=SERVER_COUNT)
else
test_bot.change_presence(game=SAYHELP)
async def on_ready(self, *args, **kwargs):
change_playing.start()
super().__on_ready__(*args, **kwargs)
Just don't forget to update the SERVER_COUNT activity whenever the bot gets added to another server. There's this event that gets fired then.
As for the command usage counter, there's a couple approaches to this that depend entirely on how you're instantiating your bot.
If you're just using the discord.ext.commands.Bot class as-is, you'll want to define an integer for that in your top-level code and increment that every time the relevant command is called.
If you're subclassing the bot, however, then you can define self.commandcounter in your bot and have the commands update that instead.
If you're also using cogs, then make sure the cog has a reference back to the bot so you can increment self.bot.commandcounter.
You could then have the #task decorated function set that as presence as well. Might need to add another check for the current activity though.
The docs for dpy are a good place to start looking if you can't get something to work.
I am making a bot and want it to ping a user every ten minutes. Here is what I have right now
#client.event
while True:
channel = client.get_channel(717346417592762457)
await message.channel.send(<#!561464966427705374>)
time.sleep(600)
But this results in a lot of errors.
One easy way to do it would be to use discord's tasks extension. You can easily set the amount of time for each loop. The only main drawback is that the loop restarts every time your bot restarts, or rather everytime you run the command, in this example. In the example below, I added the method to start the loop in a command, but you can have it start in an on_ready event or similar.
# New Import
from discord.ext import tasks, commands
# ... Other code ...
#tasks.loop(minutes=10) # #tasks.loop(seconds=600) <-- does the same amount of time
async def ping_loop():
channel = client.get_channel(717346417592762457)
await channel.send("<#!561464966427705374>")
# 1. Don't use message.channel, as you only defined channel and this isn't an event
# 2. As mentioned by Tim Roberts in the comments
# add speech marks to your send() since it's a string
# somewhere else in your code
#client.command()
async def loop(ctx):
try:
ping_loop.start() # start the loop if it isn't currently running
await ctx.send("Started pinging <#!561464966427705374>")
except: # happens if the loop is already running
ping_loop.cancel() # if so, cancel the loop
await ctx.send("Stopped pinging <#!561464966427705374>")
For the specific errors you mentioned:
#client.event
async def somefunction(ctx):
# you need to define a function to call (pass to discord.py)
# also the function has to be async, for the usage of `await`
while True:
channel = client.get_channel(717346417592762457)
await message.channel.send(<#!561464966427705374>)
# not sure if you can mention user like this
await asyncio.sleep(600)
# time.sleep(600) # prefer non-blocking asyncio sleep. make sure `asyncio` is imported
To mention a user, you need to first get user with
user_instance = ctx.guild.get_member(YOUR_USER_discord_id)
Note: You may need to enable the privilege to use the .get_member() function. If you have not enabled it yet, just follow the instructions in the exception.
Then you can sent message with
await ctx.send(f"THE_TEXT_YOU_WANT_TO_SPAM {user_instance.mention}!")
Also, if you are using it in a bot command like me, you may want to add some validation, so that not everyone can cast/invoke the command. I did this with has_role():
#commands.has_role(YOUR_ROLE_ID_THAT_CAN_CAST_THIS_COMMAND)
So in the end, it looks like this:
#bot.command('name': 'spam', 'help':'use at # to mention a user')
#commands.has_role(YOUR_ROLE_ID_THAT_CAN_CAST_THIS_COMMAND)
async def spamMention(ctx, spam_duration_second: int = 3):
user_instance = ctx.guild.get_member(YOUR_USER_discord_id)
for cntr in range(spam_duration_second):
await ctx.send(f"THE_TEXT_YOU_WANT_TO_SPAM {user_instance.mention}!")
await asyncio.sleep(THE_INTERVAL_YOU_WANT_IN_SECOND)
return
It's part of my code so I know it works (my python version: 3.7.9).
I have a Discord bot in Python / Discord.py where people can enter commands, and normally the bot responds very quickly.
However the bot is also gathering/scraping webdata every iteration of the main loop. Normally the scraping is pretty short and sweet so nobody really notices, but from time to time the code is set up to do a more thorough scraping which takes a lot more time. But during these heavy scrapings, the bot is sort of unresponsive to user commands.
#bot.command()
async def sample_command(ctx):
# may actually take a while for this command to respond if we happen to be
# in the middle of a heavier site scrape
await ctx.channel.send("Random message, something indicating bot has responded")
async def main_loop():
sem = asyncio.Semaphore(60)
connector = aiohttp.TCPConnector(limit=60)
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
while True:
# main guts of loop here ...
scrapers = [scraper_1(session, sem), scraper_2(session, sem), ...]
data = list(chain(*await asyncio.gather(*scrapers))) # this may take a while
# do stuff with data
Is there a way to sort of have it go "Hey, you want to do a heavy scrape, fine go process it elsewhere - meanwhile let's continue with the main loop and I'll hook back up with you later when you're done and we'll process the data then", if that makes sense?
I mainly want to separate this scraping step so it's not holding up the ability for people to actually interact with the rest of the bot.
You can use the discord.py tasks extension docs.
For example:
from discord.ext import tasks
#bot.event()
async def on_ready():
main_loop.start()
#bot.command()
async def sample_command(ctx):
await ctx.channel.send("Random message, something indicating bot has responded")
#tasks.loop(seconds=60)
async def main_loop():
do_something()
Note: It's not recommended to start the tasks in on_ready because the bot will reconnect to discord and the task will start several times, Put it somewhere else or on_ready check if this the first connect.
Another simple tip: you can use await ctx.send() instead of await ctx.channel.send()
You can use asyncio.create_task() to spawn the scraping in the "background":
async def scrape_and_process(...):
scrapers = [scraper_1(session, sem), scraper_2(session, sem), ...]
data = list(chain(*await asyncio.gather(*scrapers))) # this may take a while
# do stuff with data
async def main_loop():
sem = asyncio.Semaphore(60)
connector = aiohttp.TCPConnector(limit=60)
async with aiohttp.ClientSession(connector=connector, headers=headers) as session:
while True:
# main guts of loop here ...
# initiate scraping and processing in the background and
# proceed with the loop
asyncio.create_task(scrape_and_process(...))
You can try to use python threading.
Learn more here
It basically allows you to run it on different threads
example:
import threading
def 1():
print("Helo! This is the first thread")
def 2():
print("Bonjour! This is the second thread")
thread1 = threading.Thread(target=1)
thread2 = Threading.Thread(target=2)
thread1.start()
thread2.start()
I'm using pyTelegramBotAPI as framework to create a telegram bot.
I'm having some troubles with handle audio messages and I can't understand where I am wrong.
Here my code:
# If user send an audio and it's a private chat
#bot.message_handler(content_types=["audio"])
def react_to_audio(message):
if message.chat.type == "private":
bot.reply_to(message, """What a nice sound! I'm not here to listen to some audio, tho. My work is to wish a good night to all members of a group chat""")
Can anyone help me?
i don't specifically know well how to use pyTelegramBotAPI because also i got problems i couldn't solve so i abandoned it for python-telegram-bot which is better documented in comparison to pyTelegramBotAPI, has a bigger community, is more actively developed and also have an active Telegram group where you can directly ask for help from other developers using this wrapper.
So if you are interested to change to python-telegram-bot, the code for your bot would look something like this:
from telegram import Update
from telegram.ext import Updater, MessageHandler, Filters
token = "" #Insert your token here
def message_handler(update, context):
update.message.reply_text("Hello")
def audio_handler(update, context):
if update.message.chat.type == "private": #Checks if the chat is private
update.message.reply_text("What a nice sound! I'm not here to listen to some audio, tho. My work is to wish a good night to all members of a group chat")
def main():
"""Start the bot."""
updater = Updater(token, use_context=True)
# Get the dispatcher to register handlers
dispatcher = updater.dispatcher
# on noncommand i.e message
# Use this if you want to handle also other messages
dispatcher.add_handler(MessageHandler(Filters.text & ~Filters.command, message_handler))
dispatcher.add_handler(MessageHandler(Filters.audio & ~Filters.command, audio_handler))
# Start the Bot
updater.start_polling()
# Run the bot until you press Ctrl-C or the process receives SIGINT,
# SIGTERM or SIGABRT. This should be used most of the time, since
# start_polling() is non-blocking and will stop the bot gracefully.
updater.idle()
if __name__ == '__main__':
main()
Here are also some official examples of bots made with this wrapper and the that you can use to better understand the wrapper, if you want more info regard this feel free to DM me on telegram #Husnainn.
Trying to do a thing where you enter a command and get a list of the top 5 messages with a reaction emoji called :goldmedal: . when entered into the bot, everything is fine, but when executing the command it starts a infinite loop of responses to every message it can find. and doesn't show the "goldmedal" value, but rather all the reactions on that specific message. https://cdn.discordapp.com/attachments/511938736594878478/512733361819746314/unknown.png
import discord
from discord import Game
from discord.ext.commands import Bot
from discord import Channel
from discord.utils import get
from discord.ext import commands
bot = Bot(command_prefix='!')
def num_reactions(message):
return sum(react_count for react in message.reactions)
#bot.command(pass_context=True)
async def most_reacted(ctx, channel: Channel):
most_reactions = most_reactions_message = None
goldmedal_emoji = get(ctx.message.server.emojis, name="goldmedal_emoji")
async for message in bot.logs_from(channel):
num_reactions_message = ([goldmedal_emoji])
num_reactions_message = num_reactions(message)
if not most_reactions or num_reactions_message > most_reactions:
most_reactions = num_reactions_message
most_reactions_message = message
await bot.say(f"{most_reactions_message.content} has the most Gold Medals with {most_reactions}")
Your function here:
def num_reactions(message):
return sum(react_count for react in message.reactions)
only fetches the number of different reactions to a message instead of the count of reactions with the goldmedal emoji. You would probably want something like this instead:
def num_reactions(message):
for react in message.reactions:
if react.emoji.name === "goldmedal_emoji":
return react.count
There are also many long term issues with your approach being:
It only obtains the last 100 messages from the log as the default limit of logs_from() is 100 (unless this is exactly what you want)
The bot has to search the channel every single time the command is ran which will be SLOW
May I suggest a different approach that stores the highest 5 in memory (hopefully not RAM but a database, or other persistent storages such as text files, in case your bot ever crashes), replacing them instead when a new message has a higher count of "goldmedal_emoji" reactions.