diff --git a/betteruptime/commands.py b/betteruptime/commands.py index ec4a27a1..8a2ecfbf 100644 --- a/betteruptime/commands.py +++ b/betteruptime/commands.py @@ -170,7 +170,7 @@ async def uptimegraph(self, ctx: commands.Context, num_days: int = 30): sr = data.daily_connected_percentages() - if len(sr) < 2: + if len(sr) < 2 or sr is None: return await ctx.send("Give me a few more days to collect data!") async with ctx.typing(): diff --git a/birthday/birthday.py b/birthday/birthday.py index de472d75..b6195ed8 100644 --- a/birthday/birthday.py +++ b/birthday/birthday.py @@ -46,6 +46,7 @@ def __init__(self, bot: Red) -> None: setup_state=0, # 0 is not setup, 5 is everything setup. this is so it can be steadily # incremented with individual setup commands or with the interactive setup, then # easily checked + require_role=False, allow_role_mention=False, ) self.config.register_member(birthday={"year": 1, "month": 1, "day": 1}) diff --git a/birthday/commands.py b/birthday/commands.py index 6c223da5..25bcc9dc 100644 --- a/birthday/commands.py +++ b/birthday/commands.py @@ -3,7 +3,7 @@ import asyncio import datetime from collections import defaultdict -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Literal, Union import discord from redbot.core import Config, commands @@ -264,6 +264,19 @@ async def settings(self, ctx: commands.Context): table.add_row("Allow role mentions", str(conf["allow_role_mention"])) + req_role = ctx.guild.get_role(conf["require_role"]) + if req_role: + table.add_row( + "Required role", + req_role.name + + ". Only users with this role can set their birthday and have it announced.", + ) + else: + table.add_row( + "Required role", + "Not set. All users can set their birthday and have it announced.", + ) + message_w_year = conf["message_w_year"] or "No message set" message_wo_year = conf["message_wo_year"] or "No message set" @@ -296,7 +309,7 @@ async def time(self, ctx: commands.Context, *, time: TimeConverter): Minutes are ignored. **Examples:** - - `[p]bdset time 7:00` - set the time to 7:45AM UTC + - `[p]bdset time 7:00` - set the time to 7:00AM UTC - `[p]bdset time 12AM` - set the time to midnight UTC - `[p]bdset time 3PM` - set the time to 3:00PM UTC """ @@ -654,6 +667,93 @@ async def rolemention(self, ctx: commands.Context, value: bool): else: await ctx.send("Role mentions have been disabled.") + @bdset.command() + async def requiredrole(self, ctx: commands.Context, *, role: Union[discord.Role, None] = None): + """ + Set a role that users must have to set their birthday. + + If users don't have this role then they can't set their + birthday and they won't get a role or message on their birthday. + + If they set their birthday and then lose the role, their birthday + will be stored but will be ignored until they regain the role. + + You can purge birthdays of users who no longer have the role + with `[p]bdset requiredrolepurge`. + + If no role is provided, the requirement is removed. + + View the current role with `[p]bdset settings`. + + **Example:** + - `[p]bdset requiredrole @Subscribers` - set the required role to @Subscribers + - `[p]bdset requiredrole Subscribers` - set the required role to @Subscribers + - `[p]bdset requiredrole` - remove the required role + """ + if role is None: + current_role = await self.config.guild(ctx.guild).require_role() + if current_role: + await self.config.guild(ctx.guild).require_role.clear() + await ctx.send( + "The required role has been removed. Birthdays can be set by anyone and will " + "always be announced." + ) + else: + await ctx.send( + "No role is current set. Birthdays can be set by anyone and will always be " + "announced." + ) + await ctx.send_help() + else: + await self.config.guild(ctx.guild).require_role.set(role.id) + await ctx.send( + f"The required role has been set to {role.name}. Users without this role no longer" + " have their birthday announced." + ) + + @bdset.command(name="requiredrolepurge") + async def requiredrole_purge(self, ctx: commands.Context): + """Remove birthdays from the database for users who no longer have the required role. + + If you have a required role set, this will remove birthdays for users who no longer have it + + Uses without the role are temporarily ignored until they regain the role. + + This command allows you to presently remove their birthday data from the database. + """ + # group has guild check + if TYPE_CHECKING: + assert ctx.guild is not None + + required_role: int | Literal[False] = await self.config.guild(ctx.guild).require_role() + if not required_role: + await ctx.send( + "You don't have a required role set. This command is only useful if you have a" + " required role set." + ) + return + + role = ctx.guild.get_role(required_role) + if role is None: + await ctx.send( + "The required role has been deleted. This command is only useful if you have a" + " required role set." + ) + return + + all_members = await self.config.all_members(ctx.guild) + purged = 0 + for member_id, member_data in all_members.items(): + member = ctx.guild.get_member(member_id) + if member is None: + continue + + if role not in member.roles: + await self.config.member_from_ids(ctx.guild.id, member_id).birthday.clear() + purged += 1 + + await ctx.send(f"Purged {purged} users from the database.") + @bdset.command() async def stop(self, ctx: commands.Context): """ diff --git a/birthday/loop.py b/birthday/loop.py index b3ed7d48..d610c35d 100644 --- a/birthday/loop.py +++ b/birthday/loop.py @@ -143,7 +143,7 @@ async def _update_birthdays(self): all_settings: dict[int, dict[str, Any]] = await self.config.all_guilds() async for guild_id, guild_data in AsyncIter(all_birthdays.items(), steps=5): - guild = self.bot.get_guild(int(guild_id)) + guild: discord.Guild | None = self.bot.get_guild(int(guild_id)) if guild is None: log.trace("Guild %s is not in cache, skipping", guild_id) continue @@ -175,6 +175,8 @@ async def _update_birthdays(self): start = today_dt + hour_td end = start + datetime.timedelta(days=1) + required_role = guild.get_role(all_settings[guild.id].get("required_role")) + async for member_id, data in AsyncIter(guild_data.items(), steps=50): birthday = data["birthday"] if not birthday: # birthday removed but user remains in config @@ -191,6 +193,14 @@ async def _update_birthdays(self): ) this_year_bday_dt = proper_bday_dt.replace(year=today_dt.year) + hour_td + if required_role and required_role not in member.roles: + log.trace( + "Member %s for guild %s does not have required role, skipping", + member_id, + guild_id, + ) + continue + if start <= this_year_bday_dt < end: # birthday is today birthday_members[member] = proper_bday_dt diff --git a/docs/changelog.rst b/docs/changelog.rst index 0dc7a8d6..a3f91c17 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -1174,6 +1174,14 @@ StatTrack Status ====== +********* +``2.5.8`` +********* + +2024-10-21 + +- Fix webhook sending for Discord status updates + ********* ``2.5.7`` ********* diff --git a/docs/cogs/birthday.rst b/docs/cogs/birthday.rst index fe02b57b..23a46be9 100644 --- a/docs/cogs/birthday.rst +++ b/docs/cogs/birthday.rst @@ -265,7 +265,7 @@ Set the time of day for the birthday message. Minutes are ignored. **Examples:** -- ``[p]bdset time 7:00`` - set the time to 7:45AM UTC +- ``[p]bdset time 7:00`` - set the time to 7:00AM UTC - ``[p]bdset time 12AM`` - set the time to midnight UTC - ``[p]bdset time 3PM`` - set the time to 3:00PM UTC diff --git a/status/core/core.py b/status/core/core.py index 8ca2ae1a..722c0d47 100644 --- a/status/core/core.py +++ b/status/core/core.py @@ -42,7 +42,7 @@ class Status( make an issue on the GitHub repo (or even better a PR!). """ - __version__ = "2.5.7" + __version__ = "2.5.8" __author__ = "@vexingvexed" def __init__(self, bot: Red) -> None: diff --git a/status/updateloop/sendupdate.py b/status/updateloop/sendupdate.py index 81fd5b58..27924605 100644 --- a/status/updateloop/sendupdate.py +++ b/status/updateloop/sendupdate.py @@ -136,6 +136,10 @@ async def _send_webhook(self, channel: TextChannel | Thread, embed: Embed) -> No embed.set_footer(text=f"Powered by {channel.guild.me.name}") webhook = await get_webhook(channel) + sanitised_name = UPDATE_NAME.format(FEEDS[self.service]["friendly"]) + if "Discord" in sanitised_name: + sanitised_name = "Status Update" + if self.channeldata.mode == "edit": if edit_id := self.channeldata.edit_id.get(self.incidentdata.incident_id): try: @@ -144,7 +148,7 @@ async def _send_webhook(self, channel: TextChannel | Thread, embed: Embed) -> No edit_id = None if not edit_id: sent_webhook = await webhook.send( - username=UPDATE_NAME.format(FEEDS[self.service]["friendly"]), + username=sanitised_name, avatar_url=ICON_BASE.format(self.service), embed=embed, wait=True, @@ -156,7 +160,7 @@ async def _send_webhook(self, channel: TextChannel | Thread, embed: Embed) -> No else: await webhook.send( - username=UPDATE_NAME.format(FEEDS[self.service]["friendly"]), + username=sanitised_name, avatar_url=ICON_BASE.format(self.service), embed=embed, thread=channel if isinstance(channel, Thread) else discord.utils.MISSING,