Massive cleanup of code, many new features

- added logging and listing of kick/ban/mute events
- add usernotes (.note to add a note to a user, .notes to fetch them)
- split off mod.py into many files, cleanup on many features
- renamed .listwarns to .userlog
- renamed .mywarns to .myuserlog
This commit is contained in:
Ave Ozkal 2018-12-27 13:56:24 +03:00
parent 2b50f56fe7
commit 6a8819a2a8
No known key found for this signature in database
GPG key ID: 09356ABAA42C842B
9 changed files with 474 additions and 356 deletions

View file

@ -72,10 +72,10 @@ Main goal of this project is to get Robocop functionality done, secondary goal i
- [ ] New feature: Highlights (problematic words automatically get posted to modmail channel)
- [ ] New feature: Modmail
- [ ] New feature: Submiterr
- [ ] New moderation feature: Display of mutes on listwarns
- [ ] New moderation feature: User notes
- [ ] New moderation feature: mutetime (mute with time)
- [ ] New moderation feature: timelock (channel lockdown with time)
- [x] New moderation feature: Display of mutes, bans and kicks on listwarns (.userlog now)
- [x] New moderation feature: User notes
- [x] New moderation feature: Reaction removing features (thanks misson20000!)
- [x] New moderation feature: User nickname change
- [x] New self-moderation feature: .mywarns

View file

