Running multiple commands with discord.py - python

Using discord.py and python:
Ok so basically I have this bot that updates the best prices for a certain game every minute. However, while I am doing that, other people cannot access the bot. For example, lets just say I have a command called "hello" that when called prints hello out in the chat. Since the code always runs, the user cant call the command hello because the code is too busy running the code that updates every minute. Is there any way to like make it so that the updateminute code runs while others can input commands as well?
import discord
import asyncio
import bazaar
from discord.ext import commands, tasks
client = commands.Bot(command_prefix = '.')
#client.command()
async def calculate(ctx):
while True:
await ctx.send(file2.calculate())
await asyncio.sleep(210)
#client.command()
async def hello(ctx):
await ctx.send("Hello")
client.run(token)
In file2.py:
def updateminute():
for product in product_list:
#Grab Api and stuff
#check to see whether it is profitable
time.sleep(0.3) #cause if i don't i will get a key error
#calculate the stuff
#return the result
To sum up, since the bot is too busy calculating updateminute and waiting, other people cannot access the bot. Is there any way I can try to fix this so that the bot calculates its stuff and so people can use the bots commands? Thanks!

You can look into threading! Basically, run two separate threads: one for taking requests and one for updating the prices.

You could also look into turning it into an async function, essentially making it easier to run things concurrently.
So your standard def will become async def and then to call the function you simply add an await before it so await file2.calculate()
Hope it helps and is also somewhat easier to understand

Related

Discord.py Bot send messages at certain times

