If...else statements not working as expected - python-3.x

I'm actually trying to create a simple Discord bot using discord.py. The problem comes here, when I try to execute this code:
import discord
from discord.ext import commands
myid = 3586xxxxxxxxxx
cliente = commands.Bot(command_prefix="!!")
#cliente.command()
async def clear(ctx, amount):
if myid == ctx.author.id:
print("Entering If statement") # This is printed
await ctx.channel.purge(limit=int(amount)) # This is not executed
else:
await ctx.channel.send("You don't have enough permissions.") # This is executed
The output makes no sense, when I run "!!clear 2" on my server, the program enters the If and prints "Entering the If statement", It doesn't remove anything, and then the bot sends the message inside the else.
I'm so confused right now :s

I still don't know why It didn't work, but hey!, I found a great alternative that consists on using usernames. I'll leave an example:
import discord
from discord.ext import commands
myuname = "User#1234"
cliente = commands.Bot(command_prefix="!!")
#cliente.command()
async def clear(ctx, amount):
if myuname == str(ctx.author): # For some reason using str() is neccessary or It won't work properly.
print("Entering If statement") # This is printed
await ctx.channel.purge(limit=int(amount)) # This is not executed
else:
await ctx.channel.send("You don't have enough permissions")
And that's It, hope It helped someone :)

Related

How to add a cooldown timer for commands

I am very new to coding and want to just add a command timer to the code i got off the internet. I don't know how to do this and all the other codes I find are too much for me to know. I just want to be able to add around a 10 second cooldown timer to each command.
import discord
import asyncio
from discord.ext import commands
import datetime as DT
import discord.utils
class MyClient (discord.Client):
async def on_ready(self):
print('Logged in as')
print(self.user.name)
print(self.user.id)
print('------')
async def on_message(self, message):
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!discord'): # I want to add a timer
channel = client.get_user(message.author.id)
await channel.send(''.format(message))
async def on_member_join(self, member):
guild = member.guild
if guild.system_channel is not None:
to_send = 'Welcome {0.mention} to {1.name}!'.format(member, guild)
await guild.system_channel.send(to_send)
client = MyClient()
client.run('')
The time.sleep() function, which another user suggested is a blocking call. Which means it will block the entire thread from being ran. I am not sure exactly how the Discord framework works, but I imagine thread blocking might be a problem.
A better solution I think would be to use asyncio, especially since you've already imported it. It will allow the program to do other stuff, while waiting the 10 seconds. Another Stackoverflow thread if you're interested
if message.content.startswith('!discord'):
channel = client.get_user(message.author.id)
await asyncio.sleep(10)
await channel.send(''.format(message))
Edit
You can save the last time the function was called, and check with an IF-statement if 10 seconds have passed since the last call.
class MyClient (discord.Client):
last_called = None
async def on_ready(self):
# other stuff
async def on_message(self, message):
# check if 10 seconds have passed since the last call
if MyClient.last_called and DT.datetime.now() < MyClient.last_called + DT.timedelta(seconds=10):
return
# we do not want the bot to reply to itself
if message.author.id == self.user.id:
return
if message.content.startswith('!discord'):
channel = client.get_user(message.author.id)
await channel.send(''.format(message))
MyClient.last_called = DT.datetime.now() # save the last call time
If you add this to the imports at the top of the file:
from time import sleep
then you can use the sleep() function to pause the script for a set number of seconds when execution reaches that line. So for instance, adding this:
sleep(10)
somewhere inside the functions would sleep for 10 seconds whenever the script reaches that line.

using Subprocess to avoid long-running task from disconnecting discord.py bot?

