diff --git a/src/modules/__init__.py b/src/modules/__init__.py index 561c26e3..eb5b2eb3 100644 --- a/src/modules/__init__.py +++ b/src/modules/__init__.py @@ -19,6 +19,7 @@ active = [ '.video_channels', '.meta', '.blanket', + '.voicefix', '.test', ] diff --git a/src/modules/voicefix/__init__.py b/src/modules/voicefix/__init__.py new file mode 100644 index 00000000..2859cf84 --- /dev/null +++ b/src/modules/voicefix/__init__.py @@ -0,0 +1,7 @@ +import logging + +logger = logging.getLogger(__name__) + +async def setup(bot): + from .cog import VoiceFixCog + await bot.add_cog(VoiceFixCog(bot)) diff --git a/src/modules/voicefix/cog.py b/src/modules/voicefix/cog.py new file mode 100644 index 00000000..cf291179 --- /dev/null +++ b/src/modules/voicefix/cog.py @@ -0,0 +1,165 @@ +from typing import Optional +import asyncio +from cachetools import FIFOCache + +import discord +from discord.ext import commands as cmds +from discord import app_commands as appcmds + +from meta import LionBot, LionCog, LionContext +from . import logger + + +async def prepare_attachments(attachments: list[discord.Attachment]): + results = [] + for attach in attachments: + try: + as_file = await attach.to_file(spoiler=attach.is_spoiler()) + results.append(as_file) + except discord.HTTPException: + pass + return results + + +async def prepare_embeds(message: discord.Message): + embeds = [embed for embed in message.embeds if embed.type == 'rich'] + if message.reference: + embed = discord.Embed( + colour=discord.Colour.dark_gray(), + description=f"Reply to {message.reference.jump_url}" + ) + embeds.append(embed) + return embeds + + + +class VoiceFixCog(LionCog): + def __init__(self, bot: LionBot): + self.bot = bot + + # Map of linkids to list of channelids + self.link_channels = {} + + # Map of channelids to linkids + self.channel_links = {} + + # Map of channelids to initialised discord.Webhook + self.hooks = {} + + # Map of messageid to list of (channelid, webhookmsg) pairs, for updates + self.message_cache = FIFOCache(maxsize=200) + # webhook msgid -> orig msgid + self.wmessages = FIFOCache(maxsize=600) + + self.lock = asyncio.Lock() + + + async def cog_load(self): + # TODO: Just manually filling here, but move to data eventually + self.link_channels = { + 1: ( + 1122589406809821184, 1101422204471750728 + ) + } + self.channel_links = { + 1122589406809821184: (1,), + 1101422204471750728: (1,) + } + self.hooks[1101422204471750728] = discord.Webhook.from_url( + "https://discord.com/api/webhooks/1154751966153560105/DPIIo0IyE5-jfIb6vjeHYBXqME9wwYEMSXLJyotu2T9kn84ZczkZgypzb4misErOiE9l", + client=self.bot + ) + self.hooks[1122589406809821184] = discord.Webhook.from_url( + "https://discord.com/api/webhooks/1154752336628039680/mVVYsY5D3NRP-n3RAk-4Nvv7jrQ6F2y1AFVXZ5X9IFAHlk4jd23onfgsYaknBB7HZQKw", + client=self.bot + ) + + @LionCog.listener('on_message') + async def on_message(self, message: discord.Message): + # Don't need this because everything except explicit messages are webhooks now + # if self.bot.user and (message.author.id == self.bot.user.id): + # return + if message.webhook_id: + return + + async with self.lock: + sent = [] + linkids = self.channel_links.get(message.channel.id, ()) + if linkids: + if message.attachments: + files = await prepare_attachments(message.attachments) + else: + files = [] + + for linkid in linkids: + for channelid in self.link_channels[linkid]: + if channelid != message.channel.id: + hook = self.hooks[channelid] + avatar = message.author.avatar or message.author.default_avatar + msg = await hook.send( + content=message.content, + wait=True, + username=message.author.display_name, + avatar_url=avatar.url, + embeds=await prepare_embeds(message), + files=files, + allowed_mentions=discord.AllowedMentions.none() + ) + sent.append((channelid, msg)) + self.wmessages[msg.id] = message.id + if sent: + # For easier lookup + self.wmessages[message.id] = message.id + sent.append((message.channel.id, message)) + + self.message_cache[message.id] = sent + logger.info(f"Forwarded message {message.id}") + + + @LionCog.listener('on_message_edit') + async def on_message_edit(self, before, after): + async with self.lock: + cached_sent = self.message_cache.pop(before.id, ()) + new_sent = [] + for cid, msg in cached_sent: + try: + if msg.id != before.id: + msg = await msg.edit( + content=after.content, + embeds=await prepare_embeds(after), + ) + new_sent.append((cid, msg)) + except discord.NotFound: + pass + if new_sent: + self.message_cache[after.id] = new_sent + + @LionCog.listener('on_message_delete') + async def on_message_delete(self, message): + async with self.lock: + origid = self.wmessages.get(message.id, None) + if origid: + cached_sent = self.message_cache.pop(origid, ()) + for _, msg in cached_sent: + try: + if msg.id != message.id: + await msg.delete() + except discord.NotFound: + pass + + @LionCog.listener('on_reaction_add') + async def on_reaction_add(self, reaction: discord.Reaction, user: discord.User): + async with self.lock: + message = reaction.message + emoji = reaction.emoji + origid = self.wmessages.get(message.id, None) + if origid and reaction.count == 1: + cached_sent = self.message_cache.get(origid, ()) + for _, msg in cached_sent: + # TODO: Would be better to have a Message and check the reactions + try: + if msg.id != message.id: + await msg.add_reaction(emoji) + except discord.HTTPException: + pass +