aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorGravatar Ionite <[email protected]>2023-03-09 17:11:39 -0500
committerGravatar Ionite <[email protected]>2023-03-09 17:11:39 -0500
commit232836f76ccc25e8cd56c290f674d0ec90f6ba27 (patch)
tree2cd12bd1751c5823b9f6802a5d5b967d5e0d8366
parentAdd docstring info on file system support for eval (diff)
parentMerge pull request #2409 from shtlrs/bump-isort-in-precommit (diff)
Merge branch 'main' into snekbox-files
-rw-r--r--.pre-commit-config.yaml2
-rw-r--r--bot/constants.py3
-rw-r--r--bot/exts/help_channels/_channel.py30
-rw-r--r--bot/exts/info/subscribe.py121
-rw-r--r--bot/exts/moderation/infraction/_utils.py3
-rw-r--r--bot/exts/moderation/modlog.py7
-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/underscore.md27
-rw-r--r--config-default.yml4
-rw-r--r--poetry.lock18
-rw-r--r--pyproject.toml2
14 files changed, 213 insertions, 50 deletions
diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml
index d8a90ac00..47fd80f97 100644
--- a/.pre-commit-config.yaml
+++ b/.pre-commit-config.yaml
@@ -14,7 +14,7 @@ repos:
hooks:
- id: python-check-blanket-noqa
- repo: https://github.com/pycqa/isort
- rev: 5.8.0
+ rev: 5.12.0
hooks:
- id: isort
name: isort (python)
diff --git a/bot/constants.py b/bot/constants.py
index f17f2dc0c..178d3949d 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -466,6 +466,8 @@ class Channels(metaclass=YAMLGetter):
big_brother_logs: int
+ roles: int
+
class Webhooks(metaclass=YAMLGetter):
section = "guild"
@@ -488,6 +490,7 @@ class Roles(metaclass=YAMLGetter):
lovefest: int
pyweek_announcements: int
revival_of_code: int
+ legacy_help_channels_access: int
contributors: int
help_cooldown: int
diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py
index fad2a32a9..f64162006 100644
--- a/bot/exts/help_channels/_channel.py
+++ b/bot/exts/help_channels/_channel.py
@@ -14,26 +14,26 @@ from bot.log import get_logger
log = get_logger(__name__)
ASKING_GUIDE_URL = "https://pythondiscord.com/pages/asking-good-questions/"
-
+BRANDING_REPO_RAW_URL = "https://raw.githubusercontent.com/python-discord/branding"
POST_TITLE = "Python help channel"
+
NEW_POST_MSG = f"""
**Remember to:**
• **Ask** your Python question, not if you can ask or if there's an expert who can help.
-• **Show** a code sample as text (rather than a screenshot) and the error message, if you got one.
+• **Show** a code sample as text (rather than a screenshot) and the error message, if you've got one.
• **Explain** what you expect to happen and what actually happens.
For more tips, check out our guide on [asking good questions]({ASKING_GUIDE_URL}).
"""
-POST_FOOTER = f"Closes after a period of inactivity, or when you send {constants.Bot.prefix}close."
+NEW_POST_FOOTER = f"Closes after a period of inactivity, or when you send {constants.Bot.prefix}close."
+NEW_POST_ICON_URL = f"{BRANDING_REPO_RAW_URL}/main/icons/checkmark/green-checkmark-dist.png"
-DORMANT_MSG = f"""
-This help channel has been marked as **dormant** and locked. \
-It is no longer possible to send messages in this channel.
-
-If your question wasn't answered yet, you can create a new post in <#{constants.Channels.help_system_forum}>. \
-Consider rephrasing the question to maximize your chance of getting a good answer. \
-If you're not sure how, have a look through our guide for **[asking a good question]({ASKING_GUIDE_URL})**.
+CLOSED_POST_MSG = f"""
+This help channel has been closed and it's no longer possible to send messages here. \
+If your question wasn't answered, feel free to create a new post in <#{constants.Channels.help_system_forum}>. \
+To maximize your chances of getting a response, check out this guide on [asking good questions]({ASKING_GUIDE_URL}).
"""
+CLOSED_POST_ICON_URL = f"{BRANDING_REPO_RAW_URL}/main/icons/zzz/zzz-dist.png"
def is_help_forum_post(channel: discord.abc.GuildChannel) -> bool:
@@ -44,9 +44,11 @@ def is_help_forum_post(channel: discord.abc.GuildChannel) -> bool:
async def _close_help_post(closed_post: discord.Thread, closing_reason: _stats.ClosingReason) -> None:
"""Close the help post and record stats."""
- embed = discord.Embed(description=DORMANT_MSG)
+ embed = discord.Embed(description=CLOSED_POST_MSG)
+ embed.set_author(name=f"{POST_TITLE} closed", icon_url=CLOSED_POST_ICON_URL)
+
await closed_post.send(embed=embed)
- await closed_post.edit(archived=True, locked=True, reason="Locked a dormant help post")
+ await closed_post.edit(archived=True, locked=True, reason="Locked a closed help post")
_stats.report_post_count()
await _stats.report_complete_session(closed_post, closing_reason)
@@ -71,8 +73,8 @@ async def send_opened_post_message(post: discord.Thread) -> None:
color=constants.Colours.bright_green,
description=NEW_POST_MSG,
)
- embed.set_author(name=POST_TITLE)
- embed.set_footer(text=POST_FOOTER)
+ embed.set_author(name=f"{POST_TITLE} opened", icon_url=NEW_POST_ICON_URL)
+ embed.set_footer(text=NEW_POST_FOOTER)
await post.send(embed=embed)
diff --git a/bot/exts/info/subscribe.py b/bot/exts/info/subscribe.py
index 36304539f..7f4b4f95a 100644
--- a/bot/exts/info/subscribe.py
+++ b/bot/exts/info/subscribe.py
@@ -13,6 +13,7 @@ from bot import constants
from bot.bot import Bot
from bot.decorators import redirect_output
from bot.log import get_logger
+from bot.utils.channel import get_or_fetch_channel
@dataclass(frozen=True)
@@ -48,6 +49,7 @@ class AssignableRole:
ASSIGNABLE_ROLES = (
AssignableRole(constants.Roles.announcements, None),
AssignableRole(constants.Roles.pyweek_announcements, None),
+ AssignableRole(constants.Roles.legacy_help_channels_access, None),
AssignableRole(constants.Roles.lovefest, (1, 2)),
AssignableRole(constants.Roles.advent_of_code, (11, 12)),
AssignableRole(constants.Roles.revival_of_code, (7, 8, 9, 10)),
@@ -60,11 +62,25 @@ log = get_logger(__name__)
class RoleButtonView(discord.ui.View):
- """A list of SingleRoleButtons to show to the member."""
+ """
+ A view that holds the list of SingleRoleButtons to show to the member.
+
+ Attributes
+ __________
+ interaction_owner: discord.Member
+ The member that initiated the interaction
+ """
+
+ interaction_owner: discord.Member
- def __init__(self, member: discord.Member):
- super().__init__()
+ def __init__(self, member: discord.Member, assignable_roles: list[AssignableRole]):
+ super().__init__(timeout=DELETE_MESSAGE_AFTER)
self.interaction_owner = member
+ author_roles = [role.id for role in member.roles]
+
+ for index, role in enumerate(assignable_roles):
+ row = index // ITEMS_PER_ROW
+ self.add_item(SingleRoleButton(role, role.role_id in author_roles, row))
async def interaction_check(self, interaction: Interaction) -> bool:
"""Ensure that the user clicking the button is the member who invoked the command."""
@@ -78,12 +94,12 @@ class RoleButtonView(discord.ui.View):
class SingleRoleButton(discord.ui.Button):
- """A button that adds or removes a role from the member depending on it's current state."""
+ """A button that adds or removes a role from the member depending on its current state."""
ADD_STYLE = discord.ButtonStyle.success
REMOVE_STYLE = discord.ButtonStyle.red
UNAVAILABLE_STYLE = discord.ButtonStyle.secondary
- LABEL_FORMAT = "{action} role {role_name}."
+ LABEL_FORMAT = "{action} role {role_name}"
CUSTOM_ID_FORMAT = "subscribe-{role_id}"
def __init__(self, role: AssignableRole, assigned: bool, row: int):
@@ -123,7 +139,7 @@ class SingleRoleButton(discord.ui.Button):
self.assigned = not self.assigned
await self.update_view(interaction)
- await interaction.response.send_message(
+ await interaction.followup.send(
self.LABEL_FORMAT.format(action="Added" if self.assigned else "Removed", role_name=self.role.name),
ephemeral=True,
)
@@ -133,15 +149,45 @@ class SingleRoleButton(discord.ui.Button):
self.style = self.REMOVE_STYLE if self.assigned else self.ADD_STYLE
self.label = self.LABEL_FORMAT.format(action="Remove" if self.assigned else "Add", role_name=self.role.name)
try:
- await interaction.message.edit(view=self.view)
+ await interaction.response.edit_message(view=self.view)
except discord.NotFound:
log.debug("Subscribe message for %s removed before buttons could be updated", interaction.user)
self.view.stop()
+class AllSelfAssignableRolesView(discord.ui.View):
+ """A persistent view that'll hold one button allowing interactors to toggle all available self-assignable roles."""
+
+ def __init__(self, assignable_roles: list[AssignableRole]):
+ super().__init__(timeout=None)
+ self.assignable_roles = assignable_roles
+
+ @discord.ui.button(
+ style=discord.ButtonStyle.success,
+ label="Show all self assignable roles",
+ custom_id="toggle-available-roles-button",
+ row=1
+ )
+ async def show_all_self_assignable_roles(self, interaction: Interaction, button: discord.ui.Button) -> None:
+ """Sends the original subscription view containing the available self assignable roles."""
+ view = RoleButtonView(interaction.user, self.assignable_roles)
+ await interaction.response.send_message(
+ view=view,
+ ephemeral=True
+ )
+
+
class Subscribe(commands.Cog):
"""Cog to allow user to self-assign & remove the roles present in ASSIGNABLE_ROLES."""
+ GREETING_EMOJI = ":wave:"
+
+ SELF_ASSIGNABLE_ROLES_MESSAGE = (
+ f"Hi there {GREETING_EMOJI},"
+ "\nWe have self-assignable roles for server updates and events!"
+ "\nClick the button below to toggle them:"
+ )
+
def __init__(self, bot: Bot):
self.bot = bot
self.assignable_roles: list[AssignableRole] = []
@@ -150,7 +196,6 @@ class Subscribe(commands.Cog):
async def cog_load(self) -> None:
"""Initialise the cog by resolving the role IDs in ASSIGNABLE_ROLES to role names."""
await self.bot.wait_until_guild_available()
-
self.guild = self.bot.get_guild(constants.Guild.id)
for role in ASSIGNABLE_ROLES:
@@ -170,6 +215,10 @@ class Subscribe(commands.Cog):
self.assignable_roles.sort(key=operator.attrgetter("name"))
self.assignable_roles.sort(key=operator.methodcaller("is_currently_available"), reverse=True)
+ placeholder_message_view_tuple = await self._fetch_or_create_self_assignable_roles_message()
+ self_assignable_roles_message, self_assignable_roles_view = placeholder_message_view_tuple
+ self._attach_persistent_roles_view(self_assignable_roles_message, self_assignable_roles_view)
+
@commands.cooldown(1, 10, commands.BucketType.member)
@commands.command(name="subscribe", aliases=("unsubscribe",))
@redirect_output(
@@ -178,22 +227,58 @@ class Subscribe(commands.Cog):
)
async def subscribe_command(self, ctx: commands.Context, *_) -> None: # We don't actually care about the args
"""Display the member's current state for each role, and allow them to add/remove the roles."""
- button_view = RoleButtonView(ctx.author)
- author_roles = [role.id for role in ctx.author.roles]
- for index, role in enumerate(self.assignable_roles):
- row = index // ITEMS_PER_ROW
- button_view.add_item(SingleRoleButton(role, role.role_id in author_roles, row))
-
+ view = RoleButtonView(ctx.author, self.assignable_roles)
await ctx.send(
"Click the buttons below to add or remove your roles!",
- view=button_view,
- delete_after=DELETE_MESSAGE_AFTER,
+ view=view,
+ delete_after=DELETE_MESSAGE_AFTER
)
+ async def _fetch_or_create_self_assignable_roles_message(self) -> tuple[discord.Message, discord.ui.View | None]:
+ """
+ Fetches the message that holds the self assignable roles view.
+
+ If the initial message isn't found, a new one will be created.
+ This message will always be needed to attach the persistent view to it
+ """
+ roles_channel: discord.TextChannel = await get_or_fetch_channel(constants.Channels.roles)
+
+ async for message in roles_channel.history(limit=30):
+ if message.content == self.SELF_ASSIGNABLE_ROLES_MESSAGE:
+ log.debug(f"Found self assignable roles view message: {message.id}")
+ return message, None
+
+ log.debug("Self assignable roles view message hasn't been found, creating a new one.")
+ view = AllSelfAssignableRolesView(self.assignable_roles)
+ placeholder_message = await roles_channel.send(self.SELF_ASSIGNABLE_ROLES_MESSAGE, view=view)
+ return placeholder_message, view
+
+ def _attach_persistent_roles_view(
+ self,
+ placeholder_message: discord.Message,
+ persistent_roles_view: discord.ui.View | None = None
+ ) -> None:
+ """
+ Attaches the persistent view that toggles self assignable roles to its placeholder message.
+
+ The message is searched for/created upon loading the Cog.
+
+ Parameters
+ __________
+ :param placeholder_message: The message that will hold the persistent view allowing
+ users to toggle the RoleButtonView
+ :param persistent_roles_view: The view attached to the placeholder_message
+ If none, a new view will be created
+ """
+ if not persistent_roles_view:
+ persistent_roles_view = AllSelfAssignableRolesView(self.assignable_roles)
+
+ self.bot.add_view(persistent_roles_view, message_id=placeholder_message.id)
+
async def setup(bot: Bot) -> None:
- """Load the Subscribe cog."""
- if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW*5: # Discord limits views to 5 rows of buttons.
+ """Load the 'Subscribe' cog."""
+ if len(ASSIGNABLE_ROLES) > ITEMS_PER_ROW * 5: # Discord limits views to 5 rows of buttons.
log.error("Too many roles for 5 rows, not loading the Subscribe cog.")
else:
await bot.add_cog(Subscribe(bot))
diff --git a/bot/exts/moderation/infraction/_utils.py b/bot/exts/moderation/infraction/_utils.py
index 2cf7f8efb..c2ef80461 100644
--- a/bot/exts/moderation/infraction/_utils.py
+++ b/bot/exts/moderation/infraction/_utils.py
@@ -31,12 +31,13 @@ RULES_URL = "https://pythondiscord.com/pages/rules"
Infraction = t.Dict[str, t.Union[str, int, bool]]
APPEAL_SERVER_INVITE = "https://discord.gg/WXrCJxWBnm"
+MODMAIL_ACCOUNT_ID = "683001325440860340"
INFRACTION_TITLE = "Please review our rules"
INFRACTION_APPEAL_SERVER_FOOTER = f"\nTo appeal this infraction, join our [appeals server]({APPEAL_SERVER_INVITE})."
INFRACTION_APPEAL_MODMAIL_FOOTER = (
'\nIf you would like to discuss or appeal this infraction, '
- 'send a message to the ModMail bot.'
+ f'send a message to the ModMail bot (<@{MODMAIL_ACCOUNT_ID}>).'
)
INFRACTION_AUTHOR_NAME = "Infraction information"
diff --git a/bot/exts/moderation/modlog.py b/bot/exts/moderation/modlog.py
index eeaf69139..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:
@@ -833,7 +834,7 @@ class ModLog(Cog, name="ModLog"):
@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
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/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/config-default.yml b/config-default.yml
index d843dce3b..f33394a7e 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -236,6 +236,9 @@ guild:
# Watch
big_brother_logs: &BB_LOGS 468507907357409333
+ # Information
+ roles: 851270062434156586
+
moderation_categories:
- *MODS_CATEGORY
- *MODMAIL
@@ -269,6 +272,7 @@ guild:
lovefest: 542431903886606399
pyweek_announcements: 897568414044938310
revival_of_code: 988801794668908655
+ legacy_help_channels_access: 1074780483776417964
contributors: 295488872404484098
help_cooldown: 699189276025421825
diff --git a/poetry.lock b/poetry.lock
index f418c0516..b8c30b5b3 100644
--- a/poetry.lock
+++ b/poetry.lock
@@ -193,7 +193,7 @@ cli = ["clevercsv (==0.7.4)", "click (==8.1.3)", "pyyaml (==6.0)", "toml (==0.10
[[package]]
name = "discord-py"
-version = "2.1.0"
+version = "2.1.1"
description = "A Python wrapper for the Discord API"
category = "main"
optional = false
@@ -685,16 +685,16 @@ email = ["email-validator (>=1.0.3)"]
[[package]]
name = "pydis-core"
-version = "9.1.1"
+version = "9.4.1"
description = "PyDis core provides core functionality and utility to the bots of the Python Discord community."
category = "main"
optional = false
-python-versions = ">=3.10.0,<3.11.0"
+python-versions = ">=3.10.0,<3.12.0"
[package.dependencies]
aiodns = "3.0.0"
async-rediscache = {version = "1.0.0rc2", extras = ["fakeredis"], optional = true, markers = "extra == \"async-rediscache\""}
-"discord.py" = "2.1.0"
+"discord.py" = "2.1.1"
statsd = "4.0.1"
[package.extras]
@@ -1113,7 +1113,7 @@ multidict = ">=4.0"
[metadata]
lock-version = "1.1"
python-versions = "3.10.*"
-content-hash = "8c741bfe48fa990572979642dbb94c170385a15793c234044a08cde3dce629e1"
+content-hash = "378b9f9a41e79cdd30ee594a9017f709a8286b7dfbfe45215f60ca7bad0f53fd"
[metadata.files]
aiodns = [
@@ -1376,8 +1376,8 @@ deepdiff = [
{file = "deepdiff-6.2.1.tar.gz", hash = "sha256:3fe134dde5b3922ff8c51fc1e95a972e659c853797231b836a5ccf15532fd516"},
]
discord-py = [
- {file = "discord.py-2.1.0-py3-none-any.whl", hash = "sha256:a2cfa9f09e3013aaaa43600cc8dfaf67c532dd34afcb71e550f5a0dc9133a5e0"},
- {file = "discord.py-2.1.0.tar.gz", hash = "sha256:027ccdd22b5bb66a9e19cbd8daa1bc74b49271a16a074d57e52f288fcfa208e8"},
+ {file = "discord.py-2.1.1-py3-none-any.whl", hash = "sha256:b99fbf4ad74f007c680f33f5174738858146c99a2bc4f6d0e08ddbf93bc12c4d"},
+ {file = "discord.py-2.1.1.tar.gz", hash = "sha256:8258b7af641c532e9e33e186c7b867d9ff5575fe63a460b17ed55bd4fe082200"},
]
distlib = [
{file = "distlib-0.3.6-py2.py3-none-any.whl", hash = "sha256:f35c4b692542ca110de7ef0bea44d73981caeb34ca0b9b6b2e6d7790dda8f80e"},
@@ -1909,8 +1909,8 @@ pydantic = [
{file = "pydantic-1.10.2.tar.gz", hash = "sha256:91b8e218852ef6007c2b98cd861601c6a09f1aa32bbbb74fab5b1c33d4a1e410"},
]
pydis-core = [
- {file = "pydis_core-9.1.1-py3-none-any.whl", hash = "sha256:db6e29c8eb4c12ce29fbf57f7b7214a1bdf702f9464320f4c39fef4dc8741bc0"},
- {file = "pydis_core-9.1.1.tar.gz", hash = "sha256:a73fe400d9b7cea5d87cfb53d1bb9ca8fd7fd3b48c1c1b74c43117319acefdac"},
+ {file = "pydis_core-9.4.1-py3-none-any.whl", hash = "sha256:29f1f69ae2bc5c9560ac3d43149ca9e7f13d567064555b76b79be5c8fa9ba6ba"},
+ {file = "pydis_core-9.4.1.tar.gz", hash = "sha256:b52c7ea052196aae72298ef59f59514376178e713cd133e0ca6fcb1d7312024c"},
]
pydocstyle = [
{file = "pydocstyle-6.1.1-py3-none-any.whl", hash = "sha256:6987826d6775056839940041beef5c08cc7e3d71d63149b48e36727f70144dc4"},
diff --git a/pyproject.toml b/pyproject.toml
index ac6d3982d..8a94f9ffa 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -9,7 +9,7 @@ license = "MIT"
python = "3.10.*"
# See https://bot-core.pythondiscord.com/ for docs.
-pydis_core = { version = "9.1.1", extras = ["async-rediscache"] }
+pydis_core = { version = "9.4.1", extras = ["async-rediscache"] }
redis = "4.3.5"
fakeredis = { version = "2.0.0", extras = ["lua"] }