async def black_list(self, ctx): """black_list_help""" if ctx.invoked_subcommand is None: if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "automod") is False: return await ctx.send( Translator.translate(ctx.guild, "black_list_disabled", _emote="NO", prefix=ctx.prefix)) _censor_list = [ x.strip().lower() for x in DBUtils.get( db.configs, "guildId", f"{ctx.guild.id}", "censored_words") if x != "--------------" ] if len(_censor_list) < 1: return ctx.send( Translator.translate(ctx.guild, "black_list_empty", _emote="NO", prefix=ctx.prefix)) await ctx.send( Translator.translate(ctx.guild, "censor_list", guild_name=ctx.guild.name, words=_censor_list))
async def rank(self, ctx, user: discord.Member = None): """rank_help""" if user is None: user = ctx.author if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "lvlsystem") is False: return await ctx.send(Translator.translate(ctx.guild, "lvlsystem_disabled", _emote="NO")) try: level_id = f"{ctx.guild.id}-{user.id}" xp = DBUtils.get(db.ranks, "levelId", level_id, "xp") lvl = DBUtils.get(db.ranks, "levelId", level_id, "lvl") needed = 10 if lvl <= 1: needed_xp = needed + 1 return await ctx.send(embed=await self._rank(ctx, user, xp, lvl, needed_xp)) counter = 0 while counter < lvl: counter += 1 needed += 40 if counter >= lvl: break needed_xp = needed + 2 return await ctx.send(embed=await self._rank(ctx, user, xp, lvl, needed_xp)) except Exception: await ctx.send(Translator.translate(ctx.guild, "not_ranked", _emote="NO", user=user))
async def ranks(self, ctx): """ranks_help""" if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "lvlsystem") is False: return await ctx.send(Translator.translate(ctx.guild, "lvlsystem_disabled", _emote="NO")) level_roles = DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "level_roles") if len(level_roles) < 1: return await ctx.send(Translator.translate(ctx.guild, "no_lvl_roles", _emote="NO")) embed = discord.Embed( color=discord.Color.blurple(), title="{} {}".format(ctx.guild.name, Translator.translate(ctx.guild, "lvl_roles")), ) embed.set_thumbnail( url=ctx.guild.icon_url ) for r in level_roles: lvl = r.split("-")[0] _id = r.split("-")[1] if not str(_id) in [str(x.id) for x in ctx.guild.roles]: role = Translator.translate(ctx.guild, "deleted_role") else: role = "<@&{}>".format(_id) embed.add_field( name="**Level {}**".format(lvl), value="{}".format(role), inline=False ) await ctx.send(embed=embed)
async def add(self, ctx, lvl: RangedInt(2, 200), role: discord.Role): "add_help" if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "lvlsystem") is False: return await ctx.send(Translator.translate(ctx.guild, "lvlsystem_disabled", _emote="NO")) automod = await Utils.get_member(self.bot, ctx.guild, self.bot.user.id) if role.position >= automod.top_role.position: return await ctx.send(Translator.translate(ctx.guild, "role_too_high")) level_roles = DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "level_roles") roles = [x.split("-")[1] for x in level_roles] levels = [x.split("-")[0] for x in level_roles] if str(lvl) in levels: return await ctx.send(Translator.translate(ctx.guild, "already_role_for_lvl", _emote="NO", lvl=lvl)) if str(role.id) is roles: return await ctx.send(Translator.translate(ctx.guild, "already_lvl_role", _emote="NO", role=role)) if len(level_roles) > 10: return await ctx.send(Translator.translate(ctx.guild, "max_lvl_roles", _emote="NO")) level_roles.append(f"{lvl}-{role.id}") DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "level_roles", level_roles) await ctx.send(Translator.translate(ctx.guild, "added_lvl_role", _emote="YES", role=role, lvl=lvl))
async def add_to_censor_list(self, ctx, *, text: str): if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "automod") is False: return await ctx.send( Translator.translate(ctx.guild, "black_list_disabled", _emote="NO", prefix=ctx.prefix)) _censor_list = [ x for x in DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "censored_words") if x != "--------------" ] if text.lower() in [ x.strip().lower() for x in DBUtils.get( db.configs, "guildId", f"{ctx.guild.id}", "censored_words") if x != "--------------" ]: return await ctx.send( Translator.translate(ctx.guild, "already_on_black_list", _emote="NO", word=text)) _censor_list.append(str(text)) DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "censored_words", _censor_list) await ctx.send( Translator.translate(ctx.guild, "added_to_black_list", _emote="YES", word=text))
async def on_message(self, message: discord.Message): if message.guild is None: return if DBUtils.get(db.configs, "guildId", f"{message.guild.id}", "lvlsystem") is False: return user = message.author if user.id in self.cooldown_cache: return prefix = DBUtils.get(db.configs, "guildId", f"{message.guild.id}", "prefix") if user.bot is True or f"{prefix}rank" in message.content or f"{prefix}lb" in message.content or f"{prefix}leaderboard" in message.content: return self.cooldown_cache.append(user.id) lvl_id = f"{message.guild.id}-{user.id}" await self._update_data(message.guild.id, lvl_id, user.id) await self._add_xp(lvl_id, 2) await self._level_up(message, lvl_id, user) await asyncio.sleep(5) # 5 seconds cache lifetime self.cooldown_cache.remove(user.id)
async def on_message_delete(self, message: Message): await asyncio.sleep( 1 ) # sleep a bit, we don't log message deletions from the censor module if message.id in self.bot.running_msg_deletions: self.bot.running_msg_deletions.remove(message.id) return c = message.channel channel_id = c.id if isinstance(c, DMChannel): return if c is None or str(channel_id) == str( DBUtils.get( db.configs, "guildId", f"{c.guild.id}", "memberLogChannel")) or not isinstance(c, TextChannel): return if message.author.id == self.bot.user.id: return if c.guild is None: return if DBUtils.get(db.configs, "guildId", f"{c.guild.id}", "messageLogging") is False: return ignored_users = DBUtils.get(db.configs, "guildId", f"{c.guild.id}", "ignored_users") if ignored_users is None: on_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.log_to_guild( c.guild.id, "messageLogChannel", Translator.translate(message.guild, "log_message_deletion", _emote="BIN", user=message.author, user_id=message.author.id, channel=c.mention, on_time=on_time, content=message.content)) else: if message.author.id in ignored_users: return else: on_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.log_to_guild( c.guild.id, "messageLogChannel", Translator.translate(message.guild, "log_message_deletion", _emote="BIN", user=message.author, user_id=message.author.id, channel=c.mention, on_time=on_time, content=message.content))
async def handle_spam(self, guild, member, msg): if DBUtils.get(db.configs, "guildId", f"{guild.id}", "antispam") is False: return # anti spam isn't enabled for this guild c = self.spam_checker[guild.id] if not c.is_spamming(msg): return self.handling.append(member.id) try: await guild.kick(user=member, reason="[AutoMod] Spam") except discord.HTTPException: log.info(f"[Anti Spam] Error while trying to kick {member} ({member.id}) from server {guild} via the anti spam system") else: log.info(f"[Anti Spam] Kicked {member} ({member.id}) from server {guild} via the anti spam system") case = DBUtils.new_case() timestamp = datetime.datetime.utcnow().strftime("%d/%m/%Y %H:%M") mod = discord.utils.get(guild.members, id=self.bot.user.id) DBUtils.insert(db.inf, new_infraction(case, msg.guild.id, member, mod, timestamp, "Kick", "[AutoMod] Spam")) on_time = datetime.datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.log_to_guild(guild.id, "memberLogChannel", Translator.translate(guild, "log_spam", _emote="SHOE", on_time=on_time, user=member, user_id=member.id, moderator=self.bot.user, moderator_id=self.bot.user.id, channel=msg.channel.mention)) finally: self.handling.remove(member.id)
async def generate_help_pages(ctx, bot): pages = [] out = [] valid_cogs = [ c for c in bot.cogs if not str(c) in ["Admin", "AntiSpam", "Censor", "GlobalListeners"] ] for cog in valid_cogs: commands = [ " {}{}{}".format(x.name, " " * abs(18 - len(x.name)), Translator.translate(ctx.guild, x.short_doc)) for x in bot.get_cog(cog).get_commands() if x.hidden is False ] output = f"[ {cog} ]\n" + "\n".join(commands) + "\n" out.append(output) for page in Pages.paginate("{}".format("\n".join(out)), prefix="```ini\n", suffix=Translator.translate( ctx.guild, "help_suffix", prefix=DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "prefix"))): pages.append(page) return pages
async def on_member_remove(self, member: Member): await asyncio.sleep( 1) # sleep a bit, so we don't log an unban made with the bot if member.id in self.bot.running_removals: self.bot.running_removals.remove(member.id) return if DBUtils.get(db.configs, "guildId", f"{member.guild.id}", "memberLogging") is True: joined = (datetime.fromtimestamp(time.time()) - member.joined_at).days try: on_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.log_to_guild( member.guild.id, "joinLogChannel", Translator.translate(member.guild, "log_leave", _emote="LEAVE", user=member, user_id=member.id, on_time=on_time, joined=joined)) except Exception: pass else: return
async def on_message(self, message: discord.Message): if message.author.bot or message.webhook_id is not None: return if not hasattr(message.channel, "guild") or message.channel.guild is None: return automod = message.guild.me if automod is None: await Utils.get_member(self.bot, message.guild, self.bot.user.id) perms = message.channel.permissions_for(automod) if automod is None: return if not (perms.read_messages and perms.send_messages and perms.embed_links): return if message.author.id == self.bot.user.id: return prefix = DBUtils.get(db.configs, "guildId", f"{message.guild.id}", "prefix") try: cmds = self.command_cache[str(message.guild.id)] except KeyError: return if message.content.startswith(prefix, 0) and len(cmds) > 0: for entry in cmds: trigger = entry["trigger"] if message.content.lower() == prefix + trigger or (message.content.lower().startswith(trigger, len(prefix)) and message.content.lower()[len(prefix + trigger)] == " "): reply = entry["reply"] return await message.channel.send(f"{reply}")
async def on_member_join(self, member: Member): if DBUtils.get(db.configs, "guildId", f"{member.guild.id}", "memberLogging") is True: created = (datetime.fromtimestamp(time.time()) - member.created_at).days try: on_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.log_to_guild( member.guild.id, "joinLogChannel", Translator.translate(member.guild, "log_join", _emote="JOIN", user=member, user_id=member.id, on_time=on_time, age=created)) except Exception: pass msg = DBUtils.get(db.configs, "guildId", f"{member.guild.id}", "welcomeMessage") welcome_id = DBUtils.get(db.configs, "guildId", f"{member.guild.id}", "welcomeChannel") if msg is None or msg == "": return if welcome_id is None or welcome_id == "": return try: welcome_channel = await self.bot.fetch_channel(int(welcome_id)) except Exception: return members = len(member.guild.members ) # the guilds member count after the member joined mention = member.mention # mentions the new member member = member.name # displays the new members name without the discrim try: await welcome_channel.send( str(msg).format(members=members, member=member, mention=mention)) except Exception: return
async def remove(self, ctx, role: discord.Role): "remove_help" if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "lvlsystem") is False: return await ctx.send(Translator.translate(ctx.guild, "lvlsystem_disabled", _emote="NO")) level_roles = DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "level_roles") roles = [x.split("-")[1] for x in level_roles] levels = [x.split("-")[0] for x in level_roles] if len(level_roles) < 1: return await ctx.send(Translator.translate(ctx.guild, "no_lvl_roles", _emote="NO")) if not str(role.id) in roles: return await ctx.send(Translator.translate(ctx.guild, "invalid_lvl_role", _emote="NO", role=role)) lvl = levels[roles.index(str(role.id))] level_roles.remove(f"{lvl}-{role.id}") DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "level_roles", level_roles) await ctx.send(Translator.translate(ctx.guild, "removed_lvl_role", _emote="YES", role=role))
async def complex_cleaning( ctx, search): # also cleans messages that have the bots prefix in them prefix = DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "prefix") def check(m): return m.author == ctx.me or m.content.startswith(prefix) deleted = await ctx.channel.purge(limit=search, check=check, before=ctx.message) return len(deleted)
async def _fill_rank_cache(self): await asyncio.sleep(1) # wait a bit, we got time valid_guilds = [x for x in self.bot.guilds if DBUtils.get(db.configs, "guildId", f"{x.id}", "lvlsystem") is True] while len(self.cached_guilds) < len(valid_guilds): for g in valid_guilds: if g.id in self.cached_guilds: pass else: for m in g.members: self.rank_cache[m.id] = f"{m.name}#{m.discriminator}" self.cached_guilds.add(g.id)
async def log_to_guild(guild_id, log_type, text): # check if we can even log to this guild log_id = DBUtils.get(db.configs, "guildId", f"{guild_id}", f"{log_type}") if log_id == None or log_id == "": return # check if the channel is still valid log_channel = await BOT.fetch_channel(int(log_id)) if log_channel is None: return await log_channel.send(text)
async def on_message(self, message: Message): if message.guild is None or message.webhook_id is not None or message.channel is None or isinstance( message.channel, DMChannel ) or message.author.bot is True or self.bot.user.id == message.author.id: return if DBUtils.get(db.configs, "guildID", f"{message.guild.id}", "automod") is False: return user = message.guild.get_member(message.author.id) if user is None: return await self.check_message(message.author, message.content, message.channel, message)
async def _level_up(message, lvl_id, user): xp = DBUtils.get(db.levels, "levelId", lvl_id, "xp") lvl = DBUtils.get(db.levels, "levelId", lvl_id, "lvl") # calculations #TODO: It's way to easy to level up, migth have to change that starting_xp = 10 counter = 0 while counter < lvl: counter += 1 if counter > 1: starting_xp += 40 if counter >= lvl: break if lvl > 1: starting_xp += 40 if starting_xp < xp: after = lvl + 1 DBUtils.update(db.levels, "levelId", lvl_id, "lvl", after) try: await message.channel.send(Translator.translate(ctx.guild, "lvl_up", _emote="PARTY", user=user, lvl=after)) except Exception: pass lvl_roles = DBUtils.get(db.configs, "guildId", f"{message.guild.id}", "level_roles") if len(lvl_roles) < 1: return for l in lvl_roles: if str(l.split("-")[0]) == str(after): try: role = discord.utils.get(message.guild.roles, id=int(l.split("-")[1])) await user.add_roles(role) try: await user.send(Translator.translate(ctx.guild, "role_added", user=user.name, role=role.name, guild_name=message.guild.name, lvl=after)) except Exception: pass except Exception: pass
async def remove_from_censor_list(self, ctx, *, text: str): if DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "automod") is False: return await ctx.send( Translator.translate(ctx.guild, "black_list_disabled", _emote="NO", prefix=ctx.prefix)) lower = [ x.lower() for x in DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "censored_words") if x != "--------------" ] if len(lower) < 1: return await ctx.send( Translator.translate(ctx.guild, "black_list_empty", _emote="NO", prefix=ctx.prefix)) _censor_list = [ x.strip().lower() for x in DBUtils.get( db.configs, "guildId", f"{ctx.guild.id}", "censored_words") ] if not text.lower() in _censor_list: return await ctx.send( Translator.translate(ctx.guild, "not_on_black_list", _emote="NO", word=text)) _censor_list.remove(str(text.lower())) DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "censored_words", _censor_list) await ctx.send( Translator.translate(ctx.guild, "removed_from_black_list", _emote="YES", word=text))
async def on_message_edit(self, before, after): c = before.channel channel_id = int(c.id) if isinstance(c, DMChannel): return if c is None or str(channel_id) == str( DBUtils.get(db.configs, "guildId", f"{c.guild.id}", "memberLogChannel")): return if before.author.id == self.bot.user.id: return if c.guild is None: return if DBUtils.get(db.configs, "guildId", f"{c.guild.id}", "messageLogging") is False: return ignored_users = DBUtils.get(db.configs, "guildId", f"{c.guild.id}", "ignored_users") if int(before.author.id) in ignored_users: return else: if before.content != after.content: on_time = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.log_to_guild( c.guild.id, "messageLogChannel", Translator.translate(before.guild, "log_message_edit", _emote="PEN", user=before.author, user_id=before.author.id, channel=c.mention, on_time=on_time, before=before.content, after=after.content)) else: pass
def prefix_callable(bot, message): prefixes = [f"<@!{bot.user.id}> ", f"<@{bot.user.id}> "] if message.guild is None: prefixes.append("+") elif bot.READY: try: prefix = DBUtils.get(db.configs, "guildId", f"{message.guild.id}", "prefix") if prefix is not None: prefixes.append(prefix) else: prefixes.append("+") except Exception: prefixes.append("+") return prefixes
async def _remove(self, ctx, user: discord.User): ignored_users = DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "ignored_users") if not user.id in ignored_users: return await ctx.send( Translator.translate(ctx.guild, "not_existing_ignored_user", _emote="NO", user=user.name)) ignored_users.remove(int(user.id)) DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "ignored_users", ignored_users) await ctx.send( Translator.translate(ctx.guild, "ignored_user_removed", _emote="YES", user=user.name))
async def _add(self, ctx, member: discord.Member): ignored_users = DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "ignored_users") if member.id in ignored_users: return await ctx.send( Translator.translate(ctx.guild, "already_ignored_user", _emote="YES", user=member.name)) ignored_users.append(int(member.id)) DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "ignored_users", ignored_users) await ctx.send( Translator.translate(ctx.guild, "ignored_user_added", _emote="YES", user=member.name))
async def on_message_edit(self, before, after): channel = self.bot.get_channel(int(before.channel.id)) if channel is None or isinstance(channel, DMChannel) or channel.guild is None: return if DBUtils.get(db.configs, "guildID", f"{channel.guild.id}", "automod") is False: return try: message = after except (discord.NotFound, discord.Forbidden): return else: target_id = message.author.id target = await Utils.get_member(self.bot, channel.guild, target_id) if target is not None and target_id != self.bot.user.id: await self.check_message(target, after.content, message.channel, message)
async def ignored_users(self, ctx): """ignored_users_help""" try: if ctx.invoked_subcommand is None: ignored_users = [] for x in DBUtils.get(db.configs, "guildId", f"{ctx.guild.id}", "ignored_users"): ignored_users.append(str(x)) if len(ignored_users) < 1: ignored_users.append( Translator.translate(ctx.guild, "no_ignored_users")) e = discord.Embed(color=discord.Color.blurple(), title=Translator.translate( ctx.guild, "ignored_users", guild_name=ctx.guild.name), description="\n".join(ignored_users)) e.set_thumbnail(url=ctx.guild.icon_url) return await ctx.send(embed=e) except Exception: pass
async def prefix(self, ctx, new_prefix: str = None): """prefix_help""" if new_prefix is None: await ctx.send( Translator.translate(ctx.guild, "current_prefix", prefix=DBUtils.get( db.configs, "guildId", f"{ctx.guild.id}", "prefix"))) elif len(new_prefix) > 15: await ctx.send( Translator.translate(ctx.guild, "prefix_too_long", _emote="NO")) else: DBUtils.update(db.configs, "guildId", f"{ctx.guild.id}", "prefix", f"{new_prefix}") await ctx.send( Translator.translate(ctx.guild, "prefix_updated", _emote="YES", prefix=new_prefix))
async def check_mutes(bot): while True: # all guilds need to be fetched for this await asyncio.sleep(10) try: if len([_ for _ in db.mutes.find()]) > 0: for mute in db.mutes.find(): guild = bot.get_guild(int(mute["mute_id"].split("-")[0])) target = discord.utils.get( guild.members, id=int(mute["mute_id"].split("-")[1])) if datetime.utcnow() > mute["ending"]: try: mute_role_id = DBUtils.get(db.configs, "guildId", f"{guild.id}", "muteRole") mute_role = guild.get_role(int(mute_role_id)) await target.remove_roles(mute_role) except Exception: pass else: on_time = datetime.utcnow().strftime( "%Y-%m-%d %H:%M:%S") await Logging.log_to_guild( guild.id, "memberLogChannel", Translator.translate(guild, "log_unmute", _emote="ANGEL", on_time=on_time, user=target, user_id=target.id)) DBUtils.delete( db.mutes, "mute_id", f"{mute['mute_id'].split('-')[0]}-{mute['mute_id'].split('-')[1]}" ) else: pass except AttributeError: # this happens if the guild object is a NoneType (most likely because it hasn't been cached yet) pass
def translate(guild, key, _emote=None, **kwargs): if not guild.id in LANG_CACHE: try: lang = DBUtils.get(db.configs, "guildId", guild.id, "lang") except KeyError: DBUtils.update(db.configs, "guildId", guild.id, "lang", "en_US") lang = "en_US" LANG_CACHE[ guild. id] = lang # cache the language for the guild, so we don't have to fetch it from the DB every time else: lang = LANG_CACHE[guild.id] global string try: string = LANGS[lang][key] except KeyError: string = LANGS["en_US"][key] finally: if "{emote}" in string: return str(string).format(emote=str(Emotes.get(_emote)), **kwargs) else: return str(string).format(**kwargs)
async def fill_cache(bot): try: while len(bot.missing_guilds) > 0: start_time = time.time() old = len(bot.missing_guilds) while len(bot.missing_guilds) > 0: try: tasks = [ asyncio.create_task(cache_guild(bot, guild_id)) for guild_id in bot.missing_guilds ] await asyncio.wait_for(await asyncio.gather(*tasks), 600) except (CancelledError, concurrent.futures._base.CancelledError, asyncio.exceptions.CancelledError): pass except concurrent.futures._base.TimeoutError: if old == len(bot.missing_guilds): log.info( "[Caching] Timed out while fetching member chunks." ) for t in tasks: t.cancel() await asyncio.sleep(1) continue except Exception as e: log.error(f"[Caching] Fetching member info failed: \n{e}") else: if old == len(bot.missing_guilds): log.error( "[Caching] Timed out while fetching member chunks." ) for t in tasks: t.cancel() continue end_time = time.time() time_needed = (end_time - start_time) log.info("[Caching] Finished fetching members in {}".format( time_needed)) # check for guilds that aren't in the DB for some reason (added during downtime etc) log.info("[Caching] Filling up missing guilds") for g in bot.guilds: if not DBUtils.get(db.configs, "guildId", f"{g.id}", "prefix"): try: DBUtils.insert(db.configs, Schemas.guild_schema(g)) log.info(f"[Caching] Filled missing guild: {g}") except Exception as ex: log.error( f"[Caching] Error while trying to fill up missing guild {g}: \n{ex}" ) log.info("[Caching] Fill-up task completed!") end_time2 = time.time() t = datetime.utcnow().strftime("%Y-%m-%d %H:%M:%S") await Logging.bot_log( f"``[{t} UTC]`` {SALUTE} Finished building the internal cache in {round(end_time2 - start_time, 2)} seconds" ) bot.initial_fill_complete = True except Exception as e: await log.error(f"[Caching] Guild fetching failed \n{e}") finally: bot.loading_task = None
async def _add_xp(lvl_id, xp): cur = DBUtils.get(db.levels, "levelId", lvl_id, "xp") new_xp = int(cur) + xp DBUtils.update(db.levels, "levelId", lvl_id, "xp", new_xp)