@ -47,6 +47,9 @@ initial_extensions = ['cogs.common',
'cogs.legacy',
'cogs.links',
'cogs.mod',
'cogs.mod_note',
'cogs.mod_reacts',
'cogs.mod_userlog',
'cogs.meme']
bot = commands.Bot(command_prefix=get_prefix,

View file

@ -1,10 +1,9 @@
import asyncio
import discord
from discord.ext import commands
import config
import json
import time
from helpers.checks import check_if_staff
from helpers.userlogs import userlog
from helpers.restrictions import add_restriction, remove_restriction
class Mod:
@ -14,30 +13,7 @@ class Mod:
def check_if_target_is_staff(self, target):
return any(r.id in config.staff_role_ids for r in target.roles)
async def add_restriction(self, member, rst):
# from kurisu source, credits go to ihaveamac
with open("data/restrictions.json", "r") as f:
rsts = json.load(f)
if str(member.id) not in rsts:
rsts[str(member.id)] = []
if rst not in rsts[str(member.id)]:
rsts[str(member.id)].append(rst)
with open("data/restrictions.json", "w") as f:
json.dump(rsts, f)
async def remove_restriction(self, member, rst):
# from kurisu source, credits go to ihaveamac
with open("data/restrictions.json", "r") as f:
rsts = json.load(f)
if str(member.id) not in rsts:
rsts[str(member.id)] = []
if rst in rsts[str(member.id)]:
rsts[str(member.id)].remove(rst)
with open("data/restrictions.json", "w") as f:
json.dump(rsts, f)
@commands.guild_only()
@commands.bot_has_permissions(kick_members=True)
@commands.check(check_if_staff)
@commands.command()
async def mute(self, ctx, target: discord.Member, *, reason: str = ""):
@ -49,6 +25,8 @@ class Mod:
return await ctx.send("I can't mute this user as "
"they're a member of staff.")
userlog(target.id, ctx.author, reason, "mutes", target.name)
safe_name = self.bot.escape_message(str(target))
dm_message = f"You were muted!"
@ -79,10 +57,9 @@ class Mod:
log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(chan_message)
await ctx.send(f"{target.mention} can no longer speak.")
await self.add_restriction(target, config.mute_role)
await add_restriction(target, config.mute_role)
@commands.guild_only()
@commands.bot_has_permissions(kick_members=True)
@commands.check(check_if_staff)
@commands.command()
async def unmute(self, ctx, target: discord.Member):
@ -99,7 +76,7 @@ class Mod:
log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(chan_message)
await ctx.send(f"{target.mention} can now speak again.")
await self.remove_restriction(target, config.mute_role)
await remove_restriction(target, config.mute_role)
@commands.guild_only()
@commands.bot_has_permissions(kick_members=True)
@ -114,6 +91,8 @@ class Mod:
return await ctx.send("I can't kick this user as "
"they're a member of staff.")
userlog(target.id, ctx.author, reason, "kicks", target.name)
safe_name = self.bot.escape_message(str(target))
dm_message = f"You were kicked from {ctx.guild.name}."
@ -156,6 +135,8 @@ class Mod:
return await ctx.send("I can't ban this user as "
"they're a member of staff.")
userlog(target.id, ctx.author, reason, "bans", target.name)
safe_name = self.bot.escape_message(str(target))
dm_message = f"You were banned from {ctx.guild.name}."
@ -201,6 +182,8 @@ class Mod:
return await ctx.send("I can't ban this user as "
"they're a member of staff.")
userlog(target, ctx.author, reason, "bans", target_user.name)
safe_name = self.bot.escape_message(str(target_user))
await ctx.guild.ban(target_user,
@ -220,20 +203,6 @@ class Mod:
await log_channel.send(chan_message)
await ctx.send(f"{safe_name} is now b&. 👍")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=['echo'])
async def say(self, ctx, *, the_text: str):
"""Repeats a given text, staff only."""
await ctx.send(the_text)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def speak(self, ctx, channel: discord.TextChannel, *, the_text: str):
"""Repeats a given text in a given channel, staff only."""
await channel.send(the_text)
@commands.guild_only()
@commands.bot_has_permissions(ban_members=True)
@commands.check(check_if_staff)
@ -247,6 +216,8 @@ class Mod:
return await ctx.send("I can't ban this user as "
"they're a member of staff.")
userlog(target.id, ctx.author, reason, "bans", target.name)
safe_name = self.bot.escape_message(str(target))
await target.ban(reason=f"{ctx.author}, reason: {reason}",
@ -264,25 +235,6 @@ class Mod:
log_channel = self.bot.get_channel(config.log_channel)
await log_channel.send(chan_message)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def userinfo(self, ctx, *, user: discord.Member):
"""Gets user info, staff only."""
role = user.top_role.name
if role == "@everyone":
role = "@ everyone"
await ctx.send(f"user = {user}\n"
f"id = {user.id}\n"
f"avatar = {user.avatar_url}\n"
f"bot = {user.bot}\n"
f"created_at = {user.created_at}\n"
f"display_name = {user.display_name}\n"
f"joined_at = {user.joined_at}\n"
f"activities = `{user.activities}`\n"
f"color = {user.colour}\n"
f"top_role = {role}\n")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
@ -329,50 +281,6 @@ class Mod:
await log_channel.send(f"❌ Un-approved: {ctx.author.mention} removed"
f" {role} from {target.mention}")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["setplaying", "setgame"])
async def playing(self, ctx, *, game: str = ""):
"""Sets the bot's currently played game name, staff only.
Just send .playing to wipe the playing state."""
if game:
await self.bot.change_presence(activity=discord.Game(name=game))
else:
await self.bot.change_presence(activity=None)
await ctx.send("Successfully set game.")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["setbotnick", "botnick", "robotnick"])
async def botnickname(self, ctx, *, nick: str = ""):
"""Sets the bot's nickname, staff only.
Just send .botnickname to wipe the nickname."""
if nick:
await ctx.guild.me.edit(nick=nick, reason=str(ctx.author))
else:
await ctx.guild.me.edit(nick=None, reason=str(ctx.author))
await ctx.send("Successfully set nickname.")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["setnick", "nick"])
async def nickname(self, ctx, target: discord.Member, *, nick: str = ""):
"""Sets a user's nickname, staff only.
Just send .nickname <user> to wipe the nickname."""
if nick:
await target.edit(nick=nick, reason=str(ctx.author))
else:
await target.edit(nick=None, reason=str(ctx.author))
await ctx.send("Successfully set nickname.")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["clear"])
@ -399,21 +307,8 @@ class Mod:
"they're a member of staff.")
log_channel = self.bot.get_channel(config.log_channel)
with open("data/warnsv2.json", "r") as f:
warns = json.load(f)
if str(target.id) not in warns:
warns[str(target.id)] = {"warns": []}
warns[str(target.id)]["name"] = str(target)
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
warn_data = {"issuer_id": ctx.author.id,
"issuer_name": ctx.author.name,
"reason": reason,
"timestamp": timestamp}
warns[str(target.id)]["warns"].append(warn_data)
with open("data/warnsv2.json", "w") as f:
json.dump(warns, f)
warn_count = len(warns[str(target.id)]["warns"])
warn_count = userlog(target.id, ctx.author, reason,
"warns", target.name)
msg = f"You were warned on {ctx.guild.name}."
if reason:
@ -457,254 +352,83 @@ class Mod:
" as the reason is automatically sent to the user."
await log_channel.send(msg)
def get_warns_embed_for_id(self, uid: str, name: str):
embed = discord.Embed(color=discord.Color.dark_red())
embed.set_author(name=f"Warns for {name}")
with open("data/warnsv2.json", "r") as f:
warns = json.load(f)
try:
if len(warns[uid]["warns"]):
for idx, warn in enumerate(warns[uid]["warns"]):
embed.add_field(name=f"{idx + 1}: {warn['timestamp']}",
value=f"Issuer: {warn['issuer_name']}\n"
f"Reason: {warn['reason']}")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["setnick", "nick"])
async def nickname(self, ctx, target: discord.Member, *, nick: str = ""):
"""Sets a user's nickname, staff only.
Just send .nickname <user> to wipe the nickname."""
if nick:
await target.edit(nick=nick, reason=str(ctx.author))
else:
embed.description = "There are none!"
embed.color = discord.Color.green()
except KeyError: # if the user is not in the file
embed.description = "ID doesn't exist in saved "\
"warns (there likely aren't any warns)."
embed.color = discord.Color.green()
return embed
await target.edit(nick=None, reason=str(ctx.author))
def clear_warns_from_id(self, uid: str):
with open("data/warnsv2.json", "r") as f:
warns = json.load(f)
if uid not in warns:
return f"<@{uid}> has no warns!"
warn_count = len(warns[uid]["warns"])
if not warn_count:
return f"<@{uid}> has no warns!"
warns[uid]["warns"] = []
with open("data/warnsv2.json", "w") as f:
json.dump(warns, f)
return f"<@{uid}> no longer has any warns!"
def delete_warns_from_id(self, uid: str, idx: int):
with open("data/warnsv2.json", "r") as f:
warns = json.load(f)
if uid not in warns:
return f"<@{uid}> has no warns!"
warn_count = len(warns[uid]["warns"])
if not warn_count:
return f"<@{uid}> has no warns!"
if idx > warn_count:
return "Warn index is higher than "\
f"warn count ({warn_count})!"
if idx < 1:
return "Warn index is below 1!"
warn = warns[uid]["warns"][idx - 1]
embed = discord.Embed(color=discord.Color.dark_red(),
title=f"Warn {idx} on {warn['timestamp']}",
description=f"Issuer: {warn['issuer_name']}\n"
f"Reason: {warn['reason']}")
del warns[uid]["warns"][idx - 1]
with open("data/warnsv2.json", "w") as f:
json.dump(warns, f)
return embed
await ctx.send("Successfully set nickname.")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def listwarns(self, ctx, target: discord.Member):
"""Lists warns for a user, staff only."""
embed = self.get_warns_embed_for_id(str(target.id), str(target))
await ctx.send(embed=embed)
async def userinfo(self, ctx, *, user: discord.Member):
"""Gets user info, staff only."""
role = user.top_role.name
if role == "@everyone":
role = "@ everyone"
await ctx.send(f"user = {user}\n"
f"id = {user.id}\n"
f"avatar = {user.avatar_url}\n"
f"bot = {user.bot}\n"
f"created_at = {user.created_at}\n"
f"display_name = {user.display_name}\n"
f"joined_at = {user.joined_at}\n"
f"activities = `{user.activities}`\n"
f"color = {user.colour}\n"
f"top_role = {role}\n")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=['echo'])
async def say(self, ctx, *, the_text: str):
"""Repeats a given text, staff only."""
await ctx.send(the_text)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def mywarns(self, ctx):
"""Lists your warns."""
embed = discord.Embed(color=discord.Color.dark_red())
uid = str(ctx.author.id)
embed.set_author(name=f"{ctx.author.name}'s warns")
with open("data/warnsv2.json", "r") as f:
warns = json.load(f)
try:
if len(warns[uid]["warns"]):
for idx, warn in enumerate(warns[uid]["warns"]):
embed.add_field(name=f"{idx + 1}: {warn['timestamp']}",
value=f"Reason: {warn['reason']}")
async def speak(self, ctx, channel: discord.TextChannel, *, the_text: str):
"""Repeats a given text in a given channel, staff only."""
await channel.send(the_text)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["setplaying", "setgame"])
async def playing(self, ctx, *, game: str = ""):
"""Sets the bot's currently played game name, staff only.
Just send .playing to wipe the playing state."""
if game:
await self.bot.change_presence(activity=discord.Game(name=game))
else:
embed.description = "There are none! Good for you."
embed.color = discord.Color.green()
except KeyError: # if the user is not in the file
embed.description = "ID doesn't exist in saved "\
"warns (there likely aren't any warns)."
embed.color = discord.Color.green()
await ctx.send(embed=embed)
await self.bot.change_presence(activity=None)
await ctx.send("Successfully set game.")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def listwarnsid(self, ctx, target: int):
"""Lists warns for a user by ID, staff only."""
embed = self.get_warns_embed_for_id(str(target), str(target))
await ctx.send(embed=embed)
@commands.command(aliases=["setbotnick", "botnick", "robotnick"])
async def botnickname(self, ctx, *, nick: str = ""):
"""Sets the bot's nickname, staff only.
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearwarns(self, ctx, target: discord.Member):
"""Clears all warns for a user, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
msg = self.clear_warns_from_id(str(target.id))
await ctx.send(msg)
msg = f"🗑 **Cleared warns**: {ctx.member.mention} cleared"\
f" warns of {target.mention} | "\
f"{self.bot.escape_message(target)}"
await log_channel.send(msg)
Just send .botnickname to wipe the nickname."""
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearwarnsid(self, ctx, target: int):
"""Clears all warns for a user from their userid, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
msg = self.clear_warns_from_id(str(target))
await ctx.send(msg)
msg = f"🗑 **Cleared warns**: {ctx.member.mention} cleared"\
f" warns of <@{target}> "
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def delwarn(self, ctx, target: discord.Member, idx: int):
"""Removes a specific warn from a user, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
del_warn = self.delete_warns_from_id(str(target.id), idx)
# This is hell.
if isinstance(del_warn, discord.Embed):
await ctx.send(f"{target.mention} has a warning removed!")
msg = f"🗑 **Deleted warn**: {ctx.author.mention} removed "\
f"warn {idx} from {target.mention} | "\
f"{self.bot.escape_message(target)}"
await log_channel.send(msg, embed=del_warn)
if nick:
await ctx.guild.me.edit(nick=nick, reason=str(ctx.author))
else:
await ctx.send(del_warn)
await ctx.guild.me.edit(nick=None, reason=str(ctx.author))
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def delwarnid(self, ctx, target: int, idx: int):
"""Removes a specific warn from a user, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
del_warn = self.delete_warns_from_id(str(target), idx)
# This is hell.
if isinstance(del_warn, discord.Embed):
await ctx.send(f"<@{target}> has a warning removed!")
msg = f"🗑 **Deleted warn**: {ctx.author.mention} removed "\
f"warn {idx} from <@{target}> "
await log_channel.send(msg, embed=del_warn)
else:
await ctx.send(del_warn)
await ctx.send("Successfully set bot nickname.")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearreactsbyuser(self, ctx, user: discord.Member, *,
channel: discord.TextChannel = None,
limit: int = 50):
"""Clears reacts from a given user in the given channel, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
if not channel:
channel = ctx.channel
count = 0
async for msg in channel.history(limit=limit):
for react in msg.reactions:
if await react.users().find(lambda u: u == user):
count += 1
async for u in react.users():
await msg.remove_reaction(react, u)
msg = f"✏️ **Cleared reacts**: {ctx.author.mention} cleared "\
f"{user.mention}'s reacts from the last {limit} messages "\
f"in {channel.mention}."
await ctx.channel.send(f"Cleared {count} unique reactions")
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearallreacts(self, ctx, *,
limit: int = 50,
channel: discord.TextChannel = None):
"""Clears all reacts in a given channel, staff only. Use with care."""
log_channel = self.bot.get_channel(config.log_channel)
if not channel:
channel = ctx.channel
count = 0
async for msg in channel.history(limit=limit):
if msg.reactions:
count += 1
await msg.clear_reactions()
msg = f"✏️ **Cleared reacts**: {ctx.author.mention} cleared all "\
f"reacts from the last {limit} messages in {channel.mention}."
await ctx.channel.send(f"Cleared reacts from {count} messages!")
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearreactsinteractive(self, ctx):
"""Clears reacts interactively, staff only. Use with care."""
log_channel = self.bot.get_channel(config.log_channel)
msg_text = f"{ctx.author.mention}, react to the reactions you want "\
f"to remove. React to this message when you're done."
msg = await ctx.channel.send(msg_text)
tasks = []
def check(event):
# we only care about the user who is clearing reactions
if event.user_id != ctx.author.id:
return False
# this is how the user finishes
if event.message_id == msg.id:
return True
else:
# remove a reaction
async def impl():
msg = await self.bot \
.get_guild(event.guild_id) \
.get_channel(event.channel_id) \
.get_message(event.message_id)
def check_emoji(r):
if event.emoji.is_custom_emoji() == r.custom_emoji:
if event.emoji.is_custom_emoji():
return event.emoji.id == r.emoji.id
else:
# gotta love consistent APIs
return event.emoji.name == r.emoji
else:
return False
for reaction in filter(check_emoji, msg.reactions):
async for u in reaction.users():
await reaction.message.remove_reaction(reaction, u)
# schedule immediately
tasks.append(asyncio.create_task(impl()))
return False
try:
await self.bot.wait_for("raw_reaction_add",
timeout=120.0,
check=check)
except asyncio.TimeoutError:
await msg.edit(content = f"{msg_text} Timed out.")
else:
await asyncio.gather(*tasks)
await msg.edit(content = f"{msg_text} Done!")
def setup(bot):
bot.add_cog(Mod(bot))

31
cogs/mod_note.py Normal file
View file

@ -0,0 +1,31 @@
import discord
from discord.ext import commands
from helpers.checks import check_if_staff
from helpers.userlogs import userlog
class ModNote:
def __init__(self, bot):
self.bot = bot
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["addnote"])
async def note(self, ctx, target: discord.Member, *, note: str = ""):
"""Adds a note to a user, staff only."""
userlog(target.id, ctx.author, note,
"notes", target.name)
await ctx.send(f"{target.mention}: noted!")
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["addnoteid"])
async def noteid(self, ctx, target: int, *, note: str = ""):
"""Adds a note to a user by userid, staff only."""
userlog(target, ctx.author, note,
"notes")
await ctx.send(f"{target.mention}: noted!")
def setup(bot):
bot.add_cog(ModNote(bot))

109
cogs/mod_reacts.py Normal file
View file

@ -0,0 +1,109 @@
import asyncio
import discord
from discord.ext import commands
import config
from helpers.checks import check_if_staff
class ModReact:
def __init__(self, bot):
self.bot = bot
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearreactsbyuser(self, ctx, user: discord.Member, *,
channel: discord.TextChannel = None,
limit: int = 50):
"""Clears reacts from a given user in the given channel, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
if not channel:
channel = ctx.channel
count = 0
async for msg in channel.history(limit=limit):
for react in msg.reactions:
if await react.users().find(lambda u: u == user):
count += 1
async for u in react.users():
await msg.remove_reaction(react, u)
msg = f"✏️ **Cleared reacts**: {ctx.author.mention} cleared "\
f"{user.mention}'s reacts from the last {limit} messages "\
f"in {channel.mention}."
await ctx.channel.send(f"Cleared {count} unique reactions")
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearallreacts(self, ctx, *,
limit: int = 50,
channel: discord.TextChannel = None):
"""Clears all reacts in a given channel, staff only. Use with care."""
log_channel = self.bot.get_channel(config.log_channel)
if not channel:
channel = ctx.channel
count = 0
async for msg in channel.history(limit=limit):
if msg.reactions:
count += 1
await msg.clear_reactions()
msg = f"✏️ **Cleared reacts**: {ctx.author.mention} cleared all "\
f"reacts from the last {limit} messages in {channel.mention}."
await ctx.channel.send(f"Cleared reacts from {count} messages!")
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command()
async def clearreactsinteractive(self, ctx):
"""Clears reacts interactively, staff only. Use with care."""
msg_text = f"{ctx.author.mention}, react to the reactions you want "\
f"to remove. React to this message when you're done."
msg = await ctx.channel.send(msg_text)
tasks = []
def check(event):
# we only care about the user who is clearing reactions
if event.user_id != ctx.author.id:
return False
# this is how the user finishes
if event.message_id == msg.id:
return True
else:
# remove a reaction
async def impl():
msg = await self.bot \
.get_guild(event.guild_id) \
.get_channel(event.channel_id) \
.get_message(event.message_id)
def check_emoji(r):
if event.emoji.is_custom_emoji() == r.custom_emoji:
if event.emoji.is_custom_emoji():
return event.emoji.id == r.emoji.id
else:
# gotta love consistent APIs
return event.emoji.name == r.emoji
else:
return False
for reaction in filter(check_emoji, msg.reactions):
async for u in reaction.users():
await reaction.message.remove_reaction(reaction, u)
# schedule immediately
tasks.append(asyncio.create_task(impl()))
return False
try:
await self.bot.wait_for("raw_reaction_add",
timeout=120.0,
check=check)
except asyncio.TimeoutError:
await msg.edit(content=f"{msg_text} Timed out.")
else:
await asyncio.gather(*tasks)
await msg.edit(content=f"{msg_text} Done!")
def setup(bot):
bot.add_cog(ModReact(bot))

