import asyncio import datetime import json import logging from importlib import reload as importlib_reload from uuid import uuid4 import discord import discord.utils as utils from async_timeout import timeout from discord.ext import commands from utils.eval import evaluate as _evaluate log = logging.getLogger(__name__) class Communication(commands.Cog): def __init__(self, bot): self.bot = bot self.ipc_channel = self.bot.ipc_channel self.router = None bot.loop.create_task(self.register_sub()) self._messages = dict() async def register_sub(self): if not bytes(self.ipc_channel, "utf-8") in self.bot.redis.pubsub_channels: await self.bot.redis.execute_pubsub("SUBSCRIBE", self.ipc_channel) self.router = self.bot.loop.create_task(self.event_handler()) async def unregister_sub(self): if self.router and not self.router.cancelled: self.router.cancel() await self.bot.redis.execute_pubsub("UNSUBSCRIBE", self.ipc_channel) async def run_action(self, payload, args): try: if args is True: await getattr(self, payload["action"])(**json.loads(payload["args"]), command_id=payload["command_id"]) else: await getattr(self, payload["action"])(command_id=payload["command_id"]) except Exception as e: new_payload = { "error": True, "output": f"```{type(e).__name__} - {e}```", "command_id": payload["command_id"], } log.info(json.dumps(new_payload)) await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(new_payload)) async def event_handler(self): channel = self.bot.redis.pubsub_channels[bytes(self.ipc_channel, "utf-8")] while await channel.wait_message(): payload = await channel.get_json(encoding="utf-8") if payload.get("action"): if payload.get("scope") != "bot": continue if payload.get("args"): self.bot.loop.create_task(self.run_action(payload, True)) else: self.bot.loop.create_task(self.run_action(payload, False)) if payload.get("output") and payload["command_id"] in self._messages: self._messages[payload["command_id"]].append(payload["output"]) def to_dict(self, cls, ignore=None, attrs=None): if ignore is None: ignore = [] if attrs is None: attrs = [] result = {} attrs.extend(cls.__slots__) for attr in attrs: if attr in ignore or attr.startswith("_"): continue result[attr] = getattr(cls, attr) if isinstance(result[attr], datetime.datetime): result[attr] = result[attr].timestamp() elif isinstance(result[attr], discord.enums.Enum): result[attr] = result[attr].__str__ elif hasattr(result[attr], "__slots__"): if not [slot for slot in getattr(result[attr], "__slots__") if not slot.startswith("_")]: result[attr] = str(result[attr]) else: result[attr] = self.to_dict(result[attr], ignore) elif isinstance(result[attr], list) or isinstance(result[attr], tuple): new_list = [] for element in result[attr]: if hasattr(element, "__slots__"): new_list.append(self.to_dict(element, ignore)) else: new_list.append(element) result[attr] = new_list return result async def guild_count(self, command_id): payload = {"output": len(self.bot.guilds), "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def channel_count(self, command_id): payload = { "output": sum([len(guild.channels) for guild in self.bot.guilds]), "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def user_count(self, command_id): payload = {"output": len(self.bot.users), "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_user(self, user_id, command_id): if not self.bot.get_user(user_id): return payload = { "output": self.to_dict(self.bot.get_user(user_id), ["user"]), "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_user_premium(self, user_id, command_id): guild = self.bot.get_guild(self.bot.config.main_server) if not guild: return member = guild.get_member(user_id) if not member: return if user_id in self.bot.config.admins or user_id in self.bot.config.owners: amount = 1000 elif utils.get(member.roles, id=self.bot.config.premium5): amount = 5 elif utils.get(member.roles, id=self.bot.config.premium3): amount = 3 elif utils.get(member.roles, id=self.bot.config.premium1): amount = 1 else: amount = 0 payload = {"output": amount, "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_user_guilds(self, user_id, command_id): if not self.bot.get_user(user_id): return payload = { "output": [ self.to_dict(guild, ["guild"], ["text_channels", "icon_url", "default_role"]) for guild in self.bot.guilds if guild.get_member(user_id) is not None ], "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_shards(self, command_id): payload = {"output": {self.bot.cluster: [self.bot.shard_ids, self.bot.latency]}, "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_guild(self, guild_id, command_id): guild = self.bot.get_guild(guild_id) if not guild: return payload = { "output": self.to_dict(guild, ["guild"], ["text_channels", "icon_url", "default_role"]), "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_guild_member(self, guild_id, member_id, command_id): guild = self.bot.get_guild(guild_id) if not guild: return member = guild.get_member(member_id) if not member: return payload = {"output": self.to_dict(member, ["guild", "member"]), "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_guild_channel(self, guild_id, channel_id, command_id): guild = self.bot.get_guild(guild_id) if not guild: return channel = guild.get_channel(channel_id) if not channel: return payload = {"output": self.to_dict(channel, ["channel", "guild"]), "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def get_top_guilds(self, command_id): guilds = sorted(self.bot.guilds, key=lambda x: x.member_count, reverse=True)[:15] payload = { "output": [self.to_dict(guild, ["guild"], ["member_count"]) for guild in guilds], "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def find_guild(self, name, command_id): guilds = [] for guild in self.bot.guilds: if guild.name.lower().count(name.lower()) > 0: guilds.append(f"{guild.name} `{guild.id}`") payload = {"output": guilds, "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def invite_guild(self, guild_id, command_id): guild = self.bot.get_guild(guild_id) if not guild: return try: invite = (await guild.invites())[0] except (IndexError, discord.Forbidden): try: invite = await guild.text_channels[0].create_invite(max_age=120) except discord.Forbidden: return payload = {"output": self.to_dict(invite, ["invite", "guild"]), "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def leave_guild(self, guild_id, command_id): guild = self.bot.get_guild(guild_id) if not guild: return await guild.leave() payload = { "output": "Success", "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def evaluate(self, code, command_id): payload = {"output": await _evaluate(self.bot, code), "command_id": command_id} await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def unload_extension(self, cog, command_id): self.bot.unload_extension("cogs." + cog) payload = { "output": "Success", "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def load_extension(self, cog, command_id): self.bot.load_extension("cogs." + cog) payload = { "output": "Success", "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def reload_import(self, lib, command_id): importlib_reload(getattr(self.bot, lib)) payload = { "output": "Success", "command_id": command_id, } await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) async def handler(self, action, expected_count, args=None, _timeout=1, scope="bot", cluster=None): command_id = f"{uuid4()}" self._messages[command_id] = [] payload = {"scope": scope, "action": action, "command_id": command_id} if cluster: payload["id"] = cluster if args: payload["args"] = json.dumps(args) await self.bot.redis.execute("PUBLISH", self.ipc_channel, json.dumps(payload)) try: async with timeout(_timeout): while len(self._messages[command_id]) < expected_count: await asyncio.sleep(0.05) except asyncio.TimeoutError: pass return self._messages.pop(command_id, None) def setup(bot): bot.add_cog(Communication(bot))