invite.uses returning None - python

I'm trying to figure out who invited a user when they join, but when I try to get the number of times an invite has been used I keep getting None. Here's my code:
#client.event
async def on_ready(self):
global before_invites
before_invites = []
for guild in self.client.guilds:
for invite in await guild.invites():
invite = await self.client.get_invite(invite)
x = [invite.url, invite.uses, invite.inviter.id]
before_invites.append(x)
print(before_invites)
which then prints out [['http://discord.gg/xxxxxxx', None, 01234567890123456789], ['http://discord.gg/xxxxxxx', None, 01234567890123456789], ...]
So far I've double checked and the bot has all permissions in the server and made sure that there are invites for the server that have been used. Can only self accounts see invite uses?

The problem is that you're calling get_invite instead of using the Invite from Guild.invites. get_invite uses the GET Invite endpoint of the Discord API, which only returns the invite object, not including it's metadata. By contrast, Guild.invites uses the GET Channel Invites endpoint, which does return the metadata objects.
Just use the invites from Guild.invites directly:
#client.event
async def on_ready(self):
global before_invites
before_invites = []
for guild in self.client.guilds:
for invite in await guild.invites():
x = [invite.url, invite.uses, invite.inviter.id]
before_invites.append(x)
print(before_invites)

Related

Error sending message to DM of a user discord.py

I am currently trying to send a message to a user's DM but I only get the following error
user = client.get_user(499642019191324692)
AttributeError: module 'discord.client' has no attribute 'get_user'
This is my code
#bot.command()
async def test(ctx):
user = client.get_user(499642019191324692)
await user.send('Hello')
Thx #Astrogeek for your answer, even if only covering part of the issue. I got a similar issue, and this "bot" instead of client helped me A LOT.
To take the example back :
#bot.command()
async def test(ctx):
user = bot.get_user(499642019191324692)
await user.send('Hello')
Should instead be :
#bot.command()
async def test(ctx):
user = await bot.fetch_user(499642019191324692)
await user.send('Hello')
await fetch_user(user_id)
This function is a coroutine.
Retrieves a User based on their ID. This can only be used by bot accounts. You do not have to share any guilds with the user to get this information, however many operations do require that you do.
(from DiscordPy API Reference)
Since it's a coroutine, you have to "await" it. If you don't, the coroutine hasn't time to get the user from its ID and it results in a "can't send non-None value to a just-started coroutine" with bot.fetch_user(id)
As said in API you can get user from ID that way, but it only guarantees to deliver the message if bot and user are in a same guild or are friends.
you've used bot.command so it'll be bot.get_user instead of client.get_user
#bot.command()
async def test(ctx):
user = bot.get_user(499642019191324692)
await user.send('Hello')

How can I invite myself to a server my discord bot is in?

I would like to find a way to generate an invite link that would be sent to my account for each server my bot is in. I can't figure out how I would do this though. I have a list of every server my bot is in. I need to be able to generate the invite from the command prompt or as a basic function. This is coded in python 3.9.
You will have to use guild.create_invite inside on_ready looping all guilds.
Edit: You have to get a channel to create the invite to in this case you will get the first text channel and create the invite to it.
#bot.event
async def on_ready():
print("Logged in as")
print(bot.user.name)
print("------")
print(bot.owner_id)
for guild in bot.guilds:
channel = guild.text_channels[0] # get the first channel
invite = await channel.create_invite(max_uses=1) # make the invite link
user = bot.get_user(303069538315010058) # place your ID here
await user.send(invite.url) # Send the invite to the use
#client.command()
async def dm(ctx, *argument):
#creating invite link
invitelink = await ctx.channel.create_invite(max_uses=100,unique=True)
#dming it to the person
await ctx.author.send(invitelink)
This works, you would say [prefix]dm

How to snipe messages from a specific channel (discord.py)

