Brandon209-Red-bot-Cogs/rolemanagement/events.py
2023-01-25 15:44:54 -05:00

217 lines
7.2 KiB
Python

from __future__ import annotations
from datetime import datetime, timedelta
from typing import List
import discord
from redbot.core import commands
from .abc import MixinMeta
from .exceptions import RoleManagementException, PermissionOrHierarchyException
class EventMixin(MixinMeta):
def verification_level_issue(self, member: discord.Member) -> bool:
"""
Returns True if this would bypass verification level settings
prevent react roles from bypassing time limits.
It's exceptionally dumb that users can react while
restricted by verification level, but that's Discord.
They block reacting to blocked users, but interacting
with entire guilds by reaction before hand? A-OK. *eyerolls*
Can't check the email/2FA, blame discord for allowing people to react with above.
"""
guild: discord.Guild = member.guild
now = datetime.utcnow()
level: int = guild.verification_level.value
if level >= 3 and member.created_at + timedelta(minutes=5) > now: # medium
return True
if level >= 4: # high
if not member.joined_at or member.joined_at + timedelta(minutes=10) > now:
return True
return False
@commands.Cog.listener()
async def on_member_update(self, before: discord.Member, after: discord.Member):
"""
DEP-WARN
Section has been optimized assuming member._roles
remains an iterable containing snowflakes
"""
if await self.bot.cog_disabled_in_guild(self, after.guild):
return
await self.wait_for_ready()
if before._roles == after._roles:
return
lost, gained = set(before._roles), set(after._roles)
lost, gained = lost - gained, gained - lost
sym_diff = lost | gained
# check if new member roles are exclusive to others.
ex = []
for r in gained:
ex_groups = (await self.config.role_from_id(r).exclusive_to()).values()
for ex_roles in ex_groups:
ex.extend(ex_roles)
to_remove = [r for r in after.roles if r.id in ex]
if to_remove:
await after.remove_roles(*to_remove, reason="conflict with exclusive roles")
# add with roles for roles gained
for r in gained:
add_with = await self.config.role_from_id(r).add_with()
if add_with:
to_add = [
discord.utils.get(after.guild.roles, id=add) for add in add_with
]
await after.add_roles(*to_add, reason=f"add with role {r}")
for r in sym_diff:
if not await self.config.role_from_id(r).sticky():
lost.discard(r)
gained.discard(r)
async with self.config.member(after).roles() as rids:
for r in lost:
while r in rids:
rids.remove(r)
for r in gained:
if r not in rids:
rids.append(r)
@commands.Cog.listener()
async def on_member_join(self, member: discord.Member):
await self.wait_for_ready()
if await self.bot.cog_disabled_in_guild(self, member.guild):
return
guild = member.guild
if not guild.me.guild_permissions.manage_roles:
return
async with self.config.member(member).roles() as rids:
to_add: List[discord.Role] = []
for _id in rids:
role = discord.utils.get(guild.roles, id=_id)
if not role:
continue
if await self.config.role(role).sticky():
to_add.append(role)
if to_add:
to_add = [r for r in to_add if r < guild.me.top_role]
await member.add_roles(*to_add)
# join roles
async with self.config.guild(guild).join_roles() as join_roles:
to_add: List[discord.Role] = []
for role_id in join_roles:
role = discord.utils.get(guild.roles, id=role_id)
if role:
to_add.append(role)
if to_add:
to_add = [r for r in to_add if r < guild.me.top_role]
await member.add_roles(*to_add)
@commands.Cog.listener()
async def on_raw_reaction_add(
self, payload: discord.raw_models.RawReactionActionEvent
):
await self.wait_for_ready()
if not payload.guild_id:
return
emoji = payload.emoji
if emoji.is_custom_emoji():
eid = str(emoji.id)
else:
eid = self.strip_variations(str(emoji))
cfg = self.config.custom("REACTROLE", str(payload.message_id), eid)
rid = await cfg.roleid()
if rid is None:
return
guild = self.bot.get_guild(payload.guild_id)
if guild:
await self.maybe_update_guilds(guild)
else:
return
if await self.bot.cog_disabled_in_guild(self, guild):
return
member = guild.get_member(payload.user_id)
if member is None or member.bot:
return
if self.verification_level_issue(member):
return
role = guild.get_role(rid)
if role is None or role in member.roles:
return
if not await self.verify_age(role, member=member):
try:
await member.send(
f"You do not meet the minimum age requiremnt for `{role}`, if you think this is an error please contact a staff member."
)
except:
pass
return
try:
remove = await self.is_self_assign_eligible(member, role)
except (RoleManagementException, PermissionOrHierarchyException):
pass
else:
dm_msg = await self.config.role(role).dm_msg()
if dm_msg:
try:
await member.send(dm_msg)
except:
pass
await self.update_roles_atomically(who=member, give=[role], remove=remove)
@commands.Cog.listener()
async def on_raw_reaction_remove(
self, payload: discord.raw_models.RawReactionActionEvent
):
await self.wait_for_ready()
if not payload.guild_id:
return
emoji = payload.emoji
if emoji.is_custom_emoji():
eid = str(emoji.id)
else:
eid = self.strip_variations(str(emoji))
cfg = self.config.custom("REACTROLE", str(payload.message_id), eid)
rid = await cfg.roleid()
if rid is None:
return
guild = self.bot.get_guild(payload.guild_id)
if not guild:
# Where's it go?
return
if await self.bot.cog_disabled_in_guild(self, guild):
return
member = guild.get_member(payload.user_id)
if not member or member.bot:
return
role = discord.utils.get(guild.roles, id=rid)
if not role or role not in member.roles:
return
if guild.me.guild_permissions.manage_roles and guild.me.top_role > role:
await self.update_roles_atomically(who=member, give=None, remove=[role])