Retrieving TextChannel Object in command - discord

I've come to a bit of a standstill with retrieving channel objects within a command. Instead of using async def command(ctx, channel:discord.TextChannel) or similar, I would like to get the channel object from the product of a user's wait_for message content, which I will not include here.
For example, if the user said general, the id 757115231377031169, or simply mentioned the channel #general, the bot would be able to retrieve the TextChannel object no matter the valid output.
In short: How do I get a TextChannel Object within the code itself?
Here are a few things that I have tried:
I had tried to retrieve the channel object via invoking another command with the help of a converter. This would often raise the error 'str' object has no attribute 'mention' (this was my most recent option, I had better ideas than this)
#client.command()
async def chan_convert(ctx, channel:discord.TextChannel):
return channel
#client.command()
async def test(ctx, channel):
try:
ch = await ctx.invoke(client.get_command("chan_convert"), channel=channel)
await ctx.send(ch.mention)
except Exception as e:
await ctx.send(str(e))
Another way I had approached my problem was with a custom function I attempted to create, but this only worked up until a point, such as if you inputted a channel id, it would raise the error AttributeError: 'NoneType' object has no attribute 'mention'.
def custom_chan(guild:discord.Guild, channel):
if channel.isnumeric(): # if it's all numbers, it could be an id
try:
channel = guild.get_channel(channel)
return [True, channel]
except:
return [False, "ID was not valid"]
else:
if channel.startswith("<#") and channel.endswith(">"): # <#123123123123123123> format of channel mention
channel = channel.replace("<#", "")
channel = channel.replace(">", "")
try:
channel = guild.get_channel(channel)
return [True, channel]
except:
return [False, "ID was not valid"]
else: # otherwise, it's just a name, check if it exists
try:
channel = discord.utils.get(guild.channels, name=channel)
return [True, channel]
except:
return [False, "No channel with that name was found"]
def channel_check(channel):
return type(channel)
chan = custom_chan(ctx.guild, "757115231377031169") # an id I put in here originally
await ctx.send(chan[1].mention)
await ctx.send(channel_check(chan))
Thank you in advance.

If you are using discord.ext, you can use TextChannelConverter() in order to get the TextChannel object, using a string containing the ID, the mention, or the name as input.
Following your first example:
from discord.ext.commands import TextChannelConverter
#client.command()
async def chan_convert(ctx, channel):
ch = await TextChannelConverter().convert(ctx=ctx, argument=channel)
return ch
This worked for me in my own channel, using the ID, the name, or the mention as input. But if you aren't going to use this as an actual command, instead of using ctx.invoke(), you can just remove the #client.command() decorator and call the function using await chan_converter().

Related

TypeError: context menu callback 'slash' requires 2 parameters, first one being the interaction and other one explicitly annotated with discord.User

I want with this application code it to send a message to channel it is used and delete the channel
#tree.context_menu(guild=discord.Object(id=941748573937209344), name='tester')
async def slash(interaction: discord.Interaction, ctx, user: discord.User):
title = "ID deleted."
embed = discord.Embed(title=title, color=0xf1c40f)
msg = await ctx.send(embed=embed)
await asyncio.sleep(2)
channel = ctx.channel
await channel.delete()
but when I run it, I get the following error:
TypeError: context menu callback 'slash' requires 2 parameters, the first one being the interaction and the other one explicitly annotated with either discord.Message, discord.User, discord.Member, or a typing.Union of discord.Member and discord.User
it works when I delete ctx, but I can't send a message to the channel it's using and then make it delete the channel
can I use something else instead of ctx or where else can I write ctx
You should use the interaction object to get your channel object instead of ctx.
#tree.context_menu(guild=discord.Object(id=941748573937209344), name='tester')
async def slash(interaction: discord.Interaction, user: discord.User):
title = "ID deleted."
embed = discord.Embed(title=title, color=0xf1c40f)
msg = await interaction.response.send_message(embed=embed)
await asyncio.sleep(2)
channel = interaction.channel
await channel.delete()

How to prevent bot from spamming embeds? Discord.py

