Add user/modlog, fix timestamp bug on embeds, add max_message config option. Closes #10, closes #27

This commit is contained in:
Zeva Rose 2021-07-07 11:08:38 -06:00
parent 27f5730a90
commit e3c18103b3
6 changed files with 398 additions and 188 deletions

View file

@ -10,3 +10,4 @@
api_urls: api_urls:
url_name: url url_name: url
url_name2: url2 url_name2: url2
max_messages: 1000

View file

@ -142,7 +142,7 @@ async def unmute():
user = await guild.fetch_member(mute["user"]) user = await guild.fetch_member(mute["user"])
if user: if user:
if role in user.roles: if role in user.roles:
await user.remove_roles(role, reason="Unmute") await user.remove_roles(role, reason="Mute expired")
updates.append( updates.append(
pymongo.UpdateOne( pymongo.UpdateOne(
{"user": user.id, "guild": guild.id, "time": mute["time"]}, {"user": user.id, "guild": guild.id, "time": mute["time"]},
@ -196,6 +196,7 @@ def run(ctx=None):
) )
unmute.start() unmute.start()
unban.start() unban.start()
jarvis.max_messages = config.max_messages
jarvis.run(config.token, bot=True, reconnect=True) jarvis.run(config.token, bot=True, reconnect=True)
for cog in jarvis.cogs: for cog in jarvis.cogs:
session = getattr(cog, "_session", None) session = getattr(cog, "_session", None)

View file

@ -2,6 +2,7 @@ import asyncio
from datetime import datetime, timedelta from datetime import datetime, timedelta
import discord import discord
import pymongo
from discord.ext import commands from discord.ext import commands
from discord.utils import find from discord.utils import find
@ -13,198 +14,396 @@ from jarvis.utils.field import Field
class ModlogCog(commands.Cog): class ModlogCog(commands.Cog):
def __init__(self, bot): """
A hybrid user/modlog functionality for J.A.R.V.I.S.
"""
def __init__(self, bot: discord.ext.commands.Bot):
self.bot = bot self.bot = bot
self.db = DBManager(get_config().mongo).mongo self.db = DBManager(get_config().mongo).mongo
def get_latest_log(self, auditlog, target): def get_latest_log(self, auditlog, target):
before = datetime.utcnow() - timedelta(seconds=10) before = datetime.utcnow() - timedelta(seconds=10)
return find( return find(
lambda x: x.target.id == target.id and x.created_at < before, lambda x: x.target.id == target.id and x.created_at > before,
auditlog, auditlog,
) )
# @commands.Cog.listener() async def modlog_embed(
# async def on_member_ban(self, guild: discord.Guild, user: discord.User): self,
# modlog = self.db.jarvis.settings.find_one( member: discord.Member,
# {"guild": guild.id, "setting": "modlog"} admin: discord.Member,
# ) log: discord.AuditLogEntry,
# if modlog: title: str,
# channel = guild.get_channel(modlog["value"]) desc: str,
# await asyncio.sleep(0.5) # Need to wait for audit log ) -> discord.Embed:
# auditlog = await guild.audit_logs( fields = [
# limit=50, Field(
# action=discord.AuditLogAction.ban, name="Moderator",
# after=datetime.utcnow() - timedelta(seconds=15), value=f"{admin.mention} ({admin.name}"
# oldest_first=False, + f"#{admin.discriminator})",
# ) ),
# log: discord.AuditLogEntry = self.get_latest_log(auditlog, user) Field(name="Reason", value=log.reason, inline=False),
# fields = [ ]
# Field( embed = build_embed(
# name="Member", title=title,
# value=f"{user.mention} ({user.name}#{user.descriminator})", description=desc,
# ), color="#fc9e3f",
# Field( fields=fields,
# name="Moderator", timestamp=log.created_at,
# value=f"{user.mention} ({user.name}#{user.descriminator})", )
# ), embed.set_author(
# Field(name="Reason", value=log.reason, inline=False), name=f"{member.name}",
# ] icon_url=member.avatar_url,
# embed = build_embed( )
# title="User Banned", embed.set_footer(
# description=f"A user was banned from {guild.name}", text=f"{member.name}#{member.discriminator} | {member.id}"
# color="#bf2a3e", )
# fields=fields, return embed
# timestamp=log.created_at,
# ) @commands.Cog.listener()
# embed.set_author( async def on_member_ban(self, guild: discord.Guild, user: discord.User):
# name=f"{user.name}#{user.discriminator} | {user.id}", modlog = self.db.jarvis.settings.find_one(
# icon_url=user.avatar_url, {"guild": guild.id, "setting": "modlog"}
# ) )
# if modlog:
# await channel.send(embed=embed) channel = guild.get_channel(modlog["value"])
# await asyncio.sleep(0.5) # Need to wait for audit log
# @commands.Cog.listener() auditlog = await guild.audit_logs(
# async def on_member_unban(self, guild: discord.Guild, user: discord.User): limit=50,
# modlog = self.db.jarvis.settings.find_one( action=discord.AuditLogAction.ban,
# {"guild": guild.id, "setting": "modlog"} after=datetime.utcnow() - timedelta(seconds=15),
# ) oldest_first=False,
# if modlog: )
# channel = guild.get_channel(modlog["value"]) log: discord.AuditLogEntry = self.get_latest_log(auditlog, user)
# await asyncio.sleep(0.5) # Need to wait for audit log admin: discord.User = log.user
# auditlog = await guild.audit_logs( if admin.id == get_config().client_id:
# limit=50, mute = self.db.jarvis.bans.find_one(
# action=discord.AuditLogAction.unban, {
# after=datetime.utcnow() - timedelta(seconds=15), "guild": guild.id,
# oldest_first=False, "user": user.id,
# ) "active": True,
# log: discord.AuditLogEntry = self.get_latest_log(auditlog, user) },
# fields = [ sort=[("time", pymongo.DESCENDING)],
# Field( )
# name="Member", admin = guild.get_member(mute["admin"])
# value=f"{user.mention} ({user.name}#{user.descriminator})", embed = await self.modlog_embed(
# ), user,
# Field( admin,
# name="Moderator", log,
# value=f"{user.mention} ({user.name}#{user.descriminator})", "User banned",
# ), f"{user.mention} was banned from {guild.name}",
# Field(name="Reason", value=log.reason, inline=False), )
# ]
# embed = build_embed( await channel.send(embed=embed)
# title="User Unbanned",
# description=f"A user was unbanned from {guild.name}", @commands.Cog.listener()
# color="#bf2a3e", async def on_member_unban(self, guild: discord.Guild, user: discord.User):
# fields=fields, modlog = self.db.jarvis.settings.find_one(
# timestamp=log.created_at, {"guild": guild.id, "setting": "modlog"}
# ) )
# embed.set_author( if modlog:
# name=f"{user.name}#{user.discriminator} | {user.id}", channel = guild.get_channel(modlog["value"])
# icon_url=user.avatar_url, await asyncio.sleep(0.5) # Need to wait for audit log
# ) auditlog = await guild.audit_logs(
# limit=50,
# await channel.send(embed=embed) action=discord.AuditLogAction.unban,
# after=datetime.utcnow() - timedelta(seconds=15),
# @commands.Cog.listener() oldest_first=False,
# async def on_member_remove(self, user: discord.User): )
# modlog = self.db.jarvis.settings.find_one( log: discord.AuditLogEntry = self.get_latest_log(auditlog, user)
# {"guild": user.guild.id, "setting": "modlog"} admin: discord.User = log.user
# ) if admin.id == get_config().client_id:
# if modlog: mute = self.db.jarvis.bans.find_one(
# channel = user.guild.get_channel(modlog["value"]) {
# await asyncio.sleep(0.5) # Need to wait for audit log "guild": guild.id,
# auditlog = await user.guild.audit_logs( "user": user.id,
# limit=50, "active": True,
# action=discord.AuditLogAction.kick, },
# after=datetime.utcnow() - timedelta(seconds=15), sort=[("time", pymongo.DESCENDING)],
# oldest_first=False, )
# ) admin = guild.get_member(mute["admin"])
# log: discord.AuditLogEntry = self.get_latest_log(auditlog, user) embed = await self.modlog_embed(
# fields = [ user,
# Field( admin,
# name="Member", log,
# value=f"{user.mention} ({user.name}#{user.descriminator})", "User unbanned",
# ), f"{user.mention} was unbanned from {guild.name}",
# Field( )
# name="Moderator",
# value=f"{user.mention} ({user.name}#{user.descriminator})", await channel.send(embed=embed)
# ),
# Field(name="Reason", value=log.reason, inline=False), @commands.Cog.listener()
# ] async def on_member_remove(self, user: discord.User):
# embed = build_embed( modlog = self.db.jarvis.settings.find_one(
# title="User Kicked", {"guild": user.guild.id, "setting": "modlog"}
# description=f"A user was kicked from {guild.name}", )
# color="#bf2a3e", if modlog:
# fields=fields, channel = user.guild.get_channel(modlog["value"])
# timestamp=log.created_at, await asyncio.sleep(0.5) # Need to wait for audit log
# ) auditlog = await user.guild.audit_logs(
# embed.set_author( limit=50,
# name=f"{user.name}#{user.discriminator} | {user.id}", action=discord.AuditLogAction.kick,
# icon_url=user.avatar_url, after=datetime.utcnow() - timedelta(seconds=15),
# ) oldest_first=False,
# )
# await channel.send(embed=embed) log: discord.AuditLogEntry = self.get_latest_log(auditlog, user)
# admin: discord.User = log.user
# @commands.Cog.listener() if admin.id == get_config().client_id:
# async def on_member_update( mute = self.db.jarvis.kicks.find_one(
# self, before: discord.User, after: discord.User {
# ): "guild": user.guild.id,
# modlog = self.db.jarvis.settings.find_one( "user": user.id,
# {"guild": user.guild.id, "setting": "modlog"} },
# ) sort=[("time", pymongo.DESCENDING)],
# if modlog: )
# channel = user.guild.get_channel(modlog["value"]) admin = user.guild.get_member(mute["admin"])
# await asyncio.sleep(0.5) # Need to wait for audit log embed = await self.modlog_embed(
# embed = None user,
# mute = self.db.jarvis.settings.find_one( admin,
# {"guild": before.guild.id, "setting": "mute"} log,
# ) "User Kicked",
# verified = self.db.jarvis.settings.find_one( f"{user.mention} was kicked from {user.guild.name}",
# {"guild": before.guild.id, "setting": "verified"} )
# )
# if mute and before.guild.get_role(mute["value"]) in after.roles: await channel.send(embed=embed)
# # TODO: User was muted
# pass async def process_mute(self, before, after) -> discord.Embed:
# elif mute and before.guild.get_role(mute["value"]) in before.roles: auditlog = await before.guild.audit_logs(
# # TODO: User was unmuted limit=50,
# pass action=discord.AuditLogAction.member_role_update,
# elif ( after=datetime.utcnow() - timedelta(seconds=15),
# verified oldest_first=False,
# and before.guild.get_role(verified["value"]) in before.roles ).flatten()
# ): log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
# # TODO: User was verified admin: discord.User = log.user
# pass if admin.id == get_config().client_id:
# mute = self.db.jarvis.mutes.find_one(
# auditlog = await user.guild.audit_logs( {
# limit=50, "guild": before.guild.id,
# action=discord.AuditLogAction.kick, "user": before.id,
# after=datetime.utcnow() - timedelta(seconds=15), "active": True,
# oldest_first=False, },
# ) sort=[("time", pymongo.DESCENDING)],
# log: discord.AuditLogEntry = self.get_latest_log(auditlog, user) )
# fields = [ admin = before.guild.get_member(mute["admin"])
# Field( return await self.modlog_embed(
# name="Member", member=before,
# value=f"{user.mention} ({user.name}#{user.descriminator})", admin=admin,
# ), log=log,
# Field( title="User Muted",
# name="Moderator", desc=f"{before.mention} was muted",
# value=f"{user.mention} ({user.name}#{user.descriminator})", )
# ),
# Field(name="Reason", value=log.reason, inline=False), async def process_unmute(self, before, after) -> discord.Embed:
# ] auditlog = await before.guild.audit_logs(
# embed = build_embed( limit=50,
# title="User Kicked", action=discord.AuditLogAction.member_role_update,
# description=f"A user was kicked from {guild.name}", after=datetime.utcnow() - timedelta(seconds=15),
# color="#bf2a3e", oldest_first=False,
# fields=fields, ).flatten()
# timestamp=log.created_at, log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
# ) admin: discord.User = log.user
# embed.set_author( if admin.id == get_config().client_id:
# name=f"{user.name}#{user.discriminator} | {user.id}", mute = self.db.jarvis.mutes.find_one(
# icon_url=user.avatar_url, {
# ) "guild": before.guild.id,
# "user": before.id,
# await channel.send(embed=embed) "active": True,
},
sort=[("time", pymongo.DESCENDING)],
)
admin = before.guild.get_member(mute["admin"])
return await self.modlog_embed(
member=before,
admin=admin,
log=log,
title="User Muted",
desc=f"{before.mention} was muted",
)
async def process_verify(self, before, after) -> discord.Embed:
auditlog = await before.guild.audit_logs(
limit=50,
action=discord.AuditLogAction.member_role_update,
after=datetime.utcnow() - timedelta(seconds=15),
oldest_first=False,
).flatten()
log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
admin: discord.User = log.user
return await self.modlog_embed(
member=before,
admin=admin,
log=log,
title="User Verified",
desc=f"{before.mention} was verified",
)
async def process_rolechange(self, before, after) -> discord.Embed:
auditlog = await before.guild.audit_logs(
limit=50,
action=discord.AuditLogAction.member_role_update,
after=datetime.utcnow() - timedelta(seconds=15),
oldest_first=False,
).flatten()
log: discord.AuditLogEntry = self.get_latest_log(auditlog, before)
admin: discord.User = log.user
role = None
title = "User Given Role"
verb = "was given"
if len(before.roles) > len(after.roles):
title = "User Forfeited Role"
verb = "forfeited"
role = find(lambda x: x not in after.roles, before.roles)
elif len(before.roles) < len(after.roles):
role = find(lambda x: x not in before.roles, after.roles)
role_text = role.mention if role else "||`[redacted]`||"
return await self.modlog_embed(
member=before,
admin=admin,
log=log,
title=title,
desc=f"{before.mention} {verb} role {role_text}",
)
@commands.Cog.listener()
async def on_member_update(
self, before: discord.User, after: discord.User
):
modlog = self.db.jarvis.settings.find_one(
{"guild": after.guild.id, "setting": "modlog"}
)
if modlog:
channel = after.guild.get_channel(modlog["value"])
await asyncio.sleep(0.5) # Need to wait for audit log
embed = None
mute = self.db.jarvis.settings.find_one(
{"guild": before.guild.id, "setting": "mute"}
)
verified = self.db.jarvis.settings.find_one(
{"guild": before.guild.id, "setting": "verified"}
)
if mute and before.guild.get_role(mute["value"]) in after.roles:
embed = await self.process_mute(before, after)
elif mute and before.guild.get_role(mute["value"]) in before.roles:
embed = await self.process_unmute(before, after)
elif (
verified
and before.guild.get_role(verified["value"])
not in before.roles
):
embed = await self.process_verify(before, after)
elif before.nick != after.nick:
auditlog = await before.guild.audit_logs(
limit=50,
action=discord.AuditLogAction.member_update,
after=datetime.utcnow() - timedelta(seconds=15),
oldest_first=False,
).flatten()
log: discord.AuditLogEntry = self.get_latest_log(
auditlog, before
)
bname = before.nick if before.nick else before.name
aname = after.nick if after.nick else after.name
fields = [
Field(
name="Before",
value=f"{bname} ({before.name}"
+ f"#{before.discriminator})",
),
Field(
name="After",
value=f"{aname} ({after.name}"
+ f"#{after.discriminator})",
),
]
if log.user.id != before.id:
fields.append(
Field(
name="Moderator",
value=f"{log.user.mention} ({log.user.name}"
+ f"#{log.user.discriminator})",
)
)
fields.append(
Field(name="Reason", value=log.reason, inline=False),
)
embed = build_embed(
title="User Nick Changed",
description=f"{after.mention} changed their nickname",
color="#fc9e3f",
fields=fields,
timestamp=log.created_at,
)
embed.set_author(
name=f"{after.name}",
icon_url=after.avatar_url,
)
embed.set_footer(
text=f"{after.name}#{after.discriminator} | {after.id}"
)
elif len(before.roles) != len(after.roles):
# TODO: User got a new role
embed = await self.process_rolechange(before, after)
if embed:
await channel.send(embed=embed)
@commands.Cog.listener()
async def on_message_edit(
self, before: discord.Message, after: discord.Message
):
modlog = self.db.jarvis.settings.find_one(
{"guild": after.guild.id, "setting": "modlog"}
)
if modlog:
channel = before.guild.get_channel(modlog["value"])
fields = [
Field("Original Message", before.content, False),
Field("New Message", after.content, False),
]
embed = build_embed(
title="Message Edited",
description=f"{before.author.mention} edited a message",
fields=fields,
color="#fc9e3f",
timestamp=after.edited_at,
url=after.jump_url,
)
embed.set_author(
name=before.author.name,
icon_url=before.author.avatar_url,
url=after.jump_url,
)
embed.set_footer(
text=f"{before.author.name}#{before.author.discriminator}"
+ f" | {before.author.id}"
)
await channel.send(embed=embed)
@commands.Cog.listener()
async def on_message_delete(self, message: discord.Message):
modlog = self.db.jarvis.settings.find_one(
{"guild": message.guild.id, "setting": "modlog"}
)
if modlog:
channel = message.guild.get_channel(modlog["value"])
fields = [Field("Original Message", message.content, False)]
embed = build_embed(
title="Message Deleted",
description=f"{message.author.mention} deleted a message",
fields=fields,
color="#fc9e3f",
)
embed.set_author(
name=message.author.name,
icon_url=message.author.avatar_url,
url=message.jump_url,
)
embed.set_footer(
text=f"{message.author.name}#{message.author.discriminator}"
+ f" | {message.author.id}"
)
await channel.send(embed=embed)
def setup(bot): def setup(bot):

