2016-10-23 01:51:08 +02:00

1057 lines
40 KiB
Python

import discord
from discord.ext import commands
from .utils.dataIO import dataIO
from .utils import checks
from __main__ import send_cmd_help, settings
from collections import deque, defaultdict
from cogs.utils.chat_formatting import escape_mass_mentions, box
import os
import logging
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:
"""Moderation tools."""
def __init__(self, bot):
self.bot = bot
self.whitelist_list = dataIO.load_json("data/mod/whitelist.json")
self.blacklist_list = dataIO.load_json("data/mod/blacklist.json")
self.ignore_list = dataIO.load_json("data/mod/ignorelist.json")
self.filter = dataIO.load_json("data/mod/filter.json")
self.past_names = dataIO.load_json("data/mod/past_names.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 = []
self.last_case = defaultdict(lambda: dict())
@commands.group(pass_context=True, no_pm=True)
@checks.serverowner_or_permissions(administrator=True)
async def modset(self, ctx):
"""Manages server administration settings."""
if ctx.invoked_subcommand is None:
server = ctx.message.server
await send_cmd_help(ctx)
roles = settings.get_server(server).copy()
_settings = {**self.settings[server.id], **roles}
msg = ("Admin role: {ADMIN_ROLE}\n"
"Mod role: {MOD_ROLE}\n"
"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)
async def _modset_adminrole(self, ctx, role_name: str):
"""Sets the admin role for this server, case insensitive."""
server = ctx.message.server
if server.id not in settings.servers:
await self.bot.say("Remember to set modrole too.")
settings.set_server_admin(server, role_name)
await self.bot.say("Admin role set to '{}'".format(role_name))
@modset.command(name="modrole", pass_context=True, no_pm=True)
async def _modset_modrole(self, ctx, role_name: str):
"""Sets the mod role for this server, case insensitive."""
server = ctx.message.server
if server.id not in settings.servers:
await self.bot.say("Remember to set adminrole too.")
settings.set_server_mod(server, 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)
@checks.admin_or_permissions(kick_members=True)
async def kick(self, ctx, user: discord.Member):
"""Kicks user."""
author = ctx.message.author
server = author.server
try:
await self.bot.kick(user)
logger.info("{}({}) kicked {}({})".format(
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.")
except discord.errors.Forbidden:
await self.bot.say("I'm not allowed to do that.")
except Exception as e:
print(e)
@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):
"""Bans user and deletes last X days worth of messages.
Minimum 0 days, maximum 7. Defaults to 0."""
author = ctx.message.author
server = author.server
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)))
await self.new_case(server,
action="Ban \N{HAMMER}",
mod=author,
user=user)
await self.bot.say("Done. It was about time.")
except discord.errors.Forbidden:
await self.bot.say("I'm not allowed to do that.")
except Exception as e:
print(e)
finally:
await asyncio.sleep(1)
self._tmp_banned_cache.remove(user)
@commands.command(no_pm=True, pass_context=True)
@checks.admin_or_permissions(ban_members=True)
async def softban(self, ctx, user: discord.Member):
"""Kicks the user, deleting 1 day worth of messages."""
server = ctx.message.server
channel = ctx.message.channel
can_ban = channel.permissions_for(server.me).ban_members
author = ctx.message.author
try:
invite = await self.bot.create_invite(server, max_age=3600*24)
invite = "\nInvite: " + invite
except:
invite = ""
if can_ban:
try:
try: # We don't want blocked DMs preventing us from banning
msg = await self.bot.send_message(user, "You have been banned and "
"then unbanned as a quick way to delete your messages.\n"
"You can now join the server again.{}".format(invite))
except:
pass
self._tmp_banned_cache.append(user)
await self.bot.ban(user, 1)
logger.info("{}({}) softbanned {}({}), deleting 1 day worth "
"of messages".format(author.name, author.id, user.name,
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.say("Done. Enough chaos.")
except discord.errors.Forbidden:
await self.bot.say("My role is not high enough to softban that user.")
await self.bot.delete_message(msg)
except Exception as e:
print(e)
finally:
await asyncio.sleep(1)
self._tmp_banned_cache.remove(user)
else:
await self.bot.say("I'm not allowed to do that.")
@commands.command(no_pm=True, pass_context=True)
@checks.admin_or_permissions(manage_nicknames=True)
async def rename(self, ctx, user : discord.Member, *, nickname=""):
"""Changes user's nickname
Leaving the nickname empty will remove it."""
nickname = nickname.strip()
if nickname == "":
nickname = None
try:
await self.bot.change_nickname(user, nickname)
await self.bot.say("Done.")
except discord.Forbidden:
await self.bot.say("I cannot do that, I lack the "
"\"Manage Nicknames\" permission.")
@commands.group(pass_context=True, no_pm=True)
@checks.mod_or_permissions(manage_messages=True)
async def cleanup(self, ctx):
"""Deletes messages.
cleanup messages [number]
cleanup user [name/mention] [number]
cleanup text \"Text here\" [number]"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@cleanup.command(pass_context=True, no_pm=True)
async def text(self, ctx, text: str, number: int):
"""Deletes last X messages matching the specified text.
Example:
cleanup text \"test\" 5
Remember to use double quotes."""
channel = ctx.message.channel
author = ctx.message.author
server = author.server
is_bot = self.bot.user.bot
has_permissions = channel.permissions_for(server.me).manage_messages
def check(m):
if text in m.content:
return True
elif m == ctx.message:
return True
else:
return False
to_delete = [ctx.message]
if not has_permissions:
await self.bot.say("I'm not allowed to delete messages.")
return
tries_left = 5
tmp = ctx.message
while tries_left and len(to_delete) - 1 < number:
async for message in self.bot.logs_from(channel, limit=100,
before=tmp):
if len(to_delete) - 1 < number and check(message):
to_delete.append(message)
tmp = message
tries_left -= 1
logger.info("{}({}) deleted {} messages "
" containing '{}' in channel {}".format(author.name,
author.id, len(to_delete), text, channel.id))
if is_bot:
await self.mass_purge(to_delete)
else:
await self.slow_deletion(to_delete)
@cleanup.command(pass_context=True, no_pm=True)
async def user(self, ctx, user: discord.Member, number: int):
"""Deletes last X messages from specified user.
Examples:
cleanup user @\u200bTwentysix 2
cleanup user Red 6"""
channel = ctx.message.channel
author = ctx.message.author
server = author.server
is_bot = self.bot.user.bot
has_permissions = channel.permissions_for(server.me).manage_messages
def check(m):
if m.author == user:
return True
elif m == ctx.message:
return True
else:
return False
to_delete = [ctx.message]
if not has_permissions:
await self.bot.say("I'm not allowed to delete messages.")
return
tries_left = 5
tmp = ctx.message
while tries_left and len(to_delete) - 1 < number:
async for message in self.bot.logs_from(channel, limit=100,
before=tmp):
if len(to_delete) -1 < number and check(message):
to_delete.append(message)
tmp = message
tries_left -= 1
logger.info("{}({}) deleted {} messages "
" made by {}({}) in channel {}"
"".format(author.name, author.id, len(to_delete),
user.name, user.id, channel.name))
if is_bot:
await self.mass_purge(to_delete)
else:
await self.slow_deletion(to_delete)
@cleanup.command(pass_context=True, no_pm=True)
async def after(self, ctx, message_id : int):
"""Deletes all messages after specified message
To get a message id, enable developer mode in Discord's
settings, 'appearance' tab. Then right click a message
and copy its id.
"""
channel = ctx.message.channel
author = ctx.message.author
server = channel.server
is_bot = self.bot.user.bot
has_permissions = channel.permissions_for(server.me).manage_messages
to_delete = []
after = await self.bot.get_message(channel, message_id)
if not has_permissions:
await self.bot.say("I'm not allowed to delete messages.")
return
elif not after:
await self.bot.say("Message not found.")
return
async for message in self.bot.logs_from(channel, limit=2000,
after=after):
to_delete.append(message)
logger.info("{}({}) deleted {} messages in channel {}"
"".format(author.name, author.id,
len(to_delete), channel.name))
if is_bot:
await self.mass_purge(to_delete)
else:
await self.slow_deletion(to_delete)
@cleanup.command(pass_context=True, no_pm=True)
async def messages(self, ctx, number: int):
"""Deletes last X messages.
Example:
cleanup messages 26"""
channel = ctx.message.channel
author = ctx.message.author
server = author.server
is_bot = self.bot.user.bot
has_permissions = channel.permissions_for(server.me).manage_messages
to_delete = []
if not has_permissions:
await self.bot.say("I'm not allowed to delete messages.")
return
async for message in self.bot.logs_from(channel, limit=number+1):
to_delete.append(message)
logger.info("{}({}) deleted {} messages in channel {}"
"".format(author.name, author.id,
number, channel.name))
if is_bot:
await self.mass_purge(to_delete)
else:
await self.slow_deletion(to_delete)
@commands.command(pass_context=True)
@checks.mod_or_permissions(manage_messages=True)
async def reason(self, ctx, case, *, reason : str=""):
"""Lets you specify a reason for mod-log's cases
Defaults to last case assigned to yourself, if available."""
author = ctx.message.author
server = author.server
try:
case = int(case)
if not reason:
await send_cmd_help(ctx)
return
except:
if reason:
reason = "{} {}".format(case, reason)
else:
reason = case
case = self.last_case[server.id].get(author.id, None)
if case is None:
await send_cmd_help(ctx)
return
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.".format(case))
@commands.group(pass_context=True)
@checks.is_owner()
async def blacklist(self, ctx):
"""Bans user from using the bot"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@blacklist.command(name="add")
async def _blacklist_add(self, user: discord.Member):
"""Adds user to bot's blacklist"""
if user.id not in self.blacklist_list:
self.blacklist_list.append(user.id)
dataIO.save_json("data/mod/blacklist.json", self.blacklist_list)
await self.bot.say("User has been added to blacklist.")
else:
await self.bot.say("User is already blacklisted.")
@blacklist.command(name="remove")
async def _blacklist_remove(self, user: discord.Member):
"""Removes user from bot's blacklist"""
if user.id in self.blacklist_list:
self.blacklist_list.remove(user.id)
dataIO.save_json("data/mod/blacklist.json", self.blacklist_list)
await self.bot.say("User has been removed from blacklist.")
else:
await self.bot.say("User is not in blacklist.")
@blacklist.command(name="clear")
async def _blacklist_clear(self):
"""Clears the blacklist"""
self.blacklist_list = []
dataIO.save_json("data/mod/blacklist.json", self.blacklist_list)
await self.bot.say("Blacklist is now empty.")
@commands.group(pass_context=True)
@checks.is_owner()
async def whitelist(self, ctx):
"""Users who will be able to use the bot"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@whitelist.command(name="add")
async def _whitelist_add(self, user: discord.Member):
"""Adds user to bot's whitelist"""
if user.id not in self.whitelist_list:
if not self.whitelist_list:
msg = "\nAll users not in whitelist will be ignored (owner, admins and mods excluded)"
else:
msg = ""
self.whitelist_list.append(user.id)
dataIO.save_json("data/mod/whitelist.json", self.whitelist_list)
await self.bot.say("User has been added to whitelist." + msg)
else:
await self.bot.say("User is already whitelisted.")
@whitelist.command(name="remove")
async def _whitelist_remove(self, user: discord.Member):
"""Removes user from bot's whitelist"""
if user.id in self.whitelist_list:
self.whitelist_list.remove(user.id)
dataIO.save_json("data/mod/whitelist.json", self.whitelist_list)
await self.bot.say("User has been removed from whitelist.")
else:
await self.bot.say("User is not in whitelist.")
@whitelist.command(name="clear")
async def _whitelist_clear(self):
"""Clears the whitelist"""
self.whitelist_list = []
dataIO.save_json("data/mod/whitelist.json", self.whitelist_list)
await self.bot.say("Whitelist is now empty.")
@commands.group(pass_context=True, no_pm=True)
@checks.admin_or_permissions(manage_channels=True)
async def ignore(self, ctx):
"""Adds servers/channels to ignorelist"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
await self.bot.say(self.count_ignored())
@ignore.command(name="channel", pass_context=True)
async def ignore_channel(self, ctx, channel: discord.Channel=None):
"""Ignores channel
Defaults to current one"""
current_ch = ctx.message.channel
if not channel:
if current_ch.id not in self.ignore_list["CHANNELS"]:
self.ignore_list["CHANNELS"].append(current_ch.id)
dataIO.save_json("data/mod/ignorelist.json", self.ignore_list)
await self.bot.say("Channel added to ignore list.")
else:
await self.bot.say("Channel already in ignore list.")
else:
if channel.id not in self.ignore_list["CHANNELS"]:
self.ignore_list["CHANNELS"].append(channel.id)
dataIO.save_json("data/mod/ignorelist.json", self.ignore_list)
await self.bot.say("Channel added to ignore list.")
else:
await self.bot.say("Channel already in ignore list.")
@ignore.command(name="server", pass_context=True)
async def ignore_server(self, ctx):
"""Ignores current server"""
server = ctx.message.server
if server.id not in self.ignore_list["SERVERS"]:
self.ignore_list["SERVERS"].append(server.id)
dataIO.save_json("data/mod/ignorelist.json", self.ignore_list)
await self.bot.say("This server has been added to the ignore list.")
else:
await self.bot.say("This server is already being ignored.")
@commands.group(pass_context=True, no_pm=True)
@checks.admin_or_permissions(manage_channels=True)
async def unignore(self, ctx):
"""Removes servers/channels from ignorelist"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
await self.bot.say(self.count_ignored())
@unignore.command(name="channel", pass_context=True)
async def unignore_channel(self, ctx, channel: discord.Channel=None):
"""Removes channel from ignore list
Defaults to current one"""
current_ch = ctx.message.channel
if not channel:
if current_ch.id in self.ignore_list["CHANNELS"]:
self.ignore_list["CHANNELS"].remove(current_ch.id)
dataIO.save_json("data/mod/ignorelist.json", self.ignore_list)
await self.bot.say("This channel has been removed from the ignore list.")
else:
await self.bot.say("This channel is not in the ignore list.")
else:
if channel.id in self.ignore_list["CHANNELS"]:
self.ignore_list["CHANNELS"].remove(channel.id)
dataIO.save_json("data/mod/ignorelist.json", self.ignore_list)
await self.bot.say("Channel removed from ignore list.")
else:
await self.bot.say("That channel is not in the ignore list.")
@unignore.command(name="server", pass_context=True)
async def unignore_server(self, ctx):
"""Removes current server from ignore list"""
server = ctx.message.server
if server.id in self.ignore_list["SERVERS"]:
self.ignore_list["SERVERS"].remove(server.id)
dataIO.save_json("data/mod/ignorelist.json", self.ignore_list)
await self.bot.say("This server has been removed from the ignore list.")
else:
await self.bot.say("This server is not in the ignore list.")
def count_ignored(self):
msg = "```Currently ignoring:\n"
msg += str(len(self.ignore_list["CHANNELS"])) + " channels\n"
msg += str(len(self.ignore_list["SERVERS"])) + " servers\n```\n"
return msg
@commands.group(name="filter", pass_context=True, no_pm=True)
@checks.mod_or_permissions(manage_messages=True)
async def _filter(self, ctx):
"""Adds/removes words from filter
Use double quotes to add/remove sentences
Using this command with no subcommands will send
the list of the server's filtered words."""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
server = ctx.message.server
author = ctx.message.author
msg = ""
if server.id in self.filter.keys():
if self.filter[server.id] != []:
word_list = self.filter[server.id]
for w in word_list:
msg += '"' + w + '" '
await self.bot.send_message(author, "Words filtered in this server: " + msg)
@_filter.command(name="add", pass_context=True)
async def filter_add(self, ctx, *words: str):
"""Adds words to the filter
Use double quotes to add sentences
Examples:
filter add word1 word2 word3
filter add \"This is a sentence\""""
if words == ():
await send_cmd_help(ctx)
return
server = ctx.message.server
added = 0
if server.id not in self.filter.keys():
self.filter[server.id] = []
for w in words:
if w.lower() not in self.filter[server.id] and w != "":
self.filter[server.id].append(w.lower())
added += 1
if added:
dataIO.save_json("data/mod/filter.json", self.filter)
await self.bot.say("Words added to filter.")
else:
await self.bot.say("Words already in the filter.")
@_filter.command(name="remove", pass_context=True)
async def filter_remove(self, ctx, *words: str):
"""Remove words from the filter
Use double quotes to remove sentences
Examples:
filter remove word1 word2 word3
filter remove \"This is a sentence\""""
if words == ():
await send_cmd_help(ctx)
return
server = ctx.message.server
removed = 0
if server.id not in self.filter.keys():
await self.bot.say("There are no filtered words in this server.")
return
for w in words:
if w.lower() in self.filter[server.id]:
self.filter[server.id].remove(w.lower())
removed += 1
if removed:
dataIO.save_json("data/mod/filter.json", self.filter)
await self.bot.say("Words removed from filter.")
else:
await self.bot.say("Those words weren't in the filter.")
@commands.group(no_pm=True, pass_context=True)
@checks.admin_or_permissions(manage_roles=True)
async def editrole(self, ctx):
"""Edits roles settings"""
if ctx.invoked_subcommand is None:
await send_cmd_help(ctx)
@editrole.command(aliases=["color"], pass_context=True)
async def colour(self, ctx, role: discord.Role, value: discord.Colour):
"""Edits a role's colour
Use double quotes if the role contains spaces.
Colour must be in hexadecimal format.
\"http://www.w3schools.com/colors/colors_picker.asp\"
Examples:
!editrole colour \"The Transistor\" #ff0000
!editrole colour Test #ff9900"""
author = ctx.message.author
try:
await self.bot.edit_role(ctx.message.server, role, color=value)
logger.info("{}({}) changed the colour of role '{}'".format(
author.name, author.id, role.name))
await self.bot.say("Done.")
except discord.Forbidden:
await self.bot.say("I need permissions to manage roles first.")
except Exception as e:
print(e)
await self.bot.say("Something went wrong.")
@editrole.command(name="name", pass_context=True)
@checks.admin_or_permissions(administrator=True)
async def edit_role_name(self, ctx, role: discord.Role, name: str):
"""Edits a role's name
Use double quotes if the role or the name contain spaces.
Examples:
!editrole name \"The Transistor\" Test"""
if name == "":
await self.bot.say("Name cannot be empty.")
return
try:
author = ctx.message.author
old_name = role.name # probably not necessary?
await self.bot.edit_role(ctx.message.server, role, name=name)
logger.info("{}({}) changed the name of role '{}' to '{}'".format(
author.name, author.id, old_name, name))
await self.bot.say("Done.")
except discord.Forbidden:
await self.bot.say("I need permissions to manage roles first.")
except Exception as e:
print(e)
await self.bot.say("Something went wrong.")
@commands.command()
async def names(self, user : discord.Member):
"""Show previous names/nicknames of a user"""
server = user.server
names = self.past_names[user.id] if user.id in self.past_names else None
try:
nicks = self.past_nicknames[server.id][user.id]
nicks = [escape_mass_mentions(nick) for nick in nicks]
except:
nicks = None
msg = ""
if names:
names = [escape_mass_mentions(name) for name in names]
msg += "**Past 20 names**:\n"
msg += ", ".join(names)
if nicks:
if msg:
msg += "\n\n"
msg += "**Past 20 nicknames**:\n"
msg += ", ".join(nicks)
if msg:
await self.bot.say(msg)
else:
await self.bot.say("That user doesn't have any recorded name or "
"nickname change.")
async def mass_purge(self, messages):
while messages:
if len(messages) > 1:
await self.bot.delete_messages(messages[:100])
messages = messages[100:]
else:
await self.bot.delete_message(messages)
await asyncio.sleep(1.5)
async def slow_deletion(self, messages):
for message in messages:
try:
await self.bot.delete_message(message)
except:
pass
await asyncio.sleep(1.5)
def is_mod_or_superior(self, message):
user = message.author
server = message.server
admin_role = settings.get_server_admin(server)
mod_role = settings.get_server_mod(server)
if user.id == settings.owner:
return True
elif discord.utils.get(user.roles, name=admin_role):
return True
elif discord.utils.get(user.roles, name=mod_role):
return True
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:
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
if mod:
self.last_case[server.id][mod.id] = case_n
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)
else:
raise CaseMessageNotFound()
async def check_filter(self, message):
server = message.server
if server.id in self.filter.keys():
for w in self.filter[server.id]:
if w in message.content.lower():
try:
await self.bot.delete_message(message)
logger.info("Message deleted in server {}."
"Filtered: {}"
"".format(server.id, w))
return True
except:
pass
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:
if any([m.attachments for m in msgs]):
return False
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):
if before.name != after.name:
if before.id not in self.past_names.keys():
self.past_names[before.id] = [after.name]
else:
if after.name not in self.past_names[before.id]:
names = deque(self.past_names[before.id], maxlen=20)
names.append(after.name)
self.past_names[before.id] = list(names)
dataIO.save_json("data/mod/past_names.json", self.past_names)
if before.nick != after.nick and after.nick is not None:
server = before.server
if server.id not in self.past_nicknames:
self.past_nicknames[server.id] = {}
if before.id in self.past_nicknames[server.id]:
nicks = deque(self.past_nicknames[server.id][before.id],
maxlen=20)
else:
nicks = []
if after.nick not in nicks:
nicks.append(after.nick)
self.past_nicknames[server.id][before.id] = list(nicks)
dataIO.save_json("data/mod/past_nicknames.json",
self.past_nicknames)
def check_folders():
folders = ("data", "data/mod/")
for folder in folders:
if not os.path.exists(folder):
print("Creating " + folder + " folder...")
os.makedirs(folder)
def check_files():
ignore_list = {"SERVERS": [], "CHANNELS": []}
if not os.path.isfile("data/mod/blacklist.json"):
print("Creating empty blacklist.json...")
dataIO.save_json("data/mod/blacklist.json", [])
if not os.path.isfile("data/mod/whitelist.json"):
print("Creating empty whitelist.json...")
dataIO.save_json("data/mod/whitelist.json", [])
if not os.path.isfile("data/mod/ignorelist.json"):
print("Creating empty ignorelist.json...")
dataIO.save_json("data/mod/ignorelist.json", ignore_list)
if not os.path.isfile("data/mod/filter.json"):
print("Creating empty filter.json...")
dataIO.save_json("data/mod/filter.json", {})
if not os.path.isfile("data/mod/past_names.json"):
print("Creating empty past_names.json...")
dataIO.save_json("data/mod/past_names.json", {})
if not os.path.isfile("data/mod/past_nicknames.json"):
print("Creating empty 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):
global logger
check_folders()
check_files()
logger = logging.getLogger("mod")
# Prevents the logger from being loaded again in case of module reload
if logger.level == 0:
logger.setLevel(logging.INFO)
handler = logging.FileHandler(
filename='data/mod/mod.log', encoding='utf-8', mode='a')
handler.setFormatter(
logging.Formatter('%(asctime)s %(message)s', datefmt="[%d/%m/%Y %H:%M]"))
logger.addHandler(handler)
n = Mod(bot)
bot.add_listener(n.check_names, "on_member_update")
bot.add_cog(n)