I created a bot for my Discord server, that goes to the Reddit API for a given subreddit, and posts the Top 10 results for the Day in the Discord chat, based on the subreddit(s) that you input. It disregards self posts, and really only posts pictures and GIFs. The Discord message command would look something like this: =get funny awww news programming, posting the results for each subreddit as it gets them from the Reddit API (PRAW). THIS WORKS WITH NO PROBLEM. I know that the bot's ability to hit the API and post to discord works.
I added another command =getshuffled which puts all of the results from the subreddits in a large list, and then shuffles them before posting. This works really well with a request of up to ~50 subreddits.
This is what I need help with:
Because it can be such a large list of results, 1000+ results from 100+ subreddits, the bot is crashing on really big requests. Based on what help I got from my question yesterday, I understand what is going wrong. The bot is starting, it is talking to my Discord server, and when I pass it a long request, it stops talking to the server for too long while the Reddit API call is done, and it the Discord connection fails.
So, what I think I need to do, is have a subprocess for the code that goes to the Reddit API and pulls the results, (which I think will let the discord connection stay running), and then pass those results BACK to the bot when it is finished....
Or... this is something that Asyncio can handle on its own...
I'm having a hard time with the subprocess call, as I knew I would.
Basically, I either need help with this subprocess trickery, or need to know if I'm being an idiot and Asyncio can handle all of this for me. I think this is just one of those "I don't know what I don't know" instances.
So to recap: The bot worked fine with smaller amounts of subreddits being shuffled. It goes through the args sent (which are subreddits), grabbing info for each post, and then shuffling before posting the links to discord. The problem is when it is a larger set of subreddits of ~ 50+. In order to get it to work with the larger amount, I need to have the Reddit call NOT block the main discord connection, and that's why I'm trying to make a subprocess.
Python version is 3.6 and Discord.py version is 0.16.12
This bot is hosted and running on PythonAnywhere
Code:
from redditBot_auth import reddit
import discord
import asyncio
from discord.ext.commands import Bot
#from discord.ext import commands
import platform
import subprocess
import ast
client = Bot(description="Pulls posts from Reddit", command_prefix="=", pm_help = False)
#client.event
async def on_ready():
return await client.change_presence(game=discord.Game(name='Getting The Dank Memes'))
def is_number(s):
try:
int(s)
return True
except:
pass
def show_title(s):
try:
if s == 'TITLES':
return True
except:
pass
async def main_loop(*args, shuffled=False):
print(type(args))
q=10
#This takes a integer value argument from the input string.
#It sets the number variable,
#Then deletes the number from the arguments list.
title = False
for item in args:
if is_number(item):
q = item
q = int(q)
if q > 15:
q=15
args = [x for x in args if not is_number(x)]
if show_title(item):
title = True
args = [x for x in args if not show_title(x)]
number_of_posts = q * len(args)
results=[]
TESTING = False #If this is turned to True, the subreddit of each post will be posted. Will use defined list of results
if shuffled == False: #If they don't want it shuffled
for item in args:
#get subreddit results
#post links into Discord as it gets them
#The code for this works
else: #if they do want it shuffled
output = subprocess.run(["python3.6", "get_reddit.py", "*args"])
results = ast.literal_eval(output.decode("ascii"))
# ^^ this is me trying to get the results back from the other process.
. This is my get_reddit.py file:
#THIS CODE WORKS, JUST NEED TO CALL THE FUNCTION AND RETURN RESULTS
#TO THE MAIN_LOOP FUNCTION
from redditBot_auth import reddit
import random
def is_number(s):
try:
int(s)
return True
except:
pass
def show_title(s):
try:
if s == 'TITLES':
return True
except:
pass
async def get_results(*args, shuffled=False):
q=10
#This takes a integer value argument from the input string.
#It sets the number variable,
#Then deletes the number from the arguments list.
title = False
for item in args:
if is_number(item):
q = item
q = int(q)
if q > 15:
q=15
args = [x for x in args if not is_number(x)]
if show_title(item):
title = True
args = [x for x in args if not show_title(x)]
results=[]
TESTING = False #If this is turned to True, the subreddit of each post will be posted. Will use defined list of results.
NoGrabResults = False
#This pulls the data and creates a list of links for the bot to post
if NoGrabResults == False:
for item in args:
try:
#get the posts
#put them in results list
except Exception as e:
#handle error
pass
try:
#print('____SHUFFLED___')
random.shuffle(results)
random.shuffle(results)
random.shuffle(results)
except:
#error stuff
print(results)
#I should be able to read that print statement for the results,
#and then use that in the main bot function to post the results.
.
#client.command()
async def get(*args, brief="say '=get' followed by a list of subreddits", description="To get the 10 Top posts from a subreddit, say '=get' followed by a list of subreddits:\n'=get funny news pubg'\n would get the top 10 posts for today for each subreddit and post to the chat."):
#sr = '+'.join(args)
await main_loop(*args)
#THIS POSTS THE POSTS RANDOMLY
#client.command()
async def getshuffled(*args, brief="say '=getshuffled' followed by a list of subreddits", description="Does the same thing as =get, but grabs ALL of the posts and shuffles them, before posting."):
await main_loop(*args, shuffled=True)
client.run('my ID')
UPDATE: Following advice, I had the command passed through a ThreadPoolExecutor as shown:
async def main(*args, shuffled):
if shuffled==True:
with concurrent.futures.ThreadPoolExecutor() as pool:
results = await asyncio.AbstractEventLoop().run_in_executor(
executor=pool, func=await main_loop(*args, shuffled=True))
print('custom thread pool', results)
but this still results in errors when the script tries to talk to Discord:
ERROR:asyncio:Task was destroyed but it is pending!
task: <Task pending coro=<Client._run_event() running at /home/GageBrk/.local/lib/python3.6/site-packages/discord/client.py:307> wait_for=<Future pending cb=[<TaskWakeupMethWrapper object at 0x7f28acd8db28>()]>>
Event loop is closed
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
Destination must be Channel, PrivateChannel, User, or Object. Received NoneType
...
It is sending the results correctly, but discord is still losing connection.
praw relies on the requests library, which is synchronous meaning that the code is blocking. This can cause your bot to freeze if the blocking code takes too long to execute.
To get around this, a separate thread can be created that handles the blocking code. Below is an example of this. Note how blocking_function will use time.sleep to block for 10 minutes (600 seconds). This should be more than enough to freeze and eventually crash the bot. However, since the function is in it's own thread using run_in_executor, the bot continues to operate as normal.
New versions
import time
import asyncio
from discord.ext import commands
from concurrent.futures import ThreadPoolExecutor
def blocking_function():
print('entering blocking function')
time.sleep(600)
print('sleep has been completed')
return 'Pong'
client = commands.Bot(command_prefix='!')
#client.event
async def on_ready():
print('client ready')
#client.command()
async def ping(ctx):
loop = asyncio.get_event_loop()
block_return = await loop.run_in_executor(ThreadPoolExecutor(), blocking_function)
await ctx.send(block_return)
client.run('token')
Older async version
import time
import asyncio
from discord.ext import commands
from concurrent.futures import ThreadPoolExecutor
def blocking_function():
print('entering blocking function')
time.sleep(600)
print('sleep has been completed')
return 'Pong'
client = commands.Bot(command_prefix='!')
#client.event
async def on_ready():
print('client ready')
#client.command()
async def ping():
loop = asyncio.get_event_loop()
block_return = await loop.run_in_executor(ThreadPoolExecutor(), blocking_function)
await client.say(block_return)
client.run('token')

