mirror of
https://github.com/Cog-Creators/Red-DiscordBot.git
synced 2025-11-06 11:18:54 -05:00
[Mod] Added mod-log and various antispam features
Modlog with settable channel Toggeable autoban for X number of different mentions in a single message Toggeable deletion of repeated messages
This commit is contained in:
parent
55c87abf88
commit
0f6e788214
324
cogs/mod.py
324
cogs/mod.py
@ -3,12 +3,34 @@ from discord.ext import commands
|
|||||||
from .utils.dataIO import dataIO
|
from .utils.dataIO import dataIO
|
||||||
from .utils import checks
|
from .utils import checks
|
||||||
from __main__ import send_cmd_help, settings
|
from __main__ import send_cmd_help, settings
|
||||||
from collections import deque
|
from collections import deque, defaultdict
|
||||||
from cogs.utils.chat_formatting import escape_mass_mentions
|
from cogs.utils.chat_formatting import escape_mass_mentions, box
|
||||||
import os
|
import os
|
||||||
import logging
|
import logging
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
default_settings = {
|
||||||
|
"ban_mention_spam" : False,
|
||||||
|
"delete_repeats" : False,
|
||||||
|
"mod-log" : None
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class ModError(Exception):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class UnauthorizedCaseEdit(ModError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class CaseMessageNotFound(ModError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
class NoModLogChannel(ModError):
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
class Mod:
|
class Mod:
|
||||||
"""Moderation tools."""
|
"""Moderation tools."""
|
||||||
@ -21,18 +43,28 @@ class Mod:
|
|||||||
self.filter = dataIO.load_json("data/mod/filter.json")
|
self.filter = dataIO.load_json("data/mod/filter.json")
|
||||||
self.past_names = dataIO.load_json("data/mod/past_names.json")
|
self.past_names = dataIO.load_json("data/mod/past_names.json")
|
||||||
self.past_nicknames = dataIO.load_json("data/mod/past_nicknames.json")
|
self.past_nicknames = dataIO.load_json("data/mod/past_nicknames.json")
|
||||||
|
settings = dataIO.load_json("data/mod/settings.json")
|
||||||
|
self.settings = defaultdict(lambda: default_settings.copy(), settings)
|
||||||
|
self.cache = defaultdict(lambda: deque(maxlen=3))
|
||||||
|
self.cases = dataIO.load_json("data/mod/modlog.json")
|
||||||
|
self._tmp_banned_cache = []
|
||||||
|
|
||||||
@commands.group(pass_context=True, no_pm=True)
|
@commands.group(pass_context=True, no_pm=True)
|
||||||
@checks.serverowner_or_permissions(administrator=True)
|
@checks.serverowner_or_permissions(administrator=True)
|
||||||
async def modset(self, ctx):
|
async def modset(self, ctx):
|
||||||
"""Manages server administration settings."""
|
"""Manages server administration settings."""
|
||||||
if ctx.invoked_subcommand is None:
|
if ctx.invoked_subcommand is None:
|
||||||
|
server = ctx.message.server
|
||||||
await send_cmd_help(ctx)
|
await send_cmd_help(ctx)
|
||||||
msg = "```"
|
roles = settings.get_server(server).copy()
|
||||||
for k, v in settings.get_server(ctx.message.server).items():
|
_settings = {**self.settings[server.id], **roles}
|
||||||
msg += str(k) + ": " + str(v) + "\n"
|
msg = ("Admin role: {ADMIN_ROLE}\n"
|
||||||
msg += "```"
|
"Mod role: {MOD_ROLE}\n"
|
||||||
await self.bot.say(msg)
|
"Mod-log: {mod-log}\n"
|
||||||
|
"Delete repeats: {delete_repeats}\n"
|
||||||
|
"Ban mention spam: {ban_mention_spam}\n"
|
||||||
|
"".format(**_settings))
|
||||||
|
await self.bot.say(box(msg))
|
||||||
|
|
||||||
@modset.command(name="adminrole", pass_context=True, no_pm=True)
|
@modset.command(name="adminrole", pass_context=True, no_pm=True)
|
||||||
async def _modset_adminrole(self, ctx, role_name: str):
|
async def _modset_adminrole(self, ctx, role_name: str):
|
||||||
@ -52,15 +84,81 @@ class Mod:
|
|||||||
settings.set_server_mod(server, role_name)
|
settings.set_server_mod(server, role_name)
|
||||||
await self.bot.say("Mod role set to '{}'".format(role_name))
|
await self.bot.say("Mod role set to '{}'".format(role_name))
|
||||||
|
|
||||||
|
@modset.command(pass_context=True, no_pm=True)
|
||||||
|
async def modlog(self, ctx, channel : discord.Channel=None):
|
||||||
|
"""Sets a channel as mod log
|
||||||
|
|
||||||
|
Leaving the channel parameter empty will deactivate it"""
|
||||||
|
server = ctx.message.server
|
||||||
|
if channel:
|
||||||
|
self.settings[server.id]["mod-log"] = channel.id
|
||||||
|
await self.bot.say("Mod events will be sent to {}"
|
||||||
|
"".format(channel.mention))
|
||||||
|
else:
|
||||||
|
if self.settings[server.id]["mod-log"] is None:
|
||||||
|
await send_cmd_help(ctx)
|
||||||
|
return
|
||||||
|
self.settings[server.id]["mod-log"] = None
|
||||||
|
await self.bot.say("Mod log deactivated.")
|
||||||
|
dataIO.save_json("data/mod/settings.json", self.settings)
|
||||||
|
|
||||||
|
@modset.command(pass_context=True, no_pm=True)
|
||||||
|
async def banmentionspam(self, ctx, max_mentions : int=False):
|
||||||
|
"""Enables auto ban for messages mentioning X different people
|
||||||
|
|
||||||
|
Accepted values: 5 or superior"""
|
||||||
|
server = ctx.message.server
|
||||||
|
if max_mentions:
|
||||||
|
if max_mentions < 5:
|
||||||
|
max_mentions = 5
|
||||||
|
self.settings[server.id]["ban_mention_spam"] = max_mentions
|
||||||
|
await self.bot.say("Autoban for mention spam enabled. "
|
||||||
|
"Anyone mentioning {} or more different people "
|
||||||
|
"in a single message will be autobanned."
|
||||||
|
"".format(max_mentions))
|
||||||
|
else:
|
||||||
|
if self.settings[server.id]["ban_mention_spam"] is False:
|
||||||
|
await send_cmd_help(ctx)
|
||||||
|
return
|
||||||
|
self.settings[server.id]["ban_mention_spam"] = False
|
||||||
|
await self.bot.say("Autoban for mention spam disabled.")
|
||||||
|
dataIO.save_json("data/mod/settings.json", self.settings)
|
||||||
|
|
||||||
|
@modset.command(pass_context=True, no_pm=True)
|
||||||
|
async def deleterepeats(self, ctx):
|
||||||
|
"""Enables auto deletion of repeated messages"""
|
||||||
|
server = ctx.message.server
|
||||||
|
if not self.settings[server.id]["delete_repeats"]:
|
||||||
|
self.settings[server.id]["delete_repeats"] = True
|
||||||
|
await self.bot.say("Messages repeated up to 3 times will"
|
||||||
|
"be deleted.")
|
||||||
|
else:
|
||||||
|
self.settings[server.id]["delete_repeats"] = False
|
||||||
|
await self.bot.say("Repeated messages will be ignored.")
|
||||||
|
dataIO.save_json("data/mod/settings.json", self.settings)
|
||||||
|
|
||||||
|
@modset.command(pass_context=True, no_pm=True)
|
||||||
|
async def resetcases(self, ctx):
|
||||||
|
"""Resets modlog's cases"""
|
||||||
|
server = ctx.message.server
|
||||||
|
self.cases[server.id] = {}
|
||||||
|
dataIO.save_json("data/mod/modlog.json", self.cases)
|
||||||
|
await self.bot.say("Cases have been reset.")
|
||||||
|
|
||||||
@commands.command(no_pm=True, pass_context=True)
|
@commands.command(no_pm=True, pass_context=True)
|
||||||
@checks.admin_or_permissions(kick_members=True)
|
@checks.admin_or_permissions(kick_members=True)
|
||||||
async def kick(self, ctx, user: discord.Member):
|
async def kick(self, ctx, user: discord.Member):
|
||||||
"""Kicks user."""
|
"""Kicks user."""
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
|
server = author.server
|
||||||
try:
|
try:
|
||||||
await self.bot.kick(user)
|
await self.bot.kick(user)
|
||||||
logger.info("{}({}) kicked {}({})".format(
|
logger.info("{}({}) kicked {}({})".format(
|
||||||
author.name, author.id, user.name, user.id))
|
author.name, author.id, user.name, user.id))
|
||||||
|
await self.new_case(server,
|
||||||
|
action="Kick \N{WOMANS BOOTS}",
|
||||||
|
mod=author,
|
||||||
|
user=user)
|
||||||
await self.bot.say("Done. That felt good.")
|
await self.bot.say("Done. That felt good.")
|
||||||
except discord.errors.Forbidden:
|
except discord.errors.Forbidden:
|
||||||
await self.bot.say("I'm not allowed to do that.")
|
await self.bot.say("I'm not allowed to do that.")
|
||||||
@ -74,18 +172,27 @@ class Mod:
|
|||||||
|
|
||||||
Minimum 0 days, maximum 7. Defaults to 0."""
|
Minimum 0 days, maximum 7. Defaults to 0."""
|
||||||
author = ctx.message.author
|
author = ctx.message.author
|
||||||
|
server = author.server
|
||||||
if days < 0 or days > 7:
|
if days < 0 or days > 7:
|
||||||
await self.bot.say("Invalid days. Must be between 0 and 7.")
|
await self.bot.say("Invalid days. Must be between 0 and 7.")
|
||||||
return
|
return
|
||||||
try:
|
try:
|
||||||
|
self._tmp_banned_cache.append(user)
|
||||||
await self.bot.ban(user, days)
|
await self.bot.ban(user, days)
|
||||||
logger.info("{}({}) banned {}({}), deleting {} days worth of messages".format(
|
logger.info("{}({}) banned {}({}), deleting {} days worth of messages".format(
|
||||||
author.name, author.id, user.name, user.id, str(days)))
|
author.name, author.id, user.name, user.id, str(days)))
|
||||||
|
await self.new_case(server,
|
||||||
|
action="Ban \N{HAMMER}",
|
||||||
|
mod=author,
|
||||||
|
user=user)
|
||||||
await self.bot.say("Done. It was about time.")
|
await self.bot.say("Done. It was about time.")
|
||||||
except discord.errors.Forbidden:
|
except discord.errors.Forbidden:
|
||||||
await self.bot.say("I'm not allowed to do that.")
|
await self.bot.say("I'm not allowed to do that.")
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
finally:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
self._tmp_banned_cache.remove(user)
|
||||||
|
|
||||||
@commands.command(no_pm=True, pass_context=True)
|
@commands.command(no_pm=True, pass_context=True)
|
||||||
@checks.admin_or_permissions(ban_members=True)
|
@checks.admin_or_permissions(ban_members=True)
|
||||||
@ -108,10 +215,15 @@ class Mod:
|
|||||||
"You can now join the server again.{}".format(invite))
|
"You can now join the server again.{}".format(invite))
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
|
self._tmp_banned_cache.append(user)
|
||||||
await self.bot.ban(user, 1)
|
await self.bot.ban(user, 1)
|
||||||
logger.info("{}({}) softbanned {}({}), deleting 1 day worth "
|
logger.info("{}({}) softbanned {}({}), deleting 1 day worth "
|
||||||
"of messages".format(author.name, author.id, user.name,
|
"of messages".format(author.name, author.id, user.name,
|
||||||
user.id))
|
user.id))
|
||||||
|
await self.new_case(server,
|
||||||
|
action="Softban \N{DASH SYMBOL} \N{HAMMER}",
|
||||||
|
mod=author,
|
||||||
|
user=user)
|
||||||
await self.bot.unban(server, user)
|
await self.bot.unban(server, user)
|
||||||
await self.bot.say("Done. Enough chaos.")
|
await self.bot.say("Done. Enough chaos.")
|
||||||
except discord.errors.Forbidden:
|
except discord.errors.Forbidden:
|
||||||
@ -119,6 +231,9 @@ class Mod:
|
|||||||
await self.bot.delete_message(msg)
|
await self.bot.delete_message(msg)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(e)
|
print(e)
|
||||||
|
finally:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
self._tmp_banned_cache.remove(user)
|
||||||
else:
|
else:
|
||||||
await self.bot.say("I'm not allowed to do that.")
|
await self.bot.say("I'm not allowed to do that.")
|
||||||
|
|
||||||
@ -317,6 +432,27 @@ class Mod:
|
|||||||
else:
|
else:
|
||||||
await self.slow_deletion(to_delete)
|
await self.slow_deletion(to_delete)
|
||||||
|
|
||||||
|
@commands.command(pass_context=True)
|
||||||
|
@checks.mod_or_permissions(manage_messages=True)
|
||||||
|
async def reason(self, ctx, case : int, *, reason : str):
|
||||||
|
author = ctx.message.author
|
||||||
|
server = author.server
|
||||||
|
case = str(case)
|
||||||
|
try:
|
||||||
|
await self.update_case(server, case=case, mod=author,
|
||||||
|
reason=reason)
|
||||||
|
except UnauthorizedCaseEdit:
|
||||||
|
await self.bot.say("That case is not yours.")
|
||||||
|
except KeyError:
|
||||||
|
await self.bot.say("That case doesn't exist.")
|
||||||
|
except NoModLogChannel:
|
||||||
|
await self.bot.say("There's no mod-log channel set.")
|
||||||
|
except CaseMessageNotFound:
|
||||||
|
await self.bot.say("Couldn't find the case's message.")
|
||||||
|
else:
|
||||||
|
await self.bot.say("Case updated.")
|
||||||
|
|
||||||
|
|
||||||
@commands.group(pass_context=True)
|
@commands.group(pass_context=True)
|
||||||
@checks.is_owner()
|
@checks.is_owner()
|
||||||
async def blacklist(self, ctx):
|
async def blacklist(self, ctx):
|
||||||
@ -642,15 +778,7 @@ class Mod:
|
|||||||
pass
|
pass
|
||||||
await asyncio.sleep(1.5)
|
await asyncio.sleep(1.5)
|
||||||
|
|
||||||
async def _delete_message(self, message):
|
def is_mod_or_superior(self, message):
|
||||||
try:
|
|
||||||
await self.bot.delete_message(message)
|
|
||||||
except discord.errors.NotFound:
|
|
||||||
pass
|
|
||||||
except:
|
|
||||||
raise
|
|
||||||
|
|
||||||
def immune_from_filter(self, message):
|
|
||||||
user = message.author
|
user = message.author
|
||||||
server = message.server
|
server = message.server
|
||||||
admin_role = settings.get_server_admin(server)
|
admin_role = settings.get_server_admin(server)
|
||||||
@ -665,26 +793,157 @@ class Mod:
|
|||||||
else:
|
else:
|
||||||
return False
|
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:
|
||||||
|
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 responsability yet"
|
||||||
|
|
||||||
|
case_msg = ("**Case #{case}** | {action}\n"
|
||||||
|
"**User:** {user} ({user_id})\n"
|
||||||
|
"**Moderator:** {moderator} ({moderator_id})\n"
|
||||||
|
"**Reason:** {reason}"
|
||||||
|
"".format(**tmp))
|
||||||
|
|
||||||
|
try:
|
||||||
|
msg = await self.bot.send_message(channel, case_msg)
|
||||||
|
except:
|
||||||
|
msg = None
|
||||||
|
|
||||||
|
case["message"] = msg.id if msg is not None else None
|
||||||
|
|
||||||
|
self.cases[server.id][str(case_n)] = case
|
||||||
|
|
||||||
|
dataIO.save_json("data/mod/modlog.json", self.cases)
|
||||||
|
|
||||||
|
async def update_case(self, server, *, case, mod, reason):
|
||||||
|
channel = server.get_channel(self.settings[server.id]["mod-log"])
|
||||||
|
if channel is None:
|
||||||
|
raise NoModLogChannel()
|
||||||
|
|
||||||
|
case = str(case)
|
||||||
|
case = self.cases[server.id][case]
|
||||||
|
|
||||||
|
if case["moderator_id"] is not None:
|
||||||
|
if case["moderator_id"] != mod.id:
|
||||||
|
raise UnauthorizedCaseEdit()
|
||||||
|
|
||||||
|
case["reason"] = reason
|
||||||
|
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))
|
||||||
|
|
||||||
|
dataIO.save_json("data/mod/modlog.json", self.cases)
|
||||||
|
|
||||||
|
msg = await self.bot.get_message(channel, case["message"])
|
||||||
|
if msg:
|
||||||
|
await self.bot.edit_message(msg, case_msg.format(**case))
|
||||||
|
else:
|
||||||
|
raise CaseMessageNotFound()
|
||||||
|
|
||||||
async def check_filter(self, message):
|
async def check_filter(self, message):
|
||||||
if message.channel.is_private:
|
|
||||||
return
|
|
||||||
server = message.server
|
server = message.server
|
||||||
can_delete = message.channel.permissions_for(server.me).manage_messages
|
|
||||||
|
|
||||||
if (message.author.id == self.bot.user.id or
|
|
||||||
self.immune_from_filter(message) or not can_delete): # Owner, admins and mods are immune to the filter
|
|
||||||
return
|
|
||||||
|
|
||||||
if server.id in self.filter.keys():
|
if server.id in self.filter.keys():
|
||||||
for w in self.filter[server.id]:
|
for w in self.filter[server.id]:
|
||||||
if w in message.content.lower():
|
if w in message.content.lower():
|
||||||
# Something else in discord.py is throwing a 404 error
|
|
||||||
# after deletion
|
|
||||||
try:
|
try:
|
||||||
await self._delete_message(message)
|
await self.bot.delete_message(message)
|
||||||
|
logger.info("Message deleted in server {}."
|
||||||
|
"Filtered: {}"
|
||||||
|
"".format(server.id, w))
|
||||||
|
return True
|
||||||
except:
|
except:
|
||||||
pass
|
pass
|
||||||
print("Message deleted. Filtered: " + w)
|
return False
|
||||||
|
|
||||||
|
async def check_duplicates(self, message):
|
||||||
|
server = message.server
|
||||||
|
author = message.author
|
||||||
|
if server.id not in self.settings:
|
||||||
|
return False
|
||||||
|
if self.settings[server.id]["delete_repeats"]:
|
||||||
|
self.cache[author].append(message)
|
||||||
|
msgs = self.cache[author]
|
||||||
|
if len(msgs) == 3 and \
|
||||||
|
msgs[0].content == msgs[1].content == msgs[2].content:
|
||||||
|
try:
|
||||||
|
await self.bot.delete_message(message)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def check_mention_spam(self, message):
|
||||||
|
server = message.server
|
||||||
|
author = message.author
|
||||||
|
if server.id not in self.settings:
|
||||||
|
return False
|
||||||
|
if self.settings[server.id]["ban_mention_spam"]:
|
||||||
|
max_mentions = self.settings[server.id]["ban_mention_spam"]
|
||||||
|
mentions = set(message.mentions)
|
||||||
|
if len(mentions) >= max_mentions:
|
||||||
|
try:
|
||||||
|
self._tmp_banned_cache.append(author)
|
||||||
|
await self.bot.ban(author, 1)
|
||||||
|
except:
|
||||||
|
logger.info("Failed to ban member for mention spam in "
|
||||||
|
"server {}".format(server.id))
|
||||||
|
else:
|
||||||
|
await self.new_case(server,
|
||||||
|
action="Ban \N{HAMMER}",
|
||||||
|
mod=server.me,
|
||||||
|
user=author,
|
||||||
|
reason="Mention spam (Autoban)")
|
||||||
|
return True
|
||||||
|
finally:
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
self._tmp_banned_cache.remove(author)
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def on_message(self, message):
|
||||||
|
if message.channel.is_private or self.bot.user == message.author:
|
||||||
|
return
|
||||||
|
elif self.is_mod_or_superior(message):
|
||||||
|
return
|
||||||
|
deleted = await self.check_filter(message)
|
||||||
|
if not deleted:
|
||||||
|
deleted = await self.check_duplicates(message)
|
||||||
|
if not deleted:
|
||||||
|
deleted = await self.check_mention_spam(message)
|
||||||
|
|
||||||
|
async def on_member_ban(self, member):
|
||||||
|
if member not in self._tmp_banned_cache:
|
||||||
|
server = member.server
|
||||||
|
await self.new_case(server,
|
||||||
|
user=member,
|
||||||
|
action="Ban \N{HAMMER}")
|
||||||
|
|
||||||
async def check_names(self, before, after):
|
async def check_names(self, before, after):
|
||||||
if before.name != after.name:
|
if before.name != after.name:
|
||||||
@ -748,6 +1007,14 @@ def check_files():
|
|||||||
print("Creating empty past_nicknames.json...")
|
print("Creating empty past_nicknames.json...")
|
||||||
dataIO.save_json("data/mod/past_nicknames.json", {})
|
dataIO.save_json("data/mod/past_nicknames.json", {})
|
||||||
|
|
||||||
|
if not os.path.isfile("data/mod/settings.json"):
|
||||||
|
print("Creating empty settings.json...")
|
||||||
|
dataIO.save_json("data/mod/settings.json", {})
|
||||||
|
|
||||||
|
if not os.path.isfile("data/mod/modlog.json"):
|
||||||
|
print("Creating empty modlog.json...")
|
||||||
|
dataIO.save_json("data/mod/modlog.json", {})
|
||||||
|
|
||||||
|
|
||||||
def setup(bot):
|
def setup(bot):
|
||||||
global logger
|
global logger
|
||||||
@ -763,6 +1030,5 @@ def setup(bot):
|
|||||||
logging.Formatter('%(asctime)s %(message)s', datefmt="[%d/%m/%Y %H:%M]"))
|
logging.Formatter('%(asctime)s %(message)s', datefmt="[%d/%m/%Y %H:%M]"))
|
||||||
logger.addHandler(handler)
|
logger.addHandler(handler)
|
||||||
n = Mod(bot)
|
n = Mod(bot)
|
||||||
bot.add_listener(n.check_filter, "on_message")
|
|
||||||
bot.add_listener(n.check_names, "on_member_update")
|
bot.add_listener(n.check_names, "on_member_update")
|
||||||
bot.add_cog(n)
|
bot.add_cog(n)
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user