I host a discord.py bot on a server for me and some friends, and have been trying to get a certain 'feature', where the bot will send a message every day, twice daily, once in the morning, once in the night, just saying general "good morning" and "good night!" I have spent hours looking through other peoples codes, and similar questions, and this is the best I can find/have gotten (It's taken from another user's 'python alarm', and I tried to hook it up to the bot.
from datetime import datetime
from threading import Timer
x = datetime.today()
y = x.replace(hour=21, minute=45, second=40, microsecond=0)
delta_t = y - x
secs = delta_t.seconds + 1
channel = client.get_channel(806702411808768023)
async def Goodnight():
await channel.send("Good night! Make sure to go to sleep early, and get enough sleep!")
print("Night Working")
t = Timer(secs, Goodnight)
t.start()
I keep getting the same error(s), usually about the message not being async or await-'able' (?). I am fairly new to coding/python, sorry if anything is obvious. I really do not know what to do, and I have found some promising solutions, though those make the whole bot the alarm, and force it to 'sleep' while waiting, while I want mine to still function normally (run other commands), if possible? Any help appreciated
This can be done using the tasks extension:
import datetime
import discord
from discord.ext import tasks
client = discord.Client()
goodNightTime = datetime.time(hour=21, minute=45, second=40) #Create the time on which the task should always run
#tasks.loop(time=goodNightTime) #Create the task
async def Goodnight():
channel = client.get_channel(806702411808768023)
await channel.send("Good night! Make sure to go to sleep early, and get enough sleep!")
print("Night Working")
#client.event
async def on_ready():
if not Goodnight.is_running():
Goodnight.start() #If the task is not already running, start it.
print("Good night task started")
client.run(TOKEN)
Note that for that to work you need to have the latest version of either discord.py or a fork which supports version 2.0. If you don't have it yet, you can install it via
pip install -U git+https://github.com/Rapptz/discord.py

Cooldowns in discord.py with on_message

So, I basically messed up. This is the first time I've ever tried a discord bot and I've done all my code in on_message with it checking the content of the message to see if it matches with the command name (example below). I've added a few commands, which are quite long, and I don't really want to rewrite it. Is there any way around this or do I have to rewrite it?
if message.content.lower().startswith("!test"):
await message.channel.send(db[str(message.author.id)])
Simple example of what I'm doing, just a test command.
I have tried looking inside other questions but I either: don't want to do that or don't understand what people are saying.
Any help would be appreciated and since I'm new to discord.py; I might need it explained in bit easier terms please.
You can do something like this:
import asyncio
users_on_cooldown = [] # Consider renaming this if you are going to have multiple commands with cooldowns.
def on_message(msg):
if msg.content.lower().startswith("!test") and not msg.author.id in users_on_cooldown:
await msg.channel.send(db[str(msg.author.id)])
users_on_cooldown.append(msg.author.id)
await asyncio.sleep(20) # time in seconds
users_on_cooldown.remove(msg.author.id)
Since you said you are a beginner, please note that if you make another command with a separate cooldown, use another variable that users_on_cooldown, maybe something like ban_cmd_cooldown and test_cmd_cooldown.
How It Works When the command is used, the user is added to a list, and after a certain amount of seconds, they are removed. When the command is run, it is checked if the user is on the list.
Note: When the bot is reset, cooldowns will be reset too.
If you have any questions about this, feel free to ask in the comments below.
Here how to use
#client.command()
#commands.cooldown(1, 60, commands.BucketType.user)
async def test(ctx):
await ctx.send(db[str(message.author.id)])
(1, 60, commands.BucketType.user) means 1 msg per 60sec or a 60sec cooldown.
I would recommend you rewrite your bot. It may take some time but it'll be worth it.

TwitchIO bot without blocking

I'd like to use TwitchIO to talk to Twitch chat inside another program, without needing to hijack the main loop with Bot's run().
The official documentation here (https://twitchio.readthedocs.io/en/latest/quickstart.html) shows the code being run like:
from twitchio.ext import commands
class Bot(commands.Bot):
def __init__(self):
# Initialise our Bot with our access token, prefix and a list of channels to join on boot...
# prefix can be a callable, which returns a list of strings or a string...
# initial_channels can also be a callable which returns a list of strings...
super().__init__(token='ACCESS_TOKEN', prefix='?', initial_channels=['...'])
async def event_ready(self):
# Notify us when everything is ready!
# We are logged in and ready to chat and use commands...
print(f'Logged in as | {self.nick}')
#commands.command()
async def hello(self, ctx: commands.Context):
# Here we have a command hello, we can invoke our command with our prefix and command name
# e.g ?hello
# We can also give our commands aliases (different names) to invoke with.
# Send a hello back!
# Sending a reply back to the channel is easy... Below is an example.
await ctx.send(f'Hello {ctx.author.name}!')
bot = Bot()
bot.run()
# bot.run() is blocking and will stop execution of any below code here until stopped or closed.
But as that last line says, run() will block execution.
Is there some other way of running it that doesn't block? Something like (made up)
bot.poll()
That would need to be run periodically in my program's main loop?
Are you adding any more code that uses the Bot class? If not I would suggest just making 2 processes.
The simplest way to do this is just creating 2 python files and running both of them at the same time.
If you really must run them both on the same program I would look into parallel processing. The next time you post a question I would suggest putting that "other programs" code into the question so people don't have to make those assumptions.
#ps if you need to run them in the same program edit your question to show the code you need to run together and ill take another look

Discord.py async function does not give any output and does not do anything

here is the code:
print('hmm1') #testing, this one prints
import discord
from discord.ext import commands
client = commands.Bot(command_prefix='&')
client.run('my token', bot=False)
async def testFunction():
print('hmm') #<- this one does not print.
channel = await client.get_channel(708848617301082164)
message_id=715307791379595275
msg = await client.get_message(channel, message_id)
await msg.edit(content="L")
await msg.edit(content="W")
print('edited message!')
testFunction()
# none of the above works. I only get "hmm1" printed in console.
I have no clue what is happening as there is quite literally no error or output of any sort in the console. does anyone know the problem?
If you're not familiar with asynchronous functions, they need to be awaited. Examples of coroutines can be seen in msg.edit(..., as edit() is a coroutine, therefore you need to await it like so: await testFunction()
Additionally, client.get_channel() and client.get_message() aren't coroutines, so they don't need to be awaited.
As Eric mentioned, you'll also want to move your client.run('... down to the last line in your file, otherwise it'll block the rest of the script. Here's how the code should be structured:
# imports
# commands, events, functions
# last line
client.run('...
It looks like you're using some old documentation too, as d.py has moved over to rewrite (v1.x), and it looks as though the client.get_message() you were using is actually from v0.16.x.
I'd recommending wanting to read up on these changes to familiarise yourself with rewrite. Try to avoid outdated tutorials as well.
As a little headstart, your await client.get_message(channel, message_id) should become await channel.fetch_message(message_id).
References:
Rewrite docs
Client.get_channel()
TextChannel.fetch_message()

Call function from another file - Discord bot

I am not familiar with Discord bots or much of Python so here's a simple question I can't figure out the answer to.
I have two files; discord_bot.py and test.py
How do I forward a message from test.py to send it to a channel in Discord?
test.py
import discord_bot
discord_bot.signal(msg = "Hi")
discord_bot.py
import discord
from discord.ext import commands
TOKEN = '1234567890'
bot = commands.Bot(command_prefix='!')
#bot.command()
async def signal(ctx, *, msg):
await ctx.send(msg)
The Discord bot works fine but calling the signal function from test is not the correct way to do it. Any help here please?
This is a lot to unpack.
0. Never post your discord token online. Discord may automatically invalidate your token if it's posted online.
1. You are not running your bot at the moment, add bot.run(TOKEN) at the end
2. How the commands of the discord bot extension work
#bot.command() is a decorator, if you do not know how they work, read up on it. Overly simplified, they take your function and register in the bot.
The inner workings of the commands extension are basically:
Register all commands by loading the decorators
Whenever a message arrives, check if it contains a prefix and if so, check if it fits a command.
If both checks from 2 passed, construct a Context object, then pass that object to the function. Something like the following:
signal(ctx, *args)
This is why the ctx object can't be positional, because the way the function is called in the inner workings of the bot as a normal argument.
4. Do not try to mess with creating your own context object, unless you know what you're doing. You only need to create context objects if you're overriding the default message parser.
5. Don't use commands for this.
What you want to do, as far as I can tell:
Call a command yourself. This is easy enough:
file 1:
#bot.command()
async def signal(ctx, *, msg):
print(msg)
file 2:
from file1 import signal
import asyncio # if you don't know asyncio, read up on it
asyncio.run(signal(None, 'This is an argument'))
This works easily, it prints your stuff. But you don't want it to be printed, right? You want it to be sent in a channel. This is what you need the context object for, which I said before, to not construct yourself. So how do we actually do this?
The answer is: Don't use commands. They are used for reacting to messages, not to be called by themselves.
6. The solution you (probably) want
So the major changes here are:
signal is now a normal async function with no decorator
We actually specify a channel where we want the stuff to be sent in as an argument of the function
file 1:
import discord
from discord.ext import commands
TOKEN = 'do not share your token online'
bot = commands.Bot(command_prefix='!')
# as the channel_id, pass the channel_id you want the message to be sent in
async def signal(msg, channel_id):
global bot # getting our bot variable from the global context
channel = bot.get_channel(channel_id)
await channel.send(msg)
bot.run(TOKEN)
Major changes here are:
We use asyncio.run to call the function. Async functions cannot be called with regular syntax.
You'll probably need to run file2.py to start the program. Running file1 will not load file2.
file 2
from file1 import signal
from time import sleep
import asyncio
sleep(5) # We need to give our bot time to log in, or it won't work
asyncio.run(signal('hi!', 123))

Categories