Added cog to manage channels dedicated towards lists.
This commit is contained in:
parent
40570ee112
commit
4058612e96
3 changed files with 298 additions and 1 deletions
|
@ -63,7 +63,8 @@ initial_extensions = ['cogs.common',
|
|||
'cogs.meme',
|
||||
'cogs.imagemanip',
|
||||
'cogs.pin',
|
||||
'cogs.invites']
|
||||
'cogs.invites',
|
||||
'cogs.lists']
|
||||
|
||||
bot = commands.Bot(command_prefix=get_prefix,
|
||||
description=config.bot_description)
|
||||
|
|
290
cogs/lists.py
Normal file
290
cogs/lists.py
Normal file
|
@ -0,0 +1,290 @@
|
|||
import config
|
||||
import discord
|
||||
import io
|
||||
import urllib.parse
|
||||
from discord.ext import commands
|
||||
from discord.ext.commands import Cog
|
||||
|
||||
class Lists(Cog):
|
||||
"""
|
||||
Manages channels that are dedicated to lists.
|
||||
"""
|
||||
|
||||
def __init__(self, bot):
|
||||
self.bot = bot
|
||||
|
||||
# Helpers
|
||||
|
||||
def check_if_target_is_staff(self, target):
|
||||
return any(r.id in config.staff_role_ids for r in target.roles)
|
||||
|
||||
def is_edit(self, emoji):
|
||||
return str(emoji)[0] == u"✏" or str(emoji)[0] == u"📝"
|
||||
|
||||
def is_delete(self, emoji):
|
||||
return str(emoji)[0] == u"❌" or str(emoji)[0] == u"❎"
|
||||
|
||||
def is_recycle(self, emoji):
|
||||
return str(emoji)[0] == u"♻"
|
||||
|
||||
def is_insert_above(self, emoji):
|
||||
return str(emoji)[0] == u"⤴️" or str(emoji)[0] == u"⬆"
|
||||
|
||||
def is_insert_below(self, emoji):
|
||||
return str(emoji)[0] == u"⤵️" or str(emoji)[0] == u"⬇"
|
||||
|
||||
def is_reaction_valid(self, reaction):
|
||||
allowed_reactions = [
|
||||
u"✏",
|
||||
u"📝",
|
||||
u"❌",
|
||||
u"❎",
|
||||
u"♻",
|
||||
u"⤴️",
|
||||
u"⬆",
|
||||
u"⬇",
|
||||
u"⤵️",
|
||||
]
|
||||
return str(reaction.emoji)[0] in allowed_reactions
|
||||
|
||||
async def find_reactions(self, user_id, channel_id, limit = None):
|
||||
reactions = []
|
||||
channel = self.bot.get_channel(channel_id)
|
||||
async for message in channel.history(limit = limit):
|
||||
if len(message.reactions) == 0:
|
||||
continue
|
||||
|
||||
for reaction in message.reactions:
|
||||
users = await reaction.users().flatten()
|
||||
user_ids = map(lambda user: user.id, users)
|
||||
if user_id in user_ids:
|
||||
reactions.append(reaction)
|
||||
|
||||
return reactions
|
||||
|
||||
def create_log_message(self, emoji, action, user, channel, reason = ""):
|
||||
msg = f"{emoji} **{action}** \n"\
|
||||
f"from {self.bot.escape_message(user.name)} ({user.id}), in {channel.mention}"
|
||||
|
||||
if reason != "":
|
||||
msg += f":\n`{reason}`"
|
||||
|
||||
return msg
|
||||
|
||||
async def clean_up_raw_text_file_message(self, message):
|
||||
embeds = message.embeds
|
||||
if len(embeds) == 0:
|
||||
return
|
||||
|
||||
fields = embeds[0].fields
|
||||
for field in fields:
|
||||
if field.name == "Message ID":
|
||||
files_channel = self.bot.get_channel(config.list_files_channel)
|
||||
file_message = await files_channel.fetch_message(int(field.value))
|
||||
await file_message.delete()
|
||||
|
||||
await message.edit(embed = None)
|
||||
|
||||
# Commands
|
||||
|
||||
@commands.command(aliases = ["list"])
|
||||
async def listitem(self, ctx, channel: discord.TextChannel, number: int):
|
||||
"""Link to a specific list item."""
|
||||
if number <= 0:
|
||||
await ctx.send(f"Number must be greater than 0.")
|
||||
return
|
||||
|
||||
if channel.id not in config.list_channels:
|
||||
await ctx.send(f"{channel.mention} is not a list channel.")
|
||||
return
|
||||
|
||||
counter = 0
|
||||
async for message in channel.history(limit = None, oldest_first = True):
|
||||
if message.content.strip():
|
||||
counter += 1
|
||||
|
||||
if counter == number:
|
||||
embed = discord.Embed(
|
||||
title = f"Item #{number} in #{channel.name}",
|
||||
description = message.content,
|
||||
url = message.jump_url
|
||||
)
|
||||
await ctx.send(
|
||||
content = "",
|
||||
embed = embed
|
||||
)
|
||||
return
|
||||
|
||||
await ctx.send(f"Unable to find item #{number} in {channel.mention}.")
|
||||
|
||||
# Listeners
|
||||
|
||||
@Cog.listener()
|
||||
async def on_raw_reaction_add(self, payload):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
# We only care about reactions in Rules, and Support FAQ
|
||||
if payload.channel_id not in config.list_channels:
|
||||
return
|
||||
|
||||
channel = self.bot.get_channel(payload.channel_id)
|
||||
message = await channel.fetch_message(payload.message_id)
|
||||
member = channel.guild.get_member(payload.user_id)
|
||||
user = self.bot.get_user(payload.user_id)
|
||||
reaction = next((reaction for reaction in message.reactions if str(reaction.emoji) == str(payload.emoji)), None)
|
||||
if reaction is None:
|
||||
return
|
||||
|
||||
# Only staff can add reactions in these channels.
|
||||
if not self.check_if_target_is_staff(member):
|
||||
await reaction.remove(user)
|
||||
return
|
||||
|
||||
# Reactions are only allowed on messages from the bot.
|
||||
if not message.author.bot:
|
||||
await reaction.remove(user)
|
||||
return
|
||||
|
||||
# Only certain reactions are allowed.
|
||||
if not self.is_reaction_valid(reaction):
|
||||
await reaction.remove(user)
|
||||
return
|
||||
|
||||
# Remove all other reactions from user in this channel.
|
||||
for r in await self.find_reactions(payload.user_id, payload.channel_id):
|
||||
if r.message.id != message.id or (r.message.id == message.id and str(r.emoji) != str(reaction.emoji)):
|
||||
await r.remove(user)
|
||||
|
||||
# When editing we want to provide the user a copy of the raw text.
|
||||
if self.is_edit(reaction.emoji) and config.list_files_channel != 0:
|
||||
files_channel = self.bot.get_channel(config.list_files_channel)
|
||||
file = discord.File(io.BytesIO(message.content.encode("utf-8")), filename = f"{message.id}.txt")
|
||||
file_message = await files_channel.send(file = file)
|
||||
|
||||
embed = discord.Embed(
|
||||
title = "Click here to get the raw text to modify.",
|
||||
url = f"{file_message.attachments[0].url}?"
|
||||
)
|
||||
embed.add_field(name = "Message ID", value = file_message.id, inline = False)
|
||||
await message.edit(embed = embed)
|
||||
|
||||
@Cog.listener()
|
||||
async def on_raw_reaction_remove(self, payload):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
# We only care about reactions in Rules, and Support FAQ
|
||||
if payload.channel_id not in config.list_channels:
|
||||
return
|
||||
|
||||
channel = self.bot.get_channel(payload.channel_id)
|
||||
message = await channel.fetch_message(payload.message_id)
|
||||
|
||||
# Reaction was removed from a message we don"t care about.
|
||||
if not message.author.bot:
|
||||
return
|
||||
|
||||
# We want to remove the embed we added.
|
||||
if self.is_edit(payload.emoji) and config.list_files_channel != 0:
|
||||
await self.clean_up_raw_text_file_message(message)
|
||||
|
||||
@Cog.listener()
|
||||
async def on_message(self, message):
|
||||
await self.bot.wait_until_ready()
|
||||
|
||||
# We only care about messages in Rules, and Support FAQ
|
||||
if message.channel.id not in config.list_channels:
|
||||
return
|
||||
|
||||
# We don"t care about messages from bots.
|
||||
if message.author.bot:
|
||||
return
|
||||
|
||||
# Only staff can modify lists.
|
||||
if not self.check_if_target_is_staff(message.author):
|
||||
await message.delete()
|
||||
return
|
||||
|
||||
log_channel = self.bot.get_channel(config.log_channel)
|
||||
channel = message.channel
|
||||
content = message.content
|
||||
user = message.author
|
||||
|
||||
attachment_filename = None
|
||||
attachment_data = None
|
||||
if len(message.attachments) != 0:
|
||||
# Lists will only reupload the first image.
|
||||
attachment = next((a for a in message.attachments if a.filename.endswith(".png") or a.filename.endswith(".jpg") or a.filename.endswith(".jpeg")), None)
|
||||
if attachment != None:
|
||||
attachment_filename = attachment.filename
|
||||
attachment_data = await attachment.read()
|
||||
|
||||
await message.delete()
|
||||
|
||||
reactions = await self.find_reactions(user.id, channel.id)
|
||||
|
||||
# Add to the end of the list if there is no reactions or somehow more than one.
|
||||
if len(reactions) != 1:
|
||||
if attachment_filename != None and attachment_data != None:
|
||||
file = discord.File(io.BytesIO(attachment_data), filename = attachment_filename)
|
||||
await channel.send(content = content, file = file)
|
||||
else:
|
||||
await channel.send(content)
|
||||
|
||||
for reaction in reactions:
|
||||
await reaction.remove(user)
|
||||
|
||||
await log_channel.send(self.create_log_message("💬", "List item added:", user, channel))
|
||||
return
|
||||
|
||||
targeted_reaction = reactions[0]
|
||||
targeted_message = targeted_reaction.message
|
||||
|
||||
if self.is_edit(targeted_reaction):
|
||||
if config.list_files_channel != 0:
|
||||
await self.clean_up_raw_text_file_message(targeted_message)
|
||||
await targeted_message.edit(content = content)
|
||||
await targeted_reaction.remove(user)
|
||||
|
||||
await log_channel.send(self.create_log_message("📝", "List item edited:", user, channel))
|
||||
|
||||
elif self.is_delete(targeted_reaction):
|
||||
await targeted_message.delete()
|
||||
|
||||
await log_channel.send(self.create_log_message("❌", "List item deleted:", user, channel, content))
|
||||
|
||||
elif self.is_recycle(targeted_reaction):
|
||||
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten()
|
||||
await channel.purge(limit = len(messages) + 1, bulk = True)
|
||||
|
||||
await channel.send(targeted_message.content)
|
||||
for message in messages:
|
||||
await channel.send(message.content)
|
||||
|
||||
await log_channel.send(self.create_log_message("♻", "List item recycled:", user, channel, content))
|
||||
|
||||
elif self.is_insert_above(targeted_reaction):
|
||||
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten()
|
||||
await channel.purge(limit = len(messages) + 1, bulk = True)
|
||||
|
||||
await channel.send(content)
|
||||
await channel.send(targeted_message.content)
|
||||
for message in messages:
|
||||
self.bot.log.info(message.content)
|
||||
await channel.send(message.content)
|
||||
|
||||
await log_channel.send(self.create_log_message("💬", "List item added:", user, channel))
|
||||
|
||||
elif self.is_insert_below(targeted_reaction):
|
||||
messages = await channel.history(limit = None, after = targeted_message, oldest_first = True).flatten()
|
||||
await channel.purge(limit = len(messages) + 1, bulk = True)
|
||||
|
||||
await channel.send(targeted_message.content)
|
||||
await channel.send(content)
|
||||
for message in messages:
|
||||
self.bot.log.info(message.content)
|
||||
await channel.send(message.content)
|
||||
|
||||
await log_channel.send(self.create_log_message("💬", "List item added:", user, channel))
|
||||
|
||||
def setup(bot):
|
||||
bot.add_cog(Lists(bot))
|
|
@ -97,3 +97,9 @@ allowed_pin_roles = []
|
|||
|
||||
# Used for the pinboard. Leave empty if you don't wish for a gist pinboard.
|
||||
github_oauth_token = ""
|
||||
|
||||
# Channel to upload text files while editing list items. (They are cleaned up.)
|
||||
list_files_channel = 0
|
||||
|
||||
# Channels that are lists that are controlled by the lists cog.
|
||||
list_channels = []
|
||||
|
|
Loading…
Reference in a new issue