View file

@ -79,13 +79,15 @@ class VerifyCog(commands.Cog):
{"setting": "verified", "guild": ctx.guild.id} {"setting": "verified", "guild": ctx.guild.id}
) )
role = ctx.guild.get_role(setting["value"]) role = ctx.guild.get_role(setting["value"])
await ctx.author.add_roles(role, reason="Verified") await ctx.author.add_roles(role, reason="Verification passed")
setting = self.db.jarvis.settings.find_one( setting = self.db.jarvis.settings.find_one(
{"setting": "unverified", "guild": ctx.guild.id} {"setting": "unverified", "guild": ctx.guild.id}
) )
if setting: if setting:
role = ctx.guild.get_role(setting["value"]) role = ctx.guild.get_role(setting["value"])
await ctx.author.remove_roles(role, reason="Verified") await ctx.author.remove_roles(
role, reason="Verification passed"
)
await ctx.edit_origin( await ctx.edit_origin(
content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.", content=f"Welcome, {ctx.author.mention}. Please enjoy your stay.",
components=manage_components.spread_to_rows( components=manage_components.spread_to_rows(

View file

@ -18,13 +18,20 @@ class Config(object):
return it return it
def init( def init(
self, token: str, client_id: str, logo: str, mongo: dict, urls: dict self,
token: str,
client_id: str,
logo: str,
mongo: dict,
urls: dict,
max_messages: int = 1000,
): ):
self.token = token self.token = token
self.client_id = client_id self.client_id = client_id
self.logo = logo self.logo = logo
self.mongo = mongo self.mongo = mongo
self.urls = urls self.urls = urls
self.max_messages = max_messages
db = DBManager(config=mongo).mongo.jarvis.config db = DBManager(config=mongo).mongo.jarvis.config
db_config = db.find() db_config = db.find()
for item in db_config: for item in db_config:

View file

@ -58,7 +58,7 @@ def build_embed(
**kwargs, **kwargs,
) -> Embed: ) -> Embed:
if not timestamp: if not timestamp:
timestamp = datetime.now() timestamp = datetime.utcnow()
embed = Embed( embed = Embed(
title=title, title=title,
description=description, description=description,