Compare commits

...
Author SHA1 Message Date
imcompiling f5e54778d6 Update dependencies in pyproject to have specific version. 2024-10-05 12:33:45 -04:00
imcompiling 19f83ec2a5 run ruff format 2024-10-05 12:28:12 -04:00
imcompiling 7919a63e04 run ruff check --fix 2024-10-05 12:27:14 -04:00
imcompiling 00212ea09d Update dockerfile to use poetry. 2024-10-05 12:23:30 -04:00
imcompiling 96c902fcc3 Rename highscores.py to main.py 2024-10-05 12:23:16 -04:00
imcompiling c177ae67d6 Remove requirements. 2024-10-05 12:22:57 -04:00
imcompiling b73cddab53 Add ruff to project. 2024-10-05 11:52:28 -04:00
imcompiling 8f88c76e2e Add pyproject for poetry support. 2024-10-05 11:47:12 -04:00
imcompiling c977a4e958 Bug fix #64, was using wrong variable.
build_develop / build (push) Successful in 46s
Utils.py got formatted by ruff.
2024-09-30 12:55:34 -04:00
imcompiling e2e8d8de30 Don't need to print the help string.
build_develop / build (push) Successful in 1m20s
build_release / build (push) Successful in 20s
2024-09-29 23:31:49 -04:00
imcompiling 7f007ffa1f Merge commit '02e7a6df3a13bb19056f31f616101980b668ae43'
build_develop / build (push) Successful in 2m20s
2023-08-30 09:04:36 -04:00
imcompiling 913f915bf0 Clarify create category option description. 2023-08-30 09:04:24 -04:00
imcompiling 02e7a6df3a Comment out troublesome code. I believe that this causes users to have trouble submitting multiple in a row over 5 minutes. 2023-08-29 21:01:13 -04:00
imcompiling 7be869b2b6 Update registry location.
build_develop / build (push) Successful in 1m14s
2023-07-29 18:34:56 -04:00
imcompiling 15c4776918 its async. must await.
build_develop / build (push) Failing after 1m29s
2023-07-25 13:38:48 -04:00
imcompiling d3102b57cc Add new birthday action.
build_develop / build (push) Has been cancelled
2023-07-25 11:19:56 -04:00
imcompiling 9bef20de5b Clarify wording in submission process.
build
2023-04-30 21:54:02 -04:00
imcompiling 9231c04c6a Merge branch '61-guild-active-in-database'
build
2023-03-29 20:14:47 -04:00
imcompiling 36ba4d35e0 Update table definition. Add on_guild_remove listener. 2023-03-29 20:13:39 -04:00
imcompiling b80959866a Merge branch '44-limit-autofill-choices'
build
2023-03-29 19:19:20 -04:00
imcompiling de1eba025a Limit results from autofill functions.
Also define some types.
2023-03-29 19:19:04 -04:00
imcompiling 28e575f364 Remove docker-compose from repo.
build
2023-03-29 16:43:11 -04:00
imcompiling d8368b4b92 Merge branch '62-dm-on-join'
build
2023-03-29 16:42:41 -04:00
imcompiling cc10bb518c Add comment. 2023-03-29 16:42:25 -04:00
imcompiling 90131f6395 Add message to myself on join. Edit adding guild just in case they already exist in database. 2023-03-28 15:06:48 -04:00
imcompiling 1814867c09 Merge branch '58-guild-name-in-database'
build
2023-03-28 13:55:40 -04:00
imcompiling 307f61934e Merge branch '59-gitea-action'
build
2023-03-23 16:52:15 -04:00
imcompiling 00265f2f58 Change tag to v. 2023-03-23 16:50:39 -04:00
imcompiling 93d558edbc Merge commit '0f2b1aa2e655a66c777a71c3917cd61147c9d754' into 59-gitea-action
build
2023-03-23 16:45:52 -04:00
imcompiling 0f2b1aa2e6 Set push for specific tag only, set GIT_TAG env var.
build
2023-03-23 16:44:08 -04:00
imcompiling 3f6bac99ab Merge branch '59-gitea-action'
build
2023-03-23 09:30:16 -04:00
imcompiling 46e2955642 Correct on push.
build
2023-03-23 09:30:00 -04:00
imcompiling ac9bf6b35f Merge branch 'develop' 2023-03-23 09:19:47 -04:00
imcompiling 869b2ccb82 Merge branch '59-gitea-action' into develop 2023-03-23 09:19:29 -04:00
imcompiling 3ddfdfe9b0 Add release workflow. 2023-03-23 09:16:01 -04:00
imcompiling 3860e74579 Change to operate on the main branch. No need for dev branch. 2023-03-23 09:09:41 -04:00
imcompiling 3b49a6ec2a Change workflow back to develop branch. 2023-03-22 23:25:26 -04:00
imcompiling 2f89588358 Don't use action due to using local repo.
build
2023-03-22 23:14:15 -04:00
imcompiling 0513df944f correct github address.
build
2023-03-22 22:31:42 -04:00
imcompiling ddf319b9d5 Install docker onto image.
build
2023-03-22 22:27:05 -04:00
imcompiling 13f5bc0aab try https url
build
2023-03-22 21:47:28 -04:00
imcompiling e6f3fcb604 maybe need buildx to set up docker
build
2023-03-22 19:35:11 -04:00
imcompiling eaa342d479 try with docker commands instead of action.
build
2023-03-22 19:34:27 -04:00
imcompiling ac11f3e462 try localhost for repo.
build
2023-03-22 19:22:18 -04:00
imcompiling 106b394c44 Need buildx, don't understand why.
build
2023-03-22 19:16:54 -04:00
imcompiling 6c1505685b correct http url, try again without buildx.
build
2023-03-22 19:15:15 -04:00
imcompiling 2e681be690 try adding buildx to fix missing token.
build
2023-03-22 19:12:03 -04:00
imcompiling 5448f2a426 delete unneeded files. 2023-03-22 19:11:22 -04:00
imcompiling 7647c355af Change runs-on, use action to build docker image.
Explore-GitHub-Actions
build
2023-03-22 18:56:20 -04:00
imcompiling a95fca40f9 add demo workflow
Explore-GitHub-Actions
build_develop
2023-03-21 22:06:46 -04:00
imcompiling 148ba253cf Add build_develop workflow.
build_develop
2023-03-21 21:53:04 -04:00
25 changed files with 289 additions and 159 deletions
+8 -2
View File
@@ -14,7 +14,13 @@ jobs:
fetch-depth: 0
- name: Install Docker
uses: https://github.com/papodaca/install-docker-action@v1
- name: Log into container registry
uses: docker/login-action@v2
with:
registry: gitea.cheezers.org
username: imcompiling
password: ${{ secrets.PASSWORD }}
- name: Build Docker Image
run: docker build -t viker.cheezers.net:5000/agon:develop .
run: docker build -t gitea.cheezers.org/imcompiling/agon:develop .
- name: Push Docker Image
run: docker push viker.cheezers.net:5000/agon:develop
run: docker push gitea.cheezers.org/imcompiling/agon:develop
+9 -3
View File
@@ -14,11 +14,17 @@ jobs:
fetch-depth: 0
- name: Install Docker
uses: https://github.com/papodaca/install-docker-action@v1
- name: Log into container registry
uses: docker/login-action@v2
with:
registry: gitea.cheezers.org
username: imcompiling
password: ${{ secrets.PASSWORD }}
- name: Set env
run: echo "GIT_TAG=${GITHUB_REF#refs/*/}" >> $GITHUB_ENV
- name: Build Docker Images
run: docker build -t viker.cheezers.net:5000/agon:latest -t viker.cheezers.net:5000/agon:${{env.GIT_TAG}} .
run: docker build -t gitea.cheezers.org/imcompiling/agon:latest -t gitea.cheezers.org/imcompiling/agon:${{env.GIT_TAG}} .
- name: Push Latest Image
run: docker push viker.cheezers.net:5000/agon:latest
run: docker push gitea.cheezers.org/imcompiling/agon:latest
- name: Push Tagged Image
run: docker push viker.cheezers.net:5000/agon:${{env.GIT_TAG}}
run: docker push gitea.cheezers.org/imcompiling/agon:${{env.GIT_TAG}}
+1
View File
@@ -105,6 +105,7 @@ venv/
ENV/
env.bak/
venv.bak/
poetry.lock
# Spyder project settings
.spyderproject
+5 -7
View File
@@ -1,21 +1,19 @@
FROM python:3.8.10-slim
FROM python:3.12.3-slim
# install dependencies for mysqlclient
# RUN apt-get update && apt-get -y upgrade && apt-get -y install python3-dev default-libmysqlclient-dev build-essential
RUN pip install --upgrade pip
WORKDIR /home
COPY classes classes/
COPY cogs cogs/
COPY utilities utilities/
COPY highscores.py highscores.py
COPY requirements.txt requirements.txt
COPY main.py main.py
COPY pyproject.toml pyproject.toml
RUN pip install -r requirements.txt
RUN pip install --upgrade pip && pip install poetry && poetry export -f requirements.txt --output requirements.txt && pip install -r requirements.txt
# remove build deps that aren't needed to run
# RUN apt-get -y remove python3-dev build-essential && apt-get -y autoremove
CMD ["python3", "./highscores.py"]
CMD ["python3", "./main.py"]
+5 -3
View File
@@ -183,11 +183,13 @@ class Submission:
if state is SubmissionState.CATEGORY:
description = "Step 1: Choose your category!"
elif state is SubmissionState.SCORE:
description = "Step 2: Input your score!"
description = "Step 2: Send a message to input your score!"
elif state is SubmissionState.PROOF:
description = "Step 3: Submit your proof!"
description = "Step 3: Send a message with a picture attached to submit your proof!"
elif state is SubmissionState.NAME:
description = "Step 4 (Optional): Change name on submission! (proceed to next if skipping)"
description = (
"Step 4 (Optional): Send a message to change name on submission! (proceed to next if skipping)"
)
elif state is SubmissionState.SUBMIT:
description = "Step 5: Submit if everything looks correct!"
elif state is SubmissionState.VERIFY:
+2 -7
View File
@@ -2,8 +2,6 @@
Commands for creating items in bot.
"""
from typing import Optional
import nextcord
from nextcord.ext import commands
from sqlalchemy import exc, select
@@ -40,7 +38,7 @@ class CreateCog(commands.Cog, name="Create Cog"):
interaction: nextcord.Interaction,
group_id: str = nextcord.SlashOption(name="group", description="Group to add Category to.", required=True),
category_name: str = nextcord.SlashOption(description="Name of new Category.", required=True),
is_ascending: bool = nextcord.SlashOption(description="If higher value is better score.", required=True),
is_ascending: bool = nextcord.SlashOption(description="If lower value is a better score.", required=True),
is_time_record: bool = nextcord.SlashOption(
description="If the score should be shown in time format. Cannot be changed.", required=True
),
@@ -64,7 +62,6 @@ class CreateCog(commands.Cog, name="Create Cog"):
is_validated (bool): If the scores should be validated or not. Is SlashOption.
size (int): How many sores to show in the category message. Is SlashOption.
"""
new_category = Category(
CategoryName=category_name,
GroupId=int(group_id),
@@ -113,7 +110,7 @@ class CreateCog(commands.Cog, name="Create Cog"):
min_value=1,
max_value=30,
),
channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption(
channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption(
description="Channel to post Group in. If left empty, defaults to current channel.",
required=False,
channel_types=[nextcord.ChannelType.text],
@@ -132,7 +129,6 @@ class CreateCog(commands.Cog, name="Create Cog"):
channel (Optional[nextcord.abc.GuildChannel]): Channel to post group in. If None, defaults to current
channel. Is SlashOption.
"""
# if no channel provided, use current channel.
if channel is None:
channel = interaction.channel
@@ -168,5 +164,4 @@ class CreateCog(commands.Cog, name="Create Cog"):
interaction (nextcord.Interaction): Interaction with command.
group_id (str): String to run autocomplete compared to.
"""
await interaction.response.send_autocomplete(autofill_groups(interaction.guild_id, group_id))
+3 -10
View File
@@ -2,8 +2,6 @@
Commands for deleting groups/categories/scores in database and messages in discord..
"""
from typing import Optional
import nextcord
from nextcord.ext import commands
from sqlalchemy import distinct, select
@@ -48,7 +46,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
async def channel_messages(
self,
interaction: nextcord.Interaction,
channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption(
channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption(
description="Channel to clear of messages.",
required=False,
channel_types=[nextcord.ChannelType.text],
@@ -64,7 +62,6 @@ class DeleteCog(commands.Cog, name="Table Management"):
channel (Optional[nextcord.abc.GuildChannel]): Channel to clear of messages.
Is a SlashOption. Limited to text channels. Defaults to None.
"""
if not channel:
channel = interaction.channel
@@ -107,10 +104,9 @@ class DeleteCog(commands.Cog, name="Table Management"):
interaction (nextcord.Interaction): Interaction that invoked the command.
user_name (str): User to remove from highscores
"""
with Session(engine) as session:
stmt = select(distinct(Score.CategoryId)).where(Score.UserName == user_name)
category_ids: "list[int]" = list(session.scalars(stmt))
category_ids: list[int] = list(session.scalars(stmt))
stmt = select(Score).where(Score.UserName == user_name)
for score in session.scalars(stmt):
@@ -185,7 +181,6 @@ class DeleteCog(commands.Cog, name="Table Management"):
category_id (str): String representation of Category id to remove user from. Is SlashOption.
user_name (str): The user to remove. Is SlashOption.
"""
with Session(engine) as session:
stmt = select(CategoryGroup).where(CategoryGroup.GroupId == int(group_id))
group = session.scalar(stmt)
@@ -219,7 +214,6 @@ class DeleteCog(commands.Cog, name="Table Management"):
group_id (str): String representation of Group id to remove user from. Is SlashOption.
user_name (str): The user to remove. Is SlashOption.
"""
with Session(engine) as session:
stmt = select(CategoryGroup).where(CategoryGroup.GroupId == int(group_id))
group = session.scalar(stmt)
@@ -267,7 +261,7 @@ class DeleteCog(commands.Cog, name="Table Management"):
@from_category.on_autocomplete("user_name")
@from_group.on_autocomplete("user_name")
async def user_autocomplete(
self, interaction: nextcord.Interaction, user_name: str, group_id: str, category_id: Optional[str] = None
self, interaction: nextcord.Interaction, user_name: str, group_id: str, category_id: str | None = None
):
"""
Autocomplete functions for user names.
@@ -278,7 +272,6 @@ class DeleteCog(commands.Cog, name="Table Management"):
group_id (str): String representation of Group id to find category from.
category_id (Optional[str]): String representation of Category id to find User from, can be None.
"""
await interaction.response.send_autocomplete(
autofill_users_in_group_category(
interaction.guild_id, int(group_id), category_id if category_id else 0, user_name
-2
View File
@@ -30,7 +30,6 @@ class HelpCog(commands.Cog, name="Help commands."):
Args:
interaction (nextcord.Interaction): Interaction that invoked the command.
"""
# I hate this but don't want indents in the string.
# pylint: disable=line-too-long
help_str: str = """Welcome to Agon! Three simple steps are needed to get started.
@@ -42,6 +41,5 @@ This channel will be where all user submission messages are sent.
3.) Set up your highscore tables! Use "/create group" to first create a group message in the channel you would like and then use "/create category" to create a category in that group message!"""
# pylint: enable=line-too-long
print(help_str)
await interaction.response.send_message(content=help_str)
-3
View File
@@ -46,7 +46,6 @@ class RefreshCog(commands.Cog, name="Refresh Cog"):
interaction (nextcord.Interaction): Interaction that invoked the command.
group_id (str): Str representation of Group id to refresh. Is SlashOption.
"""
with Session(engine) as session:
stmt = (
select(CategoryGroup)
@@ -78,7 +77,6 @@ class RefreshCog(commands.Cog, name="Refresh Cog"):
Args:
interaction (nextcord.Interaction): Interaction that invoked the command.
"""
with Session(engine) as session:
stmt = select(CategoryGroup).where(CategoryGroup.GuildId == interaction.guild_id)
@@ -100,5 +98,4 @@ class RefreshCog(commands.Cog, name="Refresh Cog"):
interaction (nextcord.Interaction): Interaction with command.
group_id (str): String to run autocomplete compared to.
"""
await interaction.response.send_autocomplete(autofill_groups(interaction.guild_id, group_id))
+3 -10
View File
@@ -2,8 +2,6 @@
Set command heirarchy.
"""
from typing import Optional
import nextcord
from nextcord.ext import commands
from sqlalchemy import select
@@ -37,7 +35,7 @@ class SetCog(commands.Cog, name="Set command."):
group_id: str = nextcord.SlashOption(
name="group", description="Category Group to assign to channel.", required=True
),
channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption(
channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption(
description="Channel to register",
required=False,
channel_types=[nextcord.ChannelType.text],
@@ -53,7 +51,6 @@ class SetCog(commands.Cog, name="Set command."):
channel (nextcord.abc.GuildChannel, optional): Channel to register
Is a SlashOption. Limited to text channels, Defaults to None.
"""
if not channel:
channel = interaction.channel
channel_id = channel.id
@@ -93,7 +90,6 @@ class SetCog(commands.Cog, name="Set command."):
group_id (str): String representation of Group id to set pad length in. Is SlashOption.
length (int): Number of characters. Is SlashOption. Defaults to 26.
"""
with Session(engine) as session:
stmt = (
select(CategoryGroup)
@@ -111,7 +107,7 @@ class SetCog(commands.Cog, name="Set command."):
async def submission_channel(
self,
interaction: nextcord.Interaction,
channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption(
channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption(
description="Channel to register",
required=False,
channel_types=[nextcord.ChannelType.text],
@@ -126,7 +122,6 @@ class SetCog(commands.Cog, name="Set command."):
channel Optional[nextcord.abc.GuildChannel], optional): Channel to register
Is a SlashOption. Limited to text channels, Defaults to None.
"""
if not channel:
channel = interaction.channel
@@ -156,7 +151,6 @@ class SetCog(commands.Cog, name="Set command."):
interaction (nextcord.Interaction): Interaction that invoked the command
length (int): Number of characters. Is SlashOption. Defaults to 12.
"""
with Session(engine) as session:
stmt = (
select(CategoryGroup)
@@ -172,7 +166,7 @@ class SetCog(commands.Cog, name="Set command."):
async def verify_channel(
self,
interaction: nextcord.Interaction,
channel: Optional[nextcord.abc.GuildChannel] = nextcord.SlashOption(
channel: nextcord.abc.GuildChannel | None = nextcord.SlashOption(
description="Channel to register",
required=False,
channel_types=[nextcord.ChannelType.text],
@@ -187,7 +181,6 @@ class SetCog(commands.Cog, name="Set command."):
channel (Optional[nextcord.abc.GuildChannel]): Channel to register
Is a SlashOption. Limited to text channels, Defaults to None.
"""
if not channel:
channel = interaction.channel
channel_id = channel.id
-1
View File
@@ -57,7 +57,6 @@ class UpdateCog(commands.Cog, name="Update commands."):
old_username (str): Username to replace.
new_username (str): Username to replace old with.
"""
with Session(engine) as session:
stmt = select(Guild.VerificationChannel).where(Guild.GuildId == interaction.guild_id)
verify_channel = interaction.guild.get_channel(session.scalar(stmt))
+1 -1
View File
@@ -49,7 +49,7 @@ class ViewCog(commands.Cog, name="View Command"):
group_id (str): String representation of Group id to search from. Is SlashOption.
category_id (str): String representation of Category id to show leaderboard. Is SlashOption.
"""
str_list: "list[str]" = []
str_list: list[str] = []
# get group, category, and all scores sorted correctly.
with Session(engine) as session:
+1 -3
View File
@@ -2,8 +2,6 @@
Contains view and helper functions for confirming and processing deletes.
"""
from typing import Optional
import nextcord
from sqlalchemy import delete, select
from sqlalchemy.orm import Session
@@ -61,7 +59,7 @@ class DeleteConfirmView(nextcord.ui.Modal):
await interaction.response.send_message("Deletion complete.", ephemeral=True)
def create_delete_id(group_id: int, category_id: Optional[int] = None) -> str:
def create_delete_id(group_id: int, category_id: int | None = None) -> str:
"""
Creates a delete id string to tell the view what to delete.
@@ -19,9 +19,7 @@ class UpdateUsernameVerifyView(nextcord.ui.View):
super().__init__(timeout=None)
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-username-update")
async def deny_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
async def deny_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
"""
Button for denying username update. Deletes message.
@@ -32,9 +30,7 @@ class UpdateUsernameVerifyView(nextcord.ui.View):
await interaction.message.delete()
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-username-update")
async def approve_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
async def approve_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
"""
Button for approving username update. Tells guild data to update username, then updates
message to show it is complete.
@@ -43,7 +39,6 @@ class UpdateUsernameVerifyView(nextcord.ui.View):
button (nextcord.ui.Button): unused
interaction (nextcord.Interaction): Interaction with the button
"""
with Session(engine) as session:
try:
embed_content = interaction.message.embeds[0].to_dict()
@@ -51,7 +46,7 @@ class UpdateUsernameVerifyView(nextcord.ui.View):
new_username: str = embed_content["fields"][1]["value"]
stmt = select(distinct(Score.CategoryId)).where(Score.UserName == old_username)
category_ids: "list[int]" = list(session.scalars(stmt))
category_ids: list[int] = list(session.scalars(stmt))
stmt = select(Score).where(Score.UserName == old_username)
for score in session.scalars(stmt):
+40 -3
View File
@@ -4,6 +4,7 @@ Defines cog for guild listeners. Contains functions for various guild events.
import nextcord
from nextcord.ext import commands
from sqlalchemy import select
from sqlalchemy.orm import Session
# pylint: disable=import-error
@@ -18,14 +19,50 @@ class GuildListener(commands.Cog, name="Guild Listener"):
@commands.Cog.listener()
async def on_guild_join(self, guild: nextcord.Guild):
"""Listener for Guild Join. Adds guild to database."""
new_guild = Guild(GuildId=guild.id, GuildName=guild.name)
"""
Listener for Guild Join. Adds guild to database.
Args:
guild (nextcord.Guild): Guild that was joined.
"""
with Session(engine) as session:
stmt = select(Guild).where(Guild.GuildId == guild.id)
guild_data = session.scalar(stmt)
# just in case check if the guild already exists
if guild_data is None:
new_guild = Guild(GuildId=guild.id, GuildName=guild.name)
session.add(new_guild)
else:
guild_data.GuildName = guild.name
guild_data.Active = True
session.commit()
# let imcompiling#2272 know about a new server joining!
# staying with fetch due to intents not being set up correctly. Maybe change to get eventually.
imcompiling: nextcord.User = await self.bot.fetch_user(206602713511886849)
await imcompiling.send(f'Agon has joined server "{guild.name}"!')
@commands.Cog.listener()
async def on_guild_remove(self, guild: nextcord.Guild):
"""
Listens to when the bot has been removed from a guild. Update's the database to record the active flag as false.
Args:
guild (nextcord.Guild): Guild that was left.
"""
with Session(engine) as session:
stmt = select(Guild).where(Guild.GuildId == guild.id)
guild_data = session.scalar(stmt)
guild_data.Active = False
session.commit()
# let imcompiling#2272 know about leaving a server.
# staying with fetch due to intents not being set up correctly. Maybe change to get eventually.
imcompiling: nextcord.User = await self.bot.fetch_user(206602713511886849)
await imcompiling.send(f'Agon has been removed from "{guild.name}".')
# This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot.
+19 -2
View File
@@ -3,6 +3,7 @@ Defines cog for on message listener.
"""
import re
from datetime import datetime
import nextcord
from nextcord.ext import commands
@@ -11,7 +12,7 @@ from sqlalchemy.orm import Session
# pylint: disable=import-error
from classes.submission import SubmissionState
from highscores import submission_objects
from main import submission_objects
from utilities.sql_data_classes import Category, Guild, engine
@@ -28,6 +29,8 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
if message.author.id == self.bot.user.id:
return
await birthday_action(message)
guild_id = message.guild.id
if submission_objects.has_user(message.author.id):
@@ -45,7 +48,6 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
if message.channel.id == submission_channel_id:
state = submission.submission_state
if state == SubmissionState.SCORE and message.content:
stmt = select(Category.IsTimeRecord).where(Category.CategoryId == submission.category_id)
# check with regex if score matches correctly
@@ -102,3 +104,18 @@ class SubmissionListener(commands.Cog, name="Message Listener"):
def setup(bot: commands.Bot):
"""Adds function to bot"""
bot.add_cog(SubmissionListener(bot))
async def birthday_action(message: nextcord.Message) -> None:
"""
Does the birthday thing, currently for chris only.
Args:
message (nextcord.Message): The message to check for birthday thing.
"""
today = datetime.today()
birthday = datetime(today.year, 7, 31)
chris = 642330132958281750
if today.date() == birthday.date():
if message.author.id == chris or chris in message.raw_mentions:
await message.add_reaction("🎂")
+7 -12
View File
@@ -1,7 +1,5 @@
"""Views used for submission process."""
from typing import Optional
import nextcord
from nextcord.ext import commands
from sqlalchemy import select
@@ -9,7 +7,7 @@ from sqlalchemy.orm import Session, aliased
# pylint: disable=import-error
from classes.submission import Submission, SubmissionState
from highscores import submission_objects
from main import submission_objects
from utilities.sql_data_classes import Category, CategoryGroup, Guild, engine
from .verification_view import VerificationView
@@ -17,7 +15,7 @@ from .verification_view import VerificationView
def category_select_options(group_id: int) -> "list[nextcord.SelectOption]":
"""Returns list of categories for a given boss."""
options: "list[nextcord.SelectOption]" = []
options: list[nextcord.SelectOption] = []
with Session(engine) as session:
subq = select(CategoryGroup).where(CategoryGroup.GroupId == group_id).subquery()
@@ -147,11 +145,8 @@ class SubmissionCreateButton(nextcord.ui.View):
self._bot = bot
@nextcord.ui.button(label="Submit", style=nextcord.ButtonStyle.primary, custom_id="submitcreate")
async def submitcreate_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
async def submitcreate_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
"""Button for score submission."""
# Don't allow a user to start a new submission if one already exists
if submission_objects.has_user(interaction.user.id):
await interaction.send("Please finish in progress submission first.", ephemeral=True)
@@ -198,7 +193,7 @@ class SubmissionView(nextcord.ui.View):
bot: commands.Bot,
submission_user: int,
*,
timeout: Optional[float] = 300,
timeout: float | None = 300,
):
super().__init__(timeout=timeout)
self._submission_user = submission_user
@@ -231,6 +226,6 @@ class SubmissionView(nextcord.ui.View):
return check
async def on_timeout(self) -> None:
# remove user from submission objects after time out. Allows user to start new submission.
submission_objects.remove_user_submission(self._submission_user)
# async def on_timeout(self) -> None:
# # remove user from submission objects after time out. Allows user to start new submission.
# submission_objects.remove_user_submission(self._submission_user)
+2 -7
View File
@@ -13,16 +13,12 @@ class VerificationView(nextcord.ui.View):
super().__init__(timeout=None)
@nextcord.ui.button(label="Deny", style=nextcord.ButtonStyle.red, custom_id="deny-button-submission")
async def deny_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
async def deny_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
"""Button for denying score submission."""
await interaction.message.delete()
@nextcord.ui.button(label="Approve", style=nextcord.ButtonStyle.green, custom_id="approve-button-submission")
async def approve_button(
self, button: nextcord.ui.Button, interaction: nextcord.Interaction
): # pylint: disable=unused-argument
async def approve_button(self, button: nextcord.ui.Button, interaction: nextcord.Interaction): # pylint: disable=unused-argument
"""Button for approving score submission."""
for embed in interaction.message.embeds:
content = embed.to_dict()
@@ -38,7 +34,6 @@ class VerificationView(nextcord.ui.View):
user = field["value"]
try:
await submit_score(interaction, group_id, category_id, user, score)
content = interaction.message.content
content += f"\nApproved by <@{interaction.user.id}>."
-29
View File
@@ -1,29 +0,0 @@
version: "3"
services:
sqldb:
restart: unless-stopped
image: mysql:latest
container_name: "Highscores-DataBase"
volumes:
- highscores_data:/var/lib/mysql
ports:
- 3306:3306
environment:
- MYSQL_ROOT_PASSWORD=${SQL_PASS}
- MYSQL_ROOT_USERNAME=${SQL_USER}
bot:
restart: unless-stopped
build: .
container_name: "Highscores-Bot"
depends_on:
- sqldb
environment:
- DISCORD_TOKEN=${DISCORD_TOKEN}
- SQL_DB=${SQL_DB}
- SQL_HOST=${SQL_HOST}
- SQL_PASS=${SQL_PASS}
- SQL_USER=${SQL_USER}
volumes:
highscores_data:
+1
View File
@@ -6,6 +6,7 @@ CREATE TABLE Guilds(
GuildName varchar(95) not null,
SubmissionChannel bigint unsigned,
VerificationChannel bigint unsigned,
Active boolean DEFAULT true,
PRIMARY KEY (GuildId)
);
+3 -2
View File
@@ -9,9 +9,10 @@ import sys
import nextcord
from nextcord.ext import commands
from classes.submission import SubmissionMap
# pylint: disable=import-error
from utilities import config
from classes.submission import SubmissionMap
logger = logging.getLogger("nextcord")
logger.setLevel(logging.DEBUG)
@@ -29,7 +30,7 @@ def main():
print("ERROR: Python version must be >= 3.8")
sys.exit()
# Allows privledged intents for monitoring members joining, roles editing, and role assignments
# Allows privileged intents for monitoring members joining, roles editing, and role assignments
# These need to be enabled in the developer portal as well
intents = nextcord.Intents.default()
+130
View File
@@ -0,0 +1,130 @@
[tool.poetry]
name = "agon"
version = "0.1.0"
description = "A highscores discord bot"
authors = ["imcompiling <[email protected]>"]
readme = "README.md"
[tool.poetry.dependencies]
python = "^3.12"
nextcord = "^2.6.0"
python-dotenv = "^1.0.1"
pandas = "^2.2.3"
sqlalchemy = "^2.0.35"
pymysql = "^1.1.1"
cryptography = "^43.0.1"
[tool.poetry.group.dev.dependencies]
ruff = "^0.6.9"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.ruff]
# Exclude a variety of commonly ignored directories.
exclude = [
".bzr",
".direnv",
".eggs",
".git",
".git-rewrite",
".hg",
".ipynb_checkpoints",
".mypy_cache",
".nox",
".pants.d",
".pyenv",
".pytest_cache",
".pytype",
".ruff_cache",
".svn",
".tox",
".venv",
".vscode",
"__pypackages__",
"_build",
"buck-out",
"build",
"dist",
"node_modules",
"site-packages",
"venv",
"dakiny-messages", # Don't need to lint this git repo.
]
# Same as Black.
line-length = 120
indent-width = 4
# Assume Python 3.8
target-version = "py312"
[tool.ruff.lint]
# Enable Pyflakes (`F`) and a subset of the pycodestyle (`E`) codes by default.
# Unlike Flake8, Ruff doesn't enable pycodestyle warnings (`W`) or
# McCabe complexity (`C901`) by default.
select = ["ALL"]
ignore = [
"ANN101",
"B008", # I build SlashOptions as arguments.
"COM812",
"COM819",
"D107", # don't want docstrings on init functions
"D203",
"D206",
"D212",
"D300",
"E111",
"E114",
"E117",
"ISC001",
"ISC002",
"PD011", # doesn't like me using '.values' on embeds....
"Q000",
"Q001",
"Q002",
"Q003",
"W191",
]
# Allow fix for all enabled rules (when `--fix`) is provided.
fixable = ["ALL"]
unfixable = []
# Allow unused variables when underscore-prefixed.
dummy-variable-rgx = "^(_+|(_+[a-zA-Z0-9_]*[a-zA-Z0-9]+?))$"
[tool.ruff.lint.pydocstyle]
convention = "google"
[tool.ruff.lint.flake8-annotations]
mypy-init-return = true
[tool.ruff.format]
# Like Black, use double quotes for strings.
quote-style = "double"
# Like Black, indent with spaces, rather than tabs.
indent-style = "space"
# Like Black, respect magic trailing commas.
skip-magic-trailing-comma = false
# Like Black, automatically detect the appropriate line ending.
line-ending = "auto"
# Enable auto-formatting of code examples in docstrings. Markdown,
# reStructuredText code/literal blocks and doctests are all supported.
#
# This is currently disabled by default, but it is planned for this
# to be opt-out in the future.
docstring-code-format = true
# Set the line length limit used when formatting code snippets in
# docstrings.
#
# This only has an effect when the `docstring-code-format` setting is
# enabled.
docstring-code-line-length = "dynamic"
-6
View File
@@ -1,6 +0,0 @@
nextcord>=2.3.2
python-dotenv>=0.20.0
pandas>=1.5.2
sqlalchemy<2.0
pymysql>=1.0.2
cryptography>=2.8
+1
View File
@@ -1,6 +1,7 @@
"""
Simple file to set up the sqlalchemy connection to database.
"""
from sqlalchemy import create_engine
from sqlalchemy.ext.automap import automap_base
+41 -34
View File
@@ -29,7 +29,6 @@ def autofill_categories(guild_id: int, group_id: int, category_begin: str) -> "d
Returns:
dict[str, str]: List of all category names that start with category_begin.
"""
with Session(engine) as session:
# search database for all category names
subq = (
@@ -40,18 +39,18 @@ def autofill_categories(guild_id: int, group_id: int, category_begin: str) -> "d
)
cat_group_alias = aliased(CategoryGroup, subq)
stmt = select(Category).where(Category.GroupId == cat_group_alias.GroupId)
categories_dict: dict = {cat.CategoryName: str(cat.CategoryId) for cat in session.scalars(stmt)}
categories_dict: dict[str, str] = {cat.CategoryName: str(cat.CategoryId) for cat in session.scalars(stmt)}
# if string empty, return all category names
if not category_begin:
return categories_dict
return dict(list(categories_dict.items())[:25])
# else find all categories that start with category_begin
# TODO is it better to have sql handle the starts with or python?
categories: dict = {
categories: dict[str, str] = {
cat_name: cat_id for cat_name, cat_id in categories_dict.items() if category_begin.lower() in cat_name.lower()
}
return categories
return dict(list(categories.items())[:25])
def autofill_groups(guild_id: int, group_begin: str) -> "dict[str, str]":
@@ -65,24 +64,23 @@ def autofill_groups(guild_id: int, group_begin: str) -> "dict[str, str]":
Returns:
dict[str, str]: Dict of group names and ids where group name start with group_begin. Key is name, value is id.
"""
with Session(engine) as session:
# Select all groups in guild.
stmt = select(CategoryGroup).where(CategoryGroup.GuildId == guild_id)
# dict has to have key values of same type for discord.
group_dict: dict = {group.GroupName: str(group.GroupId) for group in session.scalars(stmt)}
group_dict: dict[str, str] = {group.GroupName: str(group.GroupId) for group in session.scalars(stmt)}
# if string empty, return all group names
if not group_begin:
return group_dict
return dict(list(group_dict.items())[:25])
# else find all groups that start with group_begin
groups: dict = {
groups: dict[str, str] = {
group_name: group_id for group_name, group_id in group_dict.items() if group_begin.lower() in group_name.lower()
}
return groups
return dict(list(groups.items())[:25])
def autofill_users_in_group_category(guild_id: int, group_id: int, category_id: int, user_begin: str) -> "list[str]":
@@ -99,7 +97,6 @@ def autofill_users_in_group_category(guild_id: int, group_id: int, category_id:
Returns:
list[str]: List of users that start with user_begin.
"""
with Session(engine) as session:
# get all categories in the group
subq = (
@@ -126,16 +123,16 @@ def autofill_users_in_group_category(guild_id: int, group_id: int, category_id:
category = aliased(Category, subq)
stmt = select(distinct(Score.UserName)).where(Score.CategoryId == category.CategoryId)
users_list: "list[str]" = list(session.scalars(stmt))
users_list: list[str] = list(session.scalars(stmt))
# if string empty, return all users
if not user_begin:
return users_list
return users_list[:25]
# else find all users that start with user_begin
users = [user for user in users_list if user_begin.lower() in user.lower()]
users: list[str] = [user for user in users_list if user_begin.lower() in user.lower()]
return users
return users[:25]
def autofill_users_in_guild(guild_id: int, user_begin: str) -> "list[str]":
@@ -149,21 +146,20 @@ def autofill_users_in_guild(guild_id: int, user_begin: str) -> "list[str]":
Returns:
list[str]: All users with submissions in the guild.
"""
with Session(engine) as session:
group_ids = select(CategoryGroup.GroupId).where(CategoryGroup.GuildId == guild_id)
category_ids = select(Category.CategoryId).where(Category.GroupId.in_(group_ids))
stmt = select(distinct(Score.UserName)).where(Score.CategoryId.in_(category_ids))
users_list: "list[str]" = list(session.scalars(stmt))
users_list: list[str] = list(session.scalars(stmt))
# if string empty, return all users
if not user_begin:
return users_list
return users_list[:25]
# else find all users that start with user_begin
users = [user for user in users_list if user_begin.lower() in user.lower()]
users: list[str] = [user for user in users_list if user_begin.lower() in user.lower()]
return users
return users[:25]
def format_highscore_message(session: Session, group: CategoryGroup) -> str:
@@ -179,7 +175,6 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
Returns:
str: Formatted string for discord message
"""
ret_string = f"**{group.GroupName}**\n```"
stmt = select(Guild).where(Guild.GuildId == group.GuildId)
@@ -201,17 +196,19 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
max_cat_size: int = 0
pad_length = group.PadLength
for cat in session.scalars(stmt):
if cat.Size > max_cat_size:
max_cat_size = cat.Size
max_cat_size = max(cat.Size, max_cat_size)
categories.append(cat)
category_ids.append(cat.CategoryId)
# get all scores in category group, load into data frame.
d_f = pd.read_sql_query(sql=session.query(Score).where(Score.CategoryId.in_(category_ids)).statement, con=engine)
d_f = pd.read_sql_query(
sql=session.query(Score).where(Score.CategoryId.in_(category_ids)).statement,
con=engine,
)
# this list is what will be printed in the discord message
# first string will be category list, others will be score printouts
str_list: "list[str]" = ["" for i in range(max_cat_size + 1)]
str_list: list[str] = ["" for i in range(max_cat_size + 1)]
groups_df = d_f.groupby("CategoryId")
# TODO figure out how to have less nests.
@@ -235,11 +232,15 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
score = processed_group["Score"][ind]
str_list[ind + 1] += format_score_string(
username, score, group.UsernameLength, category.IsTimeRecord, ind + 1, pad_length
username,
score,
group.UsernameLength,
category.IsTimeRecord,
ind + 1,
pad_length,
)
else:
# if not enough entries, pad out
if ind + 1 <= category.Size:
elif ind + 1 <= category.Size:
str_list[ind + 1] += f"{ind + 1}: ".ljust(pad_length, " ")
else:
str_list[ind + 1] += "".ljust(pad_length, " ")
@@ -275,7 +276,12 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
def format_score_string(
username: str, score: float, username_length: int, is_time_record: bool, rank: int, pad_length: int
username: str,
score: float,
username_length: int,
is_time_record: bool,
rank: int,
pad_length: int,
) -> str:
"""
Builds the string to show a user's score.
@@ -334,7 +340,6 @@ async def send_highscore_message(session: Session, guild: nextcord.Guild, group:
Returns:
nextcord.Message: Message object that was posted with group contents.
"""
highscore_string = format_highscore_message(session, group)
channel = guild.get_channel(group.ChannelId)
@@ -351,7 +356,11 @@ async def send_highscore_message(session: Session, guild: nextcord.Guild, group:
async def submit_score(
interaction: nextcord.Interaction, group_id: int, category_id: int, username: str, score: str
interaction: nextcord.Interaction,
group_id: int,
category_id: int,
username: str,
score: str,
) -> None:
"""
Adds passed score to group. Then updates group message by calling send_highscore_message.
@@ -364,7 +373,6 @@ async def submit_score(
username (str): The user who submitted the score.
score (str): Score to submit. Need to convert to correct type.
"""
with Session(engine) as session:
stmt = select(Category.IsTimeRecord).where(Category.CategoryId == category_id)
is_time_record = session.scalar(stmt)
@@ -401,9 +409,8 @@ def add_preset_configuration(guild_id: int, config_str: str) -> None:
guild_id (int): Guild id to add config to
config_str (str): Config name to add
"""
with Session(engine) as session:
with open(f"example_config/{config_str}.json", encoding="utf-8", mode="r") as file:
with open(f"example_config/{config_str}.json", encoding="utf-8") as file:
config = json.load(file)
for group_name, categorys in config["tables"].items():