Outcome
To snipe messages sent in X channel instead of all the channels within the Discord guild. That is, it should only track message deletions in that one channel (identified by its ID), and only respond to the !snipe command in that same channel. The current code I have here snipes every message sent within the Discord guild.
Question
How can I snipe messages sent in X channel instead of the entire guild?
I mostly intend to run this bot in one guild. However, it would be nice if it could scale to multiple guilds if needed.
The code I have so far is below.
import discord
from discord.ext import commands
from tokens import token
client = commands.Bot(command_prefix="!", self_bot=False)
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
print(f'sniped message {message}')
client.sniped_messages[message.guild.id] = (
message.content, message.author, message.channel.name, message.created_at)
#client.command()
async def snipe(ctx):
try:
contents, author, channel_name, time = client.sniped_messages[ctx.guild.id]
except:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(description=contents,
color=discord.Color.purple(), timestamp=time)
embed.set_author(
name=f"{author.name}#{author.discriminator}", icon_url=author.avatar_url)
embed.set_footer(text=f"Deleted in : #{channel_name}")
await ctx.channel.send(embed=embed)
client.run(token, bot=True)
I'm going to suggest two slightly different solutions, because the code can be simpler if you're only running this bot on one guild. What's common to both is that you need to check in what channel messages are deleted, and in what channel the !snipe command is sent.
Single-Guild Version
If you're only monitoring/sniping one channel on one guild, then you can only ever have one deleted message to keep track of. Thus, you don't need a dictionary like in your posted code; you can just keep a single message or None.
You're already importing your token from a separate file, so you might as well put the channel ID (which is an int, unlike the bot token) there too for convenience. Note that, by convention, constants (variables you don't intend to change) are usually named in all caps in Python. tokens.py would look something like this:
TOKEN = 'string of characters here'
CHANNEL_ID = 123456789 # actually a 17- or 18-digit integer
And the bot itself:
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_ID
client = commands.Bot(command_prefix='!')
client.sniped_message = None
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in the watched channel, and not one of the bot's own
# messages.
if message.channel.id == CHANNEL_ID and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_message = message
#client.command()
async def snipe(ctx):
# Only respond to the command in the watched channel.
if ctx.channel.id != CHANNEL_ID:
return
if client.sniped_message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
message = client.sniped_message
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Multi-Guild Version
If you're monitoring one channel each in multiple guilds, then you need to keep track of them separately. Handily, channel IDs are globally unique, not just within a single guild. So you can keep track of them by ID alone, without having to include the guild ID as well.
You could keep them in a list, but I recommend a set, because checking whether something is in a set or not is faster. Comments to help yourself remember which one is which are probably also a good idea.
TOKEN = 'string of characters here'
# Not a dictionary, even though it uses {}
CHANNEL_IDS = {
# That one guild
123456789,
# The other guild
987654322,
}
Then instead of checking against the single channel ID, you check if it's in the set of multiple IDs.
import discord
from discord.ext import commands
from tokens import TOKEN, CHANNEL_IDS
client = commands.Bot(command_prefix='!')
client.sniped_messages = {}
#client.event
async def on_ready():
print("Your bot is ready.")
#client.event
async def on_message_delete(message):
# Make sure it's in a watched channel, and not one of the bot's own
# messages.
if message.channel.id in CHANNEL_IDS and message.author != client.user:
print(f'sniped message: {message}')
client.sniped_messages[message.channel.id] = message
#client.command()
async def snipe(ctx):
# Only respond to the command in a watched channel.
if ctx.channel.id not in CHANNEL_IDS:
return
try:
message = client.sniped_messages[ctx.channel.id]
# See note below
except KeyError:
await ctx.channel.send("Couldn't find a message to snipe!")
return
embed = discord.Embed(
description=message.content,
color=discord.Color.purple(),
timestamp=message.created_at
)
embed.set_author(
name=f"{message.author.name}#{message.author.discriminator}",
icon_url=message.author.avatar_url
)
embed.set_footer(text=f"Deleted in: #{message.channel.name}")
await ctx.channel.send(embed=embed)
client.run(TOKEN)
Note: bare except clauses, like in your original code, are not generally a good idea. Here we only want to catch KeyError, which is what is raised if the requested key isn't in the dictionary.
You could, optionally, implement the same logic in a different way:
message = client.sniped_messages.get(ctx.channel.id)
if message is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
A dictionary's .get() method returns the corresponding item just like normal indexing. However, if there is no such key, instead of raising an exception, it returns a default value (which is None if you don't specify one in the call to get).
If you're using Python 3.8+, the first two lines could also be combined using an assignment expression (using the "walrus operator"), which assigns and checks all at once:
if (message := client.sniped_messages.get(ctx.channel.id)) is None:
await ctx.channel.send("Couldn't find a message to snipe!")
return
These alternative options are mentioned for completeness; all of these ways of doing it are perfectly fine.

