aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--.github/workflows/build.yml2
-rw-r--r--CODE_OF_CONDUCT.md3
-rw-r--r--CONTRIBUTING.md124
-rw-r--r--SECURITY.md3
-rw-r--r--bot/bot.py2
-rw-r--r--bot/constants.py6
-rw-r--r--bot/exts/help_channels/_channel.py3
-rw-r--r--bot/exts/info/information.py2
-rw-r--r--bot/exts/recruitment/talentpool/_cog.py28
-rw-r--r--bot/exts/utils/utils.py4
-rw-r--r--config-default.yml17
-rw-r--r--tests/bot/exts/info/test_information.py20
12 files changed, 61 insertions, 153 deletions
diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index e6826e09b..84a671917 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -39,7 +39,7 @@ jobs:
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
- password: ${{ secrets.GHCR_TOKEN }}
+ password: ${{ secrets.GITHUB_TOKEN }}
# Build and push the container to the GitHub Container
# Repository. The container will be tagged as "latest"
diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md
new file mode 100644
index 000000000..57ccd80e7
--- /dev/null
+++ b/CODE_OF_CONDUCT.md
@@ -0,0 +1,3 @@
+# Code of Conduct
+
+The Python Discord Code of Conduct can be found [on our website](https://pydis.com/coc).
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index addab32ff..f20b53162 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -1,123 +1,3 @@
-# Contributing to one of Our Projects
+# Contributing Guidelines
-Our projects are open-source and are automatically deployed whenever commits are pushed to the `main` branch on each repository, so we've created a set of guidelines in order to keep everything clean and in working order.
-
-Note that contributions may be rejected on the basis of a contributor failing to follow these guidelines.
-
-## Rules
-
-1. **No force-pushes** or modifying the Git history in any way.
-2. If you have direct access to the repository, **create a branch for your changes** and create a pull request for that branch. If not, create a branch on a fork of the repository and create a pull request from there.
- * It's common practice for a repository to reject direct pushes to `main`, so make branching a habit!
- * If PRing from your own fork, **ensure that "Allow edits from maintainers" is checked**. This gives permission for maintainers to commit changes directly to your fork, speeding up the review process.
-3. **Adhere to the prevailing code style**, which we enforce using [`flake8`](http://flake8.pycqa.org/en/latest/index.html) and [`pre-commit`](https://pre-commit.com/).
- * Run `flake8` and `pre-commit` against your code [**before** you push it](https://soundcloud.com/lemonsaurusrex/lint-before-you-push). Your commit will be rejected by the build server if it fails to lint.
- * [Git Hooks](https://git-scm.com/book/en/v2/Customizing-Git-Git-Hooks) are a powerful git feature for executing custom scripts when certain important git actions occur. The pre-commit hook is the first hook executed during the commit process and can be used to check the code being committed & abort the commit if issues, such as linting failures, are detected. While git hooks can seem daunting to configure, the `pre-commit` framework abstracts this process away from you and is provided as a dev dependency for this project. Run `pipenv run precommit` when setting up the project and you'll never have to worry about committing code that fails linting.
-4. **Make great commits**. A well structured git log is key to a project's maintainability; it efficiently provides insight into when and *why* things were done for future maintainers of the project.
- * Commits should be as narrow in scope as possible. Commits that span hundreds of lines across multiple unrelated functions and/or files are very hard for maintainers to follow. After about a week they'll probably be hard for you to follow too.
- * Avoid making minor commits for fixing typos or linting errors. Since you've already set up a `pre-commit` hook to run the linting pipeline before a commit, you shouldn't be committing linting issues anyway.
- * A more in-depth guide to writing great commit messages can be found in Chris Beam's [*How to Write a Git Commit Message*](https://chris.beams.io/posts/git-commit/)
-5. **Avoid frequent pushes to the main repository**. This goes for PRs opened against your fork as well. Our test build pipelines are triggered every time a push to the repository (or PR) is made. Try to batch your commits until you've finished working for that session, or you've reached a point where collaborators need your commits to continue their own work. This also provides you the opportunity to amend commits for minor changes rather than having to commit them on their own because you've already pushed.
- * This includes merging main into your branch. Try to leave merging from main for after your PR passes review; a maintainer will bring your PR up to date before merging. Exceptions to this include: resolving merge conflicts, needing something that was pushed to main for your branch, or something was pushed to main that could potentionally affect the functionality of what you're writing.
-6. **Don't fight the framework**. Every framework has its flaws, but the frameworks we've picked out have been carefully chosen for their particular merits. If you can avoid it, please resist reimplementing swathes of framework logic - the work has already been done for you!
-7. If someone is working on an issue or pull request, **do not open your own pull request for the same task**. Instead, collaborate with the author(s) of the existing pull request. Duplicate PRs opened without communicating with the other author(s) and/or PyDis staff will be closed. Communication is key, and there's no point in two separate implementations of the same thing.
- * One option is to fork the other contributor's repository and submit your changes to their branch with your own pull request. We suggest following these guidelines when interacting with their repository as well.
- * The author(s) of inactive PRs and claimed issues will be be pinged after a week of inactivity for an update. Continued inactivity may result in the issue being released back to the community and/or PR closure.
-8. **Work as a team** and collaborate wherever possible. Keep things friendly and help each other out - these are shared projects and nobody likes to have their feet trodden on.
-9. All static content, such as images or audio, **must be licensed for open public use**.
- * Static content must be hosted by a service designed to do so. Failing to do so is known as "leeching" and is frowned upon, as it generates extra bandwidth costs to the host without providing benefit. It would be best if appropriately licensed content is added to the repository itself so it can be served by PyDis' infrastructure.
-
-Above all, the needs of our community should come before the wants of an individual. Work together, build solutions to problems and try to do so in a way that people can learn from easily. Abuse of our trust may result in the loss of your Contributor role.
-
-## Changes to this Arrangement
-
-All projects evolve over time, and this contribution guide is no different. This document is open to pull requests or changes by contributors. If you believe you have something valuable to add or change, please don't hesitate to do so in a PR.
-
-## Supplemental Information
-### Developer Environment
-Instructions for setting the bot developer environment can be found on the [PyDis wiki](https://pythondiscord.com/pages/contributing/bot/)
-
-To provide a standalone development environment for this project, docker compose is utilized to pull the current version of the [site backend](https://github.com/python-discord/site). While appropriate for bot-only contributions, any contributions that necessitate backend changes will require the site repository to be appropriately configured as well. Instructions for setting up the site environment can be found on the [PyDis site](https://pythondiscord.com/pages/contributing/site/).
-
-When pulling down changes from GitHub, remember to sync your environment using `pipenv sync --dev` to ensure you're using the most up-to-date versions the project's dependencies.
-
-### Type Hinting
-[PEP 484](https://www.python.org/dev/peps/pep-0484/) formally specifies type hints for Python functions, added to the Python Standard Library in version 3.5. Type hints are recognized by most modern code editing tools and provide useful insight into both the input and output types of a function, preventing the user from having to go through the codebase to determine these types.
-
-For example:
-
-```py
-import typing as t
-
-
-def foo(input_1: int, input_2: t.Dict[str, str]) -> bool:
- ...
-```
-
-Tells us that `foo` accepts an `int` and a `dict`, with `str` keys and values, and returns a `bool`.
-
-All function declarations should be type hinted in code contributed to the PyDis organization.
-
-For more information, see *[PEP 483](https://www.python.org/dev/peps/pep-0483/) - The Theory of Type Hints* and Python's documentation for the [`typing`](https://docs.python.org/3/library/typing.html) module.
-
-### AutoDoc Formatting Directives
-Many documentation packages provide support for automatic documentation generation from the codebase's docstrings. These tools utilize special formatting directives to enable richer formatting in the generated documentation.
-
-For example:
-
-```py
-import typing as t
-
-
-def foo(bar: int, baz: t.Optional[t.Dict[str, str]] = None) -> bool:
- """
- Does some things with some stuff.
-
- :param bar: Some input
- :param baz: Optional, some dictionary with string keys and values
-
- :return: Some boolean
- """
- ...
-```
-
-Since PyDis does not utilize automatic documentation generation, use of this syntax should not be used in code contributed to the organization. Should the purpose and type of the input variables not be easily discernable from the variable name and type annotation, a prose explanation can be used. Explicit references to variables, functions, classes, etc. should be wrapped with backticks (`` ` ``).
-
-For example, the above docstring would become:
-
-```py
-import typing as t
-
-
-def foo(bar: int, baz: t.Optional[t.Dict[str, str]] = None) -> bool:
- """
- Does some things with some stuff.
-
- This function takes an index, `bar` and checks for its presence in the database `baz`, passed as a dictionary. Returns `False` if `baz` is not passed.
- """
- ...
-```
-
-### Logging Levels
-The project currently defines [`logging`](https://docs.python.org/3/library/logging.html) levels as follows, from lowest to highest severity:
-* **TRACE:** These events should be used to provide a *verbose* trace of every step of a complex process. This is essentially the `logging` equivalent of sprinkling `print` statements throughout the code.
- * **Note:** This is a PyDis-implemented logging level.
-* **DEBUG:** These events should add context to what's happening in a development setup to make it easier to follow what's going while working on a project. This is in the same vein as **TRACE** logging but at a much lower level of verbosity.
-* **INFO:** These events are normal and don't need direct attention but are worth keeping track of in production, like checking which cogs were loaded during a start-up.
-* **WARNING:** These events are out of the ordinary and should be fixed, but have not caused a failure.
- * **NOTE:** Events at this logging level and higher should be reserved for events that require the attention of the DevOps team.
-* **ERROR:** These events have caused a failure in a specific part of the application and require urgent attention.
-* **CRITICAL:** These events have caused the whole application to fail and require immediate intervention.
-
-Ensure that log messages are succinct. Should you want to pass additional useful information that would otherwise make the log message overly verbose the `logging` module accepts an `extra` kwarg, which can be used to pass a dictionary. This is used to populate the `__dict__` of the `LogRecord` created for the logging event with user-defined attributes that can be accessed by a log handler. Additional information and caveats may be found [in Python's `logging` documentation](https://docs.python.org/3/library/logging.html#logging.Logger.debug).
-
-### Work in Progress (WIP) PRs
-Github [provides a PR feature](https://github.blog/2019-02-14-introducing-draft-pull-requests/) that allows the PR author to mark it as a WIP. This provides both a visual and functional indicator that the contents of the PR are in a draft state and not yet ready for formal review.
-
-This feature should be utilized in place of the traditional method of prepending `[WIP]` to the PR title.
-
-As stated earlier, **ensure that "Allow edits from maintainers" is checked**. This gives permission for maintainers to commit changes directly to your fork, speeding up the review process.
-
-## Footnotes
-
-This document was inspired by the [Glowstone contribution guidelines](https://github.com/GlowstoneMC/Glowstone/blob/dev/docs/CONTRIBUTING.md).
+The Contributing Guidelines for Python Discord projects can be found [on our website](https://pydis.com/contributing.md).
diff --git a/SECURITY.md b/SECURITY.md
new file mode 100644
index 000000000..fa5a88a39
--- /dev/null
+++ b/SECURITY.md
@@ -0,0 +1,3 @@
+# Security Notice
+
+The Security Notice for Python Discord projects can be found [on our website](https://pydis.com/security.md).
diff --git a/bot/bot.py b/bot/bot.py
index 3a2af472d..914da9c98 100644
--- a/bot/bot.py
+++ b/bot/bot.py
@@ -111,7 +111,7 @@ class Bot(commands.Bot):
loop = asyncio.get_event_loop()
allowed_roles = [discord.Object(id_) for id_ in constants.MODERATION_ROLES]
- intents = discord.Intents().all()
+ intents = discord.Intents.all()
intents.presences = False
intents.dm_typing = False
intents.dm_reactions = False
diff --git a/bot/constants.py b/bot/constants.py
index 240198113..547a94a0b 100644
--- a/bot/constants.py
+++ b/bot/constants.py
@@ -388,6 +388,7 @@ class Categories(metaclass=YAMLGetter):
help_available: int
help_dormant: int
help_in_use: int
+ moderators: int
modmail: int
voice: int
@@ -433,9 +434,8 @@ class Channels(metaclass=YAMLGetter):
helpers: int
incidents: int
incidents_archive: int
- mods: int
mod_alerts: int
- mod_spam: int
+ nominations: int
nomination_voting: int
organisation: int
@@ -487,9 +487,11 @@ class Roles(metaclass=YAMLGetter):
admins: int
core_developers: int
devops: int
+ domain_leads: int
helpers: int
moderators: int
owners: int
+ project_leads: int
jammers: int
team_leaders: int
diff --git a/bot/exts/help_channels/_channel.py b/bot/exts/help_channels/_channel.py
index 2837bc7c5..0846b28c8 100644
--- a/bot/exts/help_channels/_channel.py
+++ b/bot/exts/help_channels/_channel.py
@@ -75,7 +75,8 @@ async def get_closing_time(channel: discord.TextChannel, init_done: bool) -> t.T
# Use the greatest offset to avoid the possibility of prematurely closing the channel.
time = Arrow.fromdatetime(msg.created_at) + timedelta(minutes=idle_minutes_claimant)
- return time, ClosingReason.LATEST_MESSSAGE
+ reason = ClosingReason.DELETED if is_empty else ClosingReason.LATEST_MESSSAGE
+ return time, reason
claimant_time = Arrow.utcfromtimestamp(claimant_time)
others_time = await _caches.non_claimant_last_message_times.get(channel.id)
diff --git a/bot/exts/info/information.py b/bot/exts/info/information.py
index 0555544ce..5e2c4b417 100644
--- a/bot/exts/info/information.py
+++ b/bot/exts/info/information.py
@@ -284,7 +284,7 @@ class Information(Cog):
embed.add_field(name=field_name, value=field_content, inline=False)
embed.set_thumbnail(url=user.avatar_url_as(static_format="png"))
- embed.colour = user.top_role.colour if roles else Colour.blurple()
+ embed.colour = user.colour if user.colour != Colour.default() else Colour.blurple()
return embed
diff --git a/bot/exts/recruitment/talentpool/_cog.py b/bot/exts/recruitment/talentpool/_cog.py
index b809cea17..fbe79382d 100644
--- a/bot/exts/recruitment/talentpool/_cog.py
+++ b/bot/exts/recruitment/talentpool/_cog.py
@@ -113,15 +113,39 @@ class TalentPool(WatchChannel, Cog, name="Talentpool"):
"""
await ctx.invoke(self.watched_command, oldest_first=True, update_cache=update_cache)
+ @nomination_group.command(name='forcewatch', aliases=('fw', 'forceadd', 'fa'), root_aliases=("forcenominate",))
+ @has_any_role(*MODERATION_ROLES)
+ async def force_watch_command(self, ctx: Context, user: FetchedMember, *, reason: str = '') -> None:
+ """
+ Adds the given `user` to the talent pool, from any channel.
+
+ A `reason` for adding the user to the talent pool is optional.
+ """
+ await self._watch_user(ctx, user, reason)
+
@nomination_group.command(name='watch', aliases=('w', 'add', 'a'), root_aliases=("nominate",))
@has_any_role(*STAFF_ROLES)
async def watch_command(self, ctx: Context, user: FetchedMember, *, reason: str = '') -> None:
"""
- Relay messages sent by the given `user` to the `#talent-pool` channel.
+ Adds the given `user` to the talent pool.
A `reason` for adding the user to the talent pool is optional.
- If given, it will be displayed in the header when relaying messages of this user to the channel.
+ This command can only be used in the `#nominations` channel.
"""
+ if ctx.channel.id != Channels.nominations:
+ if any(role.id in MODERATION_ROLES for role in ctx.author.roles):
+ await ctx.send(
+ f":x: Nominations should be run in the <#{Channels.nominations}> channel. "
+ "Use `!tp forcewatch` to override this check."
+ )
+ else:
+ await ctx.send(f":x: Nominations must be run in the <#{Channels.nominations}> channel")
+ return
+
+ await self._watch_user(ctx, user, reason)
+
+ async def _watch_user(self, ctx: Context, user: FetchedMember, reason: str) -> None:
+ """Adds the given user to the talent pool."""
if user.bot:
await ctx.send(f":x: I'm sorry {ctx.author}, I'm afraid I can't do that. I only watch humans.")
return
diff --git a/bot/exts/utils/utils.py b/bot/exts/utils/utils.py
index a5d6f69b9..cae7f2593 100644
--- a/bot/exts/utils/utils.py
+++ b/bot/exts/utils/utils.py
@@ -9,7 +9,7 @@ from discord.ext.commands import BadArgument, Cog, Context, clean_content, comma
from discord.utils import snowflake_time
from bot.bot import Bot
-from bot.constants import Channels, MODERATION_ROLES, STAFF_ROLES
+from bot.constants import Channels, MODERATION_ROLES, Roles, STAFF_ROLES
from bot.converters import Snowflake
from bot.decorators import in_whitelist
from bot.pagination import LinePaginator
@@ -175,7 +175,7 @@ class Utils(Cog):
await ctx.send(embed=embed)
@command(aliases=("poll",))
- @has_any_role(*MODERATION_ROLES)
+ @has_any_role(*MODERATION_ROLES, Roles.project_leads, Roles.domain_leads)
async def vote(self, ctx: Context, title: clean_content(fix_channel_mentions=True), *options: str) -> None:
"""
Build a quick voting poll with matching reactions with the provided options.
diff --git a/config-default.yml b/config-default.yml
index c99cfb5a2..4178fba32 100644
--- a/config-default.yml
+++ b/config-default.yml
@@ -139,6 +139,7 @@ guild:
help_dormant: 691405908919451718
help_in_use: 696958401460043776
logs: &LOGS 468520609152892958
+ moderators: &MODS_CATEGORY 749736277464842262
modmail: &MODMAIL 714494672835444826
voice: 356013253765234688
@@ -191,15 +192,12 @@ guild:
helpers: &HELPERS 385474242440986624
incidents: 714214212200562749
incidents_archive: 720668923636351037
- mods: &MODS 305126844661760000
mod_alerts: 473092532147060736
- mod_appeals: &MOD_APPEALS 808790025688711198
- mod_meta: &MOD_META 775412552795947058
- mod_spam: &MOD_SPAM 620607373828030464
- mod_tools: &MOD_TOOLS 775413915391098921
+ nominations: 822920136150745168
nomination_voting: 822853512709931008
organisation: &ORGANISATION 551789653284356126
staff_lounge: &STAFF_LOUNGE 464905259261755392
+ staff_info: &STAFF_INFO 396684402404622347
# Staff announcement channels
admin_announcements: &ADMIN_ANNOUNCEMENTS 749736155569848370
@@ -224,17 +222,13 @@ guild:
talent_pool: &TALENT_POOL 534321732593647616
moderation_categories:
+ - *MODS_CATEGORY
- *MODMAIL
- *LOGS
moderation_channels:
- *ADMINS
- *ADMIN_SPAM
- - *MOD_APPEALS
- - *MOD_META
- - *MOD_TOOLS
- - *MODS
- - *MOD_SPAM
# Modlog cog ignores events which occur in these channels
modlog_blacklist:
@@ -263,9 +257,11 @@ guild:
admins: &ADMINS_ROLE 267628507062992896
core_developers: 587606783669829632
devops: 409416496733880320
+ domain_leads: 807415650778742785
helpers: &HELPERS_ROLE 267630620367257601
moderators: &MODS_ROLE 267629731250176001
owners: &OWNERS_ROLE 267627879762755584
+ project_leads: 815701647526330398
# Code Jam
jammers: 737249140966162473
@@ -521,6 +517,7 @@ duck_pond:
- *STAFF_ANNOUNCEMENTS
- *MOD_ANNOUNCEMENTS
- *ADMIN_ANNOUNCEMENTS
+ - *STAFF_INFO
python_news:
diff --git a/tests/bot/exts/info/test_information.py b/tests/bot/exts/info/test_information.py
index 80731c9f0..a996ce477 100644
--- a/tests/bot/exts/info/test_information.py
+++ b/tests/bot/exts/info/test_information.py
@@ -283,6 +283,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
user = helpers.MockMember()
user.nick = None
user.__str__ = unittest.mock.Mock(return_value="Mr. Hemlock")
+ user.colour = 0
embed = await self.cog.create_user_embed(ctx, user)
@@ -298,6 +299,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
user = helpers.MockMember()
user.nick = "Cat lover"
user.__str__ = unittest.mock.Mock(return_value="Mr. Hemlock")
+ user.colour = 0
embed = await self.cog.create_user_embed(ctx, user)
@@ -311,10 +313,9 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
"""Created `!user` embeds should not contain mention of the @everyone-role."""
ctx = helpers.MockContext(channel=helpers.MockTextChannel(id=1))
admins_role = helpers.MockRole(name='Admins')
- admins_role.colour = 100
# A `MockMember` has the @Everyone role by default; we add the Admins to that.
- user = helpers.MockMember(roles=[admins_role], top_role=admins_role)
+ user = helpers.MockMember(roles=[admins_role], colour=100)
embed = await self.cog.create_user_embed(ctx, user)
@@ -332,12 +333,11 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
ctx = helpers.MockContext(channel=helpers.MockTextChannel(id=50))
moderators_role = helpers.MockRole(name='Moderators')
- moderators_role.colour = 100
infraction_counts.return_value = ("Infractions", "expanded infractions info")
nomination_counts.return_value = ("Nominations", "nomination info")
- user = helpers.MockMember(id=314, roles=[moderators_role], top_role=moderators_role)
+ user = helpers.MockMember(id=314, roles=[moderators_role], colour=100)
embed = await self.cog.create_user_embed(ctx, user)
infraction_counts.assert_called_once_with(user)
@@ -367,11 +367,10 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
ctx = helpers.MockContext(channel=helpers.MockTextChannel(id=100))
moderators_role = helpers.MockRole(name='Moderators')
- moderators_role.colour = 100
infraction_counts.return_value = ("Infractions", "basic infractions info")
- user = helpers.MockMember(id=314, roles=[moderators_role], top_role=moderators_role)
+ user = helpers.MockMember(id=314, roles=[moderators_role], colour=100)
embed = await self.cog.create_user_embed(ctx, user)
infraction_counts.assert_called_once_with(user)
@@ -407,12 +406,11 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
ctx = helpers.MockContext()
moderators_role = helpers.MockRole(name='Moderators')
- moderators_role.colour = 100
- user = helpers.MockMember(id=314, roles=[moderators_role], top_role=moderators_role)
+ user = helpers.MockMember(id=314, roles=[moderators_role], colour=100)
embed = await self.cog.create_user_embed(ctx, user)
- self.assertEqual(embed.colour, discord.Colour(moderators_role.colour))
+ self.assertEqual(embed.colour, discord.Colour(100))
@unittest.mock.patch(
f"{COG_PATH}.basic_user_infraction_counts",
@@ -422,7 +420,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
"""The embed should be created with a blurple colour if the user has no assigned roles."""
ctx = helpers.MockContext()
- user = helpers.MockMember(id=217)
+ user = helpers.MockMember(id=217, colour=discord.Colour.default())
embed = await self.cog.create_user_embed(ctx, user)
self.assertEqual(embed.colour, discord.Colour.blurple())
@@ -435,7 +433,7 @@ class UserEmbedTests(unittest.IsolatedAsyncioTestCase):
"""The embed thumbnail should be set to the user's avatar in `png` format."""
ctx = helpers.MockContext()
- user = helpers.MockMember(id=217)
+ user = helpers.MockMember(id=217, colour=0)
user.avatar_url_as.return_value = "avatar url"
embed = await self.cog.create_user_embed(ctx, user)