diff options
author | 2023-01-22 22:35:01 +0200 | |
---|---|---|
committer | 2023-01-22 22:35:01 +0200 | |
commit | 8af86ae253dd933dcdbdbc5c0eea9a55d056a733 (patch) | |
tree | d39d728dea44ed0b502019c128cba71b2e69326d | |
parent | fix typos in the SELF_ASSIGNABLE_ROLES_MESSAGE (diff) | |
parent | Merge pull request #2375 from python-discord/allow-passing-channel-objets-whe... (diff) |
Merge branch 'main' into 2332-permanent-role-view
-rw-r--r-- | bot/exts/help_channels/_caches.py | 2 | ||||
-rw-r--r-- | bot/exts/help_channels/_channel.py | 3 | ||||
-rw-r--r-- | bot/exts/help_channels/_cog.py | 2 | ||||
-rw-r--r-- | bot/exts/help_channels/_stats.py | 10 | ||||
-rw-r--r-- | bot/exts/info/information.py | 26 | ||||
-rw-r--r-- | bot/exts/moderation/modlog.py | 30 | ||||
-rw-r--r-- | bot/exts/recruitment/talentpool/_api.py | 4 | ||||
-rw-r--r-- | bot/exts/recruitment/talentpool/_cog.py | 14 | ||||
-rw-r--r-- | bot/exts/recruitment/talentpool/_review.py | 23 | ||||
-rw-r--r-- | bot/exts/utils/reminders.py | 5 | ||||
-rw-r--r-- | bot/resources/tags/return-gif.md (renamed from bot/resources/tags/print-return.md) | 1 | ||||
-rw-r--r-- | bot/resources/tags/underscore.md | 27 | ||||
-rw-r--r-- | poetry.lock | 6 |
13 files changed, 108 insertions, 45 deletions
diff --git a/bot/exts/help_channels/_caches.py b/bot/exts/help_channels/_caches.py index 5d98f99d3..3369fc0a6 100644 --- a/bot/exts/help_channels/_caches.py +++ b/bot/exts/help_channels/_caches.py @@ -9,6 +9,6 @@ help_dm = RedisCache(namespace="HelpChannels.help_dm") # RedisCache[discord.TextChannel.id, str[set[discord.User.id]]] session_participants = RedisCache(namespace="HelpChannels.session_participants") -# Stores posts that have had a non-claimant reply. +# Stores posts that have had a non-claimant, non-bot, reply. # Currently only used to determine whether the post was answered or not when collecting stats. posts_with_non_claimant_messages = RedisCache(namespace="HelpChannels.posts_with_non_claimant_messages") diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py index 0cee24817..fad2a32a9 100644 --- a/bot/exts/help_channels/_channel.py +++ b/bot/exts/help_channels/_channel.py @@ -94,7 +94,7 @@ async def send_opened_post_dm(post: discord.Thread) -> None: return formatted_message = textwrap.shorten(message.content, width=100, placeholder="...").strip() - if formatted_message is None: + if not formatted_message: # This most likely means the initial message is only an image or similar formatted_message = "No text content." @@ -117,6 +117,7 @@ async def send_opened_post_dm(post: discord.Thread) -> None: async def help_post_opened(opened_post: discord.Thread, *, reopen: bool = False) -> None: """Apply new post logic to a new help forum post.""" _stats.report_post_count() + bot.instance.stats.incr("help.claimed") if not isinstance(opened_post.owner, discord.Member): log.debug(f"{opened_post.owner_id} isn't a member. Closing post.") diff --git a/bot/exts/help_channels/_cog.py b/bot/exts/help_channels/_cog.py index 31f30b7aa..bc6bd0303 100644 --- a/bot/exts/help_channels/_cog.py +++ b/bot/exts/help_channels/_cog.py @@ -179,5 +179,5 @@ class HelpForum(commands.Cog): await _message.notify_session_participants(message) - if message.author.id != message.channel.owner_id: + if not message.author.bot and message.author.id != message.channel.owner_id: await _caches.posts_with_non_claimant_messages.set(message.channel.id, "sentinel") diff --git a/bot/exts/help_channels/_stats.py b/bot/exts/help_channels/_stats.py index 8ab93f19d..1075b439e 100644 --- a/bot/exts/help_channels/_stats.py +++ b/bot/exts/help_channels/_stats.py @@ -23,7 +23,7 @@ class ClosingReason(Enum): def report_post_count() -> None: """Report post count stats of the help forum.""" help_forum = bot.instance.get_channel(constants.Channels.help_system_forum) - bot.instance.stats.gauge("help_forum.total.in_use", len(help_forum.threads)) + bot.instance.stats.gauge("help.total.in_use", len(help_forum.threads)) async def report_complete_session(help_session_post: discord.Thread, closed_on: ClosingReason) -> None: @@ -32,13 +32,13 @@ async def report_complete_session(help_session_post: discord.Thread, closed_on: `closed_on` is the reason why the post was closed. See `ClosingReason` for possible reasons. """ - bot.instance.stats.incr(f"help_forum.dormant_calls.{closed_on.value}") + bot.instance.stats.incr(f"help.dormant_calls.{closed_on.value}") open_time = discord.utils.snowflake_time(help_session_post.id) in_use_time = arrow.utcnow() - open_time - bot.instance.stats.timing("help_forum.in_use_time", in_use_time) + bot.instance.stats.timing("help.in_use_time", in_use_time) if await _caches.posts_with_non_claimant_messages.get(help_session_post.id): - bot.instance.stats.incr("help_forum.sessions.answered") + bot.instance.stats.incr("help.sessions.answered") else: - bot.instance.stats.incr("help_forum.sessions.unanswered") + bot.instance.stats.incr("help.sessions.unanswered") diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py index 1a6cfcb59..c680da2bc 100644 --- a/bot/exts/info/information.py +++ b/bot/exts/info/information.py @@ -3,7 +3,7 @@ import pprint import textwrap from collections import defaultdict from textwrap import shorten -from typing import Any, DefaultDict, Mapping, Optional, Set, Tuple, Union +from typing import Any, DefaultDict, Mapping, Optional, Set, TYPE_CHECKING, Tuple, Union import rapidfuzz from discord import AllowedMentions, Colour, Embed, Guild, Message, Role @@ -31,6 +31,11 @@ DEFAULT_RULES_DESCRIPTION = ( " all members of the community to have read and understood these." ) +if TYPE_CHECKING: + from bot.exts.moderation.defcon import Defcon + from bot.exts.moderation.watchchannels.bigbrother import BigBrother + from bot.exts.recruitment.talentpool._cog import TalentPool + class Information(Cog): """A cog with commands for generating embeds with server info, such as server stats and user info.""" @@ -76,20 +81,23 @@ class Information(Cog): ) return role_stats - def get_extended_server_info(self, ctx: Context) -> str: + async def get_extended_server_info(self, ctx: Context) -> str: """Return additional server info only visible in moderation channels.""" talentpool_info = "" - if cog := self.bot.get_cog("Talentpool"): - num_nominated = len(cog.cache) if cog.cache else "-" + talentpool_cog: TalentPool | None = self.bot.get_cog("Talentpool") + if talentpool_cog: + num_nominated = len(await talentpool_cog.api.get_nominations(active=True)) talentpool_info = f"Nominated: {num_nominated}\n" bb_info = "" - if cog := self.bot.get_cog("Big Brother"): - bb_info = f"BB-watched: {len(cog.watched_users)}\n" + bb_cog: BigBrother | None = self.bot.get_cog("Big Brother") + if bb_cog: + bb_info = f"BB-watched: {len(bb_cog.watched_users)}\n" defcon_info = "" - if cog := self.bot.get_cog("Defcon"): - threshold = time.humanize_delta(cog.threshold) if cog.threshold else "-" + defcon_cog: Defcon | None = self.bot.get_cog("Defcon") + if defcon_cog: + threshold = time.humanize_delta(defcon_cog.threshold) if defcon_cog.threshold else "-" defcon_info = f"Defcon threshold: {threshold}\n" verification = f"Verification level: {ctx.guild.verification_level.name}\n" @@ -224,7 +232,7 @@ class Information(Cog): # Additional info if ran in moderation channels if is_mod_channel(ctx.channel): - embed.add_field(name="Moderation:", value=self.get_extended_server_info(ctx)) + embed.add_field(name="Moderation:", value=await self.get_extended_server_info(ctx)) await ctx.send(embed=embed) diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py index d916d1f4d..2c94d1af8 100644 --- a/bot/exts/moderation/modlog.py +++ b/bot/exts/moderation/modlog.py @@ -534,7 +534,7 @@ class ModLog(Cog, name="ModLog"): return self.is_channel_ignored(message.channel.id) - def is_channel_ignored(self, channel_id: int) -> bool: + def is_channel_ignored(self, channel: int | GuildChannel | Thread) -> bool: """ Return true if the channel, or parent channel in the case of threads, passed should be ignored by modlog. @@ -543,7 +543,8 @@ class ModLog(Cog, name="ModLog"): 2. Channels that mods do not have view permissions to 3. Channels in constants.Guild.modlog_blacklist """ - channel = self.bot.get_channel(channel_id) + if isinstance(channel, int): + channel = self.bot.get_channel(channel) # Ignore not found channels, DMs, and messages outside of the main guild. if not channel or channel.guild is None or channel.guild.id != GuildConstant.id: @@ -826,13 +827,14 @@ class ModLog(Cog, name="ModLog"): ( f"Thread {after.mention} ({after.name}, `{after.id}`) from {after.parent.mention} " f"(`{after.parent.id}`) was {action}" - ) + ), + channel_id=Channels.message_log, ) @Cog.listener() async def on_thread_delete(self, thread: Thread) -> None: """Log thread deletion.""" - if self.is_channel_ignored(thread.id): + if self.is_channel_ignored(thread): log.trace("Ignoring deletion of thread %s (%d)", thread.mention, thread.id) return @@ -843,24 +845,8 @@ class ModLog(Cog, name="ModLog"): ( f"Thread {thread.mention} ({thread.name}, `{thread.id}`) from {thread.parent.mention} " f"(`{thread.parent.id}`) deleted" - ) - ) - - @Cog.listener() - async def on_thread_create(self, thread: Thread) -> None: - """Log thread creation.""" - if self.is_channel_ignored(thread.id): - log.trace("Ignoring creation of thread %s (%d)", thread.mention, thread.id) - return - - await self.send_log_message( - Icons.hash_green, - Colours.soft_green, - "Thread created", - ( - f"Thread {thread.mention} ({thread.name}, `{thread.id}`) from {thread.parent.mention} " - f"(`{thread.parent.id}`) created" - ) + ), + channel_id=Channels.message_log, ) @Cog.listener() diff --git a/bot/exts/recruitment/talentpool/_api.py b/bot/exts/recruitment/talentpool/_api.py index fee23826d..c00c8c09c 100644 --- a/bot/exts/recruitment/talentpool/_api.py +++ b/bot/exts/recruitment/talentpool/_api.py @@ -23,6 +23,7 @@ class Nomination(BaseModel): ended_at: datetime | None entries: list[NominationEntry] reviewed: bool + thread_id: int | None class NominationAPI: @@ -65,6 +66,7 @@ class NominationAPI: end_reason: str | None = None, active: bool | None = None, reviewed: bool | None = None, + thread_id: int | None = None, ) -> Nomination: """ Edit a nomination. @@ -78,6 +80,8 @@ class NominationAPI: data["active"] = active if reviewed is not None: data["reviewed"] = reviewed + if thread_id is not None: + data["thread_id"] = thread_id result = await self.site_api.patch(f"bot/nominations/{nomination_id}", json=data) return Nomination.parse_obj(result) diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py index dbc3ea538..a41d9e8c5 100644 --- a/bot/exts/recruitment/talentpool/_cog.py +++ b/bot/exts/recruitment/talentpool/_cog.py @@ -17,6 +17,7 @@ from bot.exts.recruitment.talentpool._review import Reviewer from bot.log import get_logger from bot.pagination import LinePaginator from bot.utils import time +from bot.utils.channel import get_or_fetch_channel from bot.utils.members import get_or_fetch_member from ._api import Nomination, NominationAPI @@ -489,6 +490,17 @@ class TalentPool(Cog, name="Talentpool"): entries_string = "\n\n".join(entries) start_date = time.discord_timestamp(nomination.inserted_at) + + thread_jump_url = "*Not created*" + + if nomination.thread_id: + try: + thread = await get_or_fetch_channel(nomination.thread_id) + except discord.HTTPException: + thread_jump_url = "*Not found*" + else: + thread_jump_url = f'[Jump to thread!]({thread.jump_url})' + if nomination.active: lines = textwrap.dedent( f""" @@ -496,6 +508,7 @@ class TalentPool(Cog, name="Talentpool"): Status: **Active** Date: {start_date} Nomination ID: `{nomination.id}` + Nomination vote thread: {thread_jump_url} {entries_string} =============== @@ -509,6 +522,7 @@ class TalentPool(Cog, name="Talentpool"): Status: Inactive Date: {start_date} Nomination ID: `{nomination.id}` + Nomination vote thread: {thread_jump_url} {entries_string} diff --git a/bot/exts/recruitment/talentpool/_review.py b/bot/exts/recruitment/talentpool/_review.py index 876f95369..f41e08fe1 100644 --- a/bot/exts/recruitment/talentpool/_review.py +++ b/bot/exts/recruitment/talentpool/_review.py @@ -8,6 +8,7 @@ from collections import Counter from datetime import datetime, timedelta, timezone from typing import List, Optional, Union +import discord from discord import Embed, Emoji, Member, Message, NotFound, PartialMessage, TextChannel from pydis_core.site_api import ResponseCodeError @@ -16,6 +17,7 @@ from bot.constants import Channels, Colours, Emojis, Guild, Roles from bot.exts.recruitment.talentpool._api import Nomination, NominationAPI from bot.log import get_logger from bot.utils import time +from bot.utils.channel import get_or_fetch_channel from bot.utils.members import get_or_fetch_member from bot.utils.messages import count_unique_users_reaction, pin_no_system_message @@ -180,7 +182,7 @@ class Reviewer: ) message = await thread.send(f"<@&{Roles.mod_team}> <@&{Roles.admins}>") - await self.api.edit_nomination(nomination.id, reviewed=True) + await self.api.edit_nomination(nomination.id, reviewed=True, thread_id=thread.id) bump_cog: ThreadBumper = self.bot.get_cog("ThreadBumper") if bump_cog: @@ -433,11 +435,30 @@ class Reviewer: nomination_times = f"{num_entries} times" if num_entries > 1 else "once" rejection_times = f"{len(history)} times" if len(history) > 1 else "once" + thread_jump_urls = [] + + for nomination in history: + if nomination.thread_id is None: + continue + try: + thread = await get_or_fetch_channel(nomination.thread_id) + except discord.HTTPException: + # Nothing to do here + pass + else: + thread_jump_urls.append(thread.jump_url) + + if not thread_jump_urls: + nomination_vote_threads = "No nomination threads have been found for this user." + else: + nomination_vote_threads = ", ".join(thread_jump_urls) + end_time = time.format_relative(history[0].ended_at) review = ( f"They were nominated **{nomination_times}** before" f", but their nomination was called off **{rejection_times}**." + f"\nList of all of their nomination threads: {nomination_vote_threads}" f"\nThe last one ended {end_time} with the reason: {history[0].end_reason}" ) diff --git a/bot/exts/utils/reminders.py b/bot/exts/utils/reminders.py index 1991a687f..368f08510 100644 --- a/bot/exts/utils/reminders.py +++ b/bot/exts/utils/reminders.py @@ -13,7 +13,7 @@ from pydis_core.utils.scheduling import Scheduler from bot.bot import Bot from bot.constants import ( - Guild, Icons, MODERATION_ROLES, NEGATIVE_REPLIES, POSITIVE_REPLIES, Roles, STAFF_PARTNERS_COMMUNITY_ROLES + Channels, Guild, Icons, MODERATION_ROLES, NEGATIVE_REPLIES, POSITIVE_REPLIES, Roles, STAFF_PARTNERS_COMMUNITY_ROLES ) from bot.converters import Duration, UnambiguousUser from bot.errors import LockedResourceError @@ -280,7 +280,8 @@ class Reminders(Cog): # If they don't have permission to set a reminder in this channel if ctx.channel.id not in WHITELISTED_CHANNELS: - await send_denial(ctx, "Sorry, you can't do that here!") + bot_commands = ctx.guild.get_channel(Channels.bot_commands) + await send_denial(ctx, f"Sorry, you can only do that in {bot_commands.mention}!") return # Get their current active reminders diff --git a/bot/resources/tags/print-return.md b/bot/resources/tags/return-gif.md index 89d37053f..1229151fe 100644 --- a/bot/resources/tags/print-return.md +++ b/bot/resources/tags/return-gif.md @@ -1,4 +1,5 @@ --- +aliases: ["print-return", "return-jif"] embed: title: Print and Return image: diff --git a/bot/resources/tags/underscore.md b/bot/resources/tags/underscore.md new file mode 100644 index 000000000..4da2e86ca --- /dev/null +++ b/bot/resources/tags/underscore.md @@ -0,0 +1,27 @@ +--- +aliases: ["under"] +embed: + title: "Meanings of Underscores in Identifier Names" +--- + +• `__name__`: Used to implement special behaviour, such as the `+` operator for classes with the `__add__` method. [More info](https://dbader.org/blog/python-dunder-methods) +• `_name`: Indicates that a variable is "private" and should only be used by the class or module that defines it +• `name_`: Used to avoid naming conflicts. For example, as `class` is a keyword, you could call a variable `class_` instead +• `__name`: Causes the name to be "mangled" if defined inside a class. [More info](https://docs.python.org/3/tutorial/classes.html#private-variables) + +A single underscore, **`_`**, has multiple uses: +• To indicate an unused variable, e.g. in a for loop if you don't care which iteration you are on +```python +for _ in range(10): + print("Hello World") +``` +• In the REPL, where the previous result is assigned to the variable `_` +```python +>>> 1 + 1 # Evaluated and stored in `_` + 2 +>>> _ + 3 # Take the previous result and add 3 + 5 +``` +• In integer literals, e.g. `x = 1_500_000` can be written instead of `x = 1500000` to improve readability + +See also ["Reserved classes of identifiers"](https://docs.python.org/3/reference/lexical_analysis.html#reserved-classes-of-identifiers) in the Python docs, and [this more detailed guide](https://dbader.org/blog/meaning-of-underscores-in-python). diff --git a/poetry.lock b/poetry.lock index 4461e8b3f..f418c0516 100644 --- a/poetry.lock +++ b/poetry.lock @@ -105,7 +105,7 @@ lxml = ["lxml"] [[package]] name = "certifi" -version = "2022.9.24" +version = "2022.12.7" description = "Python package for providing Mozilla's CA Bundle." category = "main" optional = false @@ -1234,8 +1234,8 @@ beautifulsoup4 = [ {file = "beautifulsoup4-4.11.1.tar.gz", hash = "sha256:ad9aa55b65ef2808eb405f46cf74df7fcb7044d5cbc26487f96eb2ef2e436693"}, ] certifi = [ - {file = "certifi-2022.9.24-py3-none-any.whl", hash = "sha256:90c1a32f1d68f940488354e36370f6cca89f0f106db09518524c88d6ed83f382"}, - {file = "certifi-2022.9.24.tar.gz", hash = "sha256:0d9c601124e5a6ba9712dbc60d9c53c21e34f5f641fe83002317394311bdce14"}, + {file = "certifi-2022.12.7-py3-none-any.whl", hash = "sha256:4ad3232f5e926d6718ec31cfc1fcadfde020920e278684144551c91769c7bc18"}, + {file = "certifi-2022.12.7.tar.gz", hash = "sha256:35824b4c3a97115964b408844d64aa14db1cc518f6562e8d7261699d1350a9e3"}, ] cffi = [ {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, |