guild.text_channels return just the top channel instead of all channels

I'm making a discord bot and I want it to send a message whenever it joins a new guild.
However, I only want it to send the message in the #general channel of the guild it joins:
#client.event
async def on_guild_join(guild):
chans = guild.text_channels
for channel in chans:
if channel.name == 'general':
await channel.send('hi')
break
The problem that I have noticed is that guild.text_channels only returns the name of the very first channel of the server. I want to iterate through all channels and finally send message only on the #general channel.
What's the workaround for it?
There are a couple ways you can do this.
Here's an example using utils.get():
import discord # To access utils.get
#client.event
async def on_guild_join(guild):
channel = discord.utils.get(guild.text_channels, name="general")
await channel.send("Hi!")
Or if the guild has a system_channel set up, you can send a message there:
#client.event
async def on_guild_join(guild):
await guild.system_channel.send("Hi!")
You can create checks for both of these, but bear in mind that some servers might not have a text channel called general or a system channel set up, so you may receive some attribute errors complaining about NoneType not having a .send() attribute.
These errors can be avoided with either an error handler or a try/except.
References:
Guild.system_channel
utils.get()

python discord.py send DM to inviter on join guild

I currently have the following on_guild_join code:
#client.event
async def on_guild_join(guild):
embed = discord.Embed(title='Eric Bot', color=0xaa0000)
embed.add_field(name="What's up everyone? I am **Eric Bot**.", value='\nTry typing `/help` to get started.', inline=False)
embed.set_footer(text='Thanks for adding Eric Bot to your server!')
await guild.system_channel.send(embed=embed)
print(f'{c.bgreen}>>> {c.bdarkred}[GUILD JOINED] {c.black}ID: {guild.id} Name: {guild.name}{c.bgreen} <<<\n{c.darkwhite}Total Guilds: {len(client.guilds)}{c.end}')
(Ignore the c.color stuff, it's my formatting on the console)
It sends an embed with a bit of information to the system channel whenever someone adds the bot to a guild.
I want it to send a DM to whoever invited the bot (the account that used the oauth authorize link) the same message. The problem is that the on_guild_join event only takes 1 argument, guild, which does not give you any information about the person who used the authorize link to add the bot to the guild.
Is there a way to do this? Do I have to use a "cheat" method like having a custom website that logs the account that uses the invite?
Since bots aren't "invited", there's instead an audit log event for when a bot is added. This lets you iterate through the logs matching specific criteria.
If your bot has access to the audit logs, you can search for a bot_add event:
#client.event
async def on_guild_join(guild):
bot_entry = await guild.audit_logs(action=discord.AuditLogAction.bot_add).flatten()
await bot_entry[0].user.send("Hello! Thanks for inviting me!")
And if you wish to double-check the bot's ID against your own:
#client.event
async def on_guild_join(guild):
def check(event):
return event.target.id == client.user.id
bot_entry = await guild.audit_logs(action=discord.AuditLogAction.bot_add).find(check)
await bot_entry.user.send("Hello! Thanks for inviting me!")
References:
Guild.audit_logs()
AuditLogAction.bot_add
AsyncIterator.find()
From this post
With discord.py 2.0 you can get the BotIntegration of a server and with that the user who invited the bot.
Example
from discord.ext import commands
bot = commands.Bot()
#bot.event
async def on_guild_join(guild):
# get all server integrations
integrations = await guild.integrations()
for integration in integrations:
if isinstance(integration, discord.BotIntegration):
if integration.application.user.name == bot.user.name:
bot_inviter = integration.user# returns a discord.User object
# send message to the inviter to say thank you
await bot_inviter.send("Thank you for inviting my bot!!")
break
Note: guild.integrations() requires the Manage Server (manage_guild) permission.
References:
Guild.integrations
discord.BotIntegration

Categories