import discord from redbot.core.bot import Red from redbot.core import Config, bank, commands from redbot.core.i18n import Translator, cog_i18n from redbot.core.data_manager import cog_data_path from redbot.core.utils.chat_formatting import ( bold, box, humanize_timedelta, humanize_number, ) try: from redbot.cogs.audio.audio_dataclasses import Query except ImportError: Query = None from .statements import * from .utils import rgetattr from .listeners import Listeners import time import apsw import lavalink import contextlib from copy import copy from datetime import datetime from collections import Counter, defaultdict _ = Translator("MartTools", __file__) @cog_i18n(_) class MartTools(Listeners, commands.Cog): """Multiple tools that are originally used on Martine.""" __author__ = "Predä" __version__ = "1.7" def __init__(self, bot: Red): self.bot = bot self._connection = apsw.Connection(str(cog_data_path(self) / "MartTools.db")) self.cursor = self._connection.cursor() self.cursor.execute(PRAGMA_journal_mode) self.cursor.execute(PRAGMA_wal_autocheckpoint) self.cursor.execute(PRAGMA_read_uncommitted) self.cursor.execute(CREATE_TABLE_PERMA) self.cursor.execute(DROP_TEMP) self.cursor.execute(CREATE_TABLE_TEMP) self.uptime = datetime.utcnow() self.cursor.execute(INSERT_PERMA_DO_NOTHING, (-1000, "creation_time", time.time())) def upsert(self, id: int, event: str): self.cursor.execute(UPSERT_PERMA, (id, event)) self.cursor.execute(UPSERT_TEMP, (id, event)) def fetch(self, key, id=None) -> str: if id is None: query = SELECT_PERMA_GLOBAL condition = {"event": key} else: query = SELECT_PERMA_SINGLE condition = {"event": key, "guild_id": id} result = list(self.cursor.execute(query, condition)) return humanize_number(result[0][0] if result else 0) def get(self, key, id=None) -> str: if id is None: query = SELECT_TEMP_GLOBAL condition = {"event": key} else: query = SELECT_TEMP_SINGLE condition = {"event": key, "guild_id": id} result = list(self.cursor.execute(query, condition)) return humanize_number(result[0][0] if result else 0) def format_help_for_context(self, ctx: commands.Context) -> str: """Thanks Sinbad!""" pre_processed = super().format_help_for_context(ctx) return f"{pre_processed}\n\nAuthor: {self.__author__}\nCog Version: {self.__version__}" def cog_unload(self): self._connection.close() def get_bot_uptime(self): delta = datetime.utcnow() - self.uptime uptime = humanize_timedelta(timedelta=delta) return uptime @commands.command() @commands.guild_only() @commands.bot_has_permissions(embed_links=True) async def bankstats(self, ctx: commands.Context): """Show stats of the bank.""" icon = self.bot.user.avatar_url_as(static_format="png") user_bal = await bank.get_balance(ctx.author) credits_name = await bank.get_currency_name(ctx.guild) pos = await bank.get_leaderboard_position(ctx.author) bank_name = await bank.get_bank_name(ctx.guild) if hasattr(bank, "_config"): bank_config = bank._config else: bank_config = bank._conf if await bank.is_global(): all_accounts = len(await bank_config.all_users()) accounts = await bank_config.all_users() else: all_accounts = len(await bank_config.all_members(ctx.guild)) accounts = await bank_config.all_members(ctx.guild) member_account = await bank.get_account(ctx.author) created_at = str(member_account.created_at) no = "1970-01-01 00:00:00" overall = 0 for key, value in accounts.items(): overall += value["balance"] em = discord.Embed(color=await ctx.embed_colour()) em.set_author(name=_("{} stats:").format(bank_name), icon_url=icon) em.add_field( name=_("{} stats:").format("Global" if await bank.is_global() else "Bank"), value=_( "Total accounts: **{all_accounts}**\nTotal amount: **{overall} {credits_name}**" ).format( all_accounts=all_accounts, overall=humanize_number(overall), credits_name=credits_name, ), ) if pos is not None: percent = round((int(user_bal) / overall * 100), 3) em.add_field( name=_("Your stats:"), value=_( "You have **{bal} {currency}**.\n" "It's **{percent}%** of the {g}amount in the bank.\n" "You are **{pos}/{all_accounts}** in the {g}leaderboard." ).format( bal=humanize_number(user_bal), currency=credits_name, percent=percent, g="global " if await bank.is_global() else "", pos=humanize_number(pos), all_accounts=humanize_number(all_accounts), ), inline=False, ) if created_at != no: em.set_footer(text=_("Bank account created on: ") + str(created_at)) await ctx.send(embed=em) @commands.command(aliases=["usagec"]) async def usagecount(self, ctx: commands.Context): """ Show the usage count of the bot. Commands processed, messages received, and music on servers. """ uptime = str(self.get_bot_uptime()) commands_count = "`{}`".format(self.get("processed_commands")) errors_count = "`{}`".format(self.get("command_error")) messages_read = "`{}`".format(self.get("messages_read")) messages_sent = "`{}`".format(self.get("msg_sent")) try: total_num = "`{}/{}`".format( humanize_number(len(lavalink.active_players())), humanize_number(len(lavalink.all_players())), ) except AttributeError: # Remove at 3.2 total_num = "`{}/{}`".format( humanize_number(len([p for p in lavalink.players if p.current is not None])), humanize_number(len([p for p in lavalink.players])), ) tracks_played = "`{}`".format(self.get("tracks_played")) guild_join = "`{}`".format(self.get("guild_join")) guild_leave = "`{}`".format(self.get("guild_remove")) avatar = self.bot.user.avatar_url_as(static_format="png") msg = ( bold(_("Commands processed: ")) + _("{} commands.\n").format(commands_count) + bold(_("Commands errors: ")) + _("{} errors.\n").format(errors_count) + bold(_("Messages received: ")) + _("{} messages.\n").format(messages_read) + bold(_("Messages sent: ")) + _("{} messages.\n").format(messages_sent) + bold(_("Playing music on: ")) + _("{} servers.\n").format(total_num) + bold(_("Tracks played: ")) + _("{} tracks.\n\n").format(tracks_played) + bold(_("Servers joined: ")) + _("{} servers.\n").format(guild_join) + bold(_("Servers left: ")) + _("{} servers.").format(guild_leave) ) try: em = discord.Embed(color=await ctx.embed_colour()) em.add_field( name=_("Usage count of {} since last restart:").format(ctx.bot.user.name), value=msg, ) em.set_thumbnail(url=avatar) em.set_footer(text=_("Since {}").format(uptime)) await ctx.send(embed=em) except discord.Forbidden: await ctx.send( _("Usage count of {} since last restart:\n").format(ctx.bot.user.name) + msg + _("\n\nSince {}").format(uptime) ) @commands.bot_has_permissions(embed_links=True) @commands.command(aliases=["advusagec"]) async def advusagecount(self, ctx: commands.Context): """ Same as [p]usagecount command but with more stats. """ avatar = self.bot.user.avatar_url_as(static_format="png") query = SELECT_PERMA_SINGLE condition = {"event": "creation_time", "guild_id": -1000} result = list(self.cursor.execute(query, condition)) delta = datetime.utcnow() - datetime.utcfromtimestamp(result[0][0]) uptime = humanize_timedelta(timedelta=delta) try: total_num = "{}/{}".format( humanize_number(len(lavalink.active_players())), humanize_number(len(lavalink.all_players())), ) except AttributeError: # Remove at 3.2 total_num = "{}/{}".format( humanize_number(len([p for p in lavalink.players if p.current is not None])), humanize_number(len([p for p in lavalink.players])), ) em = discord.Embed( title=_("Usage count of {}:").format(ctx.bot.user.name), color=await ctx.embed_colour(), ) em.add_field( name=_("Message Stats"), value=box( _( "Messages Read : {messages_read}\n" "Messages Sent : {msg_sent}\n" "Messages Deleted : {messages_deleted}\n" "Messages Edited : {messages_edited}\n" "DMs Received : {dms_received}\n" ).format_map( { "messages_read": self.fetch("messages_read"), "msg_sent": self.fetch("msg_sent"), "messages_deleted": self.fetch("messages_deleted"), "messages_edited": self.fetch("messages_edited"), "dms_received": self.fetch("dms_received"), } ), lang="prolog", ), inline=False, ) em.add_field( name=_("Commands Stats"), value=box( _( "Commands Processed : {processed_commands}\n" "Errors Occured : {command_error}\n" "Sessions Resumed : {sessions_resumed}\n" ).format_map( { "processed_commands": self.fetch("processed_commands"), "command_error": self.fetch("command_error"), "sessions_resumed": self.fetch("sessions_resumed"), } ), lang="prolog", ), inline=False, ) em.add_field( name=_("Guild Stats"), value=box( _( "Guilds Joined : {guild_join}\n" "Guilds Left : {guild_remove}\n" ).format_map( { "guild_join": self.fetch("guild_join"), "guild_remove": self.fetch("guild_remove"), } ), lang="prolog", ), inline=False, ) em.add_field( name=_("User Stats"), value=box( _( "New Users : {new_members}\n" "Left Users : {members_left}\n" "Banned Users : {members_banned}\n" "Unbanned Users : {members_unbanned}\n" ).format_map( { "new_members": self.fetch("new_members"), "members_left": self.fetch("members_left"), "members_banned": self.fetch("members_banned"), "members_unbanned": self.fetch("members_unbanned"), } ), lang="prolog", ), inline=False, ) em.add_field( name=_("Role Stats"), value=box( _( "Roles Added : {roles_added}\n" "Roles Removed : {roles_removed}\n" "Roles Updated : {roles_updated}\n" ).format_map( { "roles_added": self.fetch("roles_added"), "roles_removed": self.fetch("roles_removed"), "roles_updated": self.fetch("roles_updated"), } ), lang="prolog", ), inline=False, ) em.add_field( name=_("Emoji Stats"), value=box( _( "Reacts Added : {reactions_added}\n" "Reacts Removed : {reactions_removed}\n" "Emoji Added : {emojis_added}\n" "Emoji Removed : {emojis_removed}\n" "Emoji Updated : {emojis_updated}\n" ).format_map( { "reactions_added": self.fetch("reactions_added"), "reactions_removed": self.fetch("reactions_removed"), "emojis_added": self.fetch("emojis_added"), "emojis_removed": self.fetch("emojis_removed"), "emojis_updated": self.fetch("emojis_updated"), } ), lang="prolog", ), inline=False, ) em.add_field( name=_("Audio Stats"), value=box( _( "Users Who Joined VC : {users_joined_bot_music_room}\n" "Tracks Played : {tracks_played}\n" "Number Of Players : {total_num}" ).format( users_joined_bot_music_room=self.fetch("users_joined_bot_music_room"), tracks_played=self.fetch("tracks_played"), total_num=total_num, ), lang="prolog", ), inline=False, ) if Query: em.add_field( name=_("Track Stats"), value=box( _( "Streams : {streams_played}\n" "YouTube Streams : {yt_streams_played}\n" "Mixer Streams : {mixer_streams_played}\n" "Twitch Streams : {ttv_streams_played}\n" "Other Streams : {streams_played}\n" "YouTube Tracks : {youtube_tracks}\n" "Soundcloud Tracks : {soundcloud_tracks}\n" "Bandcamp Tracks : {bandcamp_tracks}\n" "Vimeo Tracks : {vimeo_tracks}\n" "Mixer Tracks : {mixer_tracks}\n" "Twitch Tracks : {twitch_tracks}\n" "Other Tracks : {other_tracks}\n" ).format( streams_played=self.fetch("streams_played"), yt_streams_played=self.fetch("yt_streams_played"), mixer_streams_played=self.fetch("mixer_streams_played"), ttv_streams_played=self.fetch("ttv_streams_played"), other_streams_played=self.fetch("other_streams_played"), youtube_tracks=self.fetch("youtube_tracks"), soundcloud_tracks=self.fetch("soundcloud_tracks"), bandcamp_tracks=self.fetch("bandcamp_tracks"), vimeo_tracks=self.fetch("vimeo_tracks"), mixer_tracks=self.fetch("mixer_tracks"), twitch_tracks=self.fetch("twitch_tracks"), other_tracks=self.fetch("other_tracks"), ), lang="prolog", ), inline=False, ) em.set_thumbnail(url=avatar) em.set_footer(text=_("Since {}").format(uptime)) await ctx.send(embed=em) @commands.command(aliases=["prefixes"]) async def prefix(self, ctx: commands.Context): """Show all prefixes of the bot""" default_prefixes = await self.bot._config.prefix() try: guild_prefixes = await self.bot._config.guild(ctx.guild).prefix() except AttributeError: guild_prefixes = False bot_name = ctx.bot.user.name avatar = self.bot.user.avatar_url_as(static_format="png") if not guild_prefixes: to_send = [f"`\u200b{p}\u200b`" for p in default_prefixes] plural = _("Prefixes") if len(default_prefixes) >= 2 else _("Prefix") try: em = discord.Embed( color=await ctx.embed_colour(), title=_("{} of {}:").format(plural, bot_name), description=" ".join(to_send), ) em.set_thumbnail(url=avatar) await ctx.send(embed=em) except discord.Forbidden: await ctx.send(bold(_("{} of {}:\n")).format(plural, bot_name) + " ".join(to_send)) else: to_send = [f"`\u200b{p}\u200b`" for p in guild_prefixes] plural = _("prefixes") if len(default_prefixes) >= 2 else _("prefix") try: em = discord.Embed( color=await ctx.embed_colour(), title=_("Server {} of {}:").format(plural, bot_name), description=" ".join(to_send), ) em.set_thumbnail(url=avatar) await ctx.send(embed=em) except discord.Forbidden: await ctx.send( bold(_("Server {} of {name}:\n")).format(plural, bot_name) + " ".join(to_send) ) @commands.command(aliases=["serverc", "serversc"]) async def servercount(self, ctx: commands.Context): """Send servers stats of the bot.""" msg = _( "{name} is running on `{shard_count}` {shards}.\n" "Serving `{servs}` servers (`{channels}` channels).\n" "For a total of `{users}` users (`{unique}` unique).\n" "(`{users}` visible now, `{real_total}` total)" ).format( name=ctx.bot.user.name, shard_count=humanize_number(self.bot.shard_count), shards=_("shards") if self.bot.shard_count > 1 else _("shard"), servs=humanize_number(len(self.bot.guilds)), channels=humanize_number(sum(len(s.channels) for s in self.bot.guilds)), users=humanize_number(sum(len(s.members) for s in self.bot.guilds)), unique=humanize_number(len(self.bot.users)), real_total=humanize_number(sum(s.member_count for s in self.bot.guilds)), ) try: em = discord.Embed(color=await ctx.embed_colour(), description=msg) await ctx.send(embed=em) except discord.Forbidden: await ctx.send(msg) @commands.command(aliases=["servreg"]) async def serversregions(self, ctx: commands.Context, sort: str = "guilds"): """ Show total of regions where the bot is. You can also sort by number of users by using `[p]serversregions users` By default it sort by guilds. """ regions_dict = { "vip-us-east": ":flag_us:" + _(" __VIP__ US East"), "vip-us-west": ":flag_us:" + _(" __VIP__ US West"), "vip-amsterdam": ":flag_nl:" + _(" __VIP__ Amsterdam"), "eu-west": ":flag_eu:" + _(" EU West"), "eu-central": ":flag_eu:" + _(" EU Central"), "europe": ":flag_eu:" + _(" Europe"), "london": ":flag_gb:" + _(" London"), "frankfurt": ":flag_de:" + _(" Frankfurt"), "amsterdam": ":flag_nl:" + _(" Amsterdam"), "us-west": ":flag_us:" + _(" US West"), "us-east": ":flag_us:" + _(" US East"), "us-south": ":flag_us:" + _(" US South"), "us-central": ":flag_us:" + _(" US Central"), "singapore": ":flag_sg:" + _(" Singapore"), "sydney": ":flag_au:" + _(" Sydney"), "brazil": ":flag_br:" + _(" Brazil"), "hongkong": ":flag_hk:" + _(" Hong Kong"), "russia": ":flag_ru:" + _(" Russia"), "japan": ":flag_jp:" + _(" Japan"), "southafrica": ":flag_za:" + _(" South Africa"), "india": ":flag_in:" + _(" India"), "dubai": ":flag_ae:" + _(" Dubai"), "south-korea": ":flag_kr:" + _(" South Korea"), } regions = {} for guild in self.bot.guilds: region = str(guild.region) if region not in regions: regions[region] = {"guilds": 0, "users": 0} regions[region]["users"] += guild.member_count regions[region]["guilds"] += 1 def sort_keys(key: str): keys = ( (key[1]["guilds"], key[1]["users"]) if sort != "users" else (key[1]["users"], key[1]["guilds"]) ) return keys regions_stats = dict(sorted(regions.items(), key=lambda x: sort_keys(x), reverse=True)) msg = [ _("{flag}: {guilds_len} and {users_len}").format( flag=regions_dict[region_name], guilds_len=( f"`{values['guilds']:,} {_('server') if values['guilds'] < 2 else _('servers')}`" ), users_len=( f"`{values['users']:,} {_('user') if values['users'] < 2 else _('users')}`" ), ) for region_name, values in regions_stats.items() ] guilds_word = _("server") if len(self.bot.guilds) < 2 else _("servers") users_word = ( _("user") if sum(k["users"] for k in regions_stats.values()) < 2 else _("users") ) footer = _("For a total of {guilds} and {users}").format( guilds=f"{len(self.bot.guilds):,} {guilds_word}", users=f"{sum(k['users'] for k in regions_stats.values()):,} {users_word}", ) try: em = discord.Embed( color=await ctx.embed_colour(), title=_("Servers regions stats:"), description="\n".join(msg), ) em.set_footer(text=footer) await ctx.send(embed=em) except discord.Forbidden: msg = bold(_("Servers regions stats:\n\n")) + "\n".join(msg) + "\n" + bold(footer) await ctx.send(msg)