2020-01-30 21:10:04 +13:00
|
|
|
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
|
|
|
|
"""
|
2020-10-29 21:36:43 +13:00
|
|
|
if await self.bot.cog_disabled_in_guild(self, after.guild):
|
|
|
|
return
|
2020-01-30 21:10:04 +13:00
|
|
|
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
|
|
|
|
|
2020-02-09 08:52:28 +13:00
|
|
|
# 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")
|
|
|
|
|
2020-06-26 10:50:29 +12:00
|
|
|
# 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}")
|
|
|
|
|
2020-01-30 21:10:04 +13:00
|
|
|
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()
|
2020-10-29 21:36:43 +13:00
|
|
|
if await self.bot.cog_disabled_in_guild(self, member.guild):
|
|
|
|
return
|
2020-01-30 21:10:04 +13:00
|
|
|
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)
|
|
|
|
|
2020-02-09 08:52:28 +13:00
|
|
|
# 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)
|
|
|
|
|
2020-01-30 21:10:04 +13:00
|
|
|
@commands.Cog.listener()
|
2020-01-30 21:15:53 +13:00
|
|
|
async def on_raw_reaction_add(self, payload: discord.raw_models.RawReactionActionEvent):
|
2020-01-30 21:10:04 +13:00
|
|
|
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()
|
2020-02-09 16:32:37 +13:00
|
|
|
if rid is None:
|
2020-01-30 21:10:04 +13:00
|
|
|
return
|
|
|
|
|
|
|
|
guild = self.bot.get_guild(payload.guild_id)
|
|
|
|
if guild:
|
|
|
|
await self.maybe_update_guilds(guild)
|
|
|
|
else:
|
|
|
|
return
|
2020-10-29 21:36:43 +13:00
|
|
|
|
|
|
|
if await self.bot.cog_disabled_in_guild(self, guild):
|
|
|
|
return
|
2020-01-30 21:10:04 +13:00
|
|
|
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
|
|
|
|
|
|
|
|
try:
|
|
|
|
remove = await self.is_self_assign_eligible(member, role)
|
|
|
|
except (RoleManagementException, PermissionOrHierarchyException):
|
|
|
|
pass
|
|
|
|
else:
|
2020-02-09 21:14:07 +13:00
|
|
|
dm_msg = await self.config.role(role).dm_msg()
|
|
|
|
if dm_msg:
|
|
|
|
try:
|
|
|
|
await member.send(dm_msg)
|
|
|
|
except:
|
|
|
|
pass
|
2020-01-30 21:10:04 +13:00
|
|
|
await self.update_roles_atomically(who=member, give=[role], remove=remove)
|
|
|
|
|
|
|
|
@commands.Cog.listener()
|
2020-01-30 21:15:53 +13:00
|
|
|
async def on_raw_reaction_remove(self, payload: discord.raw_models.RawReactionActionEvent):
|
2020-01-30 21:10:04 +13:00
|
|
|
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
|
|
|
|
|
2020-02-09 16:32:37 +13:00
|
|
|
guild = self.bot.get_guild(payload.guild_id)
|
|
|
|
if not guild:
|
|
|
|
# Where's it go?
|
|
|
|
return
|
2020-10-29 21:36:43 +13:00
|
|
|
if await self.bot.cog_disabled_in_guild(self, guild):
|
|
|
|
return
|
2020-02-09 16:32:37 +13:00
|
|
|
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])
|