I'm trying to make a Discord bot with Discordpy and WikipediaApi. The bot is supposed to print Wikipedia summary after entering an appropriate sentence: "What is...?" or "Who is...?". So far, it's working well, but the Wikipedia page is defined in the code. I want take a page title as keyword from user, for example, he might ask "What is Microsoft?" or "Who is Elon Musk?" and receive a reply (if a page exists in Wikipedia, of course). I know I should use client.wait_for(), but I feel confused with that, as the input will be a part of longer sentence.
import discord
import wikipediaapi
#client.event
async def on_message(message):
if not (message.author == client.user):
if (message.mention_everyone == False):
if (client.user.mentioned_in(message) or isinstance(message.channel, discord.abc.PrivateChannel)):
async with message.channel.typing():
if message.content.startswith('What is') or message.content.startswith('Who is'):
wiki_wiki = wikipediaapi.Wikipedia('pl')
page_py = wiki_wiki.page('Elon Musk') <----- HERE
await message.channel.send("Page - Summary: %s" % page_py.summary)
client.run('token')
Seeing as you're trying to parse it out of every message, you could try just splitting the "command" (What is/Who is) and take the remainder as the query.
if message.content.startswith('What is') or message.content.startswith('Who is'):
args = message.content.split(" ") # Split the message based on spaces
if len(args) > 2: # Checks if a query was passed, avoids a potential IndexError
query = " ".join(args[2:]) # Cut out the "What is" part, and join the rest back into a single string with spaces
wiki_wiki = wikipediaapi.Wikipedia('pl')
page_py = wiki_wiki.page(query)
I do strongly recommend using the built-in Commands module though, and just making a "Wiki" command. This way, your code would look as follows:
#client.command()
async def wiki(ctx, *query):
if query: # I'm not sure what the wikipedia api does if you give it an empty string, so this if-statement makes sure something was passed
query = " ".join(query) # The rest of the args were passed as a list, so join them back into a string with spaces
wiki_wiki = wikipediaapi.Wikipedia('pl')
page_py = wiki_wiki.page(query)
And you can call it in Discord by simply doing [prefix]wiki elon musk. In your case, you check if the bot was mentioned to parse commands which can also be handled with prefixes, so you wouldn't lose any functionality on that end (and can add more prefixes if you so wish).
Related
I want to get the user that was mentioned in a message and send him a private message. That's working without problems, but now I want to add a message for the case, that the mentioned member is not on the same server.
I searched a lot and try now for over 3 hours to find a solution.
Showcase of the problem: https://youtu.be/PYZMVXYtxpE
Heres my code:
#bot.event
async def on_message(message):
if len(message.mentions) == 1:
membe1 = message.mentions[0].id
membe2 = bot.get_user(membe1)
guild = bot.get_guild(message.author.guild.id)
if guild.get_member(membe1) is not None:
await membe2.send(content=f"you was mentioned in the server chat")
else:
embed2 = discord.Embed(title=f"» :warning: | PING not possible", description=f"not possible")
await message.channel.send(content=f"{message.author.mention}", embed=embed)
await message.delete()
return
The first part is working without problems, but at the "else" part, does the bot nothing. He still posts the message with the "invalid" ping in the chat and just ignores it. What can I do to fix that?
There is an error of design in what you are trying to do.
In Discord, you are not able to mention a user if he is not on that same server, so what you are checking will never work at all, currently your code is pretty much checking if the mentioned user exists, which will always happen unless he leaves the guild at the same time the command is executed.
Say for example, to make your command work you want to mention the user user123, who is in your server, you do the following:
#user123 blablabla
And that works because Discord internal cache can find user123 in your server and it is mentioned, which means that clicking on "#user123" will show us a target with his avatar, his roles or whatsoever.
But if you try to do the same for an invalid user, let's say notuser321:
#notuser321 blablabla
This is not a mention, take for example that you know who notuser321 is but he is in the same server as the bot is. Discord cannot retrieve this user from the cache and it is not considered a mention, just a single string, so not a single part of your code will be triggered, because len(message.mentions) will be 0.
Something that you could try would be using regular expressions to find an attempt to tag within message.content. An example would be something like this:
import re
message = "I love #user1 and #user2"
mention_attempt = re.findall(r'[#]\S*', message) # ['#user1', '#user2']
I've been trying to develop a discord bot in python recently. I made it so that if a message contains a certain number, it will react and send a message. Here is the code in the cog file:
import discord
from discord.ext import commands
nice_numbers = ['69', '420']
class Auto(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_message(self, message):
msg = message.content
if message.author == self.client.user:
return
if any (word in msg for word in nice_numbers):
await message.add_reaction('👌')
await message.channel.send(f'lmao nice')
def setup(client):
client.add_cog(Auto(client))
The problem is, the bot also responds with the same message and reaction when a user mentions a certain channel (in this case #general, #super-private-testing, and #resources). I can't seem to fix it or figure out why it's happening. I'm still pretty new to python so can someone please tell me what I'm doing wrong?
Basically what is happening is that mentions have a special syntax within the Discord API where they are basically a bunch of numbers put together.
For example when you are mentioning another user like the following:
Hello #User1234!
The real syntax within the discord message is the following:
Hello <#125342019199458000>!
And in the case of mentioning channels, it works similar, as a channel mentioned like:
#general
Internally would be written as:
<#550012071928922144>
Of course, the problem is that within this big number there could be false positive of finding your nice_numbers. There could be different ways to avoid this, for example you could check if a channel or a user is being mentioned in the message and return in that case.
if message.channel_mentions or message.mentions:
return
I think a better solution would be to changing the way you are checking if the nice_numbers are within message.content.
Using if word in msg would return true if message.content also includes something like 'My favourite number is 45669'. To overcome this issue it is better to make use of regular expressions.
You can declare a new function like this answers explains, which would return a <match object> if what you pass as parameter is found.
It would be something like this:
import re
def findCoincidences(w):
return re.compile(r'\b({0})\b'.format(w)).search
findCoincidences('69')('I like 69') # -> return <match object>
findCoincidences('69')('My favourite number is 45669') # -> return None
Expanding on Shunya's answer, you can use message.clean_content instead of message.content:
A property that returns the content in a “cleaned up” manner. This basically means that mentions are transformed into the way the client shows it. e.g. <#id> will transform into #name.
This will also transform #everyone and #here mentions into non-mentions.
This will prevent you from inadvertently matching against the IDs of channels, users, roles, etc. Of course, if the actual name contains a nice_number, it'll still match.
my question is simple ,and i don't know if it has been answered before because i couldn't find an answer.So i'm trying this idea about when someone types a word the bot would automatically reply with a specific reply, i'm making a command to add these things, but i'm having a problem, which is that each parameter gets only the first and the second input,and i want to split them from each other, here's a small example:
#client.command()
async def areply(ctx,word, info):
You can add * which will take every word after the second parameter as user input, for example:
#client.command()
async def areply(ctx, word, *, info):
await ctx.send(f"The word provided was: `{word}` and the information provided was: `{info}`")
Lets say a user typed !areply Example This is test info, the bot will output the following:
The word provided was: Example and the information provided was: This is test info
I want my Discord bot to send a certain message, For example: "Hello" when he joins a new server. The bot should search for the top channel to write to and then send the message there.
i saw this, but this isn't helpful to me
async def on_guild_join(guild):
general = find(lambda x: x.name == 'general', guild.text_channels)
if general and general.permissions_for(guild.me).send_messages:
await general.send('Hello {}!'.format(guild.name))```
The code you used is actually very helpful, as it contains all of the building blocks you need:
The on_guild_join event
The list of all channels in order from top to bottom (according to the official reference). If you want to get the "top channel" you can therefore just use guild.text_channels[0]
checking the permissions of said channel
async def on_guild_join(guild):
general = guild.text_channels[0]
if general and general.permissions_for(guild.me).send_messages:
await general.send('Hello {}!'.format(guild.name))
else:
# raise an error
Now one problem you might encounter is the fact that if the top channel is something like an announcement channel, you might not have permissions to message in it. So logically you would want to try the next channel and if that doesn't work, the next etc. You can do this in a normal for loop:
async def on_guild_join(guild):
for general in guild.text_channels:
if general and general.permissions_for(guild.me).send_messages:
await general.send('Hello {}!'.format(guild.name))
return
print('I could not send a message in any channel!')
So in actuality, the piece of code you said was "not useful" was actually the key to doing the thing you want. Next time please be concise and say what of it is not useful instead of just saying "This whole thing is not useful because it does not do the thing I want".
I would like to grab user input from the user when they enter a certain command and store the input into a variable (but not the command) so that I can do things with it.
As an example, when the user types "!id 500", I want the bot to type "500" in the channel. I have tried just using message.content but it doesn't work properly because the bot spams it indefinitely, plus it shows the command as well.
#client.event
async def on_message(message):
if message.content.startswith("!id"):
await client.send_message(message.channel, message.content)
Sorry if this is a silly question, but any help would mean a lot.
If you want the bot to just send the message of the user without the command part ("!id") you'll have to specify which part of the string you want to send back. Since strings are really just lists of characters you can specify what part of the string you want by asking for an index.
For example:
"This is an example"[1]
returns "h".
In your case you just want to get rid of the beginning part of the string which can be done like so:
#client.event
async def on_message(message):
if message.content.startswith("!id"):
await client.send_message(message.channel, message.content[3:])
this works because it sends the string back starting with the 4th index place (indexes start at 0 so really [3] is starting at the fourth value)
This also applies if you just want to save the user's message as a variable as you can just do this:
userInput = message.content[3:]
You read more about string manipulation at the python docs.
I would use commands extension - https://github.com/Rapptz/discord.py/blob/async/examples/basic_bot.py
Then make a command like,
#bot.command(pass_context=True)
async def id(ctx, number : int):
await bot.say(number)
The reason your bot spams the text is because your bot is saying the command, which it follow. You need to block your bot from counting your output as a command. You could block it by:
if message.author.bot: return.
This will return None if the author is a bot. Or, you could just block your bot's own id. You won't need to do this if you actually get your command to work correctly, but you should still add it. Second of all, the way I separate the command and arguments is
cmd = message.content.split()[0].lower()[1:]
args = message.content.split()[1:]
With these variables, you could do
#client.event
async def on_message(message):
if message.content.startswith('!'):
if message.author.bot: return # Blocks bots from using commands.
cmd = message.content.split()[0].lower()[1:]
args = message.content.split()[1:]
if command == "id":
await client.send_message(message.channel, ' '.join(args)