2020-03-07 06:29:55 +13:00
|
|
|
import logging
|
|
|
|
import asyncio
|
|
|
|
import discord
|
|
|
|
from datetime import datetime
|
|
|
|
|
|
|
|
from redbot.core import checks, commands, Config, modlog
|
2020-03-09 00:24:15 +13:00
|
|
|
from redbot.core.utils.chat_formatting import pagify
|
2020-03-07 06:29:55 +13:00
|
|
|
from redbot.core.utils.predicates import MessagePredicate
|
|
|
|
|
|
|
|
try:
|
|
|
|
from redbot.core.commands import GuildContext
|
|
|
|
except ImportError:
|
|
|
|
from redbot.core.commands import Context as GuildContext
|
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
__author__ = "TheBluekr#2702"
|
|
|
|
__cogname__ = "aurelia.cogs.roletracker"
|
2020-03-07 06:29:55 +13:00
|
|
|
|
2020-03-11 17:15:20 +13:00
|
|
|
# 0 is reason, 1 is role object, 2 is extra msg
|
|
|
|
REASON_MSG = "{0}\n**Role:**{1.mention} (name: {1.name}, id: {1.id})\n{2}"
|
2020-03-07 06:29:55 +13:00
|
|
|
|
2020-03-11 17:15:41 +13:00
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
class RoleTracker(commands.Cog):
|
2020-03-07 06:29:55 +13:00
|
|
|
def __init__(self, bot):
|
|
|
|
super().__init__()
|
|
|
|
self.bot = bot
|
2020-03-09 00:24:15 +13:00
|
|
|
self.logger = logging.getLogger(__cogname__)
|
2020-03-07 06:29:55 +13:00
|
|
|
self.config = Config.get_conf(self, identifier=428038701, force_registration=True)
|
|
|
|
|
|
|
|
default_role = {"addable": False, "USERS": {}}
|
|
|
|
|
|
|
|
self.config.register_role(**default_role)
|
|
|
|
|
|
|
|
async def initialize(self):
|
|
|
|
await self.register_casetypes()
|
|
|
|
|
|
|
|
@staticmethod
|
|
|
|
async def register_casetypes():
|
|
|
|
# register mod case
|
|
|
|
role_case = {
|
2020-03-09 13:07:27 +13:00
|
|
|
"name": "roleupdate",
|
2020-03-07 06:29:55 +13:00
|
|
|
"default_setting": True,
|
|
|
|
"image": "\N{PAGE FACING UP}",
|
2020-03-09 13:07:27 +13:00
|
|
|
"case_str": "Role Update",
|
2020-03-07 06:29:55 +13:00
|
|
|
}
|
|
|
|
try:
|
|
|
|
await modlog.register_casetype(**role_case)
|
|
|
|
except RuntimeError:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Commands
|
2020-03-09 13:07:27 +13:00
|
|
|
@commands.group(aliases=["rtrack", "roletrack"])
|
2020-03-07 06:29:55 +13:00
|
|
|
@commands.guild_only()
|
2020-03-09 00:32:10 +13:00
|
|
|
@checks.mod_or_permissions(manage_roles=True)
|
2020-03-09 00:24:15 +13:00
|
|
|
async def roletracker(self, ctx: GuildContext):
|
2020-03-10 19:05:01 +13:00
|
|
|
"""Role Tracker commands"""
|
2020-03-07 06:29:55 +13:00
|
|
|
pass
|
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
@roletracker.command(name="set")
|
2020-03-10 19:05:01 +13:00
|
|
|
@checks.admin_or_permissions(administrator=True)
|
2020-03-09 00:24:15 +13:00
|
|
|
async def set_role(self, ctx: GuildContext, role: discord.Role, enabled: bool):
|
2020-03-07 06:29:55 +13:00
|
|
|
"""
|
|
|
|
Sets role to be addable/removable
|
|
|
|
"""
|
|
|
|
if not enabled:
|
|
|
|
if await self.config.role(role).USERS():
|
|
|
|
pred = MessagePredicate.yes_or_no(ctx)
|
2020-03-09 13:07:27 +13:00
|
|
|
await ctx.maybe_send_embed(f"Found logs for role {role}, do you want to erase them?")
|
|
|
|
try:
|
|
|
|
await self.bot.wait_for("message", check=pred, timeout=30)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
return await ctx.send("Timed out.")
|
|
|
|
if pred.result:
|
|
|
|
await self.config.role(role).USERS.set({})
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
await self.config.role(role).addable.set(False)
|
|
|
|
await ctx.tick()
|
|
|
|
else:
|
|
|
|
await self.config.role(role).addable.set(True)
|
|
|
|
await ctx.tick()
|
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
@roletracker.command(name="roles")
|
|
|
|
async def view_roles(self, ctx: GuildContext):
|
2020-03-07 06:29:55 +13:00
|
|
|
"""
|
|
|
|
Lists all roles which can be added/removed
|
|
|
|
"""
|
|
|
|
enabled = list()
|
|
|
|
for role in ctx.guild.roles:
|
|
|
|
if await self.config.role(role).addable() and not role.is_default():
|
|
|
|
enabled.append(role.name)
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
pages = pagify("\n".join(enabled))
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
await ctx.send("List of addable roles:")
|
|
|
|
for page in pages:
|
|
|
|
await ctx.maybe_send_embed(page)
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
@commands.bot_has_permissions(manage_roles=True)
|
2020-03-09 00:24:15 +13:00
|
|
|
@roletracker.command(name="add")
|
|
|
|
async def add_role(
|
2020-03-10 19:05:01 +13:00
|
|
|
self, ctx: GuildContext, role: discord.Role, member: discord.Member, *, reason: str = f"Added by {__cogname__}"
|
2020-03-09 00:24:15 +13:00
|
|
|
):
|
2020-03-07 06:29:55 +13:00
|
|
|
"""
|
|
|
|
Adds specified role to given user
|
|
|
|
"""
|
|
|
|
if not await self.config.role(role).addable():
|
|
|
|
return await ctx.maybe_send_embed("Role isn't set as addable.")
|
|
|
|
try:
|
|
|
|
if role in member.roles:
|
|
|
|
return await ctx.maybe_send_embed("Member already has that role.")
|
|
|
|
|
|
|
|
data = await self.config.role(role).USERS()
|
|
|
|
if len(ctx.message.attachments):
|
|
|
|
attachment = ctx.message.attachments[0]
|
2020-03-11 17:15:20 +13:00
|
|
|
reason_message = REASON_MSG.format(reason, role, attachment.url)
|
2020-03-07 06:29:55 +13:00
|
|
|
else:
|
|
|
|
await ctx.send(f"Couldn't find attachment, do you want to continue without adding attachment?")
|
2020-03-09 13:07:27 +13:00
|
|
|
pred = MessagePredicate.yes_or_no(ctx)
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
try:
|
|
|
|
await self.bot.wait_for("message", check=pred, timeout=30)
|
|
|
|
except asyncio.TimeoutError:
|
|
|
|
return await ctx.send("Timed out.")
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
if pred.result:
|
2020-03-11 17:15:20 +13:00
|
|
|
reason_message = REASON_MSG.format(reason, role, "No attachment.")
|
2020-03-07 06:29:55 +13:00
|
|
|
else:
|
|
|
|
return await ctx.maybe_send_embed("Cancelling command.")
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
case = await modlog.create_case(
|
|
|
|
self.bot,
|
|
|
|
member.guild,
|
|
|
|
ctx.message.created_at,
|
2020-03-10 19:05:01 +13:00
|
|
|
"roleupdate",
|
2020-03-09 00:24:15 +13:00
|
|
|
member,
|
|
|
|
moderator=ctx.author,
|
|
|
|
reason=reason_message,
|
|
|
|
)
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
caseno = case.case_number
|
2020-03-10 19:05:01 +13:00
|
|
|
data[member.id] = caseno
|
2020-03-07 06:29:55 +13:00
|
|
|
await self.config.role(role).USERS.set(data)
|
2020-03-10 19:05:01 +13:00
|
|
|
|
|
|
|
await member.add_roles(role)
|
2020-03-07 06:29:55 +13:00
|
|
|
await ctx.tick()
|
|
|
|
except discord.Forbidden:
|
|
|
|
return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.")
|
|
|
|
|
|
|
|
@commands.bot_has_permissions(manage_roles=True)
|
2020-03-09 00:24:15 +13:00
|
|
|
@roletracker.command(name="remove")
|
2020-03-07 06:29:55 +13:00
|
|
|
async def remove_role(
|
2020-03-09 00:24:15 +13:00
|
|
|
self,
|
|
|
|
ctx: GuildContext,
|
|
|
|
role: discord.Role,
|
2020-03-10 19:05:01 +13:00
|
|
|
member: discord.Member,
|
2020-03-09 00:24:15 +13:00
|
|
|
*,
|
|
|
|
reason: str = f"Removed by {__cogname__}.",
|
2020-03-07 06:29:55 +13:00
|
|
|
):
|
|
|
|
"""
|
|
|
|
Removes specified role from given user
|
|
|
|
"""
|
|
|
|
|
|
|
|
if not await self.config.role(role).addable():
|
2020-03-09 00:24:15 +13:00
|
|
|
return await ctx.maybe_send_embed("Role isn't set as removable.")
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
guild = ctx.guild
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
try:
|
|
|
|
if role not in member.roles:
|
|
|
|
return await ctx.maybe_send_embed("Member doesn't have that role.")
|
|
|
|
|
|
|
|
data = await self.config.role(role).USERS()
|
2020-03-10 19:05:01 +13:00
|
|
|
caseno = data.pop(str(member.id), None)
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
if caseno:
|
|
|
|
try:
|
|
|
|
case = await modlog.get_case(caseno, guild, self.bot)
|
|
|
|
except RuntimeError:
|
|
|
|
self.logger.error(f"Failed to find case for {member}, case number: {case}")
|
|
|
|
case = None
|
|
|
|
|
|
|
|
if case:
|
2020-03-10 19:05:34 +13:00
|
|
|
edits = {"reason": case.reason + f"\n**Role Removed**: {reason}"}
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
if ctx.message.author.id != case.moderator.id:
|
|
|
|
edits["amended_by"] = ctx.message.author
|
|
|
|
|
|
|
|
edits["modified_at"] = ctx.message.created_at.timestamp()
|
|
|
|
|
|
|
|
await case.edit(edits)
|
|
|
|
|
|
|
|
await self.config.role(role).USERS.set(data)
|
2020-03-10 19:05:01 +13:00
|
|
|
await member.remove_roles(role)
|
2020-03-07 06:29:55 +13:00
|
|
|
await ctx.tick()
|
|
|
|
except discord.Forbidden:
|
|
|
|
return await ctx.maybe_send_embed("Can't do that. Discord role heirarchy applies here.")
|
|
|
|
|
|
|
|
# Listeners
|
|
|
|
@commands.Cog.listener()
|
|
|
|
async def on_member_update(self, before: discord.Member, after: discord.Member):
|
|
|
|
"""
|
|
|
|
Listens for role updates and more
|
|
|
|
"""
|
|
|
|
|
|
|
|
if before.roles != after.roles:
|
|
|
|
user = self.bot.user
|
|
|
|
try:
|
|
|
|
async for entry in after.guild.audit_logs(limit=3, action=discord.AuditLogAction.member_role_update):
|
|
|
|
if entry.target.id == before.id:
|
|
|
|
user = entry.user
|
|
|
|
break
|
2020-03-10 19:05:01 +13:00
|
|
|
|
2020-03-07 06:29:55 +13:00
|
|
|
except discord.Forbidden:
|
|
|
|
self.logger.warning("Failed to retrieve the moderator from audit logs, please check the permissions")
|
|
|
|
|
|
|
|
broles = set(before.roles)
|
|
|
|
aroles = set(after.roles)
|
|
|
|
added = aroles - broles
|
|
|
|
removed = broles - aroles
|
|
|
|
|
2020-03-09 00:24:15 +13:00
|
|
|
now_date = datetime.utcnow()
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
for role in added:
|
2020-03-09 13:07:27 +13:00
|
|
|
role_dict = await self.config.role(role).all()
|
2020-03-11 17:15:41 +13:00
|
|
|
if role_dict["addable"] and not (
|
|
|
|
role_dict["USERS"].get(str(before.id), None) or user == before.guild.me
|
|
|
|
):
|
2020-03-09 13:07:27 +13:00
|
|
|
data = role_dict["USERS"]
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
case = await modlog.create_case(
|
|
|
|
self.bot,
|
|
|
|
before.guild,
|
|
|
|
now_date,
|
2020-03-10 19:05:01 +13:00
|
|
|
"roleupdate",
|
2020-03-07 06:29:55 +13:00
|
|
|
before,
|
|
|
|
moderator=user,
|
2020-03-11 17:15:20 +13:00
|
|
|
reason=REASON_MSG.format("Role manually added", role, ""),
|
2020-03-07 06:29:55 +13:00
|
|
|
)
|
|
|
|
caseno = case.case_number
|
|
|
|
|
|
|
|
data[before.id] = caseno
|
|
|
|
await self.config.role(role).USERS.set(data)
|
|
|
|
|
|
|
|
for role in removed:
|
2020-03-09 13:07:27 +13:00
|
|
|
role_dict = await self.config.role(role).all()
|
2020-03-11 17:15:20 +13:00
|
|
|
if role_dict["addable"] and not user == before.guild.me:
|
2020-03-09 13:07:27 +13:00
|
|
|
data = role_dict["USERS"]
|
2020-03-07 06:29:55 +13:00
|
|
|
|
2020-03-10 19:05:01 +13:00
|
|
|
caseno = data.pop(str(before.id), None)
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
if caseno:
|
|
|
|
try:
|
|
|
|
case = await modlog.get_case(caseno, before.guild, self.bot)
|
|
|
|
except RuntimeError:
|
|
|
|
self.logger.error(f"Failed to find case for {before.user}, case number: {case}")
|
|
|
|
case = None
|
|
|
|
|
|
|
|
if case:
|
2020-03-10 19:05:34 +13:00
|
|
|
edits = {"reason": case.reason + f"\n**Role Removed**: Role manually removed"}
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
if user.id != case.moderator.id:
|
|
|
|
edits["amended_by"] = user
|
|
|
|
|
2020-03-10 19:05:01 +13:00
|
|
|
edits["modified_at"] = now_date.timestamp()
|
2020-03-07 06:29:55 +13:00
|
|
|
|
|
|
|
await case.edit(edits)
|
|
|
|
|
|
|
|
await self.config.role(role).USERS.set(data)
|