async def change_log_level(self, ctx: utils.TypedContext, *, level: str): with open("config.ini", "w") as config_file: self.bot.config["Main"]["log_level"] = level self.bot.config.write(config_file) if self.bot.websocket is None: return self.bot.logger.setLevel(level.upper()) try: wsjson = utils.data_to_ws_json("SEND", target="*", **{ "c": "change_log_level", "a": { "level": level.upper() }, "t": "*" }) await self.bot.websocket.send(wsjson) await self.bot.wait_for("change_log_level", timeout=10) except asyncio.TimeoutError: await ctx.send(f"Didn't recieve broadcast within 10 seconds!") else: level = logging.getLevelName(self.bot.logger.level) await ctx.send(f"Broadcast complete, log level is now: {level}")
async def check_cluster(self, cluster_id: int, pid: int): process = psutil.Process(pid) websocket = self.websockets.get(cluster_id) if websocket is None: logger.error( f"Cluster ID {cluster_id} doesn't have a websocket! Killing and restarting." ) return process.kill() try: coro = self._get_from_clusters(["ping"], target=cluster_id) await asyncio.wait_for(coro, timeout=10) except (asyncio.TimeoutError, websockets.WebSocketException): logger.warning( f"Cluster {cluster_id} didn't respond to ping in 10 seconds, restarting safely!" ) ws_json = utils.data_to_ws_json(command="RESTART", target=cluster_id) try: await websocket.send(ws_json) await asyncio.sleep(15) except websockets.WebSocketException: pass if not process.is_running(): # Cluster has been killed and hopefully restarted by watcher. return logger.warning( f"{cluster_id} wasn't restarted by websocket message, killing!" ) return process.kill()
async def on_guild_join(self, guild: utils.TypedGuild): _, prefix, owner = await asyncio.gather( self.bot.channels["servers"].send( f"Just joined {guild}! I am now in {len(self.bot.guilds)} different servers!" ), self.bot.settings.get(guild, ["prefix"]), guild.fetch_member(guild.owner_id)) embed = discord.Embed( title=f"Welcome to {self.bot.user.name}!", description=WELCOME_MESSAGE.format(guild=guild, prefix=prefix[0]) ).set_footer( text= "Support Server: https://discord.gg/zWPWwQC | Bot Invite: https://bit.ly/TTSBot" ).set_author(name=str(owner), icon_url=owner.avatar.url) try: await owner.send(embed=embed) except discord.errors.HTTPException: pass if self.bot.websocket is None or self.bot.get_support_server( ) is not None: return await self.on_ofs_add(owner.id) json = {"c": "ofs_add", "a": {"owner_id": owner.id}} wsjson = utils.data_to_ws_json("SEND", target="support", **json) await self.bot.websocket.send(wsjson)
async def request_handler(self, connection: _WSSP, request: WSRequestJSON): nonce = request["a"]["nonce"] to_get = request["a"]["info"] target = request.get("t", "*") responses = await self._get_from_clusters(to_get, nonce, target=target) response_json = utils.data_to_ws_json(command="RESPONSE", target=nonce, responses=responses) await connection.send(response_json)
async def on_request(self, info: list[str], nonce: str, args: dict[str, dict[str, Any]] = {}, *_): returns: dict[str, utils.JSON_IN] = {} for key in info: func, kwargs = data_lookup[key], args.get(key, {}) returns[key] = await maybe_coroutine(func, self.bot, **kwargs) wsjson = utils.data_to_ws_json("RESPONSE", target=nonce, **returns) await self.bot.websocket.send(wsjson)
async def cluster(self, ctx: utils.TypedContext, cluster_id: int): if self.bot.websocket is None: return await ctx.send("Manager websocket is None!") if cluster_id == self.bot.cluster_id: await self.bot.close(utils.RESTART_CLUSTER) else: wsjson = utils.data_to_ws_json("SEND", target=cluster_id, **{ "c": "restart", "a": {}, }) await self.bot.websocket.send(wsjson) await ctx.send(f"Told cluster {cluster_id} to restart.")
async def __aexit__(self, etype, error, tb): self.handler._cache.pop(self.cache_id, None) self.handler._do_not_cache.remove(self.cache_id) if error is not None or self.websocket is None or not self.broadcast: return await self.websocket.send( data_to_ws_json("SEND", target="*", **{ "c": "invalidate_cache", "a": { "id": self.cache_id }, }))
async def reload_cog(self, ctx: utils.TypedContext, *, to_reload: str): try: self.bot.reload_extension(to_reload) except Exception as e: await ctx.send(f"**`ERROR:`** {type(e).__name__} - {e}") else: await ctx.send("**`SUCCESS`**") if self.bot.websocket is None: return await self.bot.websocket.send( utils.data_to_ws_json( "SEND", target="*", **{ "c": "reload", "a": {"cog": to_reload}, }) )
async def _insert_write(self, raw_id: _DK): task = self._write_tasks.pop(raw_id) try: query = self._get_query(raw_id, task) await self.pool.execute(*query) except Exception as error: task.waiter.set_exception(error) else: task.waiter.set_result(None) if self.bot.websocket is not None and self.broadcast: await self.bot.websocket.send( utils.data_to_ws_json("SEND", target="*", **{ "c": "invalidate_cache", "a": { "identifer": raw_id }, }))
async def on_request(self, info: List[str], nonce: str, *args): data = {to_get: data_lookup[to_get](self.bot) for to_get in info} wsjson = utils.data_to_ws_json("RESPONSE", target=nonce, **data) await self.bot.websocket.send(wsjson) # type: ignore
async def botstats(self, ctx: utils.TypedContext): "Shows various different stats" await ctx.trigger_typing() start_time = time.perf_counter() if self.bot.websocket is None: guilds = [ guild for guild in self.bot.guilds if not guild.unavailable ] total_members = sum(guild.member_count for guild in guilds) total_voice_clients = len(self.bot.voice_clients) total_guild_count = len(guilds) raw_ram_usage = Process(getpid()).memory_info().rss else: ws_uuid = uuid.uuid4() to_fetch = ["guild_count", "member_count", "voice_count"] wsjson = utils.data_to_ws_json("REQUEST", target="*", info=to_fetch, nonce=ws_uuid) await self.bot.websocket.send(wsjson) try: check = lambda _, nonce: uuid.UUID(nonce) == ws_uuid responses, _ = await self.bot.wait_for( timeout=10, check=check, event="response", ) except asyncio.TimeoutError: cog = cast(ErrorEvents, self.bot.get_cog("ErrorEvents")) self.bot.logger.error("Timed out fetching botstats!") error_msg = "the bot timed out fetching this info" return await cog.send_error(ctx, error_msg) raw_ram_usage = await utils.to_thread( partial(get_ram_recursive, Process(getpid()).parent())) total_voice_clients = sum(resp["voice_count"] for resp in responses) total_guild_count = sum(resp["guild_count"] for resp in responses) total_members = sum(resp["member_count"] for resp in responses) time_to_fetch = (time.perf_counter() - start_time) * 1000 footer = cleandoc(f""" Time to fetch: {time_to_fetch:.2f}ms Support Server: https://discord.gg/zWPWwQC Repository: https://github.com/Gnome-py/Discord-TTS-Bot """) sep1, sep2, *_ = utils.OPTION_SEPERATORS embed = discord.Embed(title=f"{self.bot.user.name}: Now open source!", url="https://discord.gg/zWPWwQC", colour=utils.NETURAL_COLOUR, description=cleandoc(f""" Currently in: {sep2} {total_voice_clients} voice channels {sep2} {total_guild_count} servers Currently using: {sep1} {self.bot.shard_count} shards {sep1} {raw_ram_usage / 1024 ** 2:.1f}MB of RAM and can be used by {total_members:,} people! """)).set_footer(text=footer).set_thumbnail( url=self.bot.user.avatar.url) await ctx.send(embed=embed)
async def send_unhandled_msg(self, event: str, traceback: str, extra_fields: list[tuple[Any, Any, bool]], author_name: Optional[str] = None, icon_url: Optional[str] = None, ): error_webhook = self.bot.channels["errors"] row = await self.bot.pool.fetchrow(""" UPDATE errors SET occurrences = occurrences + 1 WHERE traceback = $1 RETURNING * """, traceback) if row is not None: err_msg = await error_webhook.fetch_message(row["message_id"]) err_msg.embeds[0].set_footer(text=f"This error has occurred {row['occurrences']} times!") return await err_msg.edit(embeds=err_msg.embeds) fields = [ ("Event", event, True), ("Bot User", self.bot.user, True), BLANK, *extra_fields, ("Ram Usage", f"{psutil.virtual_memory().percent}%", True), ("Running Task Count", len(asyncio.all_tasks()), True), ("Local Thread Count", psutil.Process().num_threads(), True), ] if self.bot.cluster_id is not None: fields.extend(( ("Cluster ID", self.bot.cluster_id, True), ("Handling Shards", self.bot.shard_ids, True) )) error_msg = discord.Embed(title=traceback.split("\n")[-2][:256], colour=utils.RED) if author_name is not None: if icon_url is None: error_msg.set_author(name=author_name) else: error_msg.set_author(name=author_name, icon_url=icon_url) for name, value, inline in fields: value = value if value == "\u200B" else f"`{value}`" error_msg.add_field(name=name, value=value, inline=inline) view = utils.ShowTracebackView(f"```\n{traceback}```") err_msg = await error_webhook.send(embed=error_msg, view=view, wait=True) message_id = await self.bot.pool.fetchval(""" INSERT INTO errors(traceback, message_id) VALUES($1, $2) ON CONFLICT (traceback) DO UPDATE SET occurrences = errors.occurrences + 1 RETURNING errors.message_id """, traceback, err_msg.id) assert message_id is not None if message_id != err_msg.id: return await err_msg.delete() if self.bot.websocket is not None: ws_json = utils.data_to_ws_json("SEND", target="support", **{ "c": "load_view", "a": { "traceback": traceback, "message_id": message_id } }) await self.bot.websocket.send(ws_json)