Skip to content

Commit

Permalink
Add workflow for formatting & format codebase (#63)
Browse files Browse the repository at this point in the history
  • Loading branch information
novanai authored Jan 15, 2025
1 parent 5829bcb commit bcf82c3
Show file tree
Hide file tree
Showing 21 changed files with 237 additions and 89 deletions.
34 changes: 34 additions & 0 deletions .github/workflows/format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: Format & Type Check

on: [push, pull_request]

jobs:
formatting:
runs-on: ubuntu-latest
name: "Check code style"
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Run ruff via nox
run: |
python -m pip install nox
python -m nox -s format_check
pyright:
runs-on: ubuntu-latest
name: "Type checking"
steps:
- uses: actions/checkout@v4
- name: Set up Python 3.12
uses: actions/setup-python@v5
with:
python-version: "3.12"

- name: Run pyright via nox
run: |
python -m pip install nox
python -m nox -s pyright
29 changes: 29 additions & 0 deletions noxfile.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import os

import nox
from nox import options

PROJECT_PATH = os.path.join(".", "src")
SCRIPT_PATHS = [PROJECT_PATH, "noxfile.py"]

options.sessions = ["format_fix", "pyright"]


@nox.session()
def format_fix(session: nox.Session) -> None:
session.install("-r", "requirements_dev.txt")
session.run("python", "-m", "ruff", "format", *SCRIPT_PATHS)
session.run("python", "-m", "ruff", "check", *SCRIPT_PATHS, "--fix")


@nox.session()
def format_check(session: nox.Session) -> None:
session.install("-r", "requirements_dev.txt")
session.run("python", "-m", "ruff", "format", *SCRIPT_PATHS, "--check")
session.run("python", "-m", "ruff", "check", *SCRIPT_PATHS)


@nox.session()
def pyright(session: nox.Session) -> None:
session.install("-r", "requirements_dev.txt", "-r", "requirements.txt")
session.run("pyright", *SCRIPT_PATHS)
32 changes: 32 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,37 @@ target-version = "py312"
docstring-code-format = true
line-ending = "lf"

[tool.ruff.lint]
select = [
"F", # Pyflakes
"E", # Error (pycodestyle)
"W", # Warning (pycodestyle)
"I", # isort
"N", # pep8-naming
"ANN", # flake8-annotations
"ASYNC", # flake8-async
"A", # flake8-builtins
"COM", # flake8-commas
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"ICN", # flake8-import-conventions
"Q", # flake8-quotes
"RET", # flake8-return
"SIM", # flake8-simplify
"TID", # flake8-tidy-imports
"TC", # flake8-type-checking
"ARG", # flake8-unused-arguments
"ERA", # eradicate
"PL", # Pylint
"PERF", # Perflint
"RUF", # Ruff-specific rules
]
ignore = [
"E501", # line-too-long
"PLR2004", # magic-value-comparison
"PLR0913", # too-many-arguments
"COM812", # missing-trailing-comma
]

[tool.ruff.lint.pydocstyle]
convention = "numpy"
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
aiohttp==3.10.10
aiohttp==3.11.11
fortune-python==1.1.1
hikari==2.1.0
hikari-arc==1.4.0
Expand Down
5 changes: 4 additions & 1 deletion requirements_dev.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,5 @@
ruff==0.7.3
nox==2024.10.9
ruff==0.9.1
pre-commit==4.0.1
pyright==1.1.391

7 changes: 4 additions & 3 deletions src/bot.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import logging

import aiohttp
import arc
import hikari
import aiohttp
import miru

from src.config import DEBUG, TOKEN
Expand All @@ -28,7 +28,7 @@


@client.listen(hikari.StartingEvent)
async def on_start(event: hikari.StartingEvent) -> None:
async def on_start(_: hikari.StartingEvent) -> None:
# Create an aiohttp ClientSession to use for web requests
aiohttp_client = aiohttp.ClientSession()
client.set_type_dependency(aiohttp.ClientSession, aiohttp_client)
Expand All @@ -39,7 +39,8 @@ async def on_start(event: hikari.StartingEvent) -> None:
# so dependency injection must be enabled manually for this event listener
@client.inject_dependencies
async def on_stop(
event: hikari.StoppedEvent, aiohttp_client: aiohttp.ClientSession = arc.inject()
_: hikari.StoppedEvent,
aiohttp_client: aiohttp.ClientSession = arc.inject(),
) -> None:
await aiohttp_client.close()

Expand Down
3 changes: 2 additions & 1 deletion src/examples/commands.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ async def hello(ctx: arc.GatewayContext) -> None:


group = plugin.include_slash_group(
"base_group", "A base command group, with sub groups and sub commands."
"base_group",
"A base command group, with sub groups and sub commands.",
)


Expand Down
12 changes: 8 additions & 4 deletions src/examples/components.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def __init__(self, user_id: int) -> None:
super().__init__(timeout=60)

@miru.button("Click me!", custom_id="click_me")
async def click_button(self, ctx: miru.ViewContext, button: miru.Button) -> None:
async def click_button(self, ctx: miru.ViewContext, _: miru.Button) -> None:
await ctx.respond(f"{ctx.user.mention}, you clicked me!")

# Defining select menus: https://miru.hypergonial.com/guides/selects/
Expand All @@ -35,7 +35,9 @@ async def click_button(self, ctx: miru.ViewContext, button: miru.Button) -> None
],
)
async def colour_select(
self, ctx: miru.ViewContext, select: miru.TextSelect
self,
ctx: miru.ViewContext,
select: miru.TextSelect,
) -> None:
await ctx.respond(f"Your favourite colours are: {', '.join(select.values)}!")

