mirror of
https://github.com/brandons209/Red-bot-Cogs.git
synced 2024-05-05 04:54:11 +12:00
anniversary added, fixed discord timestamps, added watchlist cog
This commit is contained in:
parent
4049db3c24
commit
c40ae60d0e
|
@ -8,6 +8,7 @@ import discord
|
||||||
from .utils import *
|
from .utils import *
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil.relativedelta import relativedelta
|
from dateutil.relativedelta import relativedelta
|
||||||
|
from dateutil.tz import tzlocal
|
||||||
import time
|
import time
|
||||||
import os
|
import os
|
||||||
import asyncio
|
import asyncio
|
||||||
|
@ -179,12 +180,12 @@ class ActivityLogger(commands.Cog):
|
||||||
since_created = (ctx.message.created_at - user.created_at).days
|
since_created = (ctx.message.created_at - user.created_at).days
|
||||||
if joined_at is not None:
|
if joined_at is not None:
|
||||||
since_joined = (ctx.message.created_at - joined_at).days
|
since_joined = (ctx.message.created_at - joined_at).days
|
||||||
user_joined = f"<t:{int(joined_at.timestamp())}>"
|
user_joined = f"<t:{int(joined_at.astimezone(tzlocal()).timestamp())}>"
|
||||||
else:
|
else:
|
||||||
since_joined = "?"
|
since_joined = "?"
|
||||||
user_joined = "Unknown"
|
user_joined = "Unknown"
|
||||||
|
|
||||||
user_created = f"<t:{int(user.created_at.timestamp())}>"
|
user_created = f"<t:{int(user.created_at.astimezone(tzlocal()).timestamp())}>"
|
||||||
member_number = sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1
|
member_number = sorted(guild.members, key=lambda m: m.joined_at or ctx.message.created_at).index(user) + 1
|
||||||
|
|
||||||
created_on = "{}\n({} days ago)".format(user_created, since_created)
|
created_on = "{}\n({} days ago)".format(user_created, since_created)
|
||||||
|
|
|
@ -24,9 +24,11 @@ class Birthday(commands.Cog):
|
||||||
"channel": None,
|
"channel": None,
|
||||||
"role": None,
|
"role": None,
|
||||||
"dm_message": ":tada: Aurelia wishes you a very happy birthday! :tada:",
|
"dm_message": ":tada: Aurelia wishes you a very happy birthday! :tada:",
|
||||||
|
"anni_role": None,
|
||||||
|
"anni_message": ":tada: Aurelia is excited to wish you for {years} year{s} in CoE! :tada:",
|
||||||
}
|
}
|
||||||
|
|
||||||
default_member = {"birthday": None, "birthday_handeled": False}
|
default_member = {"birthday": None, "birthday_handeled": False, "anniversary": False, "anni_handled": False}
|
||||||
|
|
||||||
self.config.register_guild(**default_guild)
|
self.config.register_guild(**default_guild)
|
||||||
self.config.register_member(**default_member)
|
self.config.register_member(**default_member)
|
||||||
|
@ -49,6 +51,13 @@ class Birthday(commands.Cog):
|
||||||
|
|
||||||
return date, age
|
return date, age
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_years_in_guild(member: discord.Member):
|
||||||
|
joined = member.joined_at.date()
|
||||||
|
now = datetime.datetime.utcnow()
|
||||||
|
|
||||||
|
return now.year - joined.year
|
||||||
|
|
||||||
def cog_unload(self):
|
def cog_unload(self):
|
||||||
self.bday_task.cancel()
|
self.bday_task.cancel()
|
||||||
|
|
||||||
|
@ -69,6 +78,64 @@ class Birthday(commands.Cog):
|
||||||
continue
|
continue
|
||||||
for member in guild.members:
|
for member in guild.members:
|
||||||
await self.check_member_bday(member)
|
await self.check_member_bday(member)
|
||||||
|
await self.check_member_anni(member)
|
||||||
|
|
||||||
|
async def check_member_anni(self, member: discord.Member):
|
||||||
|
today = datetime.datetime.utcnow().date()
|
||||||
|
anni = member.joined_at.date()
|
||||||
|
|
||||||
|
if not (await self.config.member(member).anniversary()):
|
||||||
|
return
|
||||||
|
|
||||||
|
anni = anni.replace(year=today.year)
|
||||||
|
|
||||||
|
handled = await self.config.member(member).anni_handled()
|
||||||
|
if anni == today:
|
||||||
|
if not handled:
|
||||||
|
# dm user
|
||||||
|
dm = await self.config.guild(member.guild).anni_message()
|
||||||
|
y = self.get_years_in_guild(member)
|
||||||
|
s = "s" if y > 1 else ""
|
||||||
|
dm = dm.format(years=y, s=s)
|
||||||
|
try:
|
||||||
|
await member.send(dm)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# send anni in channel
|
||||||
|
channel = await self.config.guild(member.guild).channel()
|
||||||
|
channel = self.bot.get_channel(channel)
|
||||||
|
if channel:
|
||||||
|
embed = discord.Embed(color=discord.Colour.gold())
|
||||||
|
embed.description = f"{member.mention} has been in {member.guild} for **{y} year{s}!**"
|
||||||
|
# embed.set_footer("Add your birthday using the `bday` command!")
|
||||||
|
try:
|
||||||
|
content = f"Congratulations {member.mention}!"
|
||||||
|
await channel.send(content=content, embed=embed, allowed_mentions=discord.AllowedMentions.all())
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# add role, if available
|
||||||
|
role = await self.config.guild(member.guild).anni_role()
|
||||||
|
role = member.guild.get_role(role)
|
||||||
|
if role:
|
||||||
|
try:
|
||||||
|
await member.add_roles(role, reason="Birthday cog")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
await self.config.member(member).anni_handled.set(True)
|
||||||
|
else:
|
||||||
|
if handled:
|
||||||
|
# remove anni role
|
||||||
|
role = await self.config.guild(member.guild).anni_role()
|
||||||
|
role = member.guild.get_role(role)
|
||||||
|
if role:
|
||||||
|
try:
|
||||||
|
await member.remove_roles(role, reason="Birthday cog")
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
# unhandled their anniversary, cya next year!
|
||||||
|
await self.config.member(member).anni_handled.set(False)
|
||||||
|
|
||||||
async def check_member_bday(self, member: discord.Member):
|
async def check_member_bday(self, member: discord.Member):
|
||||||
today = datetime.datetime.utcnow().date()
|
today = datetime.datetime.utcnow().date()
|
||||||
|
@ -135,6 +202,53 @@ class Birthday(commands.Cog):
|
||||||
# await self.check_bdays()
|
# await self.check_bdays()
|
||||||
# await self.check_member_bday(member)
|
# await self.check_member_bday(member)
|
||||||
|
|
||||||
|
@commands.group(name="anniset")
|
||||||
|
@commands.guild_only()
|
||||||
|
@checks.admin_or_permissions(administrator=True)
|
||||||
|
async def anniset(self, ctx):
|
||||||
|
"""
|
||||||
|
Manage anniversary settings
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@anniset.command(name="dmmessage")
|
||||||
|
async def anniset_dmmessage(self, ctx, *, message: str = None):
|
||||||
|
"""Set message DMed to users when its their anniversary!
|
||||||
|
Leave empty to get/clear current message
|
||||||
|
|
||||||
|
In your message, you can use `{years}` which will be replaced with the number of years in the server,
|
||||||
|
With this, to make it grammatically correct use {s} which will be a `s` if years > 1
|
||||||
|
"""
|
||||||
|
if not message:
|
||||||
|
current = await self.config.guild(ctx.guild).anni_message()
|
||||||
|
await ctx.send(f"Current message is `{current}`\nDo you want to reset it to default?")
|
||||||
|
pred = MessagePredicate.yes_or_no(ctx)
|
||||||
|
try:
|
||||||
|
await self.bot.wait_for("message", check=pred, timeout=30)
|
||||||
|
except asyncio.TimeoutError:
|
||||||
|
await ctx.send("Took too long.")
|
||||||
|
return
|
||||||
|
if pred.result:
|
||||||
|
await self.config.guild(ctx.guild).anni_message.clear()
|
||||||
|
await ctx.send("DM message reset to default.")
|
||||||
|
else:
|
||||||
|
await ctx.send("Nothing changed.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.config.guild(ctx.guild).anni_message.set(message)
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@anniset.command(name="role")
|
||||||
|
@checks.bot_has_permissions(manage_roles=True)
|
||||||
|
async def anniset_role(self, ctx, *, role: discord.Role = None):
|
||||||
|
"""Set role to give users on their anniversary"""
|
||||||
|
if not role:
|
||||||
|
await self.config.guild(ctx.guild).anni_role.clear()
|
||||||
|
else:
|
||||||
|
await self.config.guild(ctx.guild).anni_role.set(role.id)
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
@commands.group(name="bdayset")
|
@commands.group(name="bdayset")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
@checks.admin_or_permissions(administrator=True)
|
@checks.admin_or_permissions(administrator=True)
|
||||||
|
@ -187,6 +301,60 @@ class Birthday(commands.Cog):
|
||||||
|
|
||||||
await ctx.tick()
|
await ctx.tick()
|
||||||
|
|
||||||
|
@commands.group(name="anni")
|
||||||
|
@commands.guild_only()
|
||||||
|
async def anni(self, ctx):
|
||||||
|
"""Manage your server anniversary"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@anni.command(name="set")
|
||||||
|
async def anni_set(self, ctx, toggle: bool = None):
|
||||||
|
"""
|
||||||
|
Opt in to anniversary announcements
|
||||||
|
"""
|
||||||
|
current = await self.config.member(ctx.author).anniversary()
|
||||||
|
|
||||||
|
if toggle is None:
|
||||||
|
if current:
|
||||||
|
await ctx.send("You are currently opted in for anniversary messages.")
|
||||||
|
return
|
||||||
|
else:
|
||||||
|
await ctx.send("You are currently NOT opted in for anniversary messages.")
|
||||||
|
return
|
||||||
|
|
||||||
|
await self.config.member(ctx.author).anniversary.set(toggle)
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@anni.command(name="list")
|
||||||
|
async def anni_list(self, ctx):
|
||||||
|
"""List anniversaries in the server"""
|
||||||
|
members = []
|
||||||
|
anniversaries = []
|
||||||
|
for member in ctx.guild.members:
|
||||||
|
if not (await self.config.member(member).anniversary()):
|
||||||
|
continue
|
||||||
|
anni = member.joined_at.date()
|
||||||
|
members.append(member.display_name)
|
||||||
|
anniversaries.append(anni.strftime("%b %d, %Y"))
|
||||||
|
|
||||||
|
pages = []
|
||||||
|
raw = list(
|
||||||
|
pagify(
|
||||||
|
tabulate({"Member": members, "Anniversary": anniversaries}, tablefmt="github", headers="keys"),
|
||||||
|
page_length=1700,
|
||||||
|
delims=["\n"],
|
||||||
|
priority=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
for i, page in enumerate(raw):
|
||||||
|
pages.append(box(f"{page}\n\n-----------------\nPage {i+1} of {len(raw)}"))
|
||||||
|
|
||||||
|
if not pages:
|
||||||
|
await ctx.send("No one has their anniversary set in your server!")
|
||||||
|
else:
|
||||||
|
await menu(ctx, pages, DEFAULT_CONTROLS)
|
||||||
|
|
||||||
@commands.group(name="bday")
|
@commands.group(name="bday")
|
||||||
@commands.guild_only()
|
@commands.guild_only()
|
||||||
async def bday(self, ctx):
|
async def bday(self, ctx):
|
||||||
|
|
|
@ -1,22 +1,25 @@
|
||||||
{
|
{
|
||||||
"author": [
|
"author": [
|
||||||
"PancakeSparkle",
|
"PancakeSparkle",
|
||||||
"shroomdog26",
|
"shroomdog26",
|
||||||
"brandons209"
|
"brandons209"
|
||||||
],
|
],
|
||||||
"bot_version": [
|
"bot_version": [
|
||||||
3,
|
3,
|
||||||
4,
|
4,
|
||||||
0
|
0
|
||||||
],
|
],
|
||||||
"description": "Cog used for setting birthdays and announcing them and giving people roles on their birthday",
|
"description": "Cog used for setting birthdays and announcing them and giving people roles on their birthday. Also can announce server anniversaries.",
|
||||||
"hidden": false,
|
"hidden": false,
|
||||||
"install_msg": "Use [p]bday to bring up the help menu",
|
"install_msg": "Use [p]bday to bring up the help menu",
|
||||||
"requirements": ["python-dateutil", "tabulate"],
|
"requirements": [
|
||||||
"short": "Cog used for setting birthdays and announcing them and giving people roles on their birthday",
|
"python-dateutil",
|
||||||
"tags": [
|
"tabulate"
|
||||||
"brandons209",
|
],
|
||||||
"pancakesparkle"
|
"short": "Cog used for setting birthdays and announcing them and giving people roles on their birthday, also does the same for anniversaries in the server.",
|
||||||
],
|
"tags": [
|
||||||
"end_user_data_statement": "This cog will store a user's birthday."
|
"brandons209",
|
||||||
}
|
"pancakesparkle"
|
||||||
|
],
|
||||||
|
"end_user_data_statement": "This cog will store a user's birthday."
|
||||||
|
}
|
7
watchlist/__init__.py
Normal file
7
watchlist/__init__.py
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
from .watchlist import Watchlist
|
||||||
|
|
||||||
|
__red_end_user_data_statement__ = "This doesn't store any user data."
|
||||||
|
|
||||||
|
|
||||||
|
def setup(bot):
|
||||||
|
bot.add_cog(Watchlist(bot))
|
12
watchlist/info.json
Normal file
12
watchlist/info.json
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"author": [
|
||||||
|
"brandons209"
|
||||||
|
],
|
||||||
|
"install_msg": "Thank you for installing my cog!",
|
||||||
|
"name": "Watchlist",
|
||||||
|
"short": "Watch for user's joining your guild and get notified when they join/leave.",
|
||||||
|
"description": "Allows guild staff to better keep track of persons of interest in their guild.",
|
||||||
|
"tags": [
|
||||||
|
"watchlist"
|
||||||
|
]
|
||||||
|
}
|
559
watchlist/watchlist.py
Normal file
559
watchlist/watchlist.py
Normal file
|
@ -0,0 +1,559 @@
|
||||||
|
import asyncio
|
||||||
|
import discord
|
||||||
|
import datetime
|
||||||
|
from tabulate import tabulate
|
||||||
|
|
||||||
|
from typing import Optional, Literal, Union
|
||||||
|
from redbot.core import Config, checks, commands
|
||||||
|
from redbot.core.utils.chat_formatting import *
|
||||||
|
from redbot.core.utils.menus import menu, DEFAULT_CONTROLS
|
||||||
|
|
||||||
|
|
||||||
|
class WatchlistUser:
|
||||||
|
"""
|
||||||
|
Maintains watchlist user data and provides functions for modification
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
bot,
|
||||||
|
user_id: int,
|
||||||
|
watchlist_number: int,
|
||||||
|
reason: str,
|
||||||
|
added_by: int,
|
||||||
|
message: discord.Message = None,
|
||||||
|
amended_by: int = None,
|
||||||
|
amended_time: int = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Create a new user on a watchlist
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Red): Bot instance
|
||||||
|
user_id (int): ID of the user on the watchlist
|
||||||
|
watchlist_number (int): The watchlist number this user represents
|
||||||
|
reason (str): Reason for being on the watchlist
|
||||||
|
added_by (int): Moderator/Administrator that added this user to the watchlist
|
||||||
|
message (discord.Message, optional): Message object on the watchlist. Defaults to None.
|
||||||
|
amended_by (int, optional): User ID of user who edited this watchlist user. Defaults to None.
|
||||||
|
amended_time (int, optional): When changes were last made to this watchlist user. Defaults to None.
|
||||||
|
"""
|
||||||
|
self.user_id = user_id
|
||||||
|
self.message = message
|
||||||
|
self.watchlist_number = watchlist_number
|
||||||
|
self.reason = reason
|
||||||
|
self.added_by = added_by
|
||||||
|
self.amended_by = amended_by
|
||||||
|
self.amended_time = amended_time
|
||||||
|
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
async def create_embed(self, amended_by: discord.Member = None):
|
||||||
|
"""
|
||||||
|
Create a discord Embed that represents this user on the watchlist
|
||||||
|
|
||||||
|
Args:
|
||||||
|
amended_by (discord.Member, optional): User who amended this watchlist user. Defaults to None.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
discord.Embed: The embed representing this user
|
||||||
|
"""
|
||||||
|
user = self.bot.get_user(self.user_id)
|
||||||
|
if not user:
|
||||||
|
user = await self.bot.fetch_user(self.user_id)
|
||||||
|
|
||||||
|
added_by = self.bot.get_user(self.added_by)
|
||||||
|
if not added_by:
|
||||||
|
added_by = await self.bot.fetch_user(self.added_by)
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
title = f"#{self.watchlist_number} Unknown / not found user ({self.user_id})"
|
||||||
|
avatar = None
|
||||||
|
else:
|
||||||
|
title = f"#{self.watchlist_number} {user} ({user.id})"
|
||||||
|
avatar = user.avatar_url_as(static_format="png")
|
||||||
|
|
||||||
|
embed = discord.Embed(color=discord.Color.blue(), title=title, description=self.reason)
|
||||||
|
|
||||||
|
if avatar:
|
||||||
|
embed.set_thumbnail(url=avatar)
|
||||||
|
|
||||||
|
embed.add_field(
|
||||||
|
name="Added by",
|
||||||
|
value=f"{added_by if added_by is not None else 'Unknown / not found user ({self.added_by})'}",
|
||||||
|
)
|
||||||
|
|
||||||
|
if amended_by is not None:
|
||||||
|
self.amended_by = amended_by.id
|
||||||
|
self.amended_time = int(datetime.datetime.now().timestamp())
|
||||||
|
embed.add_field(name="Amended by", value=f"{amended_by} at <t:{self.amended_time}:f>")
|
||||||
|
else:
|
||||||
|
amended_by = self.bot.get_user(self.amended_by)
|
||||||
|
if not amended_by and self.amended_by is not None:
|
||||||
|
amended_by = await self.bot.fetch_user(self.amended_by)
|
||||||
|
|
||||||
|
if amended_by is not None:
|
||||||
|
embed.add_field(name="Amended by", value=f"{amended_by} at <t:{self.amended_time}:f>")
|
||||||
|
|
||||||
|
return embed
|
||||||
|
|
||||||
|
async def send_watchlist_message(self, channel: discord.TextChannel = None):
|
||||||
|
"""
|
||||||
|
Send (or resend) watchlist message
|
||||||
|
|
||||||
|
Args:
|
||||||
|
channel (discord.TextChannel, optional): The channel to send the message to
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: If there is no channel provided and internal message is not set
|
||||||
|
"""
|
||||||
|
if channel:
|
||||||
|
message = await channel.send(embed=(await self.create_embed()))
|
||||||
|
self.message = message
|
||||||
|
elif self.message is not None:
|
||||||
|
channel = self.message.channel
|
||||||
|
try:
|
||||||
|
await self.message.delete()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
message = await channel.send(embed=(await self.create_embed()))
|
||||||
|
self.message = message
|
||||||
|
else:
|
||||||
|
raise AttributeError("Must provide a channel if there is no message for this user on watchlist.")
|
||||||
|
|
||||||
|
async def delete_watchlist_message(self):
|
||||||
|
"""
|
||||||
|
Deletes message on the watchlist
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
if self.message is None:
|
||||||
|
return False
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.message.delete()
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def update_reason(self, member: discord.Member, reason: str):
|
||||||
|
"""
|
||||||
|
Update the reason for this user
|
||||||
|
|
||||||
|
Args:
|
||||||
|
member (discord.Member): The user that requested this update.
|
||||||
|
reason (str): The new reason
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
self.reason = reason
|
||||||
|
new_embed = await self.create_embed(member)
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.message.edit(embed=new_embed)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
async def update_embed(self):
|
||||||
|
"""
|
||||||
|
Update embeds with new user information
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
bool: True if successful, False otherwise
|
||||||
|
"""
|
||||||
|
new_embed = await self.create_embed()
|
||||||
|
|
||||||
|
try:
|
||||||
|
await self.message.edit(embed=new_embed)
|
||||||
|
return True
|
||||||
|
except:
|
||||||
|
return False
|
||||||
|
|
||||||
|
def to_dict(self):
|
||||||
|
"""
|
||||||
|
Converts data for this object into dictionary
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
dict: The object data as a dictionary
|
||||||
|
"""
|
||||||
|
data = {
|
||||||
|
"user_id": self.user_id,
|
||||||
|
"added_by": self.added_by,
|
||||||
|
"channel_id": self.message.channel.id if self.message else None,
|
||||||
|
"message_id": self.message.id if self.message else None,
|
||||||
|
"watchlist_number": self.watchlist_number,
|
||||||
|
"reason": self.reason,
|
||||||
|
"amended_by": self.amended_by,
|
||||||
|
"amended_time": self.amended_time,
|
||||||
|
}
|
||||||
|
|
||||||
|
return data
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def from_dict(bot, data: dict):
|
||||||
|
"""
|
||||||
|
Create a new WatchlistUser object from data dictionary
|
||||||
|
|
||||||
|
Args:
|
||||||
|
bot (Red): Bot instance
|
||||||
|
data (dict): Data for watchlist user
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
AttributeError: If there is a missing key in the data dictionary
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
WatchlistUser: The watchlist user object
|
||||||
|
"""
|
||||||
|
needed_keys = [
|
||||||
|
"user_id",
|
||||||
|
"added_by",
|
||||||
|
"channel_id",
|
||||||
|
"message_id",
|
||||||
|
"watchlist_number",
|
||||||
|
"reason",
|
||||||
|
"amended_by",
|
||||||
|
"amended_time",
|
||||||
|
]
|
||||||
|
|
||||||
|
for k in needed_keys:
|
||||||
|
if k not in data:
|
||||||
|
raise AttributeError(f"{k} missing from dictionary!")
|
||||||
|
|
||||||
|
channel = bot.get_channel(data["channel_id"])
|
||||||
|
if not channel:
|
||||||
|
message = None
|
||||||
|
else:
|
||||||
|
message = await channel.fetch_message(data["message_id"])
|
||||||
|
|
||||||
|
return WatchlistUser(
|
||||||
|
bot,
|
||||||
|
data["user_id"],
|
||||||
|
data["watchlist_number"],
|
||||||
|
data["reason"],
|
||||||
|
data["added_by"],
|
||||||
|
message=message,
|
||||||
|
amended_by=data["amended_by"],
|
||||||
|
amended_time=data["amended_time"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
class Watchlist(commands.Cog):
|
||||||
|
"""
|
||||||
|
Watchlist of persons of interest
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, bot):
|
||||||
|
self.bot = bot
|
||||||
|
|
||||||
|
self.config = Config.get_conf(self, identifier=8946115618891655613, force_registration=True)
|
||||||
|
|
||||||
|
# watchlist user data will contain list of dictionaries that cna be converted to a watchlistuser class object
|
||||||
|
default_guild = {
|
||||||
|
"watchlist_users": [],
|
||||||
|
"removed_users": [],
|
||||||
|
"channel": None,
|
||||||
|
"alert_channel": None,
|
||||||
|
"watchlist_num": 0,
|
||||||
|
}
|
||||||
|
|
||||||
|
self.config.register_guild(**default_guild)
|
||||||
|
|
||||||
|
# store cached watchlist for each guild
|
||||||
|
self.watchlist = {}
|
||||||
|
|
||||||
|
self.task = asyncio.create_task(self.init())
|
||||||
|
|
||||||
|
def cog_unload(self):
|
||||||
|
if self.task:
|
||||||
|
self.task.cancel()
|
||||||
|
|
||||||
|
async def init(self):
|
||||||
|
await self.bot.wait_until_ready()
|
||||||
|
|
||||||
|
for guild in self.bot.guilds:
|
||||||
|
watch_list = await self.config.guild(guild).watchlist_users()
|
||||||
|
self.watchlist[guild.id] = []
|
||||||
|
for w in watch_list:
|
||||||
|
try:
|
||||||
|
self.watchlist[guild.id].append(await WatchlistUser.from_dict(self.bot, w))
|
||||||
|
except AttributeError as e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
while True:
|
||||||
|
for guild in self.bot.guilds:
|
||||||
|
if guild.id not in self.watchlist:
|
||||||
|
self.watchlist[guild.id] = []
|
||||||
|
|
||||||
|
for watchlist_user in self.watchlist[guild.id]:
|
||||||
|
await watchlist_user.update_embed()
|
||||||
|
|
||||||
|
await asyncio.sleep(28800) # update every 8 hours
|
||||||
|
|
||||||
|
@commands.group(name="watchlist")
|
||||||
|
@commands.guild_only()
|
||||||
|
@checks.admin_or_permissions(administrator=True)
|
||||||
|
async def watchlist(self, ctx):
|
||||||
|
"""
|
||||||
|
Manage guild watchlist
|
||||||
|
"""
|
||||||
|
pass
|
||||||
|
|
||||||
|
@watchlist.command(name="channel")
|
||||||
|
async def watchlist_channel(self, ctx, *, channel: discord.TextChannel = None):
|
||||||
|
"""
|
||||||
|
Change the watchlist channel
|
||||||
|
"""
|
||||||
|
if not channel:
|
||||||
|
await self.config.guild(ctx.guild).channel.clear()
|
||||||
|
await ctx.send(info("Watchlist channel cleared."))
|
||||||
|
else:
|
||||||
|
await self.config.guild(ctx.guild).channel.set(channel.id)
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@watchlist.command(name="alert")
|
||||||
|
async def watchlist_alert(self, ctx, *, channel: discord.TextChannel = None):
|
||||||
|
"""
|
||||||
|
Change the watchlist alert channel
|
||||||
|
"""
|
||||||
|
if not channel:
|
||||||
|
await self.config.guild(ctx.guild).alert_channel.clear()
|
||||||
|
await ctx.send(info("Watchlist channel cleared."))
|
||||||
|
else:
|
||||||
|
await self.config.guild(ctx.guild).alert_channel.set(channel.id)
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@watchlist.command(name="add")
|
||||||
|
async def watchlist_add(self, ctx, user_id: int, *, reason: str = None):
|
||||||
|
"""
|
||||||
|
Add a user to the watchlist, Reason is optional
|
||||||
|
|
||||||
|
Must use their user id!
|
||||||
|
"""
|
||||||
|
if ctx.guild.id not in self.watchlist:
|
||||||
|
self.watchlist[ctx.guild.id] = []
|
||||||
|
|
||||||
|
user = self.bot.get_user(user_id)
|
||||||
|
if not user:
|
||||||
|
user = await self.bot.fetch_user(user_id)
|
||||||
|
|
||||||
|
watch_list_ids = [w.user_id for w in self.watchlist[ctx.guild.id]]
|
||||||
|
|
||||||
|
if not user:
|
||||||
|
await ctx.send(error(f"Could not find user with id `{user_id}`!"))
|
||||||
|
return
|
||||||
|
elif user_id in watch_list_ids:
|
||||||
|
await ctx.send(error(f"User {user} already in the watchlist!"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if reason is None:
|
||||||
|
reason = "Use `[p]watchlist reason <watchlist number>` to add a reason."
|
||||||
|
|
||||||
|
watchlist_num = await self.config.guild(ctx.guild).watchlist_num()
|
||||||
|
channel_id = await self.config.guild(ctx.guild).channel()
|
||||||
|
channel = ctx.guild.get_channel(channel_id)
|
||||||
|
alert_channel = await self.config.guild(ctx.guild).alert_channel()
|
||||||
|
alert_channel = ctx.guild.get_channel(alert_channel)
|
||||||
|
|
||||||
|
if not channel:
|
||||||
|
await ctx.send(error(f"Could not find watchlist channel, please set it using `[p]watchlist channel` !"))
|
||||||
|
return
|
||||||
|
|
||||||
|
if not alert_channel:
|
||||||
|
await ctx.send(
|
||||||
|
warning(
|
||||||
|
"No alert channel set, you will not get alerts if this user joins! Please set it using `[p]watchlist alert`"
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
watchlist_user = WatchlistUser(self.bot, user.id, watchlist_num, reason, ctx.author.id)
|
||||||
|
await watchlist_user.send_watchlist_message(channel)
|
||||||
|
|
||||||
|
self.watchlist[ctx.guild.id].append(watchlist_user)
|
||||||
|
|
||||||
|
async with self.config.guild(ctx.guild).watchlist_users() as watchlist_users:
|
||||||
|
watchlist_users.append(watchlist_user.to_dict())
|
||||||
|
|
||||||
|
await self.config.guild(ctx.guild).watchlist_num.set(watchlist_num + 1)
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@watchlist.command(name="remove")
|
||||||
|
async def watchlist_remove(self, ctx, watchlist_num: int, *, reason: str = None):
|
||||||
|
"""
|
||||||
|
Remove a user from the watchlist.
|
||||||
|
|
||||||
|
Reason is optional
|
||||||
|
"""
|
||||||
|
if ctx.guild.id not in self.watchlist:
|
||||||
|
self.watchlist[ctx.guild.id] = []
|
||||||
|
|
||||||
|
watch_list_ids = [w.watchlist_number for w in self.watchlist[ctx.guild.id]]
|
||||||
|
|
||||||
|
if watchlist_num not in watch_list_ids:
|
||||||
|
await ctx.send(error("Unknown watchlist number!"))
|
||||||
|
return
|
||||||
|
|
||||||
|
idx = watch_list_ids.index(watchlist_num)
|
||||||
|
watchlist_user = self.watchlist[ctx.guild.id][idx]
|
||||||
|
if reason:
|
||||||
|
watchlist_user.reason = f"Removed from watchlist by {ctx.author.mention} (id: {ctx.author.id}) because: {reason}\nOriginal reason: {watchlist_user.reason}"
|
||||||
|
else:
|
||||||
|
watchlist_user.reason = f"Removed from watchlist by {ctx.author.mention} (id: {ctx.author.id})\nOriginal reason: {watchlist_user.reason}"
|
||||||
|
|
||||||
|
async with self.config.guild(ctx.guild).removed_users() as removed_users:
|
||||||
|
removed_users.append(watchlist_user.to_dict())
|
||||||
|
|
||||||
|
# delete message from watchlist channel
|
||||||
|
status = await watchlist_user.delete_watchlist_message()
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
await ctx.send(warning("There was an issue removing the message from the watchlist channel for this user!"))
|
||||||
|
|
||||||
|
del self.watchlist[ctx.guild.id][idx]
|
||||||
|
|
||||||
|
async with self.config.guild(ctx.guild).watchlist_users() as watchlist_users:
|
||||||
|
ids = [w["watchlist_number"] for w in watchlist_users]
|
||||||
|
idx = ids.index(watchlist_num)
|
||||||
|
del watchlist_users[idx]
|
||||||
|
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@watchlist.command(name="reason")
|
||||||
|
async def watchlist_reason(self, ctx, watchlist_num: int, *, reason):
|
||||||
|
"""
|
||||||
|
Change the reason for a watchlist user
|
||||||
|
|
||||||
|
Use the watchlist number to specify the user to change the reason for
|
||||||
|
"""
|
||||||
|
if ctx.guild.id not in self.watchlist:
|
||||||
|
self.watchlist[ctx.guild.id] = []
|
||||||
|
|
||||||
|
watchlist_numbers = [w.watchlist_number for w in self.watchlist[ctx.guild.id]]
|
||||||
|
|
||||||
|
if watchlist_num not in watchlist_numbers:
|
||||||
|
await ctx.send(error("Unknown watchlist number!"))
|
||||||
|
return
|
||||||
|
|
||||||
|
idx = watchlist_numbers.index(watchlist_num)
|
||||||
|
watchlist_user = self.watchlist[ctx.guild.id][idx]
|
||||||
|
|
||||||
|
status = await watchlist_user.update_reason(ctx.author, reason)
|
||||||
|
|
||||||
|
if not status:
|
||||||
|
await ctx.send(error("There was an issue updating the reason!"))
|
||||||
|
else:
|
||||||
|
await ctx.tick()
|
||||||
|
|
||||||
|
@watchlist.command(name="list")
|
||||||
|
async def watchlist_list(self, ctx):
|
||||||
|
"""
|
||||||
|
List removed users
|
||||||
|
"""
|
||||||
|
removed_users = await self.config.guild(ctx.guild).removed_users()
|
||||||
|
|
||||||
|
if len(removed_users) < 1:
|
||||||
|
await ctx.send(info("No one has been removed from the watchlist in your guild."))
|
||||||
|
return
|
||||||
|
|
||||||
|
msg = ""
|
||||||
|
for data in removed_users:
|
||||||
|
user = self.bot.get_user(data["user_id"])
|
||||||
|
if not user:
|
||||||
|
user = await self.bot.fetch_user(data["user_id"])
|
||||||
|
|
||||||
|
if user is None:
|
||||||
|
msg += f"Unknown user (id: {data['user_id']})\n"
|
||||||
|
else:
|
||||||
|
msg += f"{user.mention} (id: {user.id})\n"
|
||||||
|
|
||||||
|
msg += f"{data['reason']}\n"
|
||||||
|
msg += ("=" * 10) + "\n"
|
||||||
|
|
||||||
|
raw = list(
|
||||||
|
pagify(
|
||||||
|
msg,
|
||||||
|
page_length=1700,
|
||||||
|
delims=["\n"],
|
||||||
|
priority=True,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
pages = []
|
||||||
|
for i, page in enumerate(raw):
|
||||||
|
pages.append(f"{page}\n\n-----------------\nPage {i+1} of {len(raw)}")
|
||||||
|
if not pages:
|
||||||
|
await ctx.send("No one has their birthday set in your server!")
|
||||||
|
else:
|
||||||
|
await menu(ctx, pages, DEFAULT_CONTROLS)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_member_join(self, member: discord.Member):
|
||||||
|
guild = member.guild
|
||||||
|
if guild.id not in self.watchlist:
|
||||||
|
self.watchlist[guild.id] = []
|
||||||
|
|
||||||
|
watchlist_ids = [w.user_id for w in self.watchlist[guild.id]]
|
||||||
|
|
||||||
|
if not member.id in watchlist_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
idx = watchlist_ids.index(member.id)
|
||||||
|
watchlist_user = self.watchlist[guild.id][idx]
|
||||||
|
alert_channel = await self.config.guild(guild).alert_channel()
|
||||||
|
channel = guild.get_channel(alert_channel)
|
||||||
|
|
||||||
|
if not channel:
|
||||||
|
return
|
||||||
|
|
||||||
|
admin_roles = " ".join([r.mention for r in (await self.bot.get_admin_roles(guild))])
|
||||||
|
mod_roles = " ".join([r.mention for r in (await self.bot.get_mod_roles(guild))])
|
||||||
|
|
||||||
|
if not admin_roles or not mod_roles:
|
||||||
|
await channel.send(
|
||||||
|
f"**__Watchlist Alert for #{watchlist_user.watchlist_number}__**\n@everyone\n\nUser {member.mention} has joined!\n\n**Watchlist reason:** `{watchlist_user.reason}`",
|
||||||
|
allowed_mentions=discord.AllowedMentions(everyone=True),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await channel.send(
|
||||||
|
f"**__Watchlist Alert for #{watchlist_user.watchlist_number}__**\n{admin_roles} {mod_roles}\n\nUser {member.mention} has joined!\n\n**Watchlist reason:** `{watchlist_user.reason}`",
|
||||||
|
allowed_mentions=discord.AllowedMentions(roles=True),
|
||||||
|
)
|
||||||
|
|
||||||
|
@commands.Cog.listener()
|
||||||
|
async def on_member_remove(self, member: discord.Member):
|
||||||
|
guild = member.guild
|
||||||
|
if guild.id not in self.watchlist:
|
||||||
|
self.watchlist[guild.id] = []
|
||||||
|
|
||||||
|
watchlist_ids = [w.user_id for w in self.watchlist[guild.id]]
|
||||||
|
|
||||||
|
if not member.id in watchlist_ids:
|
||||||
|
return
|
||||||
|
|
||||||
|
idx = watchlist_ids.index(member.id)
|
||||||
|
watchlist_user = self.watchlist[guild.id][idx]
|
||||||
|
alert_channel = await self.config.guild(guild).alert_channel()
|
||||||
|
channel = guild.get_channel(alert_channel)
|
||||||
|
|
||||||
|
if not channel:
|
||||||
|
return
|
||||||
|
|
||||||
|
admin_roles = " ".join([r.mention for r in (await self.bot.get_admin_roles(guild))])
|
||||||
|
mod_roles = " ".join([r.mention for r in (await self.bot.get_mod_roles(guild))])
|
||||||
|
|
||||||
|
if not admin_roles or not mod_roles:
|
||||||
|
await channel.send(
|
||||||
|
f"**__Watchlist Alert for #{watchlist_user.watchlist_number}__**\n@everyone\n\nUser {member.mention} has left!\n\n**Watchlist reason:** `{watchlist_user.reason}`",
|
||||||
|
allowed_mentions=discord.AllowedMentions(everyone=True),
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
await channel.send(
|
||||||
|
f"**__Watchlist Alert for #{watchlist_user.watchlist_number}__**\n{admin_roles} {mod_roles}\n\nUser {member.mention} has left!\n\n**Watchlist reason:** `{watchlist_user.reason}`",
|
||||||
|
allowed_mentions=discord.AllowedMentions(roles=True),
|
||||||
|
)
|
|
@ -7,7 +7,9 @@
|
||||||
"name": "Welcome",
|
"name": "Welcome",
|
||||||
"short": "Announces membership events.",
|
"short": "Announces membership events.",
|
||||||
"description": "Announces members joining, leaving, getting banned, and getting unbanned, in a customizable text channel and with customizable messages.",
|
"description": "Announces members joining, leaving, getting banned, and getting unbanned, in a customizable text channel and with customizable messages.",
|
||||||
"requirements": [],
|
"requirements": [
|
||||||
|
"python-dateutil"
|
||||||
|
],
|
||||||
"tags": [
|
"tags": [
|
||||||
"welcome",
|
"welcome",
|
||||||
"greetings",
|
"greetings",
|
||||||
|
@ -17,4 +19,4 @@
|
||||||
],
|
],
|
||||||
"min_bot_version": "3.4.0",
|
"min_bot_version": "3.4.0",
|
||||||
"end_user_data_statement": "This cog doesn't store any user data."
|
"end_user_data_statement": "This cog doesn't store any user data."
|
||||||
}
|
}
|
|
@ -7,6 +7,7 @@ from typing import Optional, Union, Literal
|
||||||
|
|
||||||
from redbot.core import Config, checks, commands
|
from redbot.core import Config, checks, commands
|
||||||
from redbot.core.utils.chat_formatting import box, pagify, humanize_list
|
from redbot.core.utils.chat_formatting import box, pagify, humanize_list
|
||||||
|
from dateutil.tz import tzlocal
|
||||||
|
|
||||||
from .enums import WhisperType
|
from .enums import WhisperType
|
||||||
from .errors import WhisperError
|
from .errors import WhisperError
|
||||||
|
@ -815,7 +816,7 @@ class Welcome(commands.Cog):
|
||||||
plural=plural,
|
plural=plural,
|
||||||
roles=roles,
|
roles=roles,
|
||||||
stats=stats,
|
stats=stats,
|
||||||
joined_on=f"<t:{int(user.joined_at.timestamp())}>",
|
joined_on=f"<t:{int(user.joined_at.astimezone(tzlocal()).timestamp())}>",
|
||||||
),
|
),
|
||||||
allowed_mentions=discord.AllowedMentions.all(),
|
allowed_mentions=discord.AllowedMentions.all(),
|
||||||
)
|
)
|
||||||
|
|
Loading…
Reference in a new issue