aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Boris Muratov <[email protected]>2023-01-22 22:35:01 +0200
committerGravatar GitHub <[email protected]>2023-01-22 22:35:01 +0200
commit8af86ae253dd933dcdbdbc5c0eea9a55d056a733 (patch)
treed39d728dea44ed0b502019c128cba71b2e69326d
parentfix typos in the SELF_ASSIGNABLE_ROLES_MESSAGE (diff)
parentMerge 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.py2
-rw-r--r--bot/exts/help_channels/_channel.py3
-rw-r--r--bot/exts/help_channels/_cog.py2
-rw-r--r--bot/exts/help_channels/_stats.py10
-rw-r--r--bot/exts/info/information.py26
-rw-r--r--bot/exts/moderation/modlog.py30
-rw-r--r--bot/exts/recruitment/talentpool/_api.py4
-rw-r--r--bot/exts/recruitment/talentpool/_cog.py14
-rw-r--r--bot/exts/recruitment/talentpool/_review.py23
-rw-r--r--bot/exts/utils/reminders.py5
-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.md27
-rw-r--r--poetry.lock6
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"},