I have discord bot and it checks whether streamer is live or not. And I have a function that prevents it from spamming when someone is live:
if status is True:
async for message in channel.history(limit=2000):
if message.content == f"`Hey #everyone, {twitch_name} is live! Check it out:" + f"https://www.twitch.tv/{twitch_name}`":
break
else:
async for member in guild.fetch_members(limit=None):
if member.id == int(user_id):
await member.add_roles(role)
await channel.send(f"`Hey #everyone, {twitch_name} is live! Check it out:" + f"https://www.twitch.tv/{twitch_name}`")
print(f"{user} started streaming. Sending a notification.")
break
And I wonder if there is the way to do same with embeds, but idk what to use. I though I can use if embed = twitch_embed: break instead of if message.content.
I want to send both message and embed when someone is live one time per stream each like mee6 does:
And I want to combine message and embed antispam function in the code. Please help! All my code here if you need:
import os
import json
import discord
import requests
from discord.ext import tasks, commands
from server import ping
from twitchAPI.twitch import Twitch
from discord.utils import get
intents = discord.Intents.all()
bot = commands.Bot(command_prefix='$', intents=intents)
# Authentication with Twitch API.
client_id = os.getenv('client_id')
client_secret = os.getenv('Dweller_token')
twitch = Twitch(client_id, client_secret)
twitch.authenticate_app([])
TWITCH_STREAM_API_ENDPOINT_V5 = "https://api.twitch.tv/kraken/streams/{}"
API_HEADERS = {
'Client-ID': client_id,
'Accept': 'application/vnd.twitchtv.v5+json',
}
user_info = twitch.get_users(logins=['turb4ik'])
user_id = user_info['data'][0]['id']
print(user_info)
# Returns true if online, false if not.
def checkuser(user):
try:
userid = twitch.get_users(logins=[user])['data'][0]['id']
url = TWITCH_STREAM_API_ENDPOINT_V5.format(userid)
try:
req = requests.Session().get(url, headers=API_HEADERS)
jsondata = req.json()
if 'stream' in jsondata:
if jsondata['stream'] is not None:
return True
else:
return False
except Exception as e:
print("Error checking user: ", e)
return False
except IndexError:
return False
# Executes when bot is started
#bot.event
async def on_ready():
# Defines a loop that will run every 10 seconds (checks for live users every 10 seconds).
#tasks.loop(seconds=10)
async def live_notifs_loop():
# Opens and reads the json file
with open('streamers.json', 'r') as file:
streamers = json.loads(file.read())
# Makes sure the json isn't empty before continuing.
if streamers is not None:
# Gets the guild, 'twitch streams' channel, and streaming role.
guild = bot.get_guild(690995360411156531)
channel = bot.get_channel(785523710362124298)
role = get(guild.roles, id=835581408272580649)
# Loops through the json and gets the key,value which in this case is the user_id and twitch_name of
# every item in the json.
for user_id, twitch_name in streamers.items():
print("checking" + " " + str(twitch_name))
# Takes the given twitch_name and checks it using the checkuser function to see if they're live.
# Returns either true or false.
status = checkuser(twitch_name)
# Gets the user using the collected user_id in the json
user = bot.get_user(int(user_id))
# Makes sure they're live
if status is True:
# Checks to see if the live message has already been sent.
#limit = 0
#limit += 1
async for message in channel.history(limit=2000):
# If it has, break the loop (do nothing).
if message.content == f"`Hey #everyone, {twitch_name} is live! Check it out:" + f"https://www.twitch.tv/{twitch_name}`":
break
# If it hasn't, assign them the streaming role and send the message.
else:
# Gets all the members in your guild.
async for member in guild.fetch_members(limit=None):
# If one of the id's of the members in your guild matches the one from the json and
# they're live, give them the streaming role.
if member.id == int(user_id):
await member.add_roles(role)
# Sends the live notification to the 'twitch streams' channel then breaks the loop.
'''twitch_embed = discord.Embed(
title=f":red_circle: **LIVE**\n{user.name} is now streaming on Twitch!",
color=0xac1efb
)'''
#twitch_embed.add_field(name='**Game**', value='add value',inline=True)
#twitch_embed.add_field(name='**Viewers**', value='add value',inline=True)
#twitch_embed.set_image(url=f'\nhttps://www.twitch.tv/{twitch_name}')
await channel.send(f"`Hey #everyone, {twitch_name} is live! Check it out:" + f"https://www.twitch.tv/{twitch_name}`")
#await channel.send(embed=twitch_embed)
print(f"{user} started streaming. Sending a notification.")
break
# If they aren't live do this:
else:
# Gets all the members in your guild.
async for member in guild.fetch_members(limit=None):
# If one of the id's of the members in your guild matches the one from the json and they're not
# live, remove the streaming role.
if member.id == int(user_id):
await member.remove_roles(role)
# Checks to see if the live notification was sent.
async for message in channel.history(limit=200):
# If it was, delete it.
if str(user.mention) in message.content and "is now streaming" in message.content:
await message.delete()
# Start your loop.
live_notifs_loop.start()
# Command to add Twitch usernames to the json.
#bot.command(name='addtwitch', help='Adds your Twitch to the live notifs.', pass_context=True)
#commands.has_permissions(administrator = True)
async def addtwitch(ctx, twitch_name):
# Opens and reads the json file.
with open('streamers.json', 'r') as file:
streamers = json.loads(file.read())
# Gets the users id that called the command.
user_id = ctx.author.id
# Assigns their given twitch_name to their discord id and adds it to the streamers.json.
streamers[user_id] = twitch_name
# Adds the changes we made to the json file.
with open('streamers.json', 'w') as file:
file.write(json.dumps(streamers))
# Tells the user it worked.
await ctx.send(f"Added {twitch_name} for {ctx.author} to the notifications list.")
ping()
bot.run(os.getenv('token'))
Edit:
Please say if something is not right or you don't understand the question instead of just closing it.
To send the embed in the same message you can use content an example would be:
await channel.send(content=f"`Hey #everyone, {twitch_name} is live! Check it out:" + f"https://www.twitch.tv/{twitch_name}`", embed=twitch_embed)
There would be no change in your check function as the message.content would be the same text message just with an embed as well.
You should really check if the stream has finished either by web scraping or some API instead of checking message content. If the stream finishes 3 hours ago but you only check for 2 streamers there won't be a change unless you have thought of this already.