Discord.py mass dm bot

I've been trying to find out a way to make a discord bot dm everybody inside of my server. I have already found a question similar to mine and tried the answer but it didn't work. My current code looks like this
if message.content.upper().startswith('.MSG'):
if "345897570268086277" in [role.id for role in message.author.roles]:
member = discord.Member
args = message.content.split(" ")
if member == "#everyone":
for server_member in server.members:
await client.send_message(server_member, "%s" % (" ".join(args[1:])))
I would use the commands extension for this, not on_message
# import stuff we'll be using
from discord.ext import commands
from discord.utils import get
# Bot objects listen for commands on the channels they can "see" and react to them by
# calling the function with the same name
bot = commands.Bot(command_prefix='.')
# Here we register the below function with the bot Bot
# We also specify that we want to pass information about the message containing the command
# This is how we identify the author
#bot.command(pass_context=True)
# The command is named MSG
# The `, *, payload` means take everything after the command and put it in one big string
async def MSG(ctx, *, payload):
# get will return the role if the author has it. Otherwise, it will return None
if get(ctx.message.author.roles, id="345897570268086277"):
for member in ctx.message.server.members:
await bot.send_message(member, payload)
bot.run('token')
How sure are you that "345897570268086277" is the role id? It might make more sense to search for it by name.

Discord bot command list

I have a list of commands for a discord bot, so I can change or modify them later. When someone writes a command in discord, I'm trying to check and see if its in the commands list. The problem is that I get the error:
for message.content.startswith in commands:
AttributeError: 'str' object attribute 'startswith' is read-only
Is there a way to do this? How would I make it not read-only...or how would I fix this?
The code:
import discord, asyncio
client = discord.Client()
#client.event
async def on_ready():
print('logged in as: ', client.user.name, ' - ', client.user.id)
#client.event
async def on_message(message):
commands = ('!test', '!test1', '!test2')
for message.content.startswith in commands:
print('true')
if __name__ == '__main__':
client.run('token')
This part is the issue:
for message.content.startswith in commands:
print('true')
This doesn't make any sense. I assume message.content is a string. startswith is a string method, but it takes an argument, see here. You need to pass startswith the actual characters you're searching for. For example, "hello".startswith("he") will return true. I believe this is what you want:
for command in commands:
if message.content.startswith(command):
print('true')

Asynchronously writing to console from stdin and other sources

I try to try to write some kind of renderer for the command line that should be able to print data from stdin and from another data source using asyncio and blessed, which is an improved version of python-blessings.
Here is what I have so far:
import asyncio
from blessed import Terminal
#asyncio.coroutine
def render(term):
while True:
received = yield
if received:
print(term.bold + received + term.normal)
async def ping(renderer):
while True:
renderer.send('ping')
await asyncio.sleep(1)
async def input_reader(term, renderer):
while True:
with term.cbreak():
val = term.inkey()
if val.is_sequence:
renderer.send("got sequence: {0}.".format((str(val), val.name, val.code)))
elif val:
renderer.send("got {0}.".format(val))
async def client():
term = Terminal()
renderer = render(term)
render_task = asyncio.ensure_future(renderer)
pinger = asyncio.ensure_future(ping(renderer))
inputter = asyncio.ensure_future(input_reader(term, renderer))
done, pending = await asyncio.wait(
[pinger, inputter, renderer],
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
if __name__ == '__main__':
asyncio.get_event_loop().run_until_complete(client())
asyncio.get_event_loop().run_forever()
For learning and testing purposes there is just a dump ping that sends 'ping' each second and another routine, that should grab key inputs and also sends them to my renderer.
But ping only appears once in the command line using this code and the input_reader works as expected. When I replace input_reader with a pong similar to ping everything is fine.
This is how it looks when typing 'pong', although if it takes ten seconds to write 'pong':
$ python async_term.py
ping
got p.
got o.
got n.
got g.
It seems like blessed is not built to work correctly with asyncio: inkey() is a blocking method. This will block any other couroutine.
You can use a loop with kbhit() and await asyncio.sleep() to yield control to other coroutines - but this is not a clean asyncio solution.

Resources