Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 17 additions & 22 deletions .github/workflows/ClemBot.Bot-Integration.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,34 +21,29 @@ jobs:
steps:
- uses: actions/checkout@v4

- name: Set up Python 3.10
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: "3.10.6"
python-version: "3.13"

- name: install poetry
uses: snok/install-poetry@v1
with:
version: 1.1.14
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv
- name: install uv
run: pip install uv

- name: load cached venv
id: cached-poetry-dependencies
id: cached-uv-dependencies
uses: actions/cache@v3
with:
path: ClemBot.Bot/.venv
key: venv-${{ runner.os }}-${{ hashFiles('**/poetry.lock') }}-${{ hashFiles('**/ClemBot.Bot-Integration.yml') }}
key: venv-${{ runner.os }}-${{ hashFiles('**/uv.lock') }}-${{ hashFiles('**/ClemBot.Bot-Integration.yml') }}

- name: install dependencies
if: steps.cached-poetry-dependencies.outputs.cache-hit != 'true'
run: poetry install
if: steps.cached-uv-dependencies.outputs.cache-hit != 'true'
run: uv sync --extra dev

- name: Validate static typing
id: static-typing-ci
continue-on-error: true
run: poetry run python -m mypy
run: uv run python -m mypy

- name: Create lint error folder
shell: bash
Expand All @@ -64,43 +59,43 @@ jobs:
- name: Check if Black would make any changes
id: black-ci
continue-on-error: true
run: poetry run python -m black bot --check
run: uv run python -m black bot --check

- name: Check if isort would make any changes
id: isort-ci
continue-on-error: true
run: poetry run python -m isort --check-only .
run: uv run python -m isort --check-only .

- name: Black lint failure comment
shell: bash
if: steps.black-ci.outcome != 'success' && github.event_name == 'pull_request'
run: |
echo "${{github.event.number}}|**Black linting failed:** Please run \`\`\`poetry run black bot\`\`\` from the \`ClemBot.Bot\` folder" >> lint_errors/black-error.txt
echo "${{github.event.number}}|**Black linting failed:** Please run \`\`\`uv run black bot\`\`\` from the \`ClemBot.Bot\` folder" >> lint_errors/black-error.txt

- name: Isort lint failure comment
shell: bash
if: steps.isort-ci.outcome != 'success' && github.event_name == 'pull_request'
run: |
echo "${{github.event.number}}|**isort linting failed:** Please run \`\`\`poetry run isort .\`\`\` from the \`ClemBot.Bot\` folder" >> lint_errors/isort-error.txt
echo "${{github.event.number}}|**isort linting failed:** Please run \`\`\`uv run isort .\`\`\` from the \`ClemBot.Bot\` folder" >> lint_errors/isort-error.txt

- name: Upload lint errors
uses: actions/upload-artifact@v3
uses: actions/upload-artifact@v4
if: (steps.isort-ci.outcome != 'success' || steps.black-ci.outcome != 'success' || steps.static-typing-ci.outcome != 'success') && github.event_name == 'pull_request'
with:
name: lint-errors
path: ClemBot.Bot/lint_errors

- name: Fail PR if linting failed
uses: actions/github-script@v3
uses: actions/github-script@v7
if: (steps.isort-ci.outcome != 'success' || steps.black-ci.outcome != 'success' || steps.static-typing-ci.outcome != 'success') && github.event_name == 'pull_request'
with:
script: |
core.setFailed('Linting failed, please see PR comment for details')

- name: Lint with pylint
run: |
poetry run python -m pylint bot -E -r y
uv run python -m pylint bot -E -r y

- name: Test with pytest
run: |
poetry run python -m pytest
uv run python -m pytest
15 changes: 6 additions & 9 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ Make sure you can run these commands and install them if not present.

### ClemBot.Bot