Wait for Message on_member_join using checks (discord.py) | SOLVED

I want to wait for a private message that was sent only by the member. I am trying to use the check parameter for the client.wait_for() method but I can not seem to get it. I tried putting the check as the response.author but will not work since I just assigned response. Also I am executing this when a member joins using on_member_join which takes in a member argument (like this async def on_member_join(member). Anyone know how to properly do this? Any answers would be greatly appreciated!
Here is the code for this:
await member.send('What is your student number? ')
response = await client.wait_for('message')
student_number = response.content
When adding a check function to wait_for, that check function will get the received message as an argument. This means that it will check every single message the bot gets, pass all of them to your check function, and wait until one of them makes the check return True.
#client.event()
async def on_member_join(member):
def check(message):
return message.author.id == member.id and message.guild is None
await member.send('What is your student number?')
response = await client.wait_for('message', check=check)
student_number = response.content
Note how my example also makes sure the guild is None, as you explicitly mentioned waiting for a private message.

Discord.py Give role if member customstatus contains hi

Im working on a Loop that checks every 5 seconds if user Status contain "hi" if so it should give the user a role.
I dont really know how to do it, But maybe someone can help.
My code:
#Here is a little base for the Command i asked...
#I hope it can help you
#tasks.loop(seconds=15)
async def status_role():
if "hi" in ???.lower()
#here comes the code
#(Idk what the code is so i asked you guys :D)
member = ctx.message.author
role = get(member.server.roles, name="Friendly dude")
await bot.add_roles(member, role)
Looking at your new error, you're getting an IndexError because one of your members doesn't have an activity set, so activities[0] doesn't exist. Use an if-statement to check this. To see if a tuple is empty, you can just do if tuple (as empty lists/tuples are falsy), so the code below should solve that:
#tasks.loop(seconds=15)
async def status_role():
guild = client.get_guild(your guilds id)
role = get(guild.roles, name='Friendly dude')
for member in guild.members:
if member.activities and 'hi' in member.activities[0].name.lower():
await member.add_roles(role)
In addition to the fragment in the answer above.
EDIT: Apparently you didn't get my above ^ sentence saying it was meant to be combined with the other answer, so I've edited my answer & combined it myself. This should be the correct answer to your question.
Still same error
Unhandled exception in internal background task 'status_role'.
Traceback (most recent call last):
File "C:\Users\lequi\AppData\Local\Programs\Python\Python38-32\lib\site-packages\discord\ext\tasks\__init__.py", line 101, in _loop
await self.coro(*args, **kwargs)
File "C:\Users\lequi\Desktop\Programs\Clipox\ClipoxMain\main.py", line 136, in status_role
[await member.add_roles(role) for member in guild.members if 'hi' in member.activities[0].name.lower()]
File "C:\Users\lequi\Desktop\Programs\Clipox\ClipoxMain\main.py", line 136, in <listcomp>
[await member.add_roles(role) for member in guild.members if 'hi' in member.activities[0].name.lower()]
IndexError: tuple index out of range
You can use member.activities. This will return you a list of member's activities. As far as I know, you can get custom status with member.avtivities[0].name. This'll return you a string of member's custom activity.
Also, you can't use ctx.message.author. You need to iterate through members in the guild to check every one of their's activity.
So you can simply do:
#tasks.loop(seconds=15)
async def status_role():
guild = client.get_guild(your guilds id)
role = get(guild.roles, name='Friendly dude')
[await member.add_roles(role) for member in guild.members if 'hi' in member.activities[0].name.lower()]
EDIT
There're some updates about Intents in the discord.py 1.5.x. You need to define it before defining client = discord.Bot(prefix='') to get guilds, channels etc.
import discord
intents = discord.Intents().all()
client = discord.Bot(prefix='', intents=intents)
EDIT 2
If member has no status, it'll return an empty tuple, that's why you get IndexError. To prevent this, you can add a simple if block to your code.
async def status_role():
guild = client.get_guild(your guilds id)
role = get(guild.roles, name='Friendly dude')
[await member.add_roles(role) for member in guild.members if member.activities[0] and 'hi' in member.activities[0].name.lower()]

Discord Py - How can I add_role to user on_message?

I want to use regex to look at and accept literally ANY incoming message from a user and assign them a role. But ONLY if that user does not have a role already assigned to them. I don't get any errors when running the code, but it does not work.
Here is what I have:
#client.event
async def on_message(message):
match = re.search(r'(.*?)', message.content)
member = message.author
role = discord.utils.get(member.guild.roles, name="Creators")
if message.author == client.user:
return
if role not in member.roles:
if match and message.channel.id == target_channel:
# add member to role
# send message to to users
await message.channel.send(
f'Hi {message.author}, welcome to the server! Don\'t forget to choose your #roles'
)
await discord.Member.add_roles(member, role)
You wrote:
await discord.Member.add_roles(member, role)
https://discordpy.readthedocs.io/en/latest/api.html?highlight=add_role#discord.Member.add_roles
As you can see from the documentation the passed arguments is *roles, reason=None, atomic=True. roles is something you have to specify, reason and atomic are optional.
You tried passing member which is not a valid argument.
discord.Member is a class. You need to get an instance of that class. ctx.author is an instance of discord.Member.
So the final call should be:
await ctx.author.add_roles(role)
To get the Member object from on_message, you can use member = message.author.
Then simply do member.add_roles(role).
Thanks for the help guys, it turns out it was something very minor (and dumb on my part) that prevented it from working.
when comparing message.channel.id.id to target_channel, I forgot that the channel id object does not come in as a string, but target_channel was set up as a string, so it failed to see them as equal. Simply converting message.channel.id to string did the trick.

Resources