Compare commits

...
6 Commits
Author SHA1 Message Date
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
8 changed files with 217 additions and 41 deletions
+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"]
+1 -1
View File
@@ -12,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
+2 -4
View File
@@ -9,7 +9,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
@@ -147,9 +147,7 @@ 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
View File
+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.3.2"
python-dotenv = ">=0.20.0"
pandas = ">=1.5.2"
sqlalchemy = "<2.0"
pymysql = ">=1.0.2"
cryptography = ">=2.8"
[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
+78 -23
View File
@@ -16,7 +16,9 @@ from utilities.sql_data_classes import Category, CategoryGroup, Guild, Score, en
MAX_LINE_LENGTH = 142
def autofill_categories(guild_id: int, group_id: int, category_begin: str) -> "dict[str, str]":
def autofill_categories(
guild_id: int, group_id: int, category_begin: str
) -> "dict[str, str]":
"""
Returns all category names that start with category_begin.
If category_begin is empty, returns list of all category names.
@@ -40,16 +42,20 @@ 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[str, str]" = {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 dict(list(category_begin.items())[:25])
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[str, str]" = {
cat_name: cat_id for cat_name, cat_id in categories_dict.items() if category_begin.lower() in cat_name.lower()
cat_name: cat_id
for cat_name, cat_id in categories_dict.items()
if category_begin.lower() in cat_name.lower()
}
return dict(list(categories.items())[:25])
@@ -71,7 +77,9 @@ def autofill_groups(guild_id: int, group_begin: str) -> "dict[str, str]":
stmt = select(CategoryGroup).where(CategoryGroup.GuildId == guild_id)
# dict has to have key values of same type for discord.
group_dict: "dict[str, str]" = {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:
@@ -79,13 +87,17 @@ def autofill_groups(guild_id: int, group_begin: str) -> "dict[str, str]":
# else find all groups that start with group_begin
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()
group_name: group_id
for group_name, group_id in group_dict.items()
if group_begin.lower() in group_name.lower()
}
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]":
def autofill_users_in_group_category(
guild_id: int, group_id: int, category_id: int, user_begin: str
) -> "list[str]":
"""
Returns list of users that match user_begin. Either all users in group or just users in category if category is
not None. Might be expensive due to unioning all users in group.
@@ -112,7 +124,9 @@ def autofill_users_in_group_category(guild_id: int, group_id: int, category_id:
# If no category id, get all users in group.
if category_id == 0:
subq = select(Category.CategoryId).where(Category.GroupId == category_group.GroupId)
subq = select(Category.CategoryId).where(
Category.GroupId == category_group.GroupId
)
stmt = select(distinct(Score.UserName)).where(Score.CategoryId.in_(subq))
else:
@@ -124,7 +138,9 @@ def autofill_users_in_group_category(guild_id: int, group_id: int, category_id:
.subquery()
)
category = aliased(Category, subq)
stmt = select(distinct(Score.UserName)).where(Score.CategoryId == category.CategoryId)
stmt = select(distinct(Score.UserName)).where(
Score.CategoryId == category.CategoryId
)
users_list: "list[str]" = list(session.scalars(stmt))
@@ -133,7 +149,9 @@ def autofill_users_in_group_category(guild_id: int, group_id: int, category_id:
return users_list[:25]
# else find all users that start with user_begin
users: "list[str]" = [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[:25]
@@ -151,9 +169,15 @@ def autofill_users_in_guild(guild_id: int, user_begin: str) -> "list[str]":
"""
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))
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))
# if string empty, return all users
@@ -161,7 +185,9 @@ def autofill_users_in_guild(guild_id: int, user_begin: str) -> "list[str]":
return users_list[:25]
# else find all users that start with user_begin
users: "list[str]" = [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[:25]
@@ -207,7 +233,10 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
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
@@ -225,17 +254,26 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
# process data. Want each group sorted correctly and only top X results
processed_group = (
group_df.sort_values("Score", ascending=category.IsAscending).head(category.Size).reset_index()
group_df.sort_values("Score", ascending=category.IsAscending)
.head(category.Size)
.reset_index()
)
# add each score to list
for ind in range(max_cat_size): # need to subtract to deal with category name string
for ind in range(
max_cat_size
): # need to subtract to deal with category name string
if ind in processed_group.index:
username = processed_group["UserName"][ind]
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
@@ -275,7 +313,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.
@@ -321,7 +364,9 @@ def get_time_str(time_in: float) -> str:
return time.strftime(f"%-M:%S.{milliseconds}", time.gmtime(time_in))
async def send_highscore_message(session: Session, guild: nextcord.Guild, group: CategoryGroup) -> nextcord.Message:
async def send_highscore_message(
session: Session, guild: nextcord.Guild, group: CategoryGroup
) -> nextcord.Message:
"""
Function takes care of checking if a message exists for a group.
Creates and sends/edits the message.
@@ -351,7 +396,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.
@@ -376,7 +425,11 @@ async def submit_score(
else:
score = float(score)
stmt = select(Score).where(Score.UserName == username).where(Score.CategoryId == category_id)
stmt = (
select(Score)
.where(Score.UserName == username)
.where(Score.CategoryId == category_id)
)
existing_score = session.scalar(stmt)
if existing_score:
@@ -403,7 +456,9 @@ def add_preset_configuration(guild_id: int, config_str: str) -> None:
"""
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", mode="r"
) as file:
config = json.load(file)
for group_name, categorys in config["tables"].items():