Grabbing new channel ID from json file in background task - discord.py

I have a background task that send a message in a channel that the channel ID is stored in JSON. In the even that channel gets deleted, a new channel ID replaces the old channel ID in the JSON file. I'm getting discord.errors.NotFound: 404 Not Found (error code: 10003): Unknown Channel when the background task sends again. I tried to put the piece of code chanid = get_general() outside of the background task code in hopes that it pulls in the new channel ID in the loop but I feel like I a missing something here.
try:
with open("channels.json", 'r') as fp:
channels = json.load(fp)
except Exception:
channels = {}
def save_channels():
with open("channels.json", "w+") as fp:
json.dump(channels, fp, indent=4)
def get_general():
return channels.get("general", 0)
chanid = get_general()
async def hello_loop():
await client.wait_until_ready()
chan = client.get_channel(chanid)
while not client.is_closed():
msg= 'hello'
msgsend = await chan.send(msg)
client.loop.create_task(hello_loop())

What I need is, on chance that a delete channel command is used, I remove the current general channel ID inside of delete channel command and replace it with the cloned channel ID. I also stop the background task, and reset it within the same command. It works the way I want it, but maybe there is a better way of approaching it?

Related

How to change channel permissions without having a message sent first?

I have a program that changes what channel members can see at certain times of the day. To do this, I can either change the roles that every member has, or change the permissions of each channel. However, I have looked all over the web and all of the ways for either method require a message to be sent so that the data from that message can be read and put into the function, such as:
#client.command()
async def perm(ctx):
await ctx.channel.set_permissions(ctx.guild.default_role, send_messages=False
Change the permissions of a discord text channel with discord.py
or
async def addrole(ctx):
member = ctx.message.author
role = get(member.server.roles, name="Test")
await bot.add_roles(member, role)
Discord.py | add role to someone
My current program looks like this:
import discord
client = discord.Client()
import datetime
async def live_day(schedule):
current_place = "the void"
while True:
current_time = str((datetime.datetime.now() -datetime.timedelta(hours=7)).time())
int_time = int(current_time[:2] + current_time[3:5])
for x in range(len(schedule)):
try:
start_time = schedule[x][1]
end_time = schedule[x + 1][1]
except IndexError:
end_time = 2400
if current_place != schedule[x][0] and int_time >= start_time and int_time < end_time:
current_place = schedule[x][0]
#Change the channel permissions of the current place to allow viewing
#Change the channel permissions of the last channel to disallow viewing
#client.event
async def on_ready():
print("{0.user} has arrived for duty".format(client))
client.loop.create_task(live_day([
("Home", 0),
("Work", 900),
("Home", 1700),
("Nightclub", 2000),
("Home", 2200),
]))
client.run(my_secret)
Never mind the badly written code, how would I do this or where should I go to figure this out? Any help is appreciated. Thanks!
Edit: I could get the channels individually by using this,
channel1=client.get_channel(channelid)
discord.py, send message by channel id without on_message() event?
but then I can't use this for more than one server. How can I get channels by name?
Note on on_ready():
This function is not guaranteed to be the first event called. Likewise, this function is not guaranteed to only be called once. This library implements reconnection logic and thus will end up calling this event whenever a RESUME request fails.
Using this function may crash the bot.
Instead create background tasks and use wait_until_ready().
You can find examples in https://github.com/Rapptz/discord.py/blob/v1.7.3/examples/background_task.py
If you want to change only one channel's permissions (per guild) this might fit your needs:
#changing "Home" channel visibility, every day at 5 pm.
#client.event
async def on_ready():
while True:
await asyncio.sleep(1)
current_time = datetime.datetime.now()
five_pm = current_time.replace(hour=17, minute=0, second=0)
if current_time == five_pm:
for guild in client.guilds: #looping through all the guilds your bot is in
channel = discord.utils.get(client.get_all_channels(), name="Home") #getting channel by name by using "discord.utils"
await channel.set_permissions(guild.default_role, view_channel=True) #changing permissions
By using the on_ready() event you can loop through the time check without sending any message.
Remember to import discord.utils.

Discord.py logging messages

I've got this code, which when I type !snapshot should log the last 100 messages in the channel, and make them into a text file:
snapshot_channel = (my snapshot channel ID)
#commands.has_permissions(administrator=True)
#bot.command()
async def snapshot(ctx):
channel = bot.get_channel(snapshot_channel)
await ctx.message.delete()
messages = message in ctx.history(limit=100)
numbers = "\n".join(
f"{message.author}: {message.clean_content}" for message in messages
)
f = BytesIO(bytes(numbers, encoding="utf-8"))
file = discord.File(fp=f, filename="snapshot.txt")
await channel.send("Snapshot requested has completed.")
await channel.send(file=file)
I've got BytesIO imported, and it works fine for a different command which purges messages and logs them, but this code which should just make a log and then send it in the channel doesn't work. Please can you send me what it should look like for it to work. Thanks!
TextChannel.history is an async generator, you're not using it properly
messages = await ctx.channel.history(limit=100).flatten()
numbers = "\n".join([f"{message.author}: {message.clean_content}" for message in messages])
Another option would be
numbers = ""
async for message in ctx.channel.history(limit=100):
numbers += f"{message.author}: {message.clean_content}"

bot.get_all_channels() gets ignored discord.py

When obtaining all channels to send a message to all, the bot ignores the command. Here's my code.
async def lockdown(ctx):
allchannels = bot.get_all_channels()
overwrite = channel.overwrites_for(ctx.guild.default_role)
locked = overwrite.send_messages = False
await locked.send(allchannels, 'This server has been locked down.')
Try printing allchannel, you'll see where you did an error.
You can't use bot.get_all_channels() in this way that's all

get channel name and send a message over at that channel

So I am working on a little project here, and pretty much, I want to have one of those "Please type the name of a channel in this server" feature.
So pretty much, the bot asks for a channel name, and I put in for example "#changelog" - and then it will ask for what it should write in that channel, etc etc.
So need to get the channel id (I am guessing), but I don't want users to write the ID, instead only writing the #server-name. And then whenever I have done that, the bot shall write in that channel.
Here is my current code!
class Changelog(commands.Cog):
def __init__(self, client):
self.client = client
#commands.Cog.listener()
async def on_ready(self):
print('Changelog is loaded')
#commands.command()
async def clhook(self, ctx):
await ctx.send('Write text-channel: ')
text_channel = await self.client.wait_for("message", check=lambda message: message.author == ctx.author, timeout=300)
clhook = self.client.get_channel(text_channel)
def setup(client):
client.add_cog(Changelog(client))
Edit:
The channel ID shall be saved "forever", meaning that I do not have to re-write the channel name where the message should go!
You can use discord.utils.get() with this example:
text_channel = await self.client.wait_for("message", check=lambda message: message.author == ctx.author, timeout=300)
channel = discord.utils.get(ctx.guild.text_channels, name=text_channel)
await channel.send('Bla Bla')
So when you type (prefix)clhook then only the channel name, for example general, it will send Bla Bla to the channel named general .
There is another way to do this and I think it's simple than the first option, here it is:
#commands.command()
async def clhook(self, ctx, channel: discord.TextChannel):
await channel.send('Bla Bla')
So in this command, usage is changed. You can use that with this: (prefix)clhook #general(mention the channel). I suggest this solution and I think it's more useful.
You can use message.channel_mentions. This will return a list of all channels that were mentioned using the #channel-name notation. That way, you can just use channel.id to get the id of the channel they mentioned.
Don't forget, however, to check if the user did in fact tag a channel (which you can also put in your check). I put it in a separate function to make it a bit more readable for the sake of this reply, but you can fit that in your lambda if you really want to.
Also, make sure to check if it's a Text Channel and not a Voice Channel or Category Channel.
#commands.command()
async def clhook(self, ctx):
def check(self, message):
author_ok = message.author == ctx.author # Sent by the same author
mentioned_channel = len(message.channel_mentions) == 1 and isinstance(message.channel_mentions[0], discord.TextChannel)
return author_ok and mentioned_channel
await ctx.send("Write text-channel: ")
text_channel = await self.client.wait_for("message", check=check)
chlhook = text_channel.channel_mentions[0]
I put two conditions on the mentioned_channel line, because if the first one fails, the second one could cause an IndexError. Alternatively you can also use an if-statement to return sooner at that place to solve the same issue.

How to download the image of an embeded message with disord.py

I am trying to be able to download the content of an embedded message.
This is what I am running so far
async def on_message(self, message):
embedFromMessage = message.embeds
print(embedFromMessage)
I would like it to output the url of the attached image and the description but is only outputting [discord.embeds.Embed object at 0x049A8990]
The discord.py API provides you a set of tools to allowing you to find what you're looking for.
Find the embed
As I see you're trying to catch an embedded message using the on_message event.
To do so we're going to use this :
#commands.Cog.listener()
async def on_message(self, message):
if(len(message.embeds) > 0):
This simple if statment will determine if the message is an embedded one or not.
If the message is an embed it will return a list of embeds.
Note that the image preview is considered as an embedded message.
Now let's catch the embed you want :
_embed = message.embeds[0] # this will return the first embed in message.embeds
Once we've stored the embed, we want to know if it contains an image :
if not _embed.image.url == discord.Embed.Empty:
image = _embed.image.url
By default, _embed.image will return an EmbedProxy which looks like that :
EmbedProxy(width=0, url='URL', proxy_url='PROXY_URL', height=0)
To access the value of url we have done _embed.image.url.
Now you have the image URL, you can download it using the aiohttp library for example.
The full code
Here is the code you can use to catch the image inside an embedded message as those that are created using the discord.Embed() class.
#commands.Cog.listener()
async def on_message(self, message):
if(len(message.embeds) > 0):
_embed = message.embeds[0]
if not _embed.image.url == discord.Embed.Empty:
image = _embed.image.url
channel = message.channel # get the channel where the embed has been sent
await channel.send(image) # send the image into the channel
Hope it helped !
Have a nice day !

Resources