mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[Mod] Enhanced mod-log (#491)
Case management:
- Centralize formatting code
- Store raw action type
- Centralize pretty-print (emoji) representation of actions
- Store creation and update timestamps
- Allow edits from admin or bot owner
- Enable or disable case creation on a per-action basis
- Preliminary support for action durations
Mute:
- Add case creation (on by default for server, not channel)
- Show channel member was muted in in modlog entry
This commit is contained in:
parent
2ab372e557
commit
343fc80406
343
cogs/mod.py
343
cogs/mod.py
@ -3,6 +3,7 @@ from discord.ext import commands
|
||||
from .utils.dataIO import dataIO
|
||||
from .utils import checks
|
||||
from __main__ import send_cmd_help, settings
|
||||
from datetime import datetime
|
||||
from collections import deque, defaultdict
|
||||
from cogs.utils.chat_formatting import escape_mass_mentions, box
|
||||
import os
|
||||
@ -10,6 +11,23 @@ import re
|
||||
import logging
|
||||
import asyncio
|
||||
|
||||
|
||||
ACTIONS_REPR = {
|
||||
"BAN" : ("Ban", "\N{HAMMER}"),
|
||||
"KICK" : ("Kick", "\N{WOMANS BOOTS}"),
|
||||
"CMUTE" : ("Channel mute", "\N{SPEAKER WITH CANCELLATION STROKE}"),
|
||||
"SMUTE" : ("Server mute", "\N{SPEAKER WITH CANCELLATION STROKE}"),
|
||||
"SOFTBAN" : ("Softban", "\N{DASH SYMBOL} \N{HAMMER}")
|
||||
}
|
||||
|
||||
ACTIONS_CASES = {
|
||||
"BAN" : True,
|
||||
"KICK" : True,
|
||||
"CMUTE" : False,
|
||||
"SMUTE" : True,
|
||||
"SOFTBAN" : True
|
||||
}
|
||||
|
||||
default_settings = {
|
||||
"ban_mention_spam" : False,
|
||||
"delete_repeats" : False,
|
||||
@ -17,6 +35,11 @@ default_settings = {
|
||||
}
|
||||
|
||||
|
||||
for act, enabled in ACTIONS_CASES.items():
|
||||
act = act.lower() + '_cases'
|
||||
default_settings[act] = enabled
|
||||
|
||||
|
||||
class ModError(Exception):
|
||||
pass
|
||||
|
||||
@ -182,9 +205,56 @@ class Mod:
|
||||
else:
|
||||
await self.bot.say("I will not delete command messages.")
|
||||
|
||||
@modset.command(pass_context=True, no_pm=True, name='cases')
|
||||
async def set_cases(self, ctx, action: str = None, enabled: bool = None):
|
||||
"""Enables or disables case creation for each type of mod action
|
||||
|
||||
Enabled can be 'on' or 'off'"""
|
||||
server = ctx.message.server
|
||||
|
||||
if action == enabled: # No args given
|
||||
await self.bot.send_cmd_help(ctx)
|
||||
msg = "Current settings:\n```py\n"
|
||||
maxlen = max(map(lambda x: len(x[0]), ACTIONS_REPR.values()))
|
||||
for action, name in ACTIONS_REPR.items():
|
||||
action = action.lower() + '_cases'
|
||||
value = self.settings[server.id].get(action,
|
||||
default_settings[action])
|
||||
value = 'enabled' if value else 'disabled'
|
||||
msg += '%s : %s\n' % (name[0].ljust(maxlen), value)
|
||||
|
||||
msg += '```'
|
||||
await self.bot.say(msg)
|
||||
|
||||
elif action.upper() not in ACTIONS_CASES:
|
||||
msg = "That's not a valid action. Valid actions are: \n"
|
||||
msg += ', '.join(sorted(map(str.lower, ACTIONS_CASES)))
|
||||
await self.bot.say(msg)
|
||||
|
||||
elif enabled == None:
|
||||
action = action.lower() + '_cases'
|
||||
value = self.settings[server.id].get(action,
|
||||
default_settings[action])
|
||||
await self.bot.say('Case creation for %s is currently %s' %
|
||||
(action, 'enabled' if value else 'disabled'))
|
||||
else:
|
||||
name = ACTIONS_REPR[action.upper()][0]
|
||||
action = action.lower() + '_cases'
|
||||
value = self.settings[server.id].get(action,
|
||||
default_settings[action])
|
||||
if value != enabled:
|
||||
self.settings[server.id][action] = enabled
|
||||
dataIO.save_json("data/mod/settings.json", self.settings)
|
||||
msg = ('Case creation for %s actions %s %s.' %
|
||||
(name.lower(),
|
||||
'was already' if enabled == value else 'is now',
|
||||
'enabled' if enabled else 'disabled')
|
||||
)
|
||||
await self.bot.say(msg)
|
||||
|
||||
@commands.command(no_pm=True, pass_context=True)
|
||||
@checks.admin_or_permissions(kick_members=True)
|
||||
async def kick(self, ctx, user: discord.Member):
|
||||
async def kick(self, ctx, user: discord.Member, *, reason: str = None):
|
||||
"""Kicks user."""
|
||||
author = ctx.message.author
|
||||
server = author.server
|
||||
@ -192,10 +262,13 @@ class Mod:
|
||||
await self.bot.kick(user)
|
||||
logger.info("{}({}) kicked {}({})".format(
|
||||
author.name, author.id, user.name, user.id))
|
||||
if self.settings[server.id].get('kick_cases',
|
||||
default_settings['kick_cases']):
|
||||
await self.new_case(server,
|
||||
action="Kick \N{WOMANS BOOTS}",
|
||||
action="KICK",
|
||||
mod=author,
|
||||
user=user)
|
||||
user=user,
|
||||
reason=reason)
|
||||
await self.bot.say("Done. That felt good.")
|
||||
except discord.errors.Forbidden:
|
||||
await self.bot.say("I'm not allowed to do that.")
|
||||
@ -204,24 +277,42 @@ class Mod:
|
||||
|
||||
@commands.command(no_pm=True, pass_context=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def ban(self, ctx, user: discord.Member, days: int=0):
|
||||
async def ban(self, ctx, user: discord.Member, days: str = None, *, reason: str = None):
|
||||
"""Bans user and deletes last X days worth of messages.
|
||||
|
||||
If days is not a number, it's treated as the first word of the reason.
|
||||
Minimum 0 days, maximum 7. Defaults to 0."""
|
||||
author = ctx.message.author
|
||||
server = author.server
|
||||
|
||||
if days:
|
||||
if days.isdigit():
|
||||
days = int(days)
|
||||
else:
|
||||
if reason:
|
||||
reason = days + ' ' + reason
|
||||
else:
|
||||
reason = days
|
||||
days = 0
|
||||
else:
|
||||
days = 0
|
||||
|
||||
if days < 0 or days > 7:
|
||||
await self.bot.say("Invalid days. Must be between 0 and 7.")
|
||||
return
|
||||
|
||||
try:
|
||||
self._tmp_banned_cache.append(user)
|
||||
await self.bot.ban(user, days)
|
||||
logger.info("{}({}) banned {}({}), deleting {} days worth of messages".format(
|
||||
author.name, author.id, user.name, user.id, str(days)))
|
||||
if self.settings[server.id].get('ban_cases',
|
||||
default_settings['ban_cases']):
|
||||
await self.new_case(server,
|
||||
action="Ban \N{HAMMER}",
|
||||
action="BAN",
|
||||
mod=author,
|
||||
user=user)
|
||||
user=user,
|
||||
reason=reason)
|
||||
await self.bot.say("Done. It was about time.")
|
||||
except discord.errors.Forbidden:
|
||||
await self.bot.say("I'm not allowed to do that.")
|
||||
@ -233,7 +324,7 @@ class Mod:
|
||||
|
||||
@commands.command(no_pm=True, pass_context=True)
|
||||
@checks.admin_or_permissions(ban_members=True)
|
||||
async def softban(self, ctx, user: discord.Member):
|
||||
async def softban(self, ctx, user: discord.Member, *, reason: str = None):
|
||||
"""Kicks the user, deleting 1 day worth of messages."""
|
||||
server = ctx.message.server
|
||||
channel = ctx.message.channel
|
||||
@ -257,10 +348,13 @@ class Mod:
|
||||
logger.info("{}({}) softbanned {}({}), deleting 1 day worth "
|
||||
"of messages".format(author.name, author.id, user.name,
|
||||
user.id))
|
||||
if self.settings[server.id].get('softban_cases',
|
||||
default_settings['softban_cases']):
|
||||
await self.new_case(server,
|
||||
action="Softban \N{DASH SYMBOL} \N{HAMMER}",
|
||||
action="SOFTBAN",
|
||||
mod=author,
|
||||
user=user)
|
||||
user=user,
|
||||
reason=reason)
|
||||
await self.bot.unban(server, user)
|
||||
await self.bot.say("Done. Enough chaos.")
|
||||
except discord.errors.Forbidden:
|
||||
@ -292,18 +386,20 @@ class Mod:
|
||||
|
||||
@commands.group(pass_context=True, no_pm=True, invoke_without_command=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def mute(self, ctx, user : discord.Member):
|
||||
async def mute(self, ctx, user : discord.Member, *, reason: str = None):
|
||||
"""Mutes user in the channel/server
|
||||
|
||||
Defaults to channel"""
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.invoke(self.channel_mute, user=user)
|
||||
await ctx.invoke(self.channel_mute, user=user, reason=reason)
|
||||
|
||||
@mute.command(name="channel", pass_context=True, no_pm=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def channel_mute(self, ctx, user : discord.Member):
|
||||
@mute.command(name="channel", pass_context=True, no_pm=True)
|
||||
async def channel_mute(self, ctx, user : discord.Member, *, reason: str = None):
|
||||
"""Mutes user in the current channel"""
|
||||
author = ctx.message.author
|
||||
channel = ctx.message.channel
|
||||
server = ctx.message.server
|
||||
overwrites = channel.overwrites_for(user)
|
||||
if overwrites.send_messages is False:
|
||||
await self.bot.say("That user can't send messages in this "
|
||||
@ -319,12 +415,21 @@ class Mod:
|
||||
"lower than myself in the role hierarchy.")
|
||||
else:
|
||||
dataIO.save_json("data/mod/perms_cache.json", self._perms_cache)
|
||||
if self.settings[server.id].get('cmute_cases',
|
||||
default_settings['cmute_cases']):
|
||||
await self.new_case(server,
|
||||
action="CMUTE",
|
||||
channel=channel,
|
||||
mod=author,
|
||||
user=user,
|
||||
reason=reason)
|
||||
await self.bot.say("User has been muted in this channel.")
|
||||
|
||||
@mute.command(name="server", pass_context=True, no_pm=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
async def server_mute(self, ctx, user : discord.Member):
|
||||
@mute.command(name="server", pass_context=True, no_pm=True)
|
||||
async def server_mute(self, ctx, user : discord.Member, *, reason: str = None):
|
||||
"""Mutes user in the server"""
|
||||
author = ctx.message.author
|
||||
server = ctx.message.server
|
||||
register = {}
|
||||
for channel in server.channels:
|
||||
@ -350,6 +455,13 @@ class Mod:
|
||||
return
|
||||
self._perms_cache[user.id] = register
|
||||
dataIO.save_json("data/mod/perms_cache.json", self._perms_cache)
|
||||
if self.settings[server.id].get('smute_cases',
|
||||
default_settings['smute_cases']):
|
||||
await self.new_case(server,
|
||||
action="SMUTE",
|
||||
mod=author,
|
||||
user=user,
|
||||
reason=reason)
|
||||
await self.bot.say("User has been muted in this server.")
|
||||
|
||||
@commands.group(pass_context=True, no_pm=True, invoke_without_command=True)
|
||||
@ -361,8 +473,8 @@ class Mod:
|
||||
if ctx.invoked_subcommand is None:
|
||||
await ctx.invoke(self.channel_unmute, user=user)
|
||||
|
||||
@unmute.command(name="channel", pass_context=True, no_pm=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@unmute.command(name="channel", pass_context=True, no_pm=True)
|
||||
async def channel_unmute(self, ctx, user : discord.Member):
|
||||
"""Unmutes user in the current channel"""
|
||||
channel = ctx.message.channel
|
||||
@ -372,7 +484,7 @@ class Mod:
|
||||
"in this channel.")
|
||||
return
|
||||
if user.id in self._perms_cache:
|
||||
old_value = self._perms_cache[user.id].get(channel.id, None)
|
||||
old_value = self._perms_cache[user.id].get(channel.id)
|
||||
else:
|
||||
old_value = None
|
||||
overwrites.send_messages = old_value
|
||||
@ -397,8 +509,8 @@ class Mod:
|
||||
dataIO.save_json("data/mod/perms_cache.json", self._perms_cache)
|
||||
await self.bot.say("User has been unmuted in this channel.")
|
||||
|
||||
@unmute.command(name="server", pass_context=True, no_pm=True)
|
||||
@checks.mod_or_permissions(administrator=True)
|
||||
@unmute.command(name="server", pass_context=True, no_pm=True)
|
||||
async def server_unmute(self, ctx, user : discord.Member):
|
||||
"""Unmutes user in the server"""
|
||||
server = ctx.message.server
|
||||
@ -772,7 +884,7 @@ class Mod:
|
||||
reason = "{} {}".format(case, reason)
|
||||
else:
|
||||
reason = case
|
||||
case = self.last_case[server.id].get(author.id, None)
|
||||
case = self.last_case[server.id].get(author.id)
|
||||
if case is None:
|
||||
await send_cmd_help(ctx)
|
||||
return
|
||||
@ -1115,12 +1227,46 @@ class Mod:
|
||||
except:
|
||||
pass
|
||||
|
||||
def is_mod_or_superior(self, message):
|
||||
user = message.author
|
||||
server = message.server
|
||||
def is_admin_or_superior(self, obj):
|
||||
if isinstance(obj, discord.Message):
|
||||
user = obj.author
|
||||
elif isinstance(obj, discord.Member):
|
||||
user = obj
|
||||
elif isinstance(obj, discord.Role):
|
||||
pass
|
||||
else:
|
||||
raise TypeError('Only messages, members or roles may be passed')
|
||||
|
||||
server = obj.server
|
||||
admin_role = settings.get_server_admin(server)
|
||||
|
||||
if isinstance(obj, discord.Role):
|
||||
return obj.name == admin_role
|
||||
|
||||
if user.id == settings.owner:
|
||||
return True
|
||||
elif discord.utils.get(user.roles, name=admin_role):
|
||||
return True
|
||||
else:
|
||||
return False
|
||||
|
||||
def is_mod_or_superior(self, obj):
|
||||
if isinstance(obj, discord.Message):
|
||||
user = obj.author
|
||||
elif isinstance(obj, discord.Member):
|
||||
user = obj
|
||||
elif isinstance(obj, discord.Role):
|
||||
pass
|
||||
else:
|
||||
raise TypeError('Only messages, members or roles may be passed')
|
||||
|
||||
server = obj.server
|
||||
admin_role = settings.get_server_admin(server)
|
||||
mod_role = settings.get_server_mod(server)
|
||||
|
||||
if isinstance(obj, discord.Role):
|
||||
return obj.name in [admin_role, mod_role]
|
||||
|
||||
if user.id == settings.owner:
|
||||
return True
|
||||
elif discord.utils.get(user.roles, name=admin_role):
|
||||
@ -1130,46 +1276,40 @@ class Mod:
|
||||
else:
|
||||
return False
|
||||
|
||||
async def new_case(self, server, *, action, mod=None, user, reason=None):
|
||||
channel = server.get_channel(self.settings[server.id]["mod-log"])
|
||||
if channel is None:
|
||||
async def new_case(self, server, *, action, mod=None, user, reason=None, until=None, channel=None):
|
||||
mod_channel = server.get_channel(self.settings[server.id]["mod-log"])
|
||||
if mod_channel is None:
|
||||
return
|
||||
|
||||
if server.id in self.cases:
|
||||
case_n = len(self.cases[server.id]) + 1
|
||||
else:
|
||||
case_n = 1
|
||||
|
||||
case = {"case" : case_n,
|
||||
"action" : action,
|
||||
"user" : user.name,
|
||||
"user_id" : user.id,
|
||||
"reason" : reason,
|
||||
"moderator" : mod.name if mod is not None else None,
|
||||
"moderator_id" : mod.id if mod is not None else None}
|
||||
|
||||
if server.id not in self.cases:
|
||||
self.cases[server.id] = {}
|
||||
|
||||
tmp = case.copy()
|
||||
if case["reason"] is None:
|
||||
tmp["reason"] = "Type [p]reason {} <reason> to add it".format(case_n)
|
||||
if case["moderator"] is None:
|
||||
tmp["moderator"] = "Unknown"
|
||||
tmp["moderator_id"] = "Nobody has claimed responsibility yet"
|
||||
case_n = len(self.cases[server.id]) + 1
|
||||
|
||||
case_msg = ("**Case #{case}** | {action}\n"
|
||||
"**User:** {user} ({user_id})\n"
|
||||
"**Moderator:** {moderator} ({moderator_id})\n"
|
||||
"**Reason:** {reason}"
|
||||
"".format(**tmp))
|
||||
case = {
|
||||
"case" : case_n,
|
||||
"created" : datetime.utcnow().timestamp(),
|
||||
"modified" : None,
|
||||
"action" : action,
|
||||
"channel" : channel.id if channel else None,
|
||||
"user" : user.name,
|
||||
"user_id" : user.id,
|
||||
"reason" : reason,
|
||||
"moderator" : mod.name if mod is not None else None,
|
||||
"moderator_id" : mod.id if mod is not None else None,
|
||||
"amended_by" : None,
|
||||
"amended_id" : None,
|
||||
"message" : None,
|
||||
"until" : None,
|
||||
}
|
||||
|
||||
case_msg = self.format_case_msg(case)
|
||||
|
||||
try:
|
||||
msg = await self.bot.send_message(channel, case_msg)
|
||||
msg = await self.bot.send_message(mod_channel, case_msg)
|
||||
case["message"] = msg.id
|
||||
except:
|
||||
msg = None
|
||||
|
||||
case["message"] = msg.id if msg is not None else None
|
||||
pass
|
||||
|
||||
self.cases[server.id][str(case_n)] = case
|
||||
|
||||
@ -1178,7 +1318,8 @@ class Mod:
|
||||
|
||||
dataIO.save_json("data/mod/modlog.json", self.cases)
|
||||
|
||||
async def update_case(self, server, *, case, mod, reason):
|
||||
async def update_case(self, server, *, case, mod=None, reason=None,
|
||||
until=False):
|
||||
channel = server.get_channel(self.settings[server.id]["mod-log"])
|
||||
if channel is None:
|
||||
raise NoModLogChannel()
|
||||
@ -1188,17 +1329,23 @@ class Mod:
|
||||
|
||||
if case["moderator_id"] is not None:
|
||||
if case["moderator_id"] != mod.id:
|
||||
if self.is_admin_or_superior(mod):
|
||||
case["amended_by"] = mod.name
|
||||
case["amended_id"] = mod.id
|
||||
else:
|
||||
raise UnauthorizedCaseEdit()
|
||||
|
||||
case["reason"] = reason
|
||||
else:
|
||||
case["moderator"] = mod.name
|
||||
case["moderator_id"] = mod.id
|
||||
|
||||
case_msg = ("**Case #{case}** | {action}\n"
|
||||
"**User:** {user} ({user_id})\n"
|
||||
"**Moderator:** {moderator} ({moderator_id})\n"
|
||||
"**Reason:** {reason}"
|
||||
"".format(**case))
|
||||
if case["reason"]: # Existing reason
|
||||
case["modified"] = datetime.utcnow().timestamp()
|
||||
case["reason"] = reason
|
||||
|
||||
if until is not False:
|
||||
case["until"] = until
|
||||
|
||||
case_msg = self.format_case_msg(case)
|
||||
|
||||
dataIO.save_json("data/mod/modlog.json", self.cases)
|
||||
|
||||
@ -1208,6 +1355,53 @@ class Mod:
|
||||
else:
|
||||
raise CaseMessageNotFound()
|
||||
|
||||
def format_case_msg(self, case):
|
||||
tmp = case.copy()
|
||||
if case["reason"] is None:
|
||||
tmp["reason"] = "Type [p]reason %i <reason> to add it" % tmp["case"]
|
||||
if case["moderator"] is None:
|
||||
tmp["moderator"] = "Unknown"
|
||||
tmp["moderator_id"] = "Nobody has claimed responsibility yet"
|
||||
if case["action"] in ACTIONS_REPR:
|
||||
tmp["action"] = ' '.join(ACTIONS_REPR[tmp["action"]])
|
||||
|
||||
channel = case.get("channel")
|
||||
if channel:
|
||||
channel = self.bot.get_channel(channel)
|
||||
tmp["action"] += ' in ' + channel.mention
|
||||
|
||||
case_msg = (
|
||||
"**Case #{case}** | {action}\n"
|
||||
"**User:** {user} ({user_id})\n"
|
||||
"**Moderator:** {moderator} ({moderator_id})\n"
|
||||
).format(**tmp)
|
||||
|
||||
created = case.get('created')
|
||||
until = case.get('until')
|
||||
if created and until:
|
||||
start = datetime.fromtimestamp(created)
|
||||
end = datetime.fromtimestamp(until)
|
||||
end_fmt = end.strftime('%Y-%m-%d %H:%M:%S UTC')
|
||||
duration = end - start
|
||||
dur_fmt = strfdelta(duration)
|
||||
case_msg += ("**Until:** {}\n"
|
||||
"**Duration:** {}\n").format(end_fmt, dur_fmt)
|
||||
|
||||
amended = case.get('amended_by')
|
||||
if amended:
|
||||
amended_id = case.get('amended_id')
|
||||
case_msg += "**Amended by:** %s (%s)\n" % (amended, amended_id)
|
||||
|
||||
modified = case.get('modified')
|
||||
if modified:
|
||||
modified = datetime.fromtimestamp(modified)
|
||||
modified_fmt = modified.strftime('%Y-%m-%d %H:%M:%S UTC')
|
||||
case_msg += "**Last modified:** %s\n" % modified_fmt
|
||||
|
||||
case_msg += "**Reason:** %s\n" % tmp["reason"]
|
||||
|
||||
return case_msg
|
||||
|
||||
async def check_filter(self, message):
|
||||
server = message.server
|
||||
if server.id in self.filter.keys():
|
||||
@ -1259,7 +1453,7 @@ class Mod:
|
||||
"server {}".format(server.id))
|
||||
else:
|
||||
await self.new_case(server,
|
||||
action="Ban \N{HAMMER}",
|
||||
action="BAN",
|
||||
mod=server.me,
|
||||
user=author,
|
||||
reason="Mention spam (Autoban)")
|
||||
@ -1315,7 +1509,7 @@ class Mod:
|
||||
server = member.server
|
||||
await self.new_case(server,
|
||||
user=member,
|
||||
action="Ban \N{HAMMER}")
|
||||
action="BAN")
|
||||
|
||||
async def check_names(self, before, after):
|
||||
if before.name != after.name:
|
||||
@ -1351,6 +1545,27 @@ class Mod:
|
||||
return original == empty
|
||||
|
||||
|
||||
def strfdelta(delta):
|
||||
s = []
|
||||
if delta.days:
|
||||
ds = '%i day' % delta.days
|
||||
if delta.days > 1:
|
||||
ds += 's'
|
||||
s.append(ds)
|
||||
hrs, rem = divmod(delta.seconds, 60*60)
|
||||
if hrs:
|
||||
hs = '%i hr' % hrs
|
||||
if hrs > 1:
|
||||
hs += 's'
|
||||
s.append(hs)
|
||||
mins, secs = divmod(rem, 60)
|
||||
if mins:
|
||||
s.append('%i min' % mins)
|
||||
if secs:
|
||||
s.append('%i sec' % secs)
|
||||
return ' '.join(s)
|
||||
|
||||
|
||||
def check_folders():
|
||||
folders = ("data", "data/mod/")
|
||||
for folder in folders:
|
||||
@ -1384,7 +1599,7 @@ def setup(bot):
|
||||
global logger
|
||||
check_folders()
|
||||
check_files()
|
||||
logger = logging.getLogger("red.mod")
|
||||
logger = logging.getLogger("mod")
|
||||
# Prevents the logger from being loaded again in case of module reload
|
||||
if logger.level == 0:
|
||||
logger.setLevel(logging.INFO)
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user