async def rank(self, ctx, user: discord.Member = None): """rank_help""" if user == None: user = ctx.message.author config = Object(self.db.configs.get(ctx.guild.id, "lvl_sys")) if config.enabled == False: return await ctx.send( self.locale.t(ctx.guild, "lvl_sys_disabled", _emote="NO", prefix=self.get_prefix(ctx.guild))) if not self.exists(config, ctx.guild, user, insert=False): return await ctx.send( self.locale.t(ctx.guild, "not_ranked", _emote="NO")) data = self.get_user_data(ctx.guild, user) for_nxt_lvl = 3 * ((data.lvl - 1)**2) + 30 for_last_lvl = 3 * ((data.lvl - 2)**2) + 30 e = Embed( title=f"{user.name}#{user.discriminator}", description="**Level:** {} \n**Progress:** {} / {} \n**Total XP:** {}"\ .format( data.lvl, ((for_nxt_lvl - for_last_lvl) - (for_nxt_lvl - data.xp)) if data.lvl > 1 else data.xp, (for_nxt_lvl - for_last_lvl) if data.lvl > 1 else 30, data.xp ) ) e.set_thumbnail(url=user.display_avatar) await ctx.send(embed=e)
async def delete_msg(self, rule: str, found: str, msg: discord.Message, warns: int, reason: str, pattern_or_filter: Union[str, None] = None) -> None: try: await msg.delete() except (discord.NotFound, discord.Forbidden): pass else: self.bot.ignore_for_events.append(msg.id) finally: if warns > 0: await self.action_processor.execute(msg, msg.guild.me, msg.author, warns, reason) else: data = Object(LOG_DATA[rule]) self.dm_processor.execute( msg, "automod_rule_triggered", msg.author, **{ "guild_name": msg.guild.name, "rule": data.rule, "_emote": "SWORDS" }) if rule not in ["filter", "regex"]: await self.log_processor.execute( msg.guild, "automod_rule_triggered", **{ "rule": data.rule, "found": found, "user_id": msg.author.id, "user": msg.author, "case": self.action_processor.new_case( "automod", msg, msg.guild.me, msg.author, reason) }) else: await self.log_processor.execute( msg.guild, f"{rule}_triggered", **{ "pattern": f"{pattern_or_filter}", "found": found, "user_id": msg.author.id, "user": msg.author, "case": self.action_processor.new_case( rule, msg, msg.guild.me, msg.author, reason) })
async def execute(self, guild: discord.Guild, log_type: str, **log_kwargs) -> None: config = Object(LOG_TYPES[log_type]) log_channel_id = self.db.configs.get(guild.id, config.channel) if log_channel_id == None or log_channel_id == "": return log_channel = guild.get_channel(int(log_channel_id)) if log_channel == None: return if log_kwargs.get("_embed") == None: log_embed = Embed(color=config.color) log_embed.description = "{} **{}{}:** {} ({}) \n\n{}".format( self.bot.emotes.get(config.emote), f"#{log_kwargs.get('case')} " if "case" in log_kwargs else "", config.action, f"<@{log_kwargs.get('user_id')}>", log_kwargs.get("user_id"), self.bot.locale.t(guild, config.key, _emote=config.emote, **log_kwargs)) else: log_embed = log_kwargs.get("_embed") try: wid = self.bot.db.configs.get(guild.id, f"{config.channel}_webhook") if wid != "": webhook = await self.get_webhook(guild, int(wid), config.channel) if webhook == None: log_message = await log_channel.send( content=log_kwargs.get("content", None), embed=log_embed) else: try: log_message = await webhook.send( content=log_kwargs.get("content", None), embed=log_embed, wait=True) except Exception: log_message = await log_channel.send( content=log_kwargs.get("content", None), embed=log_embed) else: log_message = await log_channel.send(content=log_kwargs.get( "content", None), embed=log_embed) except Exception: pass else: if "case" in log_kwargs: self.db.cases.multi_update( f"{guild.id}-{log_kwargs.get('case')}", { "log_id": f"{log_message.id}", "jump_url": f"{log_message.jump_url}" })
async def leaderboard(self, ctx): """leaderboard_help""" config = Object(self.db.configs.get(ctx.guild.id, "lvl_sys")) if config.enabled == False: return await ctx.send( self.locale.t(ctx.guild, "lvl_sys_disabled", _emote="NO", prefix=self.get_prefix(ctx.guild))) if len(config.users) < 1: return await ctx.send( self.locale.t(ctx.guild, "no_one_ranked", _emote="NO")) users = [ Object(x) for x in self.db.level.find({"guild": f"{ctx.guild.id}"}) ] data = sorted(users, key=lambda e: e.xp, reverse=True) e = Embed(title="Leaderboard") e.set_thumbnail(url=ctx.guild.icon.url) for i, entry in enumerate(data): user = ctx.guild.get_member(int(entry.id.split("-")[-1])) if user == None: user = "******" else: user = f"{user.name}#{user.discriminator}" e.add_field( name=f"❯ {self.lb_pos(i)}", value="> **• User:** {} \n> **• Level:** {} \n> **• Total XP:** {}"\ .format( user, entry.lvl, entry.xp ) ) await ctx.send(embed=e)
async def _log(self, ctx: commands.Context, option: str, channel: Union[discord.TextChannel, str]) -> None: """ log_help examples: -log mod #mod-log -log joins 960832535867306044 -log server off """ option = option.lower() if not option in LOG_OPTIONS: return await ctx.send( self.locale.t(ctx.guild, "invalid_log_option", _emote="NO")) data = Object(LOG_OPTIONS[option]) if isinstance(channel, str): if channel.lower() == "off": self.db.configs.update(ctx.guild.id, data.db_field, "") await self.delete_webhook(ctx, data.db_field) return await ctx.send( self.locale.t(ctx.guild, "log_off", _emote="YES", _type=data.i18n_type)) else: prefix = self.get_prefix(ctx.guild) return await ctx.send( self.locale.t(ctx.guild, "invalid_log_channel", _emote="NO", prefix=prefix, option=option)) else: self.db.configs.update(ctx.guild.id, data.db_field, f"{channel.id}") self.webhook_queue.append({ "ctx": ctx, "option": data.db_field, "channel": channel }) await ctx.send( self.locale.t(ctx.guild, "log_on", _emote="YES", _type=data.i18n_type, channel=channel.mention))
async def on_message(self, msg: discord.Message): if msg.guild == None: return if msg.author == None: return if msg.author.bot == True: return if not msg.guild.chunked: await msg.guild.chunk(cache=True) ctx = await self.bot.get_context(msg) if ctx.valid and ctx.command is not None: return config = Object(self.db.configs.get(msg.guild.id, "lvl_sys")) if config.enabled == False: return if not self.exists(config, msg.guild, msg.author): return data = self.get_user_data(msg.guild, msg.author) if data.lvl < 4: xp = randint(1, 4) else: xp = randint(2, 7) for_nxt_lvl = 3 * ((data.lvl - 1)**2) + 30 new_xp = (data.xp + xp) if new_xp >= for_nxt_lvl: self.update_user_data(msg.guild, msg.author, new_xp, (data.lvl + 1)) if config.notif_mode == "channel": await msg.channel.send( self.locale.t(msg.guild, "lvl_up_channel", _emote="PARTY", mention=msg.author.mention, lvl=(data.lvl + 1))) elif config.notif_mode == "dm": try: await msg.author.send( self.locale.t(msg.guild, "lvl_up_dm", _emote="PARTY", mention=msg.author.mention, lvl=(data.lvl + 1), guild=msg.guild.name)) except discord.Forbidden: pass else: self.update_user_data(msg.guild, msg.author, new_xp, data.lvl)
async def on_message(self, msg: discord.Message) -> None: if msg.guild == None: return if not msg.guild.chunked: await msg.guild.chunk(cache=True) if not self.can_act(msg.guild, msg.guild.me, msg.author): return if not hasattr(msg.channel, "slowmode_delay"): return _id = f"{msg.guild.id}-{msg.channel.id}" if not self.db.slowmodes.exists(_id): return else: data = Object(self.db.slowmodes.get_doc(_id)) needs_update = False if f"{msg.author.id}" not in data.users: data.users.update({ f"{msg.author.id}": { "next_allowed_chat": datetime.datetime.utcnow() + datetime.timedelta(seconds=int(data.time)) } }) needs_update = True else: if data.users[f"{msg.author.id}"][ "next_allowed_chat"] > datetime.datetime.utcnow(): try: await msg.delete() except Exception: pass else: self.bot.ignore_for_events.append(msg.id) finally: data.users.update({ f"{msg.author.id}": { "next_allowed_chat": datetime.datetime.utcnow() + datetime.timedelta(seconds=int(data.time)) } }) needs_update = True if needs_update == True: self.db.slowmodes.update(_id, "users", data.users)
async def on_message(self, msg: discord.Message) -> None: if msg.guild == None \ or msg.author.bot \ or msg.author.id == self.bot.user.id: return if not msg.guild.id in self._tags: return if not msg.guild.chunked: await msg.guild.chunk(cache=True) prefix = self.get_prefix(msg.guild) if msg.content.startswith(prefix, 0) and len(self._tags[msg.guild.id]) > 0: for name in self._tags[msg.guild.id]: if msg.content.lower() == prefix + name or ( msg.content.lower().startswith(name, len(prefix)) and msg.content.lower()[len(prefix + name)] == " "): tag = Object(self._tags[msg.guild.id][name]) self.update_uses(f"{msg.guild.id}-{name}") await msg.channel.send(f"{tag.content}")
def __init__(self, *args, **kwargs) -> None: with open("backend/config.json", "r", encoding="utf8", errors="ignore") as config_file: self.config = Object(json.load(config_file)) super().__init__(command_prefix=prefix_callable, intents=self.intents, case_insensitive=True, max_messages=1000, chunk_guilds_at_startup=False, allowed_mentions=discord.AllowedMentions( everyone=False, replied_user=False), *args, **kwargs) for f in [pages, embed]: f.inject_bot_obj(self) self.ready = False self.locked = False self.avatar_as_bytes = None self.error_log = None self.uptime = datetime.datetime.utcnow() self.last_reload = datetime.datetime.utcnow().timestamp() self.used_commands = 0 self.used_tags = 0 self.command_stats = {} self.ignore_for_events = [] self.case_cmd_cache = {} self.webhook_cache = {} self.fetched_user_cache = {} if self.config.watch == True: self.observer = Observer(self) self.db = MongoDB(self) self.cache = InternalCache(self) self.emotes = Emotes(self) self.locale = Translator(self) self.run()
async def info(self, ctx: commands.Context, name: str): """ commands_info_help examples: -commands info test_cmd """ name = name.lower() if ctx.guild.id in self._tags: if not name in self._tags[ctx.guild.id]: await ctx.send( self.locale.t(ctx.guild, "tag_doesnt_exists", _emote="NO")) else: data = Object(self.db.tags.get_doc(f"{ctx.guild.id}-{name}")) e = Embed(title="Command Info") e.add_fields([{ "name": "❯ Name", "value": f"``{name}``" }, { "name": "❯ Content", "value": f"```\n{data.content}\n```" }, { "name": "❯ Uses", "value": f"``{data.uses}``" }, { "name": "❯ Creator", "value": f"<@{data.author}> (<t:{round(data.created.timestamp())}>)" }]) if data.edited != None: e.add_field( name="❯ Editor", value= f"<@{data.editor}> (<t:{round(data.edited.timestamp())}>)" ) await ctx.send(embed=e) else: await ctx.send(self.locale.t(ctx.guild, "no_tags", _emote="NO"))
async def automod(self, ctx: commands.Context, rule=None, amount: Union[int, str] = None) -> None: """ automod_help examples: -automod invites 1 -automod mentions 10 -automod files 0 -automod links off """ prefix = self.get_prefix(ctx.guild) if rule == None or amount == None: e = Embed(title="Automoderator Configuration", description=self.locale.t(ctx.guild, "automod_description", prefix=prefix)) e.add_field(name="❯ Commands", value=self.locale.t(ctx.guild, "automod_commands", prefix=prefix)) return await ctx.send(embed=e) rule = rule.lower() if not rule in AUTOMOD_RULES: return await ctx.send( self.locale.t(ctx.guild, "invalid_automod_rule", _emote="NO")) current = self.db.configs.get(ctx.guild.id, "automod") data = Object(AUTOMOD_RULES[rule]) if isinstance(amount, str): if amount.lower() == "off": self.db.configs.update( ctx.guild.id, "automod", {k: v for k, v in current.items() if k != rule}) return await ctx.send( self.locale.t(ctx.guild, "automod_off", _emote="YES", _type=data.i18n_type)) else: return await ctx.send( self.locale.t(ctx.guild, "invalid_automod_amount", _emote="NO", prefix=prefix, rule=rule, field=data.i18n_type)) else: if rule == "mentions": if amount < 8: return await ctx.send( self.locale.t(ctx.guild, "min_mentions", _emote="NO")) if amount > 100: return await ctx.send( self.locale.t(ctx.guild, "max_mentions", _emote="NO")) else: if amount < 0: return await ctx.send( self.locale.t(ctx.guild, "min_warns_esp", _emote="NO")) if amount > 100: return await ctx.send( self.locale.t(ctx.guild, "max_warns", _emote="NO")) current.update({rule: {data.int_field_name: int(amount)}}) self.db.configs.update(ctx.guild.id, "automod", current) text = "" if rule != "mentions" and amount == 0: text = self.locale.t(ctx.guild, f"{data.i18n_key}_zero", _emote="YES") else: text = self.locale.t(ctx.guild, data.i18n_key, _emote="YES", amount=amount, plural="" if amount == 1 else "s") await ctx.send(text)
async def enforce_rules(self, msg: discord.Message) -> None: content = msg.content.replace("\\", "") config = Object(self.db.configs.get_doc(msg.guild.id)) rules = config.automod filters = config.filters regexes = config.regexes antispam = config.antispam if antispam.enabled == True: if not msg.guild.id in self.spam_cache: self.spam_cache.update({ msg.guild.id: commands.CooldownMapping.from_cooldown( antispam.rate, float(antispam.per), commands.BucketType.user) }) mapping = self.spam_cache[msg.guild.id] now = msg.created_at.timestamp() users = mapping.get_bucket(msg) if users.update_rate_limit(now): return await self.delete_msg( "antispam", f"{users.rate}/{round(users.per, 0)}", msg, antispam.warns, f"Spam detected") if len(filters) > 0: for name in filters: f = filters[name] parsed = self.parse_filter(f["words"]) if parsed != None: found = parsed.findall(content) if found: print("found") return await self.delete_msg( "filter", ", ".join(found), msg, int(f["warns"]), f"Triggered filter '{name}' with '{', '.join(found)}'", name) if len(regexes) > 0: for name, data in regexes.items(): parsed = self.parse_regex(data["regex"]) if parsed != None: found = parsed.findall(content) if found: return await self.delete_msg( "regex", ", ".join(found), msg, int(data["warns"]), f"Triggered regex '{name}' with '{', '.join(found)}'", name) if len(rules) < 1: return if hasattr(rules, "invites"): found = INVITE_RE.findall(content) if found: for inv in found: try: invite: discord.Invite = await self.bot.fetch_invite( inv) except discord.NotFound: return await self.delete_msg("invites", inv, msg, rules.invites.warns, f"Advertising ({inv})") if invite.guild == None: return await self.delete_msg("invites", inv, msg, rules.invites.warns, f"Advertising ({inv})") else: if invite.guild == None \ or ( not invite.guild.id in config.allowed_invites \ and invite.guild.id != msg.guild.id ): return await self.delete_msg( "invites", inv, msg, rules.invites.warns, f"Advertising ({inv})") if hasattr(rules, "links"): found = LINK_RE.findall(content) if found: for link in found: url = urlparse(link) if url.hostname in config.black_listed_links: return await self.delete_msg( "links", url.hostname, msg, rules.links.warns, f"Forbidden link ({url.hostname})") else: if not url.hostname in config.white_listed_links: return await self.delete_msg( "links", url.hostname, msg, rules.links.warns, f"Forbidden link ({url.hostname})") if hasattr(rules, "files"): if len(msg.attachments) > 0: try: forbidden = [ x.url.split(".")[-1] for x in msg.attachments \ if not x.url.split(".")[-1].lower() in ALLOWED_FILE_FORMATS ] except Exception: forbidden = [] if len(forbidden) > 0: return await self.delete_msg( "files", ", ".join(forbidden), msg, rules.files.warns, f"Forbidden attachment type ({', '.join(forbidden)})") if hasattr(rules, "zalgo"): found = ZALGO_RE.search(content) if found: return await self.delete_msg("zalgo", found, msg, rules.zalgo.warns, "Zalgo found") if hasattr(rules, "mentions"): found = len(MENTION_RE.findall(content)) if found >= rules.mentions.threshold: return await self.delete_msg( "mentions", found, msg, abs(rules.mentions.threshold - found), f"Spamming mentions ({found})")
def get_config(self, guild_id: int) -> Object: return Object(self.db.configs.get_doc(guild_id))
async def config(self, ctx: commands.Context) -> None: """ config_help examples: -config """ config = Object(self.db.configs.get_doc(ctx.guild.id)) rules = config.automod y = self.bot.emotes.get("YES") n = self.bot.emotes.get("NO") mute_perm = n if (ctx.guild.me.guild_permissions.value & 0x10000000000) != 0x10000000000: if ctx.guild.me.guild_permissions.administrator == True: mute_perm = y else: mute_perm = y e = Embed(title=f"Server config for {ctx.guild.name}", ) e.add_fields([ { "name": "❯ General", "value": "> **• Prefix:** {} \n> **• Can mute:** {} \n> **• Filters:** {} \n> **• Regexes:** {} \n> **• Reaction Roles:** {} \n> **• Mod Role:** {}"\ .format( config.prefix, mute_perm, len(config.filters), len(config.regexes), len(config.reaction_roles), n if config.mod_role == "" else f"<@&{config.mod_role}>" ), "inline": True }, { "name": "❯ Logging", "value": "> **• Mod Log:** {} \n> **• Message Log:** {}\n> **• Server Log:** {}\n> **• Join Log:** {} \n> **• Member Log:** {} \n> **• Voice Log:** {}"\ .format( n if config.mod_log == "" else f"<#{config.mod_log}>", n if config.message_log == "" else f"<#{config.message_log}>", n if config.server_log == "" else f"<#{config.server_log}>", n if config.join_log == "" else f"<#{config.join_log}>", n if config.member_log == "" else f"<#{config.member_log}>", n if config.voice_log == "" else f"<#{config.voice_log}>", ), "inline": True }, e.blank_field(True), { "name": "❯ Automod Rules", "value": "> **• Max Mentions:** {} \n> **• Links:** {} \n> **• Invites:** {} \n> **• Bad Files:** {} \n> **• Zalgo:** {} \n> **• Spam:** {}"\ .format( n if not hasattr(rules, "mentions") else f"{rules.mentions.threshold}", n if not hasattr(rules, "links") else f"{rules.links.warns} warn{'' if rules.links.warns == 1 else 's'}" if rules.links.warns > 0 else "delete message", n if not hasattr(rules, "invites") else f"{rules.invites.warns} warn{'' if rules.invites.warns == 1 else 's'}" if rules.invites.warns > 0 else "delete message", n if not hasattr(rules, "files") else f"{rules.files.warns} warn{'' if rules.files.warns == 1 else 's'}" if rules.files.warns > 0 else "delete message", n if not hasattr(rules, "zalgo") else f"{rules.zalgo.warns} warn{'' if rules.zalgo.warns == 1 else 's'}" if rules.zalgo.warns > 0 else "delete message", n if config.antispam.enabled == False else f"{config.antispam.rate} per {config.antispam.per} ({config.antispam.warns} warn{'' if config.antispam.warns == 1 else 's'})" ), "inline": True }, { "name": "❯ Actions", "value": "\n".join([ f"> **• {x} Warn{'' if int(x) == 1 else 's'}:** {y.capitalize() if len(y.split(' ')) == 1 else y.split(' ')[0].capitalize() + ' ' + y.split(' ')[-2] + y.split(' ')[-1]}" \ for x, y in config.punishments.items() ]) if len(config.punishments.items()) > 0 else f"> {n}", "inline": True }, e.blank_field(True), { "name": "❯ Ignored Roles (automod)", "value": f"> {n}" if len(config.ignored_roles_automod) < 1 else "> {}".format(", ".join([f"<@&{x}>" for x in config.ignored_roles_automod])), "inline": True }, { "name": "❯ Ignored Channels (automod)", "value": f"> {n}" if len(config.ignored_channels_automod) < 1 else "> {}".format(", ".join([f"<#{x}>" for x in config.ignored_channels_automod])), "inline": True }, e.blank_field(True), { "name": "❯ Ignored Roles (logging)", "value": f"> {n}" if len(config.ignored_roles_log) < 1 else "> {}".format(", ".join([f"<@&{x}>" for x in config.ignored_roles_log])), "inline": True }, { "name": "❯ Ignored Channels (logging)", "value": f"> {n}" if len(config.ignored_channels_log) < 1 else "> {}".format(", ".join([f"<#{x}>" for x in config.ignored_channels_log])), "inline": True }, e.blank_field(True) ]) await ctx.send(embed=e)
def get_user_data(self, guild, user): return Object(self.db.level.get_doc(f"{guild.id}-{user.id}"))
@AutoModPlugin.can("manage_messages") async def case(self, ctx: commands.Context, case: str) -> None: """ case_help examples: -case 1234 -case #1234 """ case = case.replace("#", "") raw = self.db.cases.get_doc(f"{ctx.guild.id}-{case}") if raw == None: return await ctx.send( self.locale.t(ctx.guild, "case_not_found", _emote="NO")) data = Object(raw) log_msg_url = self.get_log_for_case(ctx, raw) e = Embed(title=f"{data.type.upper()} | #{case}") if log_msg_url != None: e.description = f"[View log message]({log_msg_url})" e.set_thumbnail(url=data.user_av) e.add_fields([ { "name": "❯ User", "value": f"<@{data.user_id}> ({data.user_id})" }, { "name": "❯ Moderator", "value": f"<@{data.mod_id}> ({data.mod_id})" },