Compare commits

...
Author SHA1 Message Date
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 390bf8a489 rename variable. 2023-03-28 13:55:18 -04:00
imcompiling 74aa78c748 Add loop for updating guild data. 2023-03-28 13:44:03 -04:00
imcompiling 379f280c11 Refactor presence to loops. 2023-03-28 13:02:07 -04:00
imcompiling 7d1e69c007 Don't need this import. 2023-03-28 12:56:50 -04:00
imcompiling b905ce7216 Change tag to v. 2023-03-28 12:51:39 -04:00
imcompiling 4bb7db3f71 Set push for specific tag only, set GIT_TAG env var. 2023-03-28 12:51:39 -04:00
imcompiling b3f48d4603 Correct on push. 2023-03-28 12:51:39 -04:00
imcompiling 90763c73a7 Add release workflow. 2023-03-28 12:51:39 -04:00
imcompiling 635419dc7c Change to operate on the main branch. No need for dev branch. 2023-03-28 12:51:39 -04:00
imcompiling 2356d5edbf Change workflow back to develop branch. 2023-03-28 12:51:39 -04:00
imcompiling 9eed0a1f4e Don't use action due to using local repo. 2023-03-28 12:51:39 -04:00
imcompiling a9a6ab4520 correct github address. 2023-03-28 12:51:39 -04:00
imcompiling e08a764591 Install docker onto image. 2023-03-28 12:51:39 -04:00
imcompiling 8b43f766b3 try https url 2023-03-28 12:51:39 -04:00
imcompiling cb9cad9783 maybe need buildx to set up docker 2023-03-28 12:51:39 -04:00
imcompiling 216becff6e try with docker commands instead of action. 2023-03-28 12:51:39 -04:00
imcompiling 836673198a try localhost for repo. 2023-03-28 12:51:39 -04:00
imcompiling e34739a0b1 Need buildx, don't understand why. 2023-03-28 12:51:39 -04:00
imcompiling 7c592e6acc correct http url, try again without buildx. 2023-03-28 12:51:39 -04:00
imcompiling 0f8637be23 try adding buildx to fix missing token. 2023-03-28 12:51:39 -04:00
imcompiling 3b597aeac7 delete unneeded files. 2023-03-28 12:51:39 -04:00
imcompiling c43b2297a4 Change runs-on, use action to build docker image. 2023-03-28 12:51:39 -04:00
imcompiling 1b337a291d add demo workflow 2023-03-28 12:51:39 -04:00
imcompiling 16b662875f Add build_develop workflow. 2023-03-28 12:51:39 -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
imcompiling 640d481018 Add guild name to database on join. 2023-03-21 21:41:51 -04:00
imcompiling 4c18513661 Merge branch '35-show-leaderboard-command' into develop 2023-03-11 15:12:59 -05:00
imcompiling 37b4d2d62e correct usage of format_score_string. Make format of view message better. 2023-03-11 15:12:36 -05:00
imcompiling 9c762fb346 Add on delete cascade to sql definition. 2023-03-10 17:56:49 -05:00
imcompiling 2c9e9e246a Create view leaderboard command. 2023-03-10 17:37:59 -05:00
imcompiling ab721c1625 create format_score_string function. 2023-03-10 16:27:40 -05:00
imcompiling 2864f342cc need to push tags separately. 2023-03-10 16:01:54 -05:00
imcompiling 92511db446 Merge branch '35-show-leaderboard-command' of https://gitea.cheezers.net/imcompiling/agon into 35-show-leaderboard-command 2023-03-10 12:18:25 -05:00
imcompiling 44c2720f80 Add new view file. 2023-03-10 12:17:58 -05:00
imcompiling 35cebdebce correct develop builder. 2023-03-10 11:57:28 -05:00
imcompiling e4437cc654 Correct the build scripts. 2023-03-10 11:56:45 -05:00
imcompiling fe2c400a2a Add build script. 2023-03-09 16:22:31 -05:00
imcompiling 39875585ef Change sqlalchemy version requirement. 2023-02-12 21:46:25 -05:00
imcompiling 7796ead644 Revert "Make command for admin only."
This reverts commit 8595a12f7f.
2023-02-12 21:37:12 -05:00
imcompiling 531d2f3a0e Merge commit '63e77958fb09c6849d48263994b69fcd92f46277' 2023-02-12 21:30:22 -05:00
imcompiling 8595a12f7f Make command for admin only. 2023-02-12 21:27:54 -05:00
imcompiling 1605c8ad50 Add new view file. 2023-02-12 13:33:05 -05:00
alwayscompiling 63e77958fb Do need to expose port for test bot. 2022-12-10 16:54:49 -05:00
12 changed files with 322 additions and 71 deletions
+20
View File
@@ -0,0 +1,20 @@
name: build_develop
on:
push:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Docker
uses: https://github.com/papodaca/install-docker-action@v1
- name: Build Docker Image
run: docker build -t viker.cheezers.net:5000/agon:develop .
- name: Push Docker Image
run: docker push viker.cheezers.net:5000/agon:develop
+24
View File
@@ -0,0 +1,24 @@
name: build_release
on:
push:
tags: ["v[0-9].[0-9].[0-9]"]
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout Code
uses: actions/checkout@v3
with:
fetch-depth: 0
- name: Install Docker
uses: https://github.com/papodaca/install-docker-action@v1
- 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}} .
- name: Push Latest Image
run: docker push viker.cheezers.net:5000/agon:latest
- name: Push Tagged Image
run: docker push viker.cheezers.net:5000/agon:${{env.GIT_TAG}}
+2
View File
@@ -10,6 +10,7 @@ from .help import HelpCog
from .refresh import RefreshCog
from .set import SetCog
from .update import UpdateCog
from .view import ViewCog
# This function will be called when this extension is loaded.
@@ -24,3 +25,4 @@ def setup(bot: commands.Bot):
bot.add_cog(RefreshCog(bot))
bot.add_cog(SetCog())
bot.add_cog(UpdateCog(bot))
bot.add_cog(ViewCog())
+112
View File
@@ -0,0 +1,112 @@
"""
View command heirarchy.
"""
import nextcord
import pandas as pd
from nextcord.ext import commands
from sqlalchemy import select
from sqlalchemy.orm import Session
# pylint: disable=import-error
from utilities.sql_data_classes import Category, CategoryGroup, Score, engine
from utilities.utils import MAX_LINE_LENGTH, autofill_categories, autofill_groups, format_score_string
class ViewCog(commands.Cog, name="View Command"):
"""
Class holds commands for viewing data.
"""
def __init__(self) -> None:
"""
Default constructor for ViewCog class. Does nothing.
"""
@nextcord.slash_command()
async def view(self, interaction: nextcord.Interaction):
"""
Parent slash command for view. Never called.
"""
@view.subcommand(description="Lists all scores for a given category.")
async def leaderboard(
self,
interaction: nextcord.Interaction,
group_id: str = nextcord.SlashOption(
name="group", description="Group to show leaderboard from.", required=True
),
category_id: str = nextcord.SlashOption(
name="category", description="Category to show leaderboard.", required=True
),
):
"""
Gathers all users that have a submission in the group/category combo and returns a message with them
in ranked order.
Args:
interaction (nextcord.Interaction): Interaction that invoked the 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]" = []
# get group, category, and all scores sorted correctly.
with Session(engine) as session:
stmt = select(CategoryGroup).where(CategoryGroup.GroupId == int(group_id))
group = session.scalar(stmt)
stmt = select(Category).where(Category.CategoryId == int(category_id))
category = session.scalar(stmt)
d_f = pd.read_sql_query(
sql=session.query(Score).where(Score.CategoryId == category_id).statement, con=engine
)
scores = d_f.sort_values("Score", ascending=category.IsAscending).reset_index()
# get the longest string
longest_str: int = scores.UserName.map(len).max()
for i, row in scores.iterrows():
str_list.append(
format_score_string(
row["UserName"], row["Score"], longest_str, category.IsTimeRecord, i + 1, MAX_LINE_LENGTH
)
)
message_str: str = f"**{group.GroupName} {category.CategoryName} Leaderboard!**\n```"
if len(str_list) == 0:
message_str += "No Submissions!"
else:
for line in str_list:
message_str += f"{line}\n"
message_str += "```"
await interaction.response.send_message(content=message_str)
@leaderboard.on_autocomplete("group_id")
async def group_autocomplete(self, interaction: nextcord.Interaction, group_id: str):
"""
Autocomplete function for table names.
Args:
interaction (nextcord.Interaction): Interaction that invoked the command.
group_id (str): String to be autocompleted with Group name.
"""
await interaction.response.send_autocomplete(autofill_groups(interaction.guild_id, group_id))
@leaderboard.on_autocomplete("category_id")
async def category_autocomplete(self, interaction: nextcord.Interaction, category_id: str, group_id: str):
"""
Autocomplete function for category names.
Args:
interaction (nextcord.Interaction): Interaction that invoked the command.
category_id (str): String to be autocompleted with Category name.
group_id (str): Group to find Category from.
"""
await interaction.response.send_autocomplete(
autofill_categories(interaction.guild_id, int(group_id), category_id)
)
+42 -4
View File
@@ -4,11 +4,11 @@ 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
from utilities.sql_data_classes import Guild, engine
from utilities.utils import add_preset_configuration
class GuildListener(commands.Cog, name="Guild Listener"):
@@ -19,14 +19,52 @@ 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."""
"""
Listener for Guild Join. Adds guild to database.
new_guild = Guild(GuildId=guild.id)
Args:
guild (nextcord.Guild): Guild that was joined.
"""
with Session(engine) as session:
session.add(new_guild)
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.
+16
View File
@@ -0,0 +1,16 @@
"""
Initializing code for all loops.
"""
from nextcord.ext import commands
from .bot_presence import BotPresence
from .guild_data import GuildData
# This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot.
def setup(bot: commands.Bot):
"""Adds function to bot"""
bot.add_cog(BotPresence(bot))
bot.add_cog(GuildData(bot))
@@ -1,5 +1,5 @@
"""
Functions for controlling the bot's presence.
Functions for controlling the bot's presence. Also contains the loop.
"""
import random
@@ -46,10 +46,3 @@ class BotPresence(commands.Cog, name="Bot Presence"):
status += f" | {len(self.bot.guilds)} servers"
await self.bot.change_presence(activity=nextcord.Game(status))
# This function will be called when this extension is loaded.
# It is necessary to add these functions to the bot.
def setup(bot: commands.Bot):
"""Adds function to bot"""
bot.add_cog(BotPresence(bot))
+48
View File
@@ -0,0 +1,48 @@
"""
Contains a loop that updates guild data in the database periodically.
"""
from nextcord.ext import commands, tasks
from sqlalchemy import select
from sqlalchemy.orm import Session
# pylint: disable=import-error
from utilities.sql_data_classes import Guild, engine
class GuildData(commands.Cog, name="Guild Data"):
"""Class to contain loop."""
def __init__(self, bot: commands.bot) -> None:
self.bot = bot
@commands.Cog.listener()
async def on_ready(self):
"""Starts the loop when bot is ready."""
await self.bot.wait_until_ready()
# pylint doesn't recognize that periodic_update_guild_data is subclassed from tasks.loop.
await self.periodic_update_guild_data.start() # pylint: disable=no-member
@tasks.loop(hours=24)
async def periodic_update_guild_data(self):
"""Loops every day, calls update_guild_data."""
self.update_guild_data()
def update_guild_data(self):
"""
Updates all known guild's data in the database.
"""
with Session(engine) as session:
for guild in self.bot.guilds:
# update given guild name for guild id
stmt = select(Guild).where(Guild.GuildId == guild.id)
guild_data = session.scalar(stmt)
# just in case checking if the guild doesn't exist.
if guild_data is None:
new_guild = Guild(GuildId=guild.id, GuildName=guild.name)
session.add(new_guild)
else:
guild_data.GuildName = guild.name
session.commit()
-27
View File
@@ -1,27 +0,0 @@
version: "3"
services:
sqldb:
restart: unless-stopped
image: mysql:latest
container_name: "Highscores-DataBase"
volumes:
- highscores_data:/var/lib/mysql
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:
+5 -3
View File
@@ -3,8 +3,10 @@ use highscores;
CREATE TABLE Guilds(
GuildId bigint unsigned not null unique,
GuildName varchar(95) not null,
SubmissionChannel bigint unsigned,
VerificationChannel bigint unsigned,
Active boolean DEFAULT true,
PRIMARY KEY (GuildId)
);
@@ -17,7 +19,7 @@ CREATE TABLE CategoryGroups(
PadLength smallint not null DEFAULT 26,
UsernameLength smallint DEFAULT 12,
CONSTRAINT UniqueGuildCategoryGroup UNIQUE (GuildId, GroupName),
FOREIGN KEY (GuildId) REFERENCES Guilds(GuildId),
FOREIGN KEY (GuildId) REFERENCES Guilds(GuildId) ON DELETE CASCADE,
PRIMARY KEY (GroupId)
);
@@ -30,7 +32,7 @@ CREATE TABLE Categories(
IsTimeRecord boolean not null DEFAULT false,
IsValidated boolean not null DEFAULT true,
CONSTRAINT UniqueCategoryInGroup UNIQUE (GroupId, CategoryName),
FOREIGN KEY (GroupId) REFERENCES CategoryGroups(GroupId),
FOREIGN KEY (GroupId) REFERENCES CategoryGroups(GroupId) ON DELETE CASCADE,
PRIMARY Key (CategoryId)
);
@@ -38,6 +40,6 @@ CREATE TABLE Scores(
UserName varchar(30) not null,
Score float not null, -- Maybe should have time and int scores?? timestamp type?
CategoryId int unsigned not null,
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId),
FOREIGN KEY (CategoryId) REFERENCES Categories(CategoryId) ON DELETE CASCADE,
PRIMARY KEY (CategoryId, UserName)
);
+1 -1
View File
@@ -1,6 +1,6 @@
nextcord>=2.3.2
python-dotenv>=0.20.0
pandas>=1.5.2
sqlalchemy>=1.4.44
sqlalchemy<2.0
pymysql>=1.0.2
cryptography>=2.8
+51 -28
View File
@@ -40,18 +40,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(category_begin.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]":
@@ -71,18 +71,18 @@ 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 = {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]":
@@ -130,12 +130,12 @@ def autofill_users_in_group_category(guild_id: int, group_id: int, category_id:
# 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]":
@@ -158,12 +158,12 @@ def autofill_users_in_guild(guild_id: int, user_begin: str) -> "list[str]":
# 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:
@@ -231,20 +231,12 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
# add each score to list
for ind in range(max_cat_size): # need to subtract to deal with category name string
if ind in processed_group.index:
user_length = group.UsernameLength
username = processed_group["UserName"][ind][:user_length].ljust(user_length, " ")
username = processed_group["UserName"][ind]
score = processed_group["Score"][ind]
# get score, pad to correct length
if category.IsTimeRecord:
# MM:SS.ms is 7 characters long
# if there are single digit minutes, add a space for formatting.
score = get_time_str(processed_group["Score"][ind])
if score.find(":") and len(score.split(":", maxsplit=1)[0]) < 2:
score = " " + score
else:
score = str(int(processed_group["Score"][ind])).rjust(7, " ")
str_list[ind + 1] += f"{ind + 1}: {username} - {score}".ljust(pad_length, " ")
str_list[ind + 1] += format_score_string(
username, score, group.UsernameLength, category.IsTimeRecord, ind + 1, pad_length
)
else:
# if not enough entries, pad out
if ind + 1 <= category.Size:
@@ -262,7 +254,6 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
# need to use index to allow for modifying the list
for ind, line in enumerate(str_list):
if len(line) > MAX_LINE_LENGTH:
split_loc = int(MAX_LINE_LENGTH / pad_length) * pad_length
new_line = line[split_loc:]
str_list[ind] = line[:split_loc]
@@ -283,6 +274,38 @@ def format_highscore_message(session: Session, group: CategoryGroup) -> str:
return ret_string + "```"
def format_score_string(
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.
Args:
username (str): username.
score (int): score in int form.
username_length (float): limit to how long username is allowed to be.
is_time_record (bool): if the score represents an number or a time record.
rank (int): the user's rank.
pad_length (int): limit to how long the string can be.
Returns:
str: string to show user's score.
"""
username = username[:username_length].ljust(username_length, " ")
# get score, pad to correct length
if is_time_record:
# MM:SS.ms is 7 characters long
# if there are single digit minutes, add a space for formatting.
score = get_time_str(score)
if score.find(":") and len(score.split(":", maxsplit=1)[0]) < 2:
score = " " + score
else:
score = str(int(score)).rjust(7, " ")
return f"{rank}: {username} - {score}".ljust(pad_length, " ")
def get_time_str(time_in: float) -> str:
"""
Formats a float value into the desired string format for a timestamp.