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"}, | 