* [Python 3.10](https://www.python.org/downloads/release/python-3100/)
* [Python 3.13](https://www.python.org/downloads/release/python-3130/)
* pip3 (packaged as python3-pip)
* A Python IDE
* Anything will work, but people generally use [Visual Studio Code](https://code.visualstudio.com/)
Expand Down Expand Up @@ -108,18 +108,15 @@ configured PostgreSQL with your desired username and password, you need to set t

## Setting up the ClemBot.Bot build environment

Installing Poetry:
`pip3 install poetry` (Windows: `py -m pip install poetry`)
Installing uv:
`pip3 install uv` (Windows: `py -m pip install uv`)

Tell Poetry to put the venv in the project folder
`poetry config virtualenvs.in-project true`

Installing dependencies with Poetry:
`poetry install`
Installing dependencies with uv:
`uv sync`

You can then test-run the bot with the command...

`poetry run python3 -m bot` (Windows: `poetry run py -m bot`)
`uv run python -m bot`

...when you are in the directory `ClemBot/ClemBot.Bot`.

Expand Down
36 changes: 22 additions & 14 deletions ClemBot.Bot/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,26 +1,34 @@
# For more information, please refer to https://aka.ms/vscode-docker-python
FROM python:3.10-slim-buster
FROM python:3.13-slim-trixie

# Keeps Python from generating .pyc files in the container
ENV PYTHONDONTWRITEBYTECODE 1
# Enable bytecode compilation
ENV UV_COMPILE_BYTECODE=1

# Turns off buffering for easier container logging
ENV PYTHONUNBUFFERED 1
# Copy from the cache instead of linking since it's a mounted volume
ENV UV_LINK_MODE=copy

RUN apt-get update && apt-get install -y git
# Ensure installed tools can be executed out of the box
ENV UV_TOOL_BIN_DIR=/usr/local/bin

# Install poetry
RUN python -m pip install poetry
# The installer requires curl (and certificates) to download the release archive
RUN apt-get update && apt-get install -y --no-install-recommends curl ca-certificates

# Install dependencies with Poetry
# Download the latest installer
ADD https://astral.sh/uv/install.sh /uv-installer.sh

# Run the installer then remove it
RUN sh /uv-installer.sh && rm /uv-installer.sh

# Ensure the installed binary is on the `PATH`
ENV PATH="/root/.local/bin/:$PATH"

# Install dependencies with uv
WORKDIR /ClemBot.Bot
ADD pyproject.toml .
ADD poetry.lock .
ADD uv.lock .

RUN poetry config virtualenvs.in-project true
RUN poetry install --no-dev --no-interaction
RUN uv sync --locked --no-install-project --no-dev

ADD . /ClemBot.Bot

# During debugging, this entry point will be overridden. For more information, please refer to https://aka.ms/vscode-docker-python-debug
CMD ["poetry", "run", "python3", "-m", "bot"]
CMD ["uv", "run", "python", "-m", "bot"]
20 changes: 10 additions & 10 deletions ClemBot.Bot/bot/bot_secrets.py
Original file line number Diff line number Diff line change
Expand Up @@ -219,23 +219,23 @@ def load_development_secrets(self, lines: str) -> None:
def load_production_secrets(self) -> None:

# Ignore these type errors, mypy doesn't know how to handle properties that return narrower types then they are assigned too
self.client_token = os.environ.get("CLIENT_TOKEN") # type: ignore
self.client_secret = os.environ.get("CLIENT_SECRET") # type: ignore
self.bot_token = os.environ.get("BOT_TOKEN") # type: ignore
self.bot_prefix = os.environ.get("BOT_PREFIX") # type: ignore
self.client_token = os.environ.get("CLIENT_TOKEN")
self.client_secret = os.environ.get("CLIENT_SECRET")
self.bot_token = os.environ.get("BOT_TOKEN")
self.bot_prefix = os.environ.get("BOT_PREFIX")
self.startup_log_channel_ids = [
int(n) for n in os.environ.get("STARTUP_LOG_CHANNEL_IDS").split(",") # type: ignore
]
self.error_log_channel_ids = [
int(n) for n in os.environ.get("ERROR_LOG_CHANNEL_IDS").split(",") # type: ignore
]
self.bot_only = os.environ.get("BOT_ONLY") # type: ignore
self.repl_url = os.environ.get("REPL_URL") # type: ignore
self.github_url = os.environ.get("GITHUB_URL") # type: ignore
self.api_url = os.environ.get("API_URL") # type: ignore
self.api_key = os.environ.get("API_KEY") # type: ignore
self.site_url = os.environ.get("SITE_URL") # type: ignore
self.docs_url = os.environ.get("DOCS_URL") # type: ignore
self.repl_url = os.environ.get("REPL_URL")
self.github_url = os.environ.get("GITHUB_URL")
self.api_url = os.environ.get("API_URL")
self.api_key = os.environ.get("API_KEY")
self.site_url = os.environ.get("SITE_URL")
self.docs_url = os.environ.get("DOCS_URL")
self.allow_bot_input_ids = [
int(n) for n in os.environ.get("ALLOW_BOT_INPUT_IDS").split(",") # type: ignore
]
Expand Down
4 changes: 2 additions & 2 deletions ClemBot.Bot/bot/clem_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -341,7 +341,7 @@ async def on_member_ban(self, guild: discord.Guild, user: discord.Member) -> Non
await self.publish_to_queue_with_error(Events.on_member_ban, guild.id, guild, user)

async def on_reaction_add(
self, reaction: discord.Reaction, user: (discord.User | discord.Member)
self, reaction: discord.Reaction, user: discord.User | discord.Member
) -> None:
if user.id != self.user.id:
assert reaction.message.guild is not None
Expand All @@ -356,7 +356,7 @@ async def on_raw_reaction_add(self, reaction: discord.RawReactionActionEvent) ->
)

async def on_reaction_remove(
self, reaction: discord.Reaction, user: (discord.User | discord.Member)
self, reaction: discord.Reaction, user: discord.User | discord.Member
) -> None:
if user.id != self.user.id:
assert reaction.message.guild is not None
Expand Down
6 changes: 3 additions & 3 deletions ClemBot.Bot/bot/cogs/assignable_roles_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -103,13 +103,13 @@ async def send_matching_roles_list(
"\u0037\ufe0f\u20e3",
"\u0038\ufe0f\u20e3",
"\u0039\ufe0f\u20e3",
"\U0001F51F",
"\U0001f51f",
]
"""
USING EMOJIS WITH EMBEDDED TEXT
What I know works:
1. Discord emoji name (e.g. ':pensive:')
2. Unicode emoji name (e.g. '\u0031\ufe0f\u20e3' or '\U0001F3D3')
2. Unicode emoji name (e.g. '\u0031\ufe0f\u20e3' or '\U0001f3d3')

More info on using unicode emojis is provided below.
"""
Expand Down Expand Up @@ -142,7 +142,7 @@ async def send_matching_roles_list(

Method 1:
say you want :ping_pong:, you would use the unicode charcter U+1F3D3 and change it to U0001F3D3.
thus, '\U0001F3D3' would be your string for ping pong
thus, '\U0001f3d3' would be your string for ping pong
as another example, if you wanted to use :skull_crossbones:,
you would use unicode character U+2620 and change it to U00002620
thus, '\U00002620' would be your string for skull and crossbones
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/cogs/claims_authorization_cog.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ def _build_claims_embed(
self,
ctx: ext.ClemBotCtx,
claims: list[Claims],
subject: (discord.Role | discord.Member),
subject: discord.Role | discord.Member,
) -> discord.Embed:

claims_str = "\n".join(sorted([c.name for c in claims])) if claims else "No current claims"
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/extensions.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def wrapper(
ClemBotCommand
| t.Callable[..., t.Any]
| discord.ext.commands.Command[t.Any, t.Any, t.Any]
)
),
) -> ClemBotCommand:
if isinstance(func, ClemBotCommand):
raise TypeError("Callback is already a command.")
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/messaging/events.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
This module is to define all application level events in one place
This module is to define all application level events in one place
to avoid attempting to remember string event names
"""

Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/messaging/messenger.py
Original file line number Diff line number Diff line change
Expand Up @@ -196,7 +196,7 @@ async def __send_guild_queue(self, guild_id: int) -> None:
):
return

def __get_weak_ref(self, obj: t.Any) -> (wr.WeakMethod[t.Any] | wr.ReferenceType[t.Any]):
def __get_weak_ref(self, obj: t.Any) -> wr.WeakMethod[t.Any] | wr.ReferenceType[t.Any]:
"""
Get a weak reference to obj. If obj is a bound method, a WeakMethod
object, that behaves like a WeakRef, is returned; if it is
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/services/delete_message_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ async def set_message_deletable(
msg: discord.Message | list[discord.Message],
roles: discord.Role | list[discord.Role] | None = None,
author: discord.Member | None = None,
timeout: int | None = None
timeout: int | None = None,
) -> None:

msg_to_delete = [msg] if not isinstance(msg, list) else msg
Expand Down
4 changes: 2 additions & 2 deletions ClemBot.Bot/bot/services/designated_channel_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ async def send_designated_message(
self,
designated_name: DesignatedChannelBase,
guild_id: int,
content: (str | discord.Embed),
content: str | discord.Embed,
dc_id: int | uuid.UUID | None = None,
) -> None:
"""
Expand Down Expand Up @@ -50,7 +50,7 @@ async def send_designated_message(

@BaseService.listener(Events.on_broadcast_designated_channel)
async def broadcast_designated_message(
self, designated_name: DesignatedChannels, content: (str | discord.Embed)
self, designated_name: DesignatedChannels, content: str | discord.Embed
) -> None:
"""
Event call back to broadcast a given string or embed message to all registered designated channels
Expand Down
16 changes: 10 additions & 6 deletions ClemBot.Bot/bot/utils/converters.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,11 @@ async def convert(
) -> relativedelta | datetime:
delta = t.cast(
relativedelta,
duration
if isinstance(duration, relativedelta)
else await super().convert(ctx, duration),
(
duration
if isinstance(duration, relativedelta)
else await super().convert(ctx, duration)
),
)
now = datetime.utcnow()
try:
Expand All @@ -141,9 +143,11 @@ async def convert(
) -> relativedelta | datetime:
delta = t.cast(
relativedelta,
duration
if isinstance(duration, relativedelta)
else await super().convert(ctx, duration),
(
duration
if isinstance(duration, relativedelta)
else await super().convert(ctx, duration)
),
)
now = datetime.utcnow()
try:
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/utils/helpers.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def format_datetime(time: datetime) -> str:


def format_duration(
duration: Annotated[datetime | relativedelta, FutureDuration | PastDuration]
duration: Annotated[datetime | relativedelta, FutureDuration | PastDuration],
) -> str:
"""
Formats the given datetime to a string.
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/utils/log_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ def log_guild(guild: discord.Guild) -> dict[str, t.Any]:
return {"id": guild.id, "name": guild.name}


def log_user(member: (discord.Member | discord.User | discord.ClientUser)) -> dict[str, t.Any]:
def log_user(member: discord.Member | discord.User | discord.ClientUser) -> dict[str, t.Any]:
return (
{"id": member.id, "name": member.name, "guild": log_guild(member.guild)}
if isinstance(member, discord.Member)
Expand Down
2 changes: 1 addition & 1 deletion ClemBot.Bot/bot/utils/user_choice.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ async def send_confirmation(
*,
content: str | None = None,
embed: discord.Embed | None = None,
is_error: bool = False
is_error: bool = False,
) -> bool:
if embed and content:
raise TypeError("Only specify the embed or the content, not both")
Expand Down
Loading