Expand All @@ -46,7 +48,8 @@ async def view_check(self, ctx: miru.ViewContext) -> bool:
# For every other user they will receive an error message.
if ctx.user.id != self.user_id:
await ctx.respond(
"You can't press this!", flags=hikari.MessageFlag.EPHEMERAL
"You can't press this!",
flags=hikari.MessageFlag.EPHEMERAL,
)
return False

Expand All @@ -69,7 +72,8 @@ async def on_timeout(self) -> None:
@plugin.include
@arc.slash_command("components", "A command with components.")
async def components_cmd(
ctx: arc.GatewayContext, miru_client: miru.Client = arc.inject()
ctx: arc.GatewayContext,
miru_client: miru.Client = arc.inject(),
) -> None:
view = View(ctx.user.id)
response = await ctx.respond("Here are some components...", components=view)
Expand Down
3 changes: 2 additions & 1 deletion src/examples/modals.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,8 @@ async def callback(self, ctx: miru.ModalContext) -> None:
@plugin.include
@arc.slash_command("modal", "A command with a modal response.")
async def modal_command(
ctx: arc.GatewayContext, miru_client: miru.Client = arc.inject()
ctx: arc.GatewayContext,
miru_client: miru.Client = arc.inject(),
) -> None:
modal = MyModal()
builder = modal.build_response(miru_client)
Expand Down
3 changes: 2 additions & 1 deletion src/examples/options.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ async def options(
ctx: arc.GatewayContext,
str_option: arc.Option[str, arc.StrParams("A string option.", name="string")],
int_option: arc.Option[
int, arc.IntParams("An integer option.", name="integer", min=5, max=150)
int,
arc.IntParams("An integer option.", name="integer", min=5, max=150),
],
attachment_option: arc.Option[
hikari.Attachment,
Expand Down
71 changes: 40 additions & 31 deletions src/extensions/action_items.py
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
import arc
import hikari
import re
import aiohttp
from urllib.parse import urlparse

from src.utils import role_mention, hedgedoc_login
from src.hooks import restrict_to_channels, restrict_to_roles
from src.config import CHANNEL_IDS, ROLE_IDS, UID_MAPS
import aiohttp
import arc
import hikari

from src.config import CHANNEL_IDS, ROLE_IDS, UID_MAPS
from src.hooks import restrict_to_channels, restrict_to_roles
from src.utils import hedgedoc_login, role_mention

action_items = arc.GatewayPlugin(name="Action Items")

Expand Down Expand Up @@ -54,7 +54,9 @@ async def get_action_items(

# extract the action items section from the minutes
action_items_section = re.search(
r"# Action Items:?\n(.*?)(\n# |\n---|$)", content, re.DOTALL
r"# Action Items:?\n(.*?)(\n# |\n---|$)",
content,
re.DOTALL,
)

if not action_items_section:
Expand All @@ -78,13 +80,13 @@ async def get_action_items(
# Replace user names with user mentions
for i, item in enumerate(formatted_bullet_points):
for name, uid in UID_MAPS.items():
item = item.replace(f"`{name}`", f"<@{uid}>")
item = item.replace(f"`{name}`", f"<@{uid}>") # noqa: PLW2901
formatted_bullet_points[i] = item

# Replace role names with role mentions
for i, item in enumerate(formatted_bullet_points):
for role, role_id in ROLE_IDS.items():
item = item.replace(f"`{role}`", role_mention(role_id))
item = item.replace(f"`{role}`", role_mention(role_id)) # noqa: PLW2901
formatted_bullet_points[i] = item

# Send title to the action-items channel
Expand All @@ -95,7 +97,7 @@ async def get_action_items(

# send each bullet point separately
for item in formatted_bullet_points:
item = await action_items.client.rest.create_message(
message = await action_items.client.rest.create_message(
CHANNEL_IDS["action-items"],
mentions_everyone=False,
user_mentions=True,
Expand All @@ -104,8 +106,8 @@ async def get_action_items(
)

await action_items.client.rest.add_reaction(
channel=item.channel_id,
message=item.id,
channel=message.channel_id,
message=message.id,
emoji="✅",
)

Expand Down Expand Up @@ -136,15 +138,14 @@ async def check_valid_reaction(

assert message.author # it will always be available

# ignore messages not sent by the bot and messages with no content
if message.author.id != bot_user.id or not message.content:
return False

return True
# verify it's a message sent by the bot and has content
return message.author.id == bot_user.id and message.content is not None


async def validate_user_reaction(
user_id: int, message_content: str, guild_id: int
user_id: int,
message_content: str,
guild_id: int,
) -> bool:
# extract user and role mentions from the message content
mention_regex = r"<@[!&]?(\d+)>"
Expand All @@ -153,26 +154,27 @@ async def validate_user_reaction(
# make a list of all mentions
mentioned_ids = [int(id_) for id_ in mentions]

# user is mentioned
if user_id in mentioned_ids:
return True

member = action_items.client.cache.get_member(
guild_id, user_id
guild_id,
user_id,
) or await action_items.client.rest.fetch_member(guild_id, user_id)

if any(role_id in mentioned_ids for role_id in member.role_ids):
return True

return False
# user's role is mentioned
return any(role_id in mentioned_ids for role_id in member.role_ids)


@action_items.listen()
async def reaction_add(event: hikari.GuildReactionAddEvent) -> None:
# retrieve the message that was reacted to
message = action_items.client.cache.get_message(
event.message_id
event.message_id,
) or await action_items.client.rest.fetch_message(
event.channel_id, event.message_id
event.channel_id,
event.message_id,
)

is_valid_reaction = await check_valid_reaction(event, message)
Expand All @@ -182,7 +184,9 @@ async def reaction_add(event: hikari.GuildReactionAddEvent) -> None:
assert message.content # check_valid_reaction verifies the message content exists

is_valid_reaction = await validate_user_reaction(
event.user_id, message.content, event.guild_id
event.user_id,
message.content,
event.guild_id,
)
if not is_valid_reaction:
return
Expand All @@ -192,7 +196,9 @@ async def reaction_add(event: hikari.GuildReactionAddEvent) -> None:
# add strikethrough and checkmark
updated_content = f"- ✅ ~~{message.content[2:]}~~"
await action_items.client.rest.edit_message(
event.channel_id, event.message_id, content=updated_content
event.channel_id,
event.message_id,
content=updated_content,
)


Expand All @@ -201,7 +207,8 @@ async def reaction_remove(event: hikari.GuildReactionDeleteEvent) -> None:
# retrieve the message that was un-reacted to
# NOTE: cannot use cached message as the reaction count will be outdated
message = await action_items.client.rest.fetch_message(
event.channel_id, event.message_id
event.channel_id,
event.message_id,
)

is_valid_reaction = await check_valid_reaction(event, message)
Expand All @@ -225,8 +232,8 @@ async def reaction_remove(event: hikari.GuildReactionDeleteEvent) -> None:
filter(
lambda r: r is True,
reactions,
)
)
),
),
)

assert message.content # check_valid_reaction verifies the message content exists
Expand All @@ -236,7 +243,9 @@ async def reaction_remove(event: hikari.GuildReactionDeleteEvent) -> None:
# add strikethrough and checkmark
updated_content = f"- {message.content[6:-2]}"
await action_items.client.rest.edit_message(
event.channel_id, event.message_id, content=updated_content
event.channel_id,
event.message_id,
content=updated_content,
)


Expand Down
Loading

0 comments on commit bcf82c3

Please sign in to comment.