import os, re, hashlib, sys, traceback import asyncio, aiohttp import nextcord class GatewayDiscord(nextcord.Client): def __init__(self, plexus, settings, gateway_name, gateway_data): nextcord.Client.__init__(self, loop = asyncio.get_event_loop(), intents = nextcord.Intents.all()) global PLEXUS PLEXUS = plexus self.settings = settings self.gateway_name = gateway_name self.discord_ready = False self.room_name_to_pair = {} self.pair_to_room_name = {} self.discord_id_to_xdm_id = {} self.xdm_id_to_discord_id = {} self.my_puppets = {} self.webhooks = {} self.active_users = {} self.new_user_queue = [] self.avatar_cache_url = {} self.avatar_cache = {} self.webhook_avatars = {} def new_user(identification, nick_name): if identification[0] == "discord" and identification[1] == self.gateway_name: # Ignore ours return self.new_user_queue.append(identification) if self.discord_ready: self.handle_new_user_queue() PLEXUS.sub("new_user", new_user) def message(msg_unique_id, room_name, nick_name, avatar, gateway_type, gateway_name, unique_id, body, attachments, was_edit): if gateway_type == "discord" and gateway_name == self.gateway_name: # Ignore ours return if len(attachments): body = str(body) + "\n\n" + "\n".join([str(a) for a in attachments]) guild, channel = self.room_name_to_pair[room_name] wh = self.webhooks[channel] if was_edit: if msg_unique_id not in self.xdm_id_to_discord_id: # Too late return asyncio.ensure_future(wh.edit_message(self.xdm_id_to_discord_id[msg_unique_id], content = str(body))) else: async def aw(): if avatar is None: self.webhook_avatars[wh.id] = None await wh.edit(avatar = None) else: current_avatar_hash = hashlib.sha256(avatar).hexdigest() if self.webhook_avatars[wh.id] != current_avatar_hash: self.webhook_avatars[wh.id] = current_avatar_hash await wh.edit(avatar = avatar) wh_msg = await wh.send(content = str(body), username = nick_name, wait = True, allowed_mentions = nextcord.AllowedMentions(everyone = False)) self.xdm_id_to_discord_id[msg_unique_id] = wh_msg.id self.discord_id_to_xdm_id[wh_msg.id] = msg_unique_id asyncio.ensure_future(aw()) PLEXUS.sub("message", message) PLEXUS.sub("ready", lambda: asyncio.ensure_future(self.start(gateway_data["token"]))) def handle_new_user_queue(self): for identification in self.new_user_queue: self.my_puppets[identification] = {} self.new_user_queue.clear() def ensure_user_is_active(self, member): if member.id not in self.active_users: self.active_users[member.id] = {} PLEXUS.pub("new_user", ("discord", self.gateway_name, member.id), member.display_name) async def cache_avatar(self, memb): avatar_url = memb.display_avatar.url avatar_url = avatar_url.split("?")[0] + "?size=128" if (memb.id not in self.avatar_cache_url) or (self.avatar_cache_url[memb.id] != avatar_url): self.avatar_cache_url[memb.id] = avatar_url async with aiohttp.ClientSession() as session: async with session.get(avatar_url) as resp: self.avatar_cache[memb.id] = await resp.read() async def on_message(self, message): if message.author.bot: # Ignore ours return if not isinstance(message.channel, nextcord.TextChannel): return guild_id = message.channel.guild.id channel_id = message.channel.id if (guild_id, channel_id) not in self.pair_to_room_name: return room_name = self.pair_to_room_name[(guild_id, channel_id)] self.ensure_user_is_active(message.author) msg_unique_id = os.urandom(16) self.xdm_id_to_discord_id[msg_unique_id] = message.id self.discord_id_to_xdm_id[message.id] = msg_unique_id await self.cache_avatar(message.author) PLEXUS.pub("message", msg_unique_id, room_name, message.author.display_name, self.avatar_cache[message.author.id], "discord", self.gateway_name, message.author.id, message.clean_content, [a.url for a in message.attachments], False) async def on_message_edit(self, before, after): if before.author.bot: # Ignore ours return if not isinstance(before.channel, nextcord.TextChannel): return guild_id = after.channel.guild.id channel_id = after.channel.id if (guild_id, channel_id) not in self.pair_to_room_name: return if before.clean_content == after.clean_content: return # Message content was edited if before.id not in self.discord_id_to_xdm_id: return await self.cache_avatar(after.author) msg_unique_id = self.discord_id_to_xdm_id[after.id] room_name = self.pair_to_room_name[(guild_id, channel_id)] PLEXUS.pub("message", msg_unique_id, room_name, after.author.display_name, self.avatar_cache[after.author.id], "discord", self.gateway_name, after.author.id, after.clean_content, [a.url for a in after.attachments], True) async def on_message_delete(self, message): if message.author.bot: # Ignore ours return if not isinstance(message.channel, nextcord.TextChannel): return guild_id = message.channel.guild.id channel_id = message.channel.id if (guild_id, channel_id) not in self.pair_to_room_name: return room_name = self.pair_to_room_name[(guild_id, channel_id)] if message.author.id not in self.active_users: return if message.id not in self.discord_id_to_xdm_id: return msg_unique_id = self.discord_id_to_xdm_id[message.id] PLEXUS.pub("message_delete", msg_unique_id, room_name, "discord", self.gateway_name, message.author.id) async def on_ready(self): for room_name, room_gateways in self.settings.ROOMS.items(): for gateway in room_gateways: if "discord" in gateway and gateway["discord"] == self.gateway_name: discord_channel = self.get_channel(gateway["channel"]) wh = None for webhook in await discord_channel.webhooks(): if webhook.name == "xdm": if not webhook.is_authenticated(): await webhook.delete(reason = "Must recreate") else: wh = webhook if not wh: wh = await discord_channel.create_webhook(name = "xdm", reason = "XDM bridge") self.webhooks[gateway["channel"]] = wh self.webhook_avatars[wh.id] = None self.room_name_to_pair[room_name] = (gateway["guild"], gateway["channel"]) self.pair_to_room_name[(gateway["guild"], gateway["channel"])] = room_name self.discord_ready = True self.handle_new_user_queue()