import config from discord.ext import commands from discord.ext.commands import Cog from discord.enums import MessageType from discord import Embed import aiohttp import gidgethub.aiohttp from helpers.checks import check_if_collaborator from helpers.checks import check_if_pin_channel class Pin(Cog): """ Allow users to pin things """ def __init__(self, bot): self.bot = bot def is_pinboard(self, msg): return ( msg.author == self.bot.user and len(msg.embeds) > 0 and msg.embeds[0].title == "Pinboard" ) async def get_pinboard(self, gh, channel): # Find pinboard pin pinboard_msg = None for msg in reversed(await channel.pins()): if self.is_pinboard(msg): # Found pinboard, return content and gist id id = msg.embeds[0].url.split("/")[-1] data = await gh.getitem(f"/gists/{id}") return (id, data["files"]["pinboard.md"]["content"]) # Create pinboard pin if it does not exist data = await gh.post( "/gists", data={ "files": { "pinboard.md": {"content": "Old pins are available here:\n\n"} }, "description": f"Pinboard for SwitchRoot #{channel.name}", "public": True, }, ) msg = await channel.send( embed=Embed( title="Pinboard", description="Old pins are moved to the pinboard to make space for \ new ones. Check it out!", url=data["html_url"], ) ) await msg.pin() return (data["id"], data["files"]["pinboard.md"]["content"]) async def add_pin_to_pinboard(self, channel, data): if config.github_oauth_token == "": # Don't add to gist pinboard if we don't have an oauth token return async with aiohttp.ClientSession() as session: gh = gidgethub.aiohttp.GitHubAPI( session, "RoboCop-NG", oauth_token=config.github_oauth_token ) (id, content) = await self.get_pinboard(gh, channel) content += "- " + data + "\n" await gh.patch( f"/gists/{id}", data={"files": {"pinboard.md": {"content": content}}} ) @commands.command() @commands.guild_only() @commands.check(check_if_collaborator) @commands.check(check_if_pin_channel) async def unpin(self, ctx, idx: int): """Unpins a pinned message.""" if idx <= 50: # Get message by pin idx target_msg = (await ctx.message.channel.pins())[idx] else: # Get message by ID target_msg = await ctx.message.channel.get_message(idx) if self.is_pinboard(target_msg): await ctx.send("Cannot unpin pinboard!") else: await target_msg.unpin() await target_msg.remove_reaction("📌", self.bot.user) await ctx.send(f"Unpinned {target_msg.jump_url}") # TODO: Remove from pinboard? # Use raw_reaction to allow pinning old messages. @Cog.listener() async def on_raw_reaction_add(self, payload): # Check that the user wants to pin this message if payload.emoji.name not in ["📌", "📍"]: return # Check that reaction pinning is allowd in this channel if payload.channel_id not in config.allowed_pin_channels: return target_guild = self.bot.get_guild(payload.guild_id) if target_guild is None: return # Check that the user is allowed to reaction-pin target_user = target_guild.get_member(payload.user_id) for role in config.staff_role_ids + config.allowed_pin_roles: if role in [role.id for role in target_user.roles]: target_chan = self.bot.get_channel(payload.channel_id) target_msg = await target_chan.get_message(payload.message_id) # Check that the message hasn't already been pinned for reaction in target_msg.reactions: if reaction.emoji == "📌": if reaction.me: return else: break # Add pin to pinboard, create one if none is found await self.add_pin_to_pinboard(target_chan, target_msg.jump_url) # Avoid staying "stuck" waiting for the pin message if message # was already manually pinned if not target_msg.pinned: # If we already have 50 pins, we should unpin the oldest. # We should avoid unpinning the pinboard. pins = await target_chan.pins() if len(pins) >= 50: for msg in reversed(pins): if not self.is_pinboard(msg): await msg.unpin() break # Wait for the automated "Pinned" message so we can delete it waitable = self.bot.wait_for("message", check=check) # Pin the message await target_msg.pin() # Delete the automated Pinned message msg = await waitable await msg.delete() # Add a Pin reaction so we remember that the message is pinned await target_msg.add_reaction("📌") def check(msg): return msg.type is MessageType.pins_add async def setup(bot): await bot.add_cog(Pin(bot))