diff --git a/activitylog/activitylog.py b/activitylog/activitylog.py index 451632e..211db1f 100644 --- a/activitylog/activitylog.py +++ b/activitylog/activitylog.py @@ -8,6 +8,7 @@ import discord from .utils import * from datetime import datetime from dateutil.relativedelta import relativedelta +from dateutil.tz import tzlocal import time import os import asyncio @@ -179,12 +180,12 @@ class ActivityLogger(commands.Cog): since_created = (ctx.message.created_at - user.created_at).days if joined_at is not None: since_joined = (ctx.message.created_at - joined_at).days - user_joined = f"" + user_joined = f"" else: since_joined = "?" user_joined = "Unknown" - user_created = f"" + user_created = f"" 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) diff --git a/birthday/birthday.py b/birthday/birthday.py index 904d67b..ea0e4d7 100644 --- a/birthday/birthday.py +++ b/birthday/birthday.py @@ -24,9 +24,11 @@ class Birthday(commands.Cog): "channel": None, "role": None, "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_member(**default_member) @@ -49,6 +51,13 @@ class Birthday(commands.Cog): 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): self.bday_task.cancel() @@ -69,6 +78,64 @@ class Birthday(commands.Cog): continue for member in guild.members: 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): today = datetime.datetime.utcnow().date() @@ -135,6 +202,53 @@ class Birthday(commands.Cog): # await self.check_bdays() # 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.guild_only() @checks.admin_or_permissions(administrator=True) @@ -187,6 +301,60 @@ class Birthday(commands.Cog): 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.guild_only() async def bday(self, ctx): diff --git a/birthday/info.json b/birthday/info.json index 654d267..1942869 100644 --- a/birthday/info.json +++ b/birthday/info.json @@ -1,22 +1,25 @@ { - "author": [ - "PancakeSparkle", - "shroomdog26", - "brandons209" - ], - "bot_version": [ - 3, - 4, - 0 - ], - "description": "Cog used for setting birthdays and announcing them and giving people roles on their birthday", - "hidden": false, - "install_msg": "Use [p]bday to bring up the help menu", - "requirements": ["python-dateutil", "tabulate"], - "short": "Cog used for setting birthdays and announcing them and giving people roles on their birthday", - "tags": [ - "brandons209", - "pancakesparkle" - ], - "end_user_data_statement": "This cog will store a user's birthday." - } + "author": [ + "PancakeSparkle", + "shroomdog26", + "brandons209" + ], + "bot_version": [ + 3, + 4, + 0 + ], + "description": "Cog used for setting birthdays and announcing them and giving people roles on their birthday. Also can announce server anniversaries.", + "hidden": false, + "install_msg": "Use [p]bday to bring up the help menu", + "requirements": [ + "python-dateutil", + "tabulate" + ], + "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": [ + "brandons209", + "pancakesparkle" + ], + "end_user_data_statement": "This cog will store a user's birthday." +} \ No newline at end of file diff --git a/watchlist/__init__.py b/watchlist/__init__.py new file mode 100644 index 0000000..5c4e079 --- /dev/null +++ b/watchlist/__init__.py @@ -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)) diff --git a/watchlist/info.json b/watchlist/info.json new file mode 100644 index 0000000..df16b3e --- /dev/null +++ b/watchlist/info.json @@ -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" + ] +} \ No newline at end of file diff --git a/watchlist/watchlist.py b/watchlist/watchlist.py new file mode 100644 index 0000000..b715dfb --- /dev/null +++ b/watchlist/watchlist.py @@ -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 ") + 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 ") + + 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 ` 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), + ) diff --git a/welcome/info.json b/welcome/info.json index 605c3af..fba99a1 100644 --- a/welcome/info.json +++ b/welcome/info.json @@ -7,7 +7,9 @@ "name": "Welcome", "short": "Announces membership events.", "description": "Announces members joining, leaving, getting banned, and getting unbanned, in a customizable text channel and with customizable messages.", - "requirements": [], + "requirements": [ + "python-dateutil" + ], "tags": [ "welcome", "greetings", @@ -17,4 +19,4 @@ ], "min_bot_version": "3.4.0", "end_user_data_statement": "This cog doesn't store any user data." -} +} \ No newline at end of file diff --git a/welcome/welcome.py b/welcome/welcome.py index d6b6b98..7926298 100644 --- a/welcome/welcome.py +++ b/welcome/welcome.py @@ -7,6 +7,7 @@ from typing import Optional, Union, Literal from redbot.core import Config, checks, commands from redbot.core.utils.chat_formatting import box, pagify, humanize_list +from dateutil.tz import tzlocal from .enums import WhisperType from .errors import WhisperError @@ -815,7 +816,7 @@ class Welcome(commands.Cog): plural=plural, roles=roles, stats=stats, - joined_on=f"", + joined_on=f"", ), allowed_mentions=discord.AllowedMentions.all(), )