Why doesn't isalpha() work for sanitizing user input? - python-3.x

The trouble I'm having here is that the isalpha() function isn't doing what it's supposed to do, users are able to send messages that contain characters that are not alphabetical. I'm not sure why this occurs. I assumed that it was some kind of hosting issue (which from the looks of it wasn't) prior to assuming that there was a logical error in my code, but logically it makes sense to me which perhaps is wrong? I kinda also made the assumption that it had to do with the function not being asynchronous, I don't know I probably am reaching with that one.
import discord
from discord.ext import commands
import re
class Shiritori(commands.Cog):
""" Start a new word from the last letter of the one before. """
def __init__(self, client):
self.client = client
self.repeats = re.compile(r'(.)\1{2,}') # Matches spam like 'eeeeeee'
self.shiritori_channel = 578788555120312330 # Official
#commands.Cog.listener()
async def on_message(self, message):
try:
if message.channel.id == self.shiritori_channel:
previous_message = (await message.channel.history(limit=2).flatten())[1].content
repeats_mo = self.repeats.search(message.content)
if not repeats_mo and message.content.isalpha():
if previous_message[-1].lower() != message.content[0].lower():
await message.author.send(
f'Your message must start with the last letter of the latest message!')
await message.delete()
else:
await message.author.send("You can't do that here!")
await message.delete()
except discord.Forbidden:
await message.delete()

I guess you are expecting that any non-ascii English character is not alpha.
But
According to the doc,
str.isalpha() Return True if all characters in the string are
alphabetic and there is at least one character, False otherwise.
Alphabetic characters are those characters defined in the Unicode
character database as “Letter”, i.e., those with general category
property being one of “Lm”, “Lt”, “Lu”, “Ll”, or “Lo”. Note that this
is different from the “Alphabetic” property defined in the Unicode
Standard.
So for example à (french a) or любовь are also considered alpha
'à'.isalpha()
True
'любовь'.isalpha()
True
If you want the english letters, use isascii():
'à'.isascii()
False

Related

Get Discord username and discriminator through a mention

I have tried reading the docs, but I don't understand what is going on here and how to fix it. I am trying to map a mention to its proper Name#NNNN form, but alas, it is proving to be a fruitless endeavor for me.
import discord
from discord.ext import commands
from collections import defaultdict
client = commands.Bot(command_prefix=">")
#client.event
async def on_ready():
print('Ready!')
jobz = {}
'''PART 1 v v v'''
#client.event
if message.content.startswith('>jobsched'):
author = message.author
jobz[author].append(...)
await channel.send(jobz[author])
'''PART 2 v v v'''
if message.content.startswith('>when '):
channel = message.channel
worker = list(filter(None, message.content[6:].split(' ')))[0]
uname = message.mentions[0].mention
await channel.send(jobz[uname])
PART 1:
I run this first, the send works as expected as seen below:
>jobsched 'a'
>jobsched 'b'
As seen in the last line, this spits out ['1a', '2b']
PART 2:
Here is where I have my issue.
>when #Name
I expected this to spit out ['1a', '2b'] because I expected it to look up or translate the mentioned name, find its respective name and discriminator. I thought this should happen since, in the above piece, that is how the name gets written into the dictionary is i.e. Name#1234: ['1a','2b']
Printing out .keys() shows that the key has the name and discriminator i.e. Name#1234 in the 'jobz' dictionary.
However, I can't seem to get the mention to give me the Name and Discriminator. I have tried doing mentions[0].mention from what I have seen here on stackoverflow, but it doesn't result in a Member class for me, just a string, presumably just '#Name'. If I leave it alone, as shown in my 'worker' variable, it passes an empty list. It should pull the list because when I override it to jobz['Name#1234'] it gives me the list I expect.
Can anyone please help?
just cast the member object to string to get the name and discriminator as it says in the discord.py docs. To mention someone, put the internal representation like this: f'<#{member.id}>'. To stop problems like this, use client.command() it's way easier to put in parameters, and easier to access info. So, here would be the code:
#client.command()
async def when(ctx, member: discord.Member):
await ctx.send(jobz[str(member)])
Also, if your worker variable is returning None, you're not passing a parameter at all
mentions is a list of Member objects, so when you do mentions[0] you are referencing a Member. Thus, mentions[0].mention is the formatted mention string for the first-mentioned (element 0) Member.
You probably want mentions[0].name and mentions[0].discriminator
See: https://discordpy.readthedocs.io/en/latest/api.html#discord.Message.mentions

How do I fix a counting program where the current number is incremented when a duplicate author is detected?

I'm having a bit of trouble: I made a counting channel and this part of my code:
recent_author = (await message.channel.history(limit=2).flatten())[1].author
if message.author == recent_author:
await message.author.send(f"You can't hog this channel, let someone else have a turn!")
await message.delete()
prevents users from sending messages more than once; but, when the message is deleted, the variable that holds the current number is incremented. That's not supposed to happen but it does: I've tried doing so many things at this point like moving this piece of code below other pieces of code in the event function or merging the code into an if else statement--nothing seems to solve my problem. I was thinking about subtracting 1 from the current number when a duplicate author is detected; however, that would mean subtracting 1 for anything other than a number.
Here's my full code:
import discord
from discord.ext import commands
import re
class Counting(commands.Cog):
""" Users take turns incrementing a number. """
def __init__(self, client):
self.client = client
self.count_channel = 736241527847911494
self.cum_num = 98
self.num_regex = re.compile(r'(^\d+)')
#commands.Cog.listener()
async def on_message(self, message):
if message.channel.id == self.count_channel:
try:
recent_author = (await message.channel.history(limit=2).flatten())[1].author
if message.author == recent_author:
await message.author.send(f"You can't hog this channel, let someone else have a turn!")
await message.delete()
match_obj = self.num_regex.search(message.content)
if int(match_obj.group(1)) != self.cum_num + 1:
await message.author.send(f'Your message must start with **{self.cum_num + 1}**!')
await message.delete()
else:
self.cum_num += 1
except AttributeError:
await message.delete()
This looks like the if/else logic needs tweaking.
Directly after your check for the duplicate author it initialises and sets match_obj then runs the if statement.
So despite the message being removed from the text channel if the message content was valid then it will still increment the counter because nothing is preventing it from running that logic.
It looks to me like you will want to structure it as follows:
recent_author = (await message.channel.history(limit=2).flatten())[1].author
if message.author == recent_author:
await message.author.send(f"You can't hog this channel, let someone else have a turn!")
await message.delete()
else:
match_obj = self.num_regex.search(message.content)
if int(match_obj.group(1)) != self.cum_num + 1:
await message.author.send(f'Your message must start with **{self.cum_num + 1}**!')
await message.delete()
else:
self.cum_num += 1
This way if the latest author is the duplicate author it won't call the code in the else block. However, if it is not a duplicate author it will run the next stage of the code.
You could try using wait_for and in the check compare user names to the previous message, if they are the same then you can do something if not you can ignore it. You could also try using a database or memory for this.

Giving a role to another user with DiscordPy

I am trying to use a command to give a specific role using DiscordPy, but every search I use brings me to the same answers, that don't help: 1, 2, 3.
Clearly, I'm missing a fundamental part, but i have no idea what. The documentation, covers that there is a add_roles command, but that doesn't give any explanation of how to use it for another user. In fact, trying await add_roles("Team Captain") gives the error NameError: name 'add_roles' is not defined.
What am I missing here? Why does add_roles not exist when it's documented, and how do I use it against a different user.
This is (some of) what I have at the moment, but obviously, doesn't work:
import discord, discord.utils, random, os, re, json
from discord.ext import commands
from discord.utils import get
from dotenv import load_dotenv
client = discord.Client()
load_dotenv()
key = os.getenv('DISCORD_KEY')
#client.event
async def on_message(message):
if message.author == client.user:
return
print('Message from {0.author}: {0.content}'.format(message))
#Want these commands in the right channel
if str(message.channel).lower().startswith("bot"):
if message.content.lower().startswith("$addcaptain"):
if len(message.mentions) == 0:
await message.channel.send("Needs a user to give Team Captain role.")
return
else:
await add_roles("Team Captain") #Doesn't work, and I assume would be against the user who sent the message, not the mentioned one
client.run(key)
key = os.getenv('DISCORD_KEY')
add_roles is a method that belongs to the Member object. This means that you'll need to get the target member, in your case message.mentions[0] as message.mentions returns a list of Members, and then stick .add_roles(..) on the end of it.
Additionally, when adding a role, it accepts Role objects, not just a name or ID. This means you'll need to fetch the role first, which can be done in a multitude of ways, but the one I'll use is utils.get() (other methods such as Guild.get_role() are also available)
This brings us to your code:
#client.event
async def on_message(message):
# code
if str(message.channel).lower().startswith("bot"):
if message.content.lower().startswith("$addcaptain"):
if len(message.mentions) == 0:
await message.channel.send("Needs a user to give Team Captain role.")
return
else:
role = discord.utils.get(message.guild.roles, name="Team Captain")
await message.mentions[0].add_roles(role)
References:
utils.get()
Message.mentions - message.mentions[0] gets the first element of the list, which is the discord.Member that was mentioned.
Member.add_roles()
Guild.get_role() - If you want to use this, you'll do:
role = message.guild.get_role(112233445566778899)
where 112233445566778899 is the Team Captain's role ID.

Discord.py random choice bug

Code:
# Random Choice
#client.command(aliases=["rand_c"])
async def random_choice(ctx, python_list):
await ctx.send(random.choice(python_list))
Weird error when I type a proper Python list (["Cats", "Dogs", "No pet"]):
discord.ext.commands.errors.UnexpectedQuoteError: Unexpected quote mark, '"', in non-quoted string
It works fine in regular Python, but why not in discord.py?
All of the inputs to your commands are initially treated as strings. You need to provide a converter function to tell the command what to do with that string:
from ast import literal_eval
#client.command(aliases=["rand_c"])
async def random_choice(ctx, *, python_list: literal_eval):
await ctx.send(str(python_list))

Display all emotes in server but with spaces

Seems quite easy but I just can't figure it out. I basically have a script that makes the bot list all the emotes of the server, but the problem is it doesn't make spaces which means it won't preview the emotes and it will all be just text and boring as i have alot of emotes, lets use actual emojis as an example ":laughing:" shows 😂. ":laughing::laughing:" should show 😂😂 but just shows ':laughing::laughing:' as it is instead. This is because there isn't space, how can i fix that in my script? In this image you can see what i mean Now And then this
#client.command(pass_context=True)
async def emotes(ctx, msg: str = None):
"""List all emotes in this server."""
if msg:
server, found = client.find_server(msg)
if not found:
return await client.send(server)
else:
server = ctx.message.server
emojis = [str(x) for x in server.emojis]
await client.say("".join(emojis))
I suppose the problem is in absence of a space in the separator string "", which you're using for joining emojis.
Replace this
await client.say("".join(emojis))
By this
await client.say(" ".join(emojis))
#client.command(pass_context=True)
async def emotes(ctx, msg: str = None):
"""List all emotes in this server."""
if msg:
server, found = client.find_server(msg)
if not found:
return await client.send(server)
else:
server = ctx.message.server
emojis = [str(x) for x in server.emojis]
await client.say(" ".join(emojis))
different account, turns out it wasn't the spaces as it still didn't work with them, it's because I was using Async, rewrite works well and shows the emotes without having to put spaces

Resources