181
cogs/mod_userlog.py Normal file
View file

@ -0,0 +1,181 @@
import discord
from discord.ext import commands
import config
import json
from helpers.checks import check_if_staff
from helpers.userlogs import get_userlog, set_userlog, userlog_event_types
class ModUserlog:
def __init__(self, bot):
self.bot = bot
def get_userlog_embed_for_id(self, uid: str, name: str, own: bool = False,
event=""):
own_note = " Good for you!" if own else ""
wanted_events = ["warns", "bans", "kicks", "mutes"]
if event:
wanted_events = [event]
embed = discord.Embed(color=discord.Color.dark_red())
embed.set_author(name=f"Userlog for {name}")
userlog = get_userlog()
if uid not in userlog:
embed.description = f"There are none!{own_note} (no entry)"
embed.color = discord.Color.green()
return embed
for event_type in wanted_events:
if event_type in userlog[uid] and userlog[uid][event_type]:
event_name = userlog_event_types[event_type]
for idx, event in enumerate(userlog[uid][event_type]):
issuer = "" if own else f"Issuer: {event['issuer_name']} "\
f"({event['issuer_id']})\n"
embed.add_field(name=f"{event_name} {idx + 1}: "
f"{event['timestamp']}",
value=issuer + f"Reason: {event['reason']}",
inline=False)
if not own and "watch" in userlog[uid]:
watch_state = "" if userlog[uid]["watch"] else "NOT "
embed.set_footer(text=f"User is {watch_state}under watch.")
if not embed.fields:
embed.description = f"There are none!{own_note}"
embed.color = discord.Color.green()
return embed
def clear_event_from_id(self, uid: str, event_type):
userlog = get_userlog()
if uid not in userlog:
return f"<@{uid}> has no {event_type}!"
event_count = len(userlog[uid][event_type])
if not event_count:
return f"<@{uid}> has no {event_type}!"
userlog[uid][event_type] = []
set_userlog(json.dumps(userlog))
return f"<@{uid}> no longer has any {event_type}!"
def delete_event_from_id(self, uid: str, idx: int, event_type):
userlog = get_userlog()
if uid not in userlog:
return f"<@{uid}> has no {event_type}!"
event_count = len(userlog[uid][event_type])
if not event_count:
return f"<@{uid}> has no {event_type}!"
if idx > event_count:
return "Index is higher than "\
f"count ({event_count})!"
if idx < 1:
return "Index is below 1!"
event = userlog[uid][event_type][idx - 1]
embed = discord.Embed(color=discord.Color.dark_red(),
title=f"{event_type} {idx} on "
f"{event['timestamp']}",
description=f"Issuer: {event['issuer_name']}\n"
f"Reason: {event['reason']}")
del userlog[uid][event_type][idx - 1]
set_userlog(json.dumps(userlog))
return embed
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(name="userlog",
aliases=["listwarns", "getuserlog", "listuserlog"])
async def userlog_cmd(self, ctx, target: discord.Member):
"""Lists the userlog events for a user, staff only."""
embed = self.get_userlog_embed_for_id(str(target.id), str(target))
await ctx.send(embed=embed)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["listnotes", "usernotes"])
async def notes(self, ctx, target: discord.Member):
"""Lists the notes for a user, staff only."""
embed = self.get_userlog_embed_for_id(str(target.id), str(target),
event="notes")
await ctx.send(embed=embed)
@commands.guild_only()
@commands.command(aliases=["mywarns"])
async def myuserlog(self, ctx):
"""Lists your userlog events (warns etc)."""
embed = self.get_userlog_embed_for_id(str(ctx.author.id),
str(ctx.author), True)
await ctx.send(embed=embed)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["listwarnsid"])
async def userlogid(self, ctx, target: int):
"""Lists the userlog events for a user by ID, staff only."""
embed = self.get_userlog_embed_for_id(str(target), str(target))
await ctx.send(embed=embed)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["clearwarns"])
async def clearevent(self, ctx, target: discord.Member,
event="warns"):
"""Clears all events of given type for a user, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
msg = self.clear_event_from_id(str(target.id), event)
await ctx.send(msg)
msg = f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"\
f" all {event} events of {target.mention} | "\
f"{self.bot.escape_message(target)}"
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["clearwarnsid"])
async def cleareventid(self, ctx, target: int, event="warns"):
"""Clears all events of given type for a userid, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
msg = self.clear_event_from_id(str(target), event)
await ctx.send(msg)
msg = f"🗑 **Cleared {event}**: {ctx.author.mention} cleared"\
f" all {event} events of <@{target}> "
await log_channel.send(msg)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["delwarn"])
async def delevent(self, ctx, target: discord.Member, idx: int,
event="warns"):
"""Removes a specific event from a user, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
del_event = self.delete_event_from_id(str(target.id), idx, event)
event_name = userlog_event_types[event].lower()
# This is hell.
if isinstance(del_event, discord.Embed):
await ctx.send(f"{target.mention} has a {event_name} removed!")
msg = f"🗑 **Deleted {event_name}**: "\
f"{ctx.author.mention} removed "\
f"{event_name} {idx} from {target.mention} | "\
f"{self.bot.escape_message(target)}"
await log_channel.send(msg, embed=del_event)
else:
await ctx.send(del_event)
@commands.guild_only()
@commands.check(check_if_staff)
@commands.command(aliases=["delwarnid"])
async def deleventid(self, ctx, target: int, idx: int, event="warns"):
"""Removes a specific event from a userid, staff only."""
log_channel = self.bot.get_channel(config.log_channel)
del_event = self.delete_event_from_id(str(target), idx, event)
event_name = userlog_event_types[event].lower()
# This is hell.
if isinstance(del_event, discord.Embed):
await ctx.send(f"<@{target}> has a {event_name} removed!")
msg = f"🗑 **Deleted {event_name}**: "\
f"{ctx.author.mention} removed "\
f"{event_name} {idx} from <@{target}> "
await log_channel.send(msg, embed=del_event)
else:
await ctx.send(del_event)
def setup(bot):
bot.add_cog(ModUserlog(bot))

25
helpers/restrictions.py Normal file
View file

@ -0,0 +1,25 @@
import json
def add_restriction(self, member, rst):
# from kurisu source, credits go to ihaveamac
with open("data/restrictions.json", "r") as f:
rsts = json.load(f)
if str(member.id) not in rsts:
rsts[str(member.id)] = []
if rst not in rsts[str(member.id)]:
rsts[str(member.id)].append(rst)
with open("data/restrictions.json", "w") as f:
json.dump(rsts, f)
def remove_restriction(self, member, rst):
# from kurisu source, credits go to ihaveamac
with open("data/restrictions.json", "r") as f:
rsts = json.load(f)
if str(member.id) not in rsts:
rsts[str(member.id)] = []
if rst in rsts[str(member.id)]:
rsts[str(member.id)].remove(rst)
with open("data/restrictions.json", "w") as f:
json.dump(rsts, f)

45
helpers/userlogs.py Normal file
View file

@ -0,0 +1,45 @@
import json
import time
userlog_event_types = {"warns": "Warn",
"bans": "Ban",
"kicks": "Kick",
"mutes": "Mute",
"notes": "Note"}
def get_userlog():
with open("data/warnsv2.json", "r") as f:
return json.load(f)
def set_userlog(contents):
with open("data/warnsv2.json", "w") as f:
f.write(contents)
def userlog(uid, issuer, reason, event_type, uname: str = ""):
with open("data/warnsv2.json", "r") as f:
userlogs = json.load(f)
uid = str(uid)
if uid not in userlogs:
userlogs[uid] = {"warns": [],
"mutes": [],
"kicks": [],
"bans": [],
"notes": [],
"watch": False,
"name": "n/a"}
if uname:
userlogs[uid]["name"] = uname
timestamp = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())
log_data = {"issuer_id": issuer.id,
"issuer_name": f"{issuer}",
"reason": reason,
"timestamp": timestamp}
if event_type not in userlogs[uid]:
userlogs[uid][event_type] = []
userlogs[uid][event_type].append(log_data)
with open("data/warnsv2.json", "w") as f:
json.dump(userlogs, f)
return len(userlogs[uid][event_type])