diff --git a/.env b/.env deleted file mode 100644 index e232e93..0000000 --- a/.env +++ /dev/null @@ -1,152 +0,0 @@ -# .env file for UtilityBot -# version edited: 1.1.0 -# -# Version is used for tracking purposes (imagine that) -VERSION='1.0.4' -# -# Cycle time is how often the bot's periodic task runs. This is in seconds -CYCLE_TIME=600 -# -# Discord token is the private API token supplied by Discord's developer portal when the bot is created -# This is a secret token, DO NOT SHARE THIS TOKEN WITH ANYONE! -# The first link describes how to create a bot -# https://discordpy.readthedocs.io/en/stable/discord.html -# The second link takes you to the developer portal in order to make a bot -# https://discord.com/developers/applications -DISCORD_TOKEN= -# -# -# *************************************************************** -# These are the Guild IDs for all Franchises in the MLE league -# *************************************************************** -AVIATORS=651145112591663157 -BEARS=308471171840737293 -BLIZZARD= -BULLS=630706426582663170 -COMETS=471514625138229248 -DEMOLITION=540551409800708128 -DODGERS=540299529174384650 -DUCKS=265899410204917760 -ECLIPSE=762333628461875232 -ELITE=721124808989081640 -EXPRESS=601547100337078409 -FLAMES=387356593508974592 -FOXES= -HAWKS=593939558341541929 -HIVE=539465795705634826 -HURRICANES= -JETS=319431481162334218 -KNIGHTS= -LIGHTNING=722157564686762135 -OUTLAWS= -PANDAS=407369418956603393 -PIRATES= -PUFFINS=387532936548974592 -RHINOS=721102068768702485 -SABRES=973616136912506890 -SHADOW= -SHARKS=721820175762063432 -SPARTANS=539954225795563530 -SPECTRE=650858349700579338 -TYRANTS= -WIZARDS=426199791593193472 -WOLVES=885004686866907186 -# -# *************************************************************** -# These are the Roster Channel IDs for all Franchises in the MLE league -# *************************************************************** -AVIATORS_ROSTER= -BEARS_ROSTER= -BLIZZARD_ROSTER= -BULLS_ROSTER= -COMETS_ROSTER= -DEMOLITION_ROSTER= -DODGERS_ROSTER= -DUCKS_ROSTER= -ECLIPSE_ROSTER= -ELITE_ROSTER= -EXPRESS_ROSTER= -FLAMES_ROSTER= -FOXES_ROSTER= -HAWKS_ROSTER= -HIVE_ROSTER= -HURRICANES_ROSTER= -JETS_ROSTER= -KNIGHTS_ROSTER= -LIGHTNING_ROSTER= -OUTLAWS_ROSTER= -PANDAS_ROSTER= -PIRATES_ROSTER= -PUFFINS_ROSTER= -RHINOS_ROSTER= -SABRES_ROSTER=998001955215515659 -SHADOW_ROSTER= -SHARKS_ROSTER= -SPARTANS_ROSTER= -SPECTRE_ROSTER= -TYRANTS_ROSTER= -WIZARDS_ROSTER=1249528481612824586 -WOLVES_ROSTER= - -# *************************************************************** -# Fill the following in per the requirements of your application -# *************************************************************** -# -# guild ID (server ID) -# This is the ID of the server this bot belongs to. Right click and copy from Discord -# To get the Discord ID, enable developer options in Discord and right click on the server. -# Select "Copy Server ID" and paste it here -GUILD=748052192342310925 -# -# admin channel ID -# The admin channel posts uptime data of the bot. -# This is not required (yet) but is recommended. -# To get the Discord ID, enable developer options in Discord and right click on the channel. -# Select "Copy Channel ID" and paste it here -ADMIN_CHANNEL=1257523645937483807 -# -# notification channel ID -# The channel the bot will post notifications to when necessary. Like, sprocket updates or errors. -# This is not required (yet) but is recommended -# To get the Discord ID, enable developer options in Discord and right click on the channel. -# Select "Copy Channel ID" and paste it here -NOTIFICATION_CHANNEL=1214359860502597812 -# -# server icon -# This is the 'Emoji' ID of the 'Emoji' that would represent your franchise. -# This emoji will appear on certain embedded posts / messages. -# Getting the server icon out of an emoji can be funky. -# Post the emoji, right click and "Open Link" and copy the number from {} below -# https://cdn.discordapp.com/emojis/ {THIS IS THE NUMBER YOU WANT }.webp?sizeblahblahblah -SERVER_ICON = 'https://images-ext-1.discordapp.net/external/8g0PflayqZyQZe8dqTD_wYGPcCpucRWAsnIjV4amZhc/https/i.imgur.com/6anH1sI.png?format=webp&quality=lossless&width=619&height=619' -# -# roster images -# Images that the bot uses during the runroster command -IMG_STAFF='team\imgs\STAFF.png' -# IMG_PREMIER='team\imgs\PREMIER.png' -IMG_MASTER='team\imgs\MASTER.png' -IMG_CHAMPION='team\imgs\CHAMPION.png' -IMG_ACADEMY='team\imgs\ACADEMY.png' -IMG_FOUNDATION='team\imgs\FOUNDATION.png' -# -# tourney data -# TBD -TOURNEY_INFO_CHANNEL=1232807413967622204 -TOURNEY_CMD_CHANNEL=1232807969301987559 -TOURNEY_GOAL_MUL=1 -TOURNEY_ASSISTS_MUL=1 -TOURNEY_SAVES_MUL=1 -TOURNEY_SUB200_MUL=1 -TOURNEY_200300_MUL=2 -TOURNEY_400PLU_MUL=3 -TOURNEY_RANKED_1TEAM_MUL=1 -TOURNEY_RANKED_2TEAM_MUL=2 -TOURNEY_RANKED_3TEAM_MUL=3 -TOURNEY_SCRIM_RR_1TEAM_MUL=3 -TOURNEY_SCRIM_RR_2TEAM_MUL=8 -TOURNEY_SCRIM_RR_3TEAM_MUL=10 -TOURNEY_SCRIM_TEAM_MUL=5 -# -# roles data -# TBD -ROLES_CHANNEL=1232744137951019018 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e63870e --- /dev/null +++ b/.gitignore @@ -0,0 +1,183 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# vscode environment +.vscode/ + +# json data files +*.json + +# C extensions +*.so + +# Batch files +*.bat + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc diff --git a/.idea/.gitignore b/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/.idea/MLEBot.iml b/.idea/MLEBot.iml deleted file mode 100644 index 121e58b..0000000 --- a/.idea/MLEBot.iml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - - - - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 2aad73d..0000000 --- a/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/inspectionProfiles/profiles_settings.xml b/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/.idea/material_theme_project_new.xml b/.idea/material_theme_project_new.xml deleted file mode 100644 index 2c51b19..0000000 --- a/.idea/material_theme_project_new.xml +++ /dev/null @@ -1,12 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml deleted file mode 100644 index 5a63ecb..0000000 --- a/.idea/misc.xml +++ /dev/null @@ -1,7 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml deleted file mode 100644 index b25bf6f..0000000 --- a/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml deleted file mode 100644 index 35eb1dd..0000000 --- a/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MLEBot/.idea/.gitignore b/MLEBot/.idea/.gitignore deleted file mode 100644 index 13566b8..0000000 --- a/MLEBot/.idea/.gitignore +++ /dev/null @@ -1,8 +0,0 @@ -# Default ignored files -/shelf/ -/workspace.xml -# Editor-based HTTP Client requests -/httpRequests/ -# Datasource local storage ignored files -/dataSources/ -/dataSources.local.xml diff --git a/MLEBot/.idea/MLEBot.iml b/MLEBot/.idea/MLEBot.iml deleted file mode 100644 index d0876a7..0000000 --- a/MLEBot/.idea/MLEBot.iml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/MLEBot/.idea/inspectionProfiles/Project_Default.xml b/MLEBot/.idea/inspectionProfiles/Project_Default.xml deleted file mode 100644 index 64b7622..0000000 --- a/MLEBot/.idea/inspectionProfiles/Project_Default.xml +++ /dev/null @@ -1,52 +0,0 @@ - - - - \ No newline at end of file diff --git a/MLEBot/.idea/inspectionProfiles/profiles_settings.xml b/MLEBot/.idea/inspectionProfiles/profiles_settings.xml deleted file mode 100644 index 105ce2d..0000000 --- a/MLEBot/.idea/inspectionProfiles/profiles_settings.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - \ No newline at end of file diff --git a/MLEBot/.idea/material_theme_project_new.xml b/MLEBot/.idea/material_theme_project_new.xml deleted file mode 100644 index 2915cc9..0000000 --- a/MLEBot/.idea/material_theme_project_new.xml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MLEBot/.idea/misc.xml b/MLEBot/.idea/misc.xml deleted file mode 100644 index 08235dc..0000000 --- a/MLEBot/.idea/misc.xml +++ /dev/null @@ -1,4 +0,0 @@ - - - - \ No newline at end of file diff --git a/MLEBot/.idea/modules.xml b/MLEBot/.idea/modules.xml deleted file mode 100644 index b25bf6f..0000000 --- a/MLEBot/.idea/modules.xml +++ /dev/null @@ -1,8 +0,0 @@ - - - - - - - - \ No newline at end of file diff --git a/MLEBot/.idea/vcs.xml b/MLEBot/.idea/vcs.xml deleted file mode 100644 index 6c0b863..0000000 --- a/MLEBot/.idea/vcs.xml +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/MLEBot/__init__.py b/MLEBot/__init__.py index fc92533..e9ddd03 100644 --- a/MLEBot/__init__.py +++ b/MLEBot/__init__.py @@ -1,3 +1,19 @@ -from .mle_bot import MLEBot -from .mle_commands import MLECommands -from .lo_commands import LoCommands \ No newline at end of file +"""Minor League E-Sports Bot + """ +from . import commands +from . import frames +from . import services +from . import tasks +from . import types +from .mlebot import MLEBot + +__version__ = "1.1.4" + +__all__ = ( + 'commands', + 'frames', + 'services', + 'tasks', + 'types', + "MLEBot", +) diff --git a/MLEBot/__pycache__/__init__.cpython-311.pyc b/MLEBot/__pycache__/__init__.cpython-311.pyc deleted file mode 100644 index abf7f8c..0000000 Binary files a/MLEBot/__pycache__/__init__.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/enums.cpython-311.pyc b/MLEBot/__pycache__/enums.cpython-311.pyc deleted file mode 100644 index d54507c..0000000 Binary files a/MLEBot/__pycache__/enums.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/franchise.cpython-311.pyc b/MLEBot/__pycache__/franchise.cpython-311.pyc deleted file mode 100644 index 600cfe5..0000000 Binary files a/MLEBot/__pycache__/franchise.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/lo_commands.cpython-311.pyc b/MLEBot/__pycache__/lo_commands.cpython-311.pyc deleted file mode 100644 index 7edb48d..0000000 Binary files a/MLEBot/__pycache__/lo_commands.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/member.cpython-311.pyc b/MLEBot/__pycache__/member.cpython-311.pyc deleted file mode 100644 index 9df5ae2..0000000 Binary files a/MLEBot/__pycache__/member.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/mle_bot.cpython-311.pyc b/MLEBot/__pycache__/mle_bot.cpython-311.pyc deleted file mode 100644 index 19592a3..0000000 Binary files a/MLEBot/__pycache__/mle_bot.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/mle_commands.cpython-311.pyc b/MLEBot/__pycache__/mle_commands.cpython-311.pyc deleted file mode 100644 index 44cda51..0000000 Binary files a/MLEBot/__pycache__/mle_commands.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/roles.cpython-311.pyc b/MLEBot/__pycache__/roles.cpython-311.pyc deleted file mode 100644 index 5c7bf67..0000000 Binary files a/MLEBot/__pycache__/roles.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/sprocket_data_link.cpython-311.pyc b/MLEBot/__pycache__/sprocket_data_link.cpython-311.pyc deleted file mode 100644 index 0348cac..0000000 Binary files a/MLEBot/__pycache__/sprocket_data_link.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/task_roster.cpython-311.pyc b/MLEBot/__pycache__/task_roster.cpython-311.pyc deleted file mode 100644 index 4031a08..0000000 Binary files a/MLEBot/__pycache__/task_roster.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/task_sprocket.cpython-311.pyc b/MLEBot/__pycache__/task_sprocket.cpython-311.pyc deleted file mode 100644 index 827366c..0000000 Binary files a/MLEBot/__pycache__/task_sprocket.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/__pycache__/team.cpython-311.pyc b/MLEBot/__pycache__/team.cpython-311.pyc deleted file mode 100644 index 8ac7400..0000000 Binary files a/MLEBot/__pycache__/team.cpython-311.pyc and /dev/null differ diff --git a/MLEBot/commands/__init__.py b/MLEBot/commands/__init__.py new file mode 100644 index 0000000..3d02747 --- /dev/null +++ b/MLEBot/commands/__init__.py @@ -0,0 +1,16 @@ +"""Minor League E-Sports Bot Commands + """ + +from .lo import Commands as lo_commands +from .mle import Commands as mle_commands +from .rl import Commands as rl_commands + + +Commands = lo_commands + mle_commands + rl_commands + + +__version__ = '1.1.3' + +__all__ = ( + 'Commands', +) diff --git a/MLEBot/commands/lo/__init__.py b/MLEBot/commands/lo/__init__.py new file mode 100644 index 0000000..ef1a7c4 --- /dev/null +++ b/MLEBot/commands/lo/__init__.py @@ -0,0 +1,41 @@ +"""in honor of some folks who helped make MLE what it is, + we have immortalized them with these commands. + """ + +from .achilles import Achilles +from .adi import Adi +from .bw import Bw +from .haim import Haim +from .hoos import Hoos +from .kd import Kd +from .kunics import Kunics +from .maple import Maple +from .ondo import Ondo +from .rexton import Rexton +from .riz import Riz +from .soviet import Soviet +from .zb import Zb + + +Commands = [ + Achilles, + Adi, + Bw, + Haim, + Hoos, + Kd, + Kunics, + Maple, + Ondo, + Rexton, + Riz, + Soviet, + Zb, +] + + +__version__ = '1.1.3' + +__all__ = ( + 'Commands', +) diff --git a/MLEBot/commands/lo/achilles.py b/MLEBot/commands/lo/achilles.py new file mode 100644 index 0000000..0d26007 --- /dev/null +++ b/MLEBot/commands/lo/achilles.py @@ -0,0 +1,23 @@ +"""mr. worldwide + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Achilles(Cog): + """mr. worldwide + """ + + @app_commands.command(name='achilles', + description='mr. worldwide') + @app_commands.default_permissions() + async def achilles(self, + interaction: discord.Interaction): + """mr. worldwide""" + await interaction.response.send_message("""https://media.discordapp.net/attachments/1101172529676161155/1257818646005415966/A_Chillis_tendon.png?ex=6685ca66&is=668478e6&hm=59e3c53b51b45477562b895acd641397a1144ebc5e690e6b4b875b9ca83c0ca0&=&format=webp&quality=lossless""") diff --git a/MLEBot/commands/lo/adi.py b/MLEBot/commands/lo/adi.py new file mode 100644 index 0000000..f2f90cb --- /dev/null +++ b/MLEBot/commands/lo/adi.py @@ -0,0 +1,25 @@ +"""Brick by boring brick. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Adi(Cog): + """Brick by boring brick. + """ + + @app_commands.command(name='adi', + description='Brick by boring brick.') + @app_commands.default_permissions() + async def adi(self, + interaction: discord.Interaction): + """Brick by boring brick. + """ + _bricks = [':brick:'] * 20 + await interaction.response.send_message(' '.join(_bricks)) diff --git a/MLEBot/commands/lo/bw.py b/MLEBot/commands/lo/bw.py new file mode 100644 index 0000000..8e1b3ad --- /dev/null +++ b/MLEBot/commands/lo/bw.py @@ -0,0 +1,24 @@ +"""Galaxy brain + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Bw(Cog): + """Galaxy brain + """ + + @app_commands.command(name='bw', + description='Galaxy brain.') + @app_commands.default_permissions() + async def bw(self, + interaction: discord.Interaction): + """Galaxy brain + """ + await interaction.response.send_message('KISSEWDAKHTKISS') diff --git a/MLEBot/commands/lo/haim.py b/MLEBot/commands/lo/haim.py new file mode 100644 index 0000000..d785bc3 --- /dev/null +++ b/MLEBot/commands/lo/haim.py @@ -0,0 +1,25 @@ +"""S(HAIM). + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Haim(Cog): + """S(HAIM). + """ + + @app_commands.command(name='haim', + description='S(HAIM).') + @app_commands.default_permissions() + async def haim(self, + interaction: discord.Interaction): + """S(HAIM). + """ + await interaction.response.send_message( + 'https://media.discordapp.net/attachments/670665177863028750/940985245786837022/ezgif.com-gif-maker_1.gif?ex=667cd58d&is=667b840d&hm=0f7548f793f01ec70d4a45093083e3c13310754eb3b7d07a4b78cfd1085c9405&=') diff --git a/MLEBot/commands/lo/hoos.py b/MLEBot/commands/lo/hoos.py new file mode 100644 index 0000000..feeed1a --- /dev/null +++ b/MLEBot/commands/lo/hoos.py @@ -0,0 +1,25 @@ +""":kissing_heart: + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Hoos(Cog): + """:kissing_heart: + """ + + @app_commands.command(name='hoos', + description=':kissing_heart:') + @app_commands.default_permissions() + async def hoos(self, + interaction: discord.Interaction): + """:kissing_heart: + """ + await interaction.response.send_message( + 'Friends share more DNA than strangers.') diff --git a/MLEBot/commands/lo/kd.py b/MLEBot/commands/lo/kd.py new file mode 100644 index 0000000..42bbc16 --- /dev/null +++ b/MLEBot/commands/lo/kd.py @@ -0,0 +1,24 @@ +"""KD + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Kd(Cog): + """KD + """ + + @app_commands.command(name='kd', + description='KD') + @app_commands.default_permissions() + async def kd(self, + interaction: discord.Interaction): + """KD + """ + await interaction.response.send_message('` `') diff --git a/MLEBot/commands/lo/kunics.py b/MLEBot/commands/lo/kunics.py new file mode 100644 index 0000000..b7020c3 --- /dev/null +++ b/MLEBot/commands/lo/kunics.py @@ -0,0 +1,28 @@ +"""PIVOT + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Kunics(Cog): + """PIVOT + """ + + @app_commands.command(name='kunics', + description='PIVOT') + @app_commands.default_permissions() + async def kunics(self, + interaction: discord.Interaction): + """PIVOT + """ + await interaction.response.send_message("""***P I V O T +I V O T +V O T +O T +T***""") diff --git a/MLEBot/commands/lo/maple.py b/MLEBot/commands/lo/maple.py new file mode 100644 index 0000000..308959d --- /dev/null +++ b/MLEBot/commands/lo/maple.py @@ -0,0 +1,24 @@ +"""Oh, Canada... + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Maple(Cog): + """Oh, Canada... + """ + + @app_commands.command(name='maple', + description='Oh, Canada...') + @app_commands.default_permissions() + async def maple(self, + interaction: discord.Interaction): + """Oh, Canada... + """ + await interaction.response.send_message(':flag_ca: Sorry. :flag_ca:') diff --git a/MLEBot/commands/lo/ondo.py b/MLEBot/commands/lo/ondo.py new file mode 100644 index 0000000..8b13332 --- /dev/null +++ b/MLEBot/commands/lo/ondo.py @@ -0,0 +1,25 @@ +"""ondofir + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Ondo(Cog): + """ondofir + """ + + @app_commands.command(name='ondo', + description='ondofir') + @app_commands.default_permissions() + async def ondo(self, + interaction: discord.Interaction): + """ondofir + """ + await interaction.response.send_message( + 'https://media.discordapp.net/attachments/532946038080798730/709472405914910840/unknown.png?ex=667d06ea&is=667bb56a&hm=968a10b7a304b47c14a13b4333766419a3ae24f4a8c809910d241ee57f1689f0&=&format=webp&quality=lossless') diff --git a/MLEBot/commands/lo/rexton.py b/MLEBot/commands/lo/rexton.py new file mode 100644 index 0000000..3ab8265 --- /dev/null +++ b/MLEBot/commands/lo/rexton.py @@ -0,0 +1,24 @@ +"""opens door... + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Rexton(Cog): + """opens door... + """ + + @app_commands.command(name='rexton', + description='opens door...') + @app_commands.default_permissions() + async def rexton(self, + interaction: discord.Interaction): + """opens door... + """ + await interaction.response.send_message('https://media.discordapp.net/attachments/832819833611223081/996629449242058852/image_15_1.png?ex=667ce601&is=667b9481&hm=d3c4f0f7ae0074167a828db9b5b6deb8681a2c7bf1b93e428187327a1e3cea9e&=&format=webp&quality=lossless&width=1179&height=619') diff --git a/MLEBot/commands/lo/riz.py b/MLEBot/commands/lo/riz.py new file mode 100644 index 0000000..98f6539 --- /dev/null +++ b/MLEBot/commands/lo/riz.py @@ -0,0 +1,24 @@ +"""@Riz + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Riz(Cog): + """@Riz + """ + + @app_commands.command(name='riz', + description='@Riz') + @app_commands.default_permissions() + async def riz(self, + interaction: discord.Interaction): + """@Riz + """ + await interaction.response.send_message('neck') diff --git a/MLEBot/commands/lo/soviet.py b/MLEBot/commands/lo/soviet.py new file mode 100644 index 0000000..dec31f9 --- /dev/null +++ b/MLEBot/commands/lo/soviet.py @@ -0,0 +1,26 @@ +"""Oh, the burning... + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Soviet(Cog): + """Oh, the burning... + """ + + @app_commands.command(name='soviet', + description='Oh, the burning...') + @app_commands.default_permissions() + async def soviet(self, + interaction: discord.Interaction): + """Oh, the burning... + """ + await interaction.response.send_message("""***The Crucifixion of Morality*** +*The Crucifixion of Morality is an image of 69 horseshoe crabmen and an alpaca. The alpaca is striking a menacing pose. The 69 horseshoe crabmen are burning. * +https://cdn.mlesports.dev/public/The_Crucifixion_of_Morality.png""") diff --git a/MLEBot/commands/lo/zb.py b/MLEBot/commands/lo/zb.py new file mode 100644 index 0000000..d24d4d8 --- /dev/null +++ b/MLEBot/commands/lo/zb.py @@ -0,0 +1,23 @@ +"""My link is borked, halp + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Zb(Cog): + """My link is borked, halp + """ + @app_commands.command(name='zb', + description='My link is borked, halp') + @app_commands.default_permissions() + async def zb(self, + interaction: discord.Interaction): + """My link is borked, halp + """ + await interaction.response.send_message("https://tinyurl.com/rmblmv8") diff --git a/MLEBot/commands/mle/__init__.py b/MLEBot/commands/mle/__init__.py new file mode 100644 index 0000000..8ba2130 --- /dev/null +++ b/MLEBot/commands/mle/__init__.py @@ -0,0 +1,32 @@ +"""Minor League E-Sports Bot Commands + """ +from . import test_commands + +from .lookup import Lookup +from .query import Query +from .rebuild import Rebuild +from .salary import Salary +from .showusage import ShowUsage +from .teameligibility import TeamEligibility +from .teaminfo import TeamInfo +from .updatesprocket import UpdateSprocket + + +Commands = [ + Lookup, + Query, + Rebuild, + Salary, + ShowUsage, + TeamEligibility, + TeamInfo, + UpdateSprocket, +] + + +__version__ = '1.1.4' + +__all__ = ( + 'Commands', + 'test_commands', +) diff --git a/MLEBot/commands/mle/lookup.py b/MLEBot/commands/mle/lookup.py new file mode 100644 index 0000000..116a6b2 --- /dev/null +++ b/MLEBot/commands/mle/lookup.py @@ -0,0 +1,38 @@ +"""Lookup player by MLE name. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +from ...const import ERR_BAD_NAME +from ...frames import salary_card +from ...services.sprocket import lookup_rl +from ...types import Member + + +class Lookup(Cog): + """Lookup player by MLE name. + """ + + @app_commands.command(name='lookup', + description='Lookup player by MLE name.') + @app_commands.describe(mle_name='Player name.') + @app_commands.default_permissions() + async def lookup(self, + interaction: discord.Interaction, + mle_name: str) -> None: + 'Lookup player by MLE name provided.' + member: Member = lookup_rl(self._parent.sprocket.links, + name=mle_name) + if not member: + await self._parent.send_notification(interaction, + ERR_BAD_NAME, + as_reply=True) + return + await interaction.response.send_message(embed=salary_card(member)) diff --git a/MLEBot/commands/mle/query.py b/MLEBot/commands/mle/query.py new file mode 100644 index 0000000..91305ad --- /dev/null +++ b/MLEBot/commands/mle/query.py @@ -0,0 +1,96 @@ +"""Lookup groups of players by provided filter. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog, InteractionPagination + + +from ... import const +from ...frames import mle_card + + +class Query(Cog): + """Lookup groups of players by provided filter. + """ + + @app_commands.command(name='query', + description='Lookup groups of players by provided filter.') + @app_commands.describe(league_filter='[PL, ML, CL, AL, FL]') + @app_commands.describe(query_filter='[FA, RFA, Waivers, Pend]') + @app_commands.describe(sorting='[salary, current_scrim_points, name]') + @app_commands.choices(league_filter=[ + app_commands.Choice(name='PL', value=const.SPR_SG_PL), + app_commands.Choice(name='ML', value=const.SPR_SG_ML), + app_commands.Choice(name='CL', value=const.SPR_SG_CL), + app_commands.Choice(name='AL', value=const.SPR_SG_AL), + app_commands.Choice(name='FL', value=const.SPR_SG_FL) + ], + query_filter=[ + app_commands.Choice(name='FA', value='fa'), + app_commands.Choice(name='RFA', value='rfa'), + app_commands.Choice(name='Waivers', value='waivers'), + app_commands.Choice(name='Pend', value='pend') + ], + sorting=[ + app_commands.Choice(name='Salary', value='salary'), + app_commands.Choice(name='Scrim Points', + value='current_scrim_points'), + app_commands.Choice(name='Name', value='name') + ]) + @app_commands.default_permissions() + async def query(self, + interaction: discord.Interaction, + league_filter: app_commands.Choice[str], + query_filter: str, + sorting: str) -> None: + """query sprocket db with filters + + Args: + interaction (discord.Interaction): interaction of query + league_filter (app_commands.Choice[str]): which league? + query_filter (str): which player pool? + sorting (str): sorting type? + """ + _players = sorted([x for x in self._parent.sprocket.links.players.data if x['franchise'].lower( + ) == query_filter.lower() and x['skill_group'] == league_filter.value], key=lambda x: x[sorting], reverse=True) + + if len(_players) == 0: + emb = mle_card('**Filtered Players**\n\n', + 'There were no players to be found for this query!') + await interaction.response.send_message(embed=emb) + return + + async def get_page(page: int, + as_timout: bool = False): + emb: discord.Embed = mle_card('**Filtered Players**\n\n', + f'Players filtered for `{query_filter}`\n' + f'Sorted by `{sorting}`') + + if as_timout: + emb.add_field(name='**`Timeout`**', + value='This command has timed out. Type `[ub.help]` for help.') + emb.set_footer(text='Page `1` of 1') + return emb, 0 + + elements_per_page = 15 + offset = (page - 1) * elements_per_page + + emb.add_field(name='**Sal | Points | Name**', + value='\n'.join( + [ + f"`{str(_p['salary']).ljust(4)} | {str(_p['current_scrim_points']).ljust(4)} | {_p['name']}`" + for _p in _players[offset:offset + elements_per_page]]), + inline=False) + + total_pages = InteractionPagination.compute_total_pages(len(_players), + elements_per_page) + + emb.set_footer(text=f'Page {page} of {total_pages}') + return emb, total_pages + + await InteractionPagination(interaction, get_page).navigate() diff --git a/MLEBot/commands/mle/rebuild.py b/MLEBot/commands/mle/rebuild.py new file mode 100644 index 0000000..6123d3f --- /dev/null +++ b/MLEBot/commands/mle/rebuild.py @@ -0,0 +1,32 @@ +"""Rebuild bot meta data. +""" +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class Rebuild(Cog): + """Rebuild bot meta data. + """ + + @app_commands.command(name='rebuild', + description='Rebuild bot meta data.') + @app_commands.guilds(1043295434828947547) + @app_commands.default_permissions() + async def rebuild(self, + interaction: discord.Interaction) -> None: + """Rebuild bot meta data.""" + await interaction.response.defer() + if await self._parent.rebuild(): + await self._parent.send_notification(interaction, + 'Success!.', + as_followup=True) + else: + await self._parent.send_notification(interaction, + 'An error has occured.', + as_followup=True) diff --git a/MLEBot/commands/mle/salary.py b/MLEBot/commands/mle/salary.py new file mode 100644 index 0000000..f6292d3 --- /dev/null +++ b/MLEBot/commands/mle/salary.py @@ -0,0 +1,36 @@ +"""Show player salary. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +from ...const import ERR_BAD_NAME +from ...frames import salary_card +from ...services.sprocket import lookup_rl +from ...types import Member + + +class Salary(Cog): + """Show player salary. + """ + + @app_commands.command(name='salary', + description='Lookup your salary card.') + @app_commands.default_permissions() + async def lookup(self, + interaction: discord.Interaction) -> None: + 'Show player salary.' + member: Member = lookup_rl(self._parent.sprocket.links, + discord_id=interaction.user.id) + if not member: + await self._parent.send_notification(interaction, + ERR_BAD_NAME, + as_reply=True) + return + await interaction.response.send_message(embed=salary_card(member)) diff --git a/MLEBot/commands/mle/showusage.py b/MLEBot/commands/mle/showusage.py new file mode 100644 index 0000000..cae0784 --- /dev/null +++ b/MLEBot/commands/mle/showusage.py @@ -0,0 +1,41 @@ +"""show play usage of players for a franchise. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +from ...const import ERR_BAD_FRANCHISE +from ...frames import usage_card +from ...services.sprocket import lookup_franchise + + +class ShowUsage(Cog): + """show play usage of players for a franchise. + """ + + @app_commands.command(name='showusage', + description='show play usage of players for a franchise.') + @app_commands.default_permissions() + async def showusage(self, + interaction: discord.Interaction) -> None: + """show usage of members from a user's franchise + + Args: + interaction (discord.Interaction): discord.Interaction + """ + await interaction.response.defer() + franchise = lookup_franchise(self._parent.sprocket.links, + discord_id=interaction.user.id) + if not franchise: + await self._parent.send_notification(interaction, + ERR_BAD_FRANCHISE, + as_followup=True) + return + + await interaction.followup.send(embed=usage_card(franchise)) diff --git a/MLEBot/commands/mle/teameligibility.py b/MLEBot/commands/mle/teameligibility.py new file mode 100644 index 0000000..5056018 --- /dev/null +++ b/MLEBot/commands/mle/teameligibility.py @@ -0,0 +1,53 @@ +"""Show team eligibility. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +from ... import const +from ...frames import teameligibility_card +from ...types import Franchise +from ...services.sprocket import lookup_franchise + + +class TeamEligibility(Cog): + """Show team eligibility. + """ + + @app_commands.command(name='teameligibility', + description='Show team eligibility.') + @app_commands.describe(league='[PL, ML, CL, AL, FL]') + @app_commands.choices(league=[ + app_commands.Choice(name='PL', value=const.SPR_SG_PL), + app_commands.Choice(name='ML', value=const.SPR_SG_ML), + app_commands.Choice(name='CL', value=const.SPR_SG_CL), + app_commands.Choice(name='AL', value=const.SPR_SG_AL), + app_commands.Choice(name='FL', value=const.SPR_SG_FL) + ]) + @app_commands.default_permissions() + async def teameligibility(self, + interaction: discord.Interaction, + league: app_commands.Choice[str]) -> None: + """get team eligibility + + Args: + interaction (discord.Interaction): discord interaction + league (app_commands.Choice[str]): which league to display + """ + await interaction.response.defer() + franchise: Franchise = lookup_franchise(self._parent.sprocket.links, + discord_id=interaction.user.id) + if not franchise: + await self._parent.send_notification(interaction, + const.ERR_BAD_FRANCHISE, + as_followup=True) + return + + await interaction.followup.send(embed=teameligibility_card(franchise, + league.value)) diff --git a/MLEBot/commands/mle/teaminfo.py b/MLEBot/commands/mle/teaminfo.py new file mode 100644 index 0000000..1855d84 --- /dev/null +++ b/MLEBot/commands/mle/teaminfo.py @@ -0,0 +1,39 @@ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +from ...const import ERR_BAD_FRANCHISE +from ...frames import teaminfo_card +from ...services.sprocket import lookup_franchise +from ...types import Franchise + + +class TeamInfo(Cog): + """Get info about a team. + """ + + @app_commands.command(name='teaminfo', + description='Get info about a team.') + @app_commands.describe(team_name='MLE Team to get info about.') + @app_commands.default_permissions() + async def teaminfo(self, + interaction: discord.Interaction, + team_name: str): + """Get info about a team. + """ + await interaction.response.defer() + franchise: Franchise = lookup_franchise(self._parent.sprocket.links, + name=team_name) + if not franchise: + await self._parent.send_notification(interaction, + ERR_BAD_FRANCHISE, + as_followup=True) + return + + await interaction.followup.send(embed=teaminfo_card(franchise)) diff --git a/MLEBot/commands/mle/test_commands.py b/MLEBot/commands/mle/test_commands.py new file mode 100644 index 0000000..4adecdc --- /dev/null +++ b/MLEBot/commands/mle/test_commands.py @@ -0,0 +1,25 @@ +"""test commands for pydisco bot + """ +from __future__ import annotations + + +import unittest + + +from .lookup import Lookup + + +__all__ = ( + 'TestCommands', +) + + +class TestCommands(unittest.TestCase): + """test commands for pydisco bot + """ + + def test_lookup(self): + """test lookup command + """ + cmd = Lookup() + self.assertIsNotNone(cmd) # to update later diff --git a/MLEBot/commands/mle/updatesprocket.py b/MLEBot/commands/mle/updatesprocket.py new file mode 100644 index 0000000..46e563e --- /dev/null +++ b/MLEBot/commands/mle/updatesprocket.py @@ -0,0 +1,27 @@ +"""Update info from sprocket. + """ +from __future__ import annotations + + +import discord +from discord import app_commands + + +from pydiscobot import Cog + + +class UpdateSprocket(Cog): + """Update info from sprocket. + """ + + @app_commands.command(name='updatesprocket', + description='Update info from sprocket.') + @app_commands.default_permissions() + async def updatesprocket(self, + interaction: discord.Interaction) -> None: + await interaction.response.defer() + self._parent.sprocket.reset() + await self._parent.sprocket.run() + await self._parent.send_notification(interaction, + 'League-Sprocket update complete.', + as_followup=True) diff --git a/MLEBot/commands/rl/__init__.py b/MLEBot/commands/rl/__init__.py new file mode 100644 index 0000000..1ab79dd --- /dev/null +++ b/MLEBot/commands/rl/__init__.py @@ -0,0 +1,12 @@ +"""Rocket League Specific Commands + """ + +__version__ = '1.1.1' + +__all__ = ( + +) + +Commands = [ + +] diff --git a/MLEBot/const.py b/MLEBot/const.py new file mode 100644 index 0000000..10ead5f --- /dev/null +++ b/MLEBot/const.py @@ -0,0 +1,199 @@ +"""Minor League E-Sports Constants + """ + +# ERR - Error +ERR_BAD_NAME = 'The name provided could not be found in our database.\nIf this is an issue please contact support.' +ERR_BAD_FRANCHISE = 'Could not resolve this franchise from sprocket data!' + +# SPR - Sprocket +SPR_INFO = '\n'.join([ + 'Data gathered by sprocket public data links.', + 'See more at [sprocket links](https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html)' +]) + +# DL - Datalink +SPR_DL_MEMBERS = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/members.json" +SPR_DL_PLAYERS = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/players.json" +SPR_DL_PLA_STATS = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/player_stats.json" +SPR_DL_TEAMS = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/teams.json" +SPR_DL_SCRIMS = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/scrim_stats.json" +SPR_DL_FIXT = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/schedules/fixtures.json" +SPR_DL_MATCHES = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/schedules/matches.json" +SPR_DL_MAT_GRP = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/schedules/match_groups.json" +SPR_DL_TRACKER = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/trackers.json" +SPR_DL_USAGE = "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/role_usages.json" + +# HK - Hash-Key +# If None: No unique id to identify +SPR_HK_MEMBERS = "discord_id" +SPR_HK_MEMBERS_SECONDARY = "name" +SPR_HK_PLAYERS = "member_id" +SPR_HK_PLA_STATS = "member_id" +SPR_HK_TEAMS = "Franchise" +SPR_HK_SCRIMS = None +SPR_HK_FIXT = 'fixture_id' +SPR_HK_MATCHES = 'match_id' +SPR_HK_MAT_GRP = 'match_group_id' +SPR_HK_TRACKER = "mleid" +SPR_HK_USAGE = None + +# SG - Skill Group +SPR_SG_PL = 'Premier League' +SPR_SG_ML = 'Master League' +SPR_SG_CL = 'Champion League' +SPR_SG_AL = 'Academy League' +SPR_SG_FL = 'Foundation League' + +SPR_SG_ALL = [ + SPR_SG_PL, + SPR_SG_ML, + SPR_SG_CL, + SPR_SG_AL, + SPR_SG_FL, +] + +SALARY_CAP_PL = 95.0 +SALARY_CAP_ML = 82.0 +SALARY_CAP_CL = 69.5 +SALARY_CAP_AL = 57.5 +SALARY_CAP_FL = 39.5 + +SLOT_USAGE_STD = 8 +SLOT_USAGE_DBL = 6 +SLOT_USAGE_TTL = 12 + +AVIATORS = "Aviators" +BEARS = "Bears" +BLIZZARD = "Blizzard" +BULLS = "Bulls" +COMETS = "Comets" +DEMOLITION = "Demolition" +DODGERS = "Dodgers" +DUCKS = "Ducks" +ECLIPSE = "Eclipse" +ELITE = "Elite" +EXPRESS = "Express" +FLAMES = "Flames" +FOXES = "Foxes" +HAWKS = "Hawks" +HIVE = "Hive" +HURRICANES = "Hurricanes" +JETS = "Jets" +KNIGHTS = "Knights" +LIGHTNING = "Lightning" +OUTLAWS = "Outlaws" +PANDAS = "Pandas" +PIRATES = "Pirates" +PUFFINS = "Puffins" +RHINOS = "Rhinos" +SABRES = "Sabres" +SHADOW = "Shadow" +SHARKS = "Sharks" +SPARTANS = "Spartans" +SPECTRE = "Spectre" +TYRANTS = "Tyrants" +WAIVERS = "Waivers" +WIZARDS = "Wizards" +WOLVES = "Wolves" +FRANCHISE_MANAGER = 'Franchise Manager' +GENERAL_MANAGER = 'General Manager' +ASSISTANT_GENERAL_MANAGER = 'Assistant General Manager' +CAPTAIN = 'Captain' +SOCIAL_MEDIA = 'Social Media' +PREMIER_LEAGUE = 'Premier League' +MASTER_LEAGUE = 'Master League' +CHAMPION_LEAGUE = 'Champion League' +ACADEMY_LEAGUE = 'Academy League' +FOUNDATION_LEAGUE = 'Foundation League' +ROCKET_LEAGUE = 'Rocket League' +PR_SUPPORT = 'PR Support' +FA = 'Free Agent' +FP = 'Former Player' +PEND = 'Pending' + +ALL_MLE_ROLES = [ + AVIATORS, + BEARS, + BLIZZARD, + BULLS, + COMETS, + DEMOLITION, + DODGERS, + DUCKS, + ECLIPSE, + ELITE, + EXPRESS, + FLAMES, + FOXES, + HAWKS, + HIVE, + HURRICANES, + JETS, + KNIGHTS, + LIGHTNING, + OUTLAWS, + PANDAS, + PIRATES, + PUFFINS, + RHINOS, + SABRES, + SHADOW, + SHARKS, + SPARTANS, + SPECTRE, + TYRANTS, + WAIVERS, + WIZARDS, + WOLVES, + FRANCHISE_MANAGER, + GENERAL_MANAGER, + ASSISTANT_GENERAL_MANAGER, + CAPTAIN, + SOCIAL_MEDIA, + PREMIER_LEAGUE, + MASTER_LEAGUE, + CHAMPION_LEAGUE, + ACADEMY_LEAGUE, + FOUNDATION_LEAGUE, + ROCKET_LEAGUE, + PR_SUPPORT, + FA, + FP, + PEND, +] + +ALL_TEAMS = [ + AVIATORS, + BEARS, + BLIZZARD, + BULLS, + COMETS, + DEMOLITION, + DODGERS, + DUCKS, + ECLIPSE, + ELITE, + EXPRESS, + FLAMES, + FOXES, + HAWKS, + HIVE, + HURRICANES, + JETS, + KNIGHTS, + LIGHTNING, + OUTLAWS, + PANDAS, + PIRATES, + PUFFINS, + RHINOS, + SABRES, + SHADOW, + SHARKS, + SPARTANS, + SPECTRE, + TYRANTS, + WAIVERS, + WIZARDS, + WOLVES, +] diff --git a/MLEBot/enums.py b/MLEBot/enums.py deleted file mode 100644 index 34fdbcc..0000000 --- a/MLEBot/enums.py +++ /dev/null @@ -1,21 +0,0 @@ -#!/usr/bin/env python -""" Minor League E-Sports Enumerations -# Author: irox_rl -# Purpose: Host MLE related enumerations to be used throughout this project -# Version 1.0.2 -""" - -# local imports # - -# non-local imports # -from enum import Enum - - -class LeagueEnum(Enum): - """ MLE League Enumeration Class - """ - Premier_League = 1 - Master_League = 2 - Champion_League = 3 - Academy_League = 4 - Foundation_League = 5 diff --git a/MLEBot/frames.py b/MLEBot/frames.py new file mode 100644 index 0000000..5a2321c --- /dev/null +++ b/MLEBot/frames.py @@ -0,0 +1,292 @@ +"""frames module for MLEBot + """ +from __future__ import annotations + + +import os + + +import discord + + +from pydiscobot import ( + frame, + EmbedField, +) + + +from . import const +from .types.sprocket import Member, Franchise, PlayerRL, MLEFranchiseTeam + + +def mle_card(title: str, + descr: str | None = None, + franchise: dict | None = None, + fields: list[EmbedField] | None = None) -> discord.Embed: + """get generic Minor League E-Sports Embed 'card' for consistent formatting. + + Args: + title (str): title of the embed + descr (str | None, optional): description to add. Defaults to None. + franchise (dict | None, optional): franchise for colouring & thumbnails. Defaults to None. + + Returns: + discord.Embed: generic MLE formatted embed + """ + if not fields: + fields = [] + + color_str = os.getenv('MLE_COLOR') if not franchise else\ + franchise['Primary Color'] + + url_str = os.getenv('MLE_LOGO_URL') if not franchise else\ + franchise['Photo URL'] + + embed = frame.get_frame(title, + descr, + fields, + color_str, + url_str) + + return embed + + +def salary_card(member: Member): + """Minor League E-Sports Rocket League Player + Salary Card + + Args: + member (dict): sprocket member + player (dict): sprocket player + franchise (dict): sprocket franchise + tracker (dict): sprocket tracker + + Returns: + discord.Embed: salary card for specified player + """ + player = member.rl_player + tracker = member.rl_player.tracker + + title = f"**{member.name} Sprocket Info**" + + descr = const.SPR_INFO + + fields = [ + EmbedField('MLE Name', f"`{member.name}`", True), + EmbedField('MLE ID', f"`{member.mle_id}`", True), + EmbedField('Salary', f"`{player.salary}`", True), + EmbedField('League', f"`{player.skill_group}`", True), + EmbedField('Scrim Pts', f"`{player.scrim_points}`", True), + EmbedField('Eligible?', "`Yes`" if player.eligible else "`No`", True), + EmbedField('Franchise', f"`{player.franchise}`", True), + EmbedField('Staff?', f"`{player.staff_position}`", True), + EmbedField('Role', f"`{player.slot}`", True), + ] + + if tracker: + fields.append(EmbedField('**Tracker Link**', + member.rl_player.tracker['tracker'])) + + embed = mle_card(title, descr, member.franchise, fields) + + return embed + + +def teameligibility_card(franchise: Franchise, + league: str): + """Minor League E-Sports Rocket League + Team Eligibility Card + + Args: + franchise (dict): sprocket franchise + players (dict): sprocket players dictionary + + Returns: + discord.Embed: team usage card + """ + title = f"**{franchise.name} Slot Usage Info**" + descr = const.SPR_INFO + + fields: list[EmbedField] = [] + + players: MLEFranchiseTeam = franchise.players_rl.by_skill_group( + league).slot_sorted_players() + + if not players: + fields.append(EmbedField(name='**Error**', + value='An error has occured...')) + return mle_card(title, descr, franchise.franchise_meta, fields) + + ljust_limit = 8 + + for _p in players: + fields.append(EmbedField(name=f"**{_p.name}**", + value=f"`{'Role:'.ljust(ljust_limit)}` {_p.slot}\n" + f"`{'Salary:'.ljust(ljust_limit)}` {_p.salary}\n" + f"`{'Points:'.ljust(ljust_limit)}` {str(_p.scrim_points)}\n" + f"`{'Until:'.ljust(ljust_limit)}` {_p.eligibility_date}")) + + return mle_card(title, descr, franchise.franchise_meta, fields) + + +def teaminfo_card(franchise: Franchise): + """Minor League E-Sports Rocket League + Team Info Card + + Args: + franchise (dict): sprocket franchise + + Returns: + discord.Embed: team info card + """ + title = f"{franchise.name} Roster" + descr = const.SPR_INFO + + fields = [ + EmbedField('**Franchise Manager**', + f"`{franchise.fm['name']}`" if franchise.fm else "N/A"), + EmbedField('**General Managers**', + "\n".join([f"`{gm['name']}`" for gm in franchise.gms])), + ] + + if len(franchise.agms) > 0: + fields.append(EmbedField('**Assistant General Managers**', + "\n".join([f"`{agm['name']}`" for agm in franchise.agms]))) + + if len(franchise.captains) > 0: + fields.append(EmbedField('**Captains**', + "\n".join([f"`{x['name']}`" for x in franchise.captains]))) + + if len(franchise.pr) > 0: + fields.append(EmbedField('**PR Supports**', + "\n".join([f"`{x['name']}`" for x in franchise.pr]))) + + def _team_info(players: list[PlayerRL], + league_name: str, + salary_cap: float) -> list[EmbedField]: + if not players: + return None + + @staticmethod + def _player_info(p) -> str | None: + """get string of info for player""" + slot = p['slot'].removeprefix('PLAYER') + return f"`{slot} | {p['salary']} | {p['name']}`" + + def _team_salary(players: list[PlayerRL], + league_name: str, + salary_cap: float) -> str: + top_sals = sorted(players, + key=lambda p: p.player['salary'], + reverse=True) + sal_ceiling = 0.0 + range_length = 5 if len(top_sals) >= 5 else len(top_sals) + for i in range(range_length): + sal_ceiling += top_sals[i].player['salary'] + _signable_str = f"+{top_sals[-1].player['salary'] + (salary_cap - sal_ceiling)}" if sal_ceiling <= salary_cap else "NONE" + return f'**`[{sal_ceiling} / {salary_cap}] [{_signable_str}]` {league_name}**' + + fields: list[EmbedField] = [] + players_strings = '\n'.join( + [_player_info(player.player) for player in players if player.player is not None]) + + fields.append(EmbedField(name=_team_salary(players, + league_name, + salary_cap), + value=players_strings)) + return fields + + fields.append(EmbedField('**Roster**', + '**`[Top5/SalCap] [CanSign] League`**')) + + pl = _team_info(franchise.players_rl.pl.slot_sorted_players(), + const.SPR_SG_PL, + const.SALARY_CAP_PL) + ml = _team_info(franchise.players_rl.ml.slot_sorted_players(), + const.SPR_SG_ML, + const.SALARY_CAP_ML) + cl = _team_info(franchise.players_rl.cl.slot_sorted_players(), + const.SPR_SG_CL, + const.SALARY_CAP_CL) + al = _team_info(franchise.players_rl.al.slot_sorted_players(), + const.SPR_SG_AL, + const.SALARY_CAP_AL) + fl = _team_info(franchise.players_rl.fl.slot_sorted_players(), + const.SPR_SG_FL, + const.SALARY_CAP_FL) + if pl: + fields.extend(pl) + if ml: + fields.extend(ml) + if cl: + fields.extend(cl) + if al: + fields.extend(al) + if fl: + fields.extend(fl) + + return mle_card(title, + descr, + franchise.franchise_meta, + fields) + + +def usage_card(franchise: Franchise): + """Minor League E-Sports Rocket League + Usage Card + + Args: + franchise (dict): sprocket franchise + players (dict): sprocket players dictionary + + Returns: + discord.Embed: team usage card + """ + title = f"**{franchise.name} Slot Usage Info**" + descr = const.SPR_INFO + + fields = [ + EmbedField('**Season Slot Allowances**', + f'`Dbl: {const.SLOT_USAGE_DBL} | Std: {const.SLOT_USAGE_STD} | Ttl: {const.SLOT_USAGE_TTL}`') + ] + + if franchise.players_rl.pl: + fields.append(EmbedField( + name='Premier', + value='\n'.join([_player_usage_string(x) + for x in franchise.players_rl.pl]))) + + if franchise.players_rl.ml: + fields.append(EmbedField( + name='Master', + value='\n'.join([_player_usage_string(x) + for x in franchise.players_rl.ml]))) + + if franchise.players_rl.cl: + fields.append(EmbedField( + name='Champion', + value='\n'.join([_player_usage_string(x) + for x in franchise.players_rl.cl]))) + + if franchise.players_rl.al: + fields.append(EmbedField( + name='Academy', + value='\n'.join([_player_usage_string(x) + for x in franchise.players_rl.al]))) + + if franchise.players_rl.fl: + fields.append(EmbedField( + name='Foundation', + value='\n'.join([_player_usage_string(x) + for x in franchise.players_rl.fl]))) + + return mle_card(title, descr, franchise.franchise_meta, fields) + + +def _player_usage_string(player: PlayerRL): + slot = player.slot.removeprefix('PLAYER') + dbl_use = player.usage_doubles + std_use = player.usage_standard + ttl_use = player.usage_total + name = player.name + return f"`{slot} 2s: {dbl_use} | 3s: {std_use} | All: {ttl_use} | {name}`" diff --git a/MLEBot/franchise.py b/MLEBot/franchise.py deleted file mode 100644 index a635b28..0000000 --- a/MLEBot/franchise.py +++ /dev/null @@ -1,171 +0,0 @@ -#!/usr/bin/env python -""" Minor League E-Sports Franchise -# Author: irox_rl -# Purpose: General Functions of a League Franchise -# Version 1.0.6 -# -# v1.0.6 - include salary caps until sprocket does. -""" - -# local imports # -from enums import * -from team import Team - -# non-local imports # -import discord -from discord.ext import commands - -# constants -SALARY_CAP_PL = 95.0 -SALARY_CAP_ML = 82.0 -SALARY_CAP_CL = 69.5 -SALARY_CAP_AL = 57.5 -SALARY_CAP_FL = 39.5 - - -class Franchise: - """ Minor League E-Sports Discord Franchise - This class houses all leagues associated with a franchise - """ - - def __init__(self, - master_bot, - guild: discord.Guild, - franchise_name: str) -> None: - """ Initialize method\n - **param guild**: reference to guild this franchise belongs to\n - **param team_name**: string representation of this team's name (e.g. **'Sabres'**)\n - **param team_name**: asynchronous callback method for status updates\n - All data is initialized to zero. Franchise load will be called 'on_ready' of the bot - """ - self.bot = master_bot - self.guild = guild - self.franchise_name = franchise_name - self.premier_league = Team(self.guild, - self, - LeagueEnum.Premier_League) - self.master_league = Team(self.guild, - self, - LeagueEnum.Master_League) - self.champion_league = Team(self.guild, - self, - LeagueEnum.Champion_League) - self.academy_league = Team(self.guild, - self, - LeagueEnum.Academy_League) - self.foundation_league = Team(self.guild, - self, - LeagueEnum.Foundation_League) - self._sprocket_team: {} = None - self._sprocket_members: [{}] = [] - self._sprocket_players: [{}] = [] - - @property - def all_members(self) -> [[], - [], - [], - [], - []]: - """ return a list containing all lists of members from each team in the franchise - """ - lst = [] - for _team in self.teams: - lst.extend(_team.players) - return lst - - @property - def sprocket_team(self): - return self._sprocket_team - - @property - def sprocket_members(self): - return self._sprocket_members - - @property - def sprocket_players(self): - return self._sprocket_players - - @property - def teams(self) -> [Team]: - lst = [] - if self.premier_league: - lst.append(self.premier_league) - if self.master_league: - lst.append(self.master_league) - if self.champion_league: - lst.append(self.champion_league) - if self.academy_league: - lst.append(self.academy_league) - if self.foundation_league: - lst.append(self.foundation_league) - return lst - - def build_sprocket_data(self): - self._sprocket_team = next((x for x in self.bot.sprocket.data['sprocket_teams'] if self.franchise_name == x['name']), None) - self._sprocket_players = [x for x in self.bot.sprocket.data['sprocket_players'] if x['franchise'] == self.franchise_name] - self._sprocket_members = [] - for _player in self.sprocket_players: - _mem = next( - (x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _player['member_id']), None) - if _mem: - self._sprocket_members.append(_mem) - - async def get_team_eligibility(self, - team: LeagueEnum): - if team == LeagueEnum.Premier_League and self.premier_league: - _players = await self.premier_league.get_updated_players() - elif team == LeagueEnum.Master_League and self.master_league: - _players = await self.master_league.get_updated_players() - elif team == LeagueEnum.Champion_League and self.champion_league: - _players = await self.champion_league.get_updated_players() - elif team == LeagueEnum.Academy_League and self.academy_league: - _players = await self.academy_league.get_updated_players() - elif team == LeagueEnum.Foundation_League and self.foundation_league: - _players = await self.foundation_league.get_updated_players() - else: - return None - return sorted(_players, key=lambda x: x.role) - - async def init(self): - """ initialization method\n - **`optional`param sprocket_delegate**: sprocket method delegate that we can append internally\n - **`optional`param premier_channel**: channel to post quick info\n - **`optional`param master_channel**: channel to post quick info\n - **`optional`param champion_channel**: channel to post quick info\n - **`optional`param academy_channel**: channel to post quick info\n - **`optional`param foundation_channel**: channel to post quick info\n - **returns**: status string of the init method\n - """ - """ check if our method is in delegate, then add - """ - """ assign datas locally - """ - await self.rebuild() - - async def post_season_stats_html(self, - league: str, - ctx: discord.ext.commands.Context | discord.TextChannel | None = None): - _league = next((x for x in self.teams if league in x.league_name.lower()), None) - if not _league: - await self.bot.send_notification(ctx, - f'{league} was not a valid league name!', - True) - await _league.post_season_stats_html('Standard', - ctx) - await _league.post_season_stats_html('Doubles', - ctx) - - async def rebuild(self) -> None: - """ rebuild franchise - """ - self.build_sprocket_data() - self.premier_league = Team(self.guild, self, LeagueEnum.Premier_League) - self.master_league = Team(self.guild, self, LeagueEnum.Master_League) - self.champion_league = Team(self.guild, self, LeagueEnum.Champion_League) - self.academy_league = Team(self.guild, self, LeagueEnum.Academy_League) - self.foundation_league = Team(self.guild, self, LeagueEnum.Foundation_League) - self.premier_league.build() - self.master_league.build() - self.champion_league.build() - self.academy_league.build() - self.foundation_league.build() diff --git a/MLEBot/lo_commands.py b/MLEBot/lo_commands.py deleted file mode 100644 index b55aae7..0000000 --- a/MLEBot/lo_commands.py +++ /dev/null @@ -1,124 +0,0 @@ -#!/usr/bin/env python -""" Minor League E-Sports Bot League Operation Specific Commands -# Author: irox_rl -# Purpose: General Functions and Commands -# Version 1.0.6 -# -# v1.0.6 - Include slash commands -""" - -# local imports # - -# non-local imports # -import discord -from discord import app_commands -from discord.ext import commands - - -class LoCommands(commands.Cog): - def __init__(self, - master_bot): - self.bot = master_bot - - @app_commands.command(name='achilles', - description='mr. worldwide') - @app_commands.default_permissions() - async def achilles(self, - interaction: discord.Interaction): - await interaction.response.send_message("""https://media.discordapp.net/attachments/1101172529676161155/1257818646005415966/A_Chillis_tendon.png?ex=6685ca66&is=668478e6&hm=59e3c53b51b45477562b895acd641397a1144ebc5e690e6b4b875b9ca83c0ca0&=&format=webp&quality=lossless""") - - @app_commands.command(name='adi', - description='Brick by boring brick.') - @app_commands.default_permissions() - async def adi(self, - interaction: discord.Interaction): - _bricks = [':brick:'] * 20 - await interaction.response.send_message(' '.join(_bricks)) - - @app_commands.command(name='bw', - description='Galaxy brain.') - @app_commands.default_permissions() - async def bw(self, - interaction: discord.Interaction): - await interaction.response.send_message('KISSEWDAKHTKISS') - - @app_commands.command(name='haim', - description='S(HAIM).') - @app_commands.default_permissions() - async def haim(self, - interaction: discord.Interaction): - await interaction.response.send_message( - 'https://media.discordapp.net/attachments/670665177863028750/940985245786837022/ezgif.com-gif-maker_1.gif?ex=667cd58d&is=667b840d&hm=0f7548f793f01ec70d4a45093083e3c13310754eb3b7d07a4b78cfd1085c9405&=') - - @app_commands.command(name='hoos', - description=':kissing_heart:') - @app_commands.default_permissions() - async def hoos(self, - interaction: discord.Interaction): - await interaction.response.send_message( - 'Friends share more DNA than strangers.') - - @app_commands.command(name='kd', - description='KD') - @app_commands.default_permissions() - async def kd(self, - interaction: discord.Interaction): - await interaction.response.send_message( - '` `') - - @app_commands.command(name='kunics', - description='PIVOT') - @app_commands.default_permissions() - async def kunics(self, - interaction: discord.Interaction): - await interaction.response.send_message("""***P I V O T -I V O T -V O T -O T -T***""") - - @app_commands.command(name='maple', - description='Oh, Canada...') - @app_commands.default_permissions() - async def maple(self, - interaction: discord.Interaction): - await interaction.response.send_message(':flag_ca: Sorry. :flag_ca:') - - @app_commands.command(name='ondo', - description='ondofir') - @app_commands.default_permissions() - async def ondo(self, - interaction: discord.Interaction): - await interaction.response.send_message( - 'https://media.discordapp.net/attachments/532946038080798730/709472405914910840/unknown.png?ex=667d06ea&is=667bb56a&hm=968a10b7a304b47c14a13b4333766419a3ae24f4a8c809910d241ee57f1689f0&=&format=webp&quality=lossless') - - @app_commands.command(name='rexton', - description='opens door...') - @app_commands.default_permissions() - async def rexton(self, - interaction: discord.Interaction): - await interaction.response.send_message('https://media.discordapp.net/attachments/832819833611223081/996629449242058852/image_15_1.png?ex=667ce601&is=667b9481&hm=d3c4f0f7ae0074167a828db9b5b6deb8681a2c7bf1b93e428187327a1e3cea9e&=&format=webp&quality=lossless&width=1179&height=619') - - @app_commands.command(name='riz', - description='@Riz') - @app_commands.default_permissions() - async def riz(self, - interaction: discord.Interaction): - await interaction.response.send_message('neck') - - @app_commands.command(name='soviet', - description='Oh, the burning...') - @app_commands.default_permissions() - async def soviet(self, - interaction: discord.Interaction): - await interaction.response.send_message("""***The Crucifixion of Morality*** - -*The Crucifixion of Morality is an image of 69 horseshoe crabmen and an alpaca. The alpaca is striking a menacing pose. The 69 horseshoe crabmen are burning. * -https://cdn.mlesports.dev/public/The_Crucifixion_of_Morality.png""") - - @app_commands.command(name='zb', - description='My link is borked, halp') - @app_commands.default_permissions() - async def zb(self, - interaction: discord.Interaction): - await interaction.response.send_message("https://tinyurl.com/rmblmv8") diff --git a/MLEBot/member.py b/MLEBot/member.py deleted file mode 100644 index de8d917..0000000 --- a/MLEBot/member.py +++ /dev/null @@ -1,178 +0,0 @@ -#!/usr/bin/env python -""" Minor League E-Sports Member -# Author: irox_rl -# Purpose: General Functions of a League Member -# Version 1.0.4 -""" - -# local imports # -# from MLEBot.enums import LeagueEnum -from enums import LeagueEnum -import roles - -# non-local imports # -import discord -from discord.ext import commands - - -class Member: - """ Minor League E-Sports Member - """ - - def __init__(self, - discord_member: discord.Member | None, - league: LeagueEnum | None = None): - """ Initiate member with reference to parent bot and discord.Member - Other attributes are initialized to None or equivalent - Members support saving and loading """ - self.discord_member: discord.Member = discord_member - if not league: - self.league: LeagueEnum | None = self.__get_league_role__( - self.discord_member) if self.discord_member else None - else: - self.league: LeagueEnum = league - self.mle_name: str | None = None - self.mle_id = None - self.mle_player_id = None - self.member_id = None - self.sprocket_id = None - self.schedule_confirmed = False - self.salary = None - self.scrim_points = None - self.eligible = False - self.role = None - self.dpi = 0.0 - self.gpi = 0.0 - self.opi = 0.0 - self.goals = 0.0 - self.saves = 0.0 - self.score = 0.0 - self.shots = 0.0 - self.assists = 0.0 - self.goals_against = 0.0 - self.shots_against = 0.0 - - def __eq__(self, - other): - if self.discord_member and other.discord_member: - if self.discord_member == other.discord_member: - return True - if self.mle_id and other.mle_id: - if self.mle_id == other.mle_id: - return True - if self.sprocket_id and other.sprocket_id: - if self.sprocket_id == other.sprocket_id: - return True - return False - - @staticmethod - def __get_league_role__(member: discord.Member) -> LeagueEnum | None: - """ Returns league enumeration if user has associated role - else returns None """ - for role in member.roles: - if role.name == roles.PREMIER_LEAGUE: - return LeagueEnum.Premier_League - if role.name == roles.MASTER_LEAGUE: - return LeagueEnum.Master_League - if role.name == roles.CHAMPION_LEAGUE: - return LeagueEnum.Champion_League - if role.name == roles.ACADEMY_LEAGUE: - return LeagueEnum.Academy_League - if role.name == roles.FOUNDATION_LEAGUE: - return LeagueEnum.Foundation_League - - def __update_from_sprocket_players__(self, - sprocket_players: {}) -> None: - """ Update sprocket_id from sprocket_players.json data from sprocket database """ - if not sprocket_players: - return - player = next((x for x in sprocket_players if x['member_id'] == self.member_id), None) - if not player: - return - self.sprocket_id = player['member_id'] - self.salary = player['salary'] - self.role = player['slot'] - self.scrim_points = player['current_scrim_points'] - self.eligible = True if self.scrim_points >= 30 else False - - def __update_from_sprocket_player_stats__(self, - sprocket_player_stats): - if not sprocket_player_stats: - return - player_stats = next((x for x in sprocket_player_stats if x['member_id'] == self.member_id), None) - if not player_stats: - return - self.dpi = player_stats['dpi'] - self.gpi = player_stats['gpi'] - self.opi = player_stats['opi'] - self.goals = player_stats['goals'] - self.saves = player_stats['saves'] - self.score = player_stats['score'] - self.shots = player_stats['shots'] - self.assists = player_stats['assists'] - self.goals_against = player_stats['goals_against'] - self.shots_against = player_stats['shots_against'] - - def __update_from_members__(self, - sprocket_members: {}) -> None: - if not sprocket_members: - return - member = next((x for x in sprocket_members if x['discord_id'] == self.discord_member.id.__str__()), None) - if not member: - return - self.mle_name = member['name'] - self.member_id = member['member_id'] - self.mle_id = member['mle_id'] - self.mle_player_id = member['mle_player_id'] - - async def post_quick_info(self, ctx: discord.ext.commands.Context): - embed = discord.Embed(color=discord.Color.dark_red(), title=f'**{self.mle_name} Quick Info**', - description='Quick info gathered by sprocket public datasets.\n') - embed.add_field(name='`Name`', value=self.discord_member.name, inline=True) - embed.add_field(name='`Display Name`', value=self.discord_member.mention, inline=True) - embed.add_field(name='`MLE Name`', value=self.mle_name, inline=True) - embed.add_field(name='`MLE ID`', value=self.mle_id, inline=True) - embed.add_field(name='`Sprocket ID`', value=self.sprocket_id, inline=True) - embed.add_field(name='`Salary`', value=self.salary, inline=True) - embed.add_field(name='Scrim Points', value=self.scrim_points, inline=True) - embed.add_field(name='Eligible?', value=self.eligible, inline=True) - embed.add_field(name='Role', value=self.role, inline=True) - embed.add_field(name='dpi', value=self.dpi, - inline=True) # This is disabled until sprocket gives out better data(?) - embed.add_field(name='opi', value=self.opi, inline=True) - embed.add_field(name='goals', value=self.goals, inline=True) - embed.add_field(name='saves', value=self.saves, inline=True) - embed.add_field(name='score', value=self.score, inline=True) - embed.add_field(name='shots', value=self.shots, inline=True) - embed.add_field(name='assists', value=self.assists, inline=True) - embed.add_field(name='goals_against', value=self.goals_against, inline=True) - embed.add_field(name='shots_against', value=self.shots_against, inline=True) - await ctx.send(embed=embed) - - def update(self, sprocket_data: {}): - if not sprocket_data: - return - self.__update_from_members__(sprocket_data['sprocket_members']) - self.__update_from_sprocket_players__(sprocket_data['sprocket_players']) - self.__update_from_sprocket_player_stats__(sprocket_data['sprocket_player_stats']) - return self - - -def get_member_by_id(guild: discord.Guild, member_id: int) -> discord.Member | None: - return next((x for x in guild.members if x.id == member_id), None) - - -def get_member_by_name(guild: discord.Guild, member_name: str): - return next((x for x in guild.members if x.name.lower() == member_name.lower()), None) - - -def get_members_by_role_name(guild: discord.Guild, role: str): - return [x for x in guild.members for y in x.roles if y.name == role] - - -def get_members_by_role(guild: discord.Guild, role: discord.Role): - return [member for member in guild.members if role in member.roles] - - -def has_role(member: discord.Member, roles: [discord.Role]) -> bool: - return next((True for role in roles if role in member.roles), False) diff --git a/MLEBot/mle_bot.py b/MLEBot/mle_bot.py deleted file mode 100644 index 66afd84..0000000 --- a/MLEBot/mle_bot.py +++ /dev/null @@ -1,123 +0,0 @@ -""" Minor League E-Sports Bot -# Author: irox_rl -# Purpose: General Functions of a League Franchise summarized in bot fashion! -# Version 1.1.0 -# -# v1.0.6 - server integration -""" - -from PyDiscoBot import Bot -from PyDiscoBot.commands import Commands - -# local imports # -from lo_commands import LoCommands -from mle_commands import MLECommands -import roles -from task_roster import Task_Roster -from task_sprocket import Task_Sprocket - -# non-local imports # -import discord -from discord.ext import commands as disco_commands -import dotenv -import os - - -class MLEBot(Bot): - mle_logo_url = "https://images-ext-1.discordapp.net/external/8g0PflayqZyQZe8dqTD_wYGPcCpucRWAsnIjV4amZhc/https/i.imgur.com/6anH1sI.png?format=webp&quality=lossless&width=619&height=619" - - def __init__(self, - command_prefix: str | None, - bot_intents: discord.Intents | None, - command_cogs: [disco_commands.Cog]): - super().__init__(command_prefix=command_prefix, - bot_intents=bot_intents, - command_cogs=command_cogs) - self._sprocket = Task_Sprocket(self) - self._roster = Task_Roster(self) - self._guild_ids: [{}] = [] - - @property - def guild_ids(self): - return self._guild_ids - - @property - def roster(self) -> Task_Roster: - return self._roster - - @property - def sprocket(self) -> Task_Sprocket | None: - return self._sprocket - - async def build_guilds(self): - self._guild_ids.clear() - dotenv.load_dotenv() - for team in roles.ALL_TEAMS: - try: - _id = os.getenv(team).upper() - self._guild_ids.append({'team': team, - 'id': _id}) - except AttributeError: - continue - - async def get_help_cmds_by_user(self, - ctx: discord.ext.commands.Context) -> [disco_commands.command]: - user_cmds = [cmd for cmd in self.commands] - for cmd in self.commands: - for check in cmd.checks: - try: - can_run = await check(ctx) - if not can_run: - user_cmds.remove(cmd) - break - except disco_commands.CheckFailure: - user_cmds.remove(cmd) - break - except AttributeError: # in this instance, a predicate could not be checked. It does not exist (part of the base bot) - break - return user_cmds - - async def rebuild(self): - await self.build_guilds() - return True - - async def on_ready(self, - suppress_task=False) -> None: - """ Method that is called by discord when the bot connects to the supplied guild\n - **param suppress_task**: if True, do NOT run periodic task at the end of this call\n - **returns**: None\n - """ - """ do not initialize more than once - """ - if self._initialized: - await self.change_presence( - activity=discord.Activity(type=discord.ActivityType.listening, name='sweet EMILIO. 🪭')) - return - await super().on_ready(suppress_task) - - await self.sprocket.run() - await self.build_guilds() - - await self.change_presence( - activity=discord.Activity(type=discord.ActivityType.listening, name='sweet EMILIO. 🪭')) - - async def on_task(self) -> None: - await super().on_task() - await self._sprocket.run() - - -if __name__ == '__main__': - dotenv.load_dotenv() - intents = discord.Intents(8) - # noinspection PyDunderSlots - intents.guilds = True - # noinspection PyDunderSlots - intents.members = True - # noinspection PyDunderSlots - intents.message_content = True - # noinspection PyDunderSlots - intents.messages = True - bot = MLEBot(['ub.', 'Ub.', 'UB.'], - intents, - [Commands, MLECommands, LoCommands]) - bot.run(os.getenv('DISCORD_TOKEN')) diff --git a/MLEBot/mle_commands.py b/MLEBot/mle_commands.py deleted file mode 100644 index a7e98a7..0000000 --- a/MLEBot/mle_commands.py +++ /dev/null @@ -1,606 +0,0 @@ -#!/usr/bin/env python -""" Minor League E-Sports Bot Commands -# Author: irox_rl -# Purpose: General Functions and Commands -# Version 1.0.6 -# -# v1.0.6 - Include slash commands -""" -from PyDiscoBot import channels -from PyDiscoBot import Pagination, InteractionPagination -from PyDiscoBot import ReportableError - -# local imports # -from enums import * -import team -from team import get_league_text -from franchise import SALARY_CAP_PL, SALARY_CAP_ML, SALARY_CAP_CL, SALARY_CAP_AL, SALARY_CAP_FL - -# non-local imports # -import difflib -import discord -from discord import app_commands -from discord.ext import commands -import os -import dotenv - - -class MLECommands(commands.Cog): - def __init__(self, - master_bot): - self.bot = master_bot - - async def __local_lookup__(self, - interaction: discord.Interaction, - mle_name: str = None): - data = self.bot.sprocket.data # easier to write, shorter code - - # Find player in Sprocket Members dataset - if mle_name: - _member = next((x for x in data['sprocket_members'] if x['name'] == mle_name), None) - if not _member: - matches = difflib.get_close_matches(mle_name, [x['name'] for x in data['sprocket_members']], - 1) - if matches: - await self.bot.send_notification(interaction, - f"Could not find `{mle_name}` in sprocket `Members` dataset. Did you mean `{matches[0]}`?") - return - else: - _member = next( - (x for x in data['sprocket_members'] if x['discord_id'].__str__() == interaction.user.id.__str__()), - None) - if not _member: - await self.bot.send_notification(interaction, - 'mle member not found in sprocket `Members` dataset') - return - - # Find player in Sprocket Players dataset - _player = next((x for x in data['sprocket_players'] if x['member_id'] == _member['member_id']), None) - if not _player: - await self.bot.send_notification(interaction, - 'mle member not found in sprocket `Players` dataset') - return - - # more data - _team = next((x for x in data['sprocket_teams'] if x['name'] == _player['franchise']), None) - tracker_player = next((x for x in data['sprocket_trackers'] if x['mleid'] == _member['mle_id']), None) - - # embed - embed = (discord.Embed( - color=discord.Color.from_str(_team['primary_color']) if _team else self.bot.default_embed_color, - title=f"**{_member['name']} Sprocket Info**", - description='Data gathered by sprocket public data links.\n' - 'See more at [sprocket links](https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html)\n') - .set_footer(text=f'Generated: {self.bot.last_time}')) - embed.set_thumbnail(url=self.bot.mle_logo_url if not _team else _team['logo_img_link']) - embed.add_field(name='MLE Name', value=f"`{_member['name']}`", inline=True) - embed.add_field(name='MLE ID', value=f"`{_member['mle_id']}`", inline=True) - embed.add_field(name='Salary', value=f"`{_player['salary']}`", inline=True) - embed.add_field(name='League', value=f"`{_player['skill_group']}`", inline=True) - embed.add_field(name='Scrim Points', value=f"`{_player['current_scrim_points']}`", inline=True) - embed.add_field(name='Eligible?', value="`Yes`" if _player['current_scrim_points'] >= 30 else "`No`", - inline=True) - embed.add_field(name='Franchise', value=f"`{_player['franchise']}`", inline=True) - embed.add_field(name='Staff Position', value=f"`{_player['Franchise Staff Position']}`", inline=True) - embed.add_field(name='Role', value=f"`{_player['slot']}`", inline=True) - if tracker_player: - embed.add_field(name='**Tracker Link**', value=tracker_player['tracker'], inline=False) - await interaction.response.send_message(embed=embed) - - async def __resolve_franchise__(self, - interaction: discord.Interaction): - _known_guild = next((x for x in self.bot.guild_ids if str(x['id']) == interaction.guild.id.__str__()), None) - if not _known_guild: - return None - return next( - (x for x in self.bot.sprocket.data['sprocket_teams'] if x['name'].upper() == _known_guild['team'].upper()), - None) - - @staticmethod - def get_sprocket_player_team_info(sprocket_player) -> str | None: - return f"`{sprocket_player['slot'].removeprefix('PLAYER')} | {sprocket_player['salary']} | {sprocket_player['name']}`" - - def get_sprocket_usage_team_info(self, - sprocket_player) -> str | None: - role_usage = next((x for x in self.bot.sprocket.data['role_usages'] if - (x['role'] == sprocket_player['slot']) and x['team_name'] == sprocket_player['franchise'] and - x['league'].lower() in sprocket_player['skill_group'].lower()), - None) - if not role_usage: - return None - return f"`{sprocket_player['slot'].removeprefix('PLAYER')} | 2s: {role_usage['doubles_uses']} | 3s: {role_usage['standard_uses']} | Total: {role_usage['total_uses']} | {sprocket_player['name']}`" - - @app_commands.command(name='clearchannel', - description='Clear channel messages. Include amt of messages to delete.\n Max is 100. (e.g. ub.clearchannel 55)') - @app_commands.default_permissions() - async def clearchannel(self, - interaction: discord.Interaction, - message_count: int): - await interaction.response.defer() - await channels.clear_channel_messages(interaction.channel, message_count) - - @app_commands.command(name='lookup', - description='Lookup player by MLE name provided.\nTo lookup yourself, just type {ub.lookup}') - @app_commands.describe(mle_name='MLE Name of player to look up.') - @app_commands.default_permissions() - async def lookup(self, - interaction: discord.Interaction, - mle_name: str): - await self.__local_lookup__(interaction, - mle_name) - - @app_commands.command(name='rebuild', - description='Rebuild bot meta data.') - @app_commands.guilds(1043295434828947547) - @app_commands.default_permissions() - async def rebuild(self, - interaction: discord.Interaction): - await interaction.response.defer() - if await self.bot.rebuild(): - await self.bot.send_notification(interaction, - 'Success!.', - as_followup=True) - else: - await self.bot.send_notification(interaction, - 'An error has occured.', - as_followup=True) - - @app_commands.command(name='query', - description='Lookup groups of players by provided filter.') - @app_commands.describe(league_filter='[PL, ML, CL, AL, FL]') - @app_commands.describe(query_filter='[FA, RFA, Waivers, Pend]') - @app_commands.describe(sorting='[salary, current_scrim_points, name]') - @app_commands.choices(league_filter=[ - app_commands.Choice(name='PL', value='pl'), - app_commands.Choice(name='ML', value='ml'), - app_commands.Choice(name='CL', value='cl'), - app_commands.Choice(name='AL', value='al'), - app_commands.Choice(name='FL', value='fl') - ], - query_filter=[ - app_commands.Choice(name='FA', value='fa'), - app_commands.Choice(name='RFA', value='rfa'), - app_commands.Choice(name='Waivers', value='waivers'), - app_commands.Choice(name='Pend', value='pend') - ], - sorting=[ - app_commands.Choice(name='Salary', value='salary'), - app_commands.Choice(name='Scrim Points', value='current_scrim_points'), - app_commands.Choice(name='Name', value='name') - ]) - @app_commands.default_permissions() - async def query(self, - interaction: discord.Interaction, - league_filter: app_commands.Choice[str], - query_filter: str, - sorting: str): - _league_enum = get_league_enum_by_short_text(league_filter.value) - if not _league_enum: - return await self.bot.send_notification(interaction, - 'League not found. Please enter a valid league.)', - True) - - valid_queries = ['fa', 'waivers', 'rfa', 'pend'] - valid_sorts = ['salary', 'current_scrim_points', 'name'] - if query_filter.lower() not in valid_queries: - return await self.bot.send_notification(interaction, - f'`{query_filter}` is an invalid query. Please try again.', - True) - - if sorting.lower() not in valid_sorts: - return await self.bot.send_notification(interaction, - f'`{sorting}` is an invalid sorting type. Please try again.') - - data = self.bot.sprocket.data - _players = sorted([x for x in data['sprocket_players'] if - x['franchise'].lower() == query_filter.lower() and x[ - 'skill_group'] == _league_enum.name.replace( - '_', ' ')], key=lambda x: x[sorting]) - - if len(_players) == 0: - emb: discord.Embed = self.bot.default_embed(f'**Filtered Players**\n\n', - f'There were no players to be found for this query!') - emb.set_thumbnail(url=self.bot.mle_logo_url) - await interaction.followup.send(embed=emb) - return - - async def get_page(page: int, - as_timout: bool = False): - emb: discord.Embed = self.bot.default_embed(f'**Filtered Players**\n\n', - f'Players filtered for `{query_filter}`\n' - f'Sorted by `{sorting}`') - emb.set_thumbnail(url=self.bot.mle_logo_url) - if as_timout: - emb.add_field(name=f'**`Timeout`**', - value='This command has timed out. Type `[ub.help]` for help.') - emb.set_footer(text=f'Page 1 of 1') - return emb, 0 - - elements_per_page = 15 - offset = (page - 1) * elements_per_page - emb.add_field(name=f'**Sal | Points | Name**', - value='\n'.join( - [ - f"`{_p['salary'].__str__().ljust(4)} | {_p['current_scrim_points'].__str__().ljust(4)} | {_p['name']}`" - for _p in _players[offset:offset + elements_per_page]]), - inline=False) - total_pages = Pagination.compute_total_pages(len(_players), - elements_per_page) - - emb.set_footer(text=f'Page {page} of {total_pages}') - return emb, total_pages - - await InteractionPagination(interaction, get_page).navigate() - - @app_commands.command(name='regrosterchannel', - description='Register Franchise Roster Channel for roster posting functionality.') - @app_commands.default_permissions() - async def regrosterchannel(self, - interaction: discord.Interaction): - _team = await self.__resolve_franchise__(interaction) - if not _team: - await self.bot.send_notification(interaction, - 'Could not resolve this franchise from sprocket data!', - as_followup=False) - return - try: - dotenv.set_key(dotenv.find_dotenv(), - f"{_team['name'].upper()}_ROSTER", - str(interaction.channel_id), - quote_mode='never') - # os.environ[f"{_team['name'].upper()}_ROSTER"] = str(interaction.channel_id) - await self.bot.send_notification(interaction, - 'Success!', - as_followup=False) - except KeyError: - await self.bot.send_notification(interaction, - 'An error has occurred!', - as_followup=False) - - @app_commands.command(name='runroster', - description='Run a refresh of the roster channel.') - @app_commands.default_permissions() - async def runroster(self, - interaction: discord.Interaction): - await interaction.response.defer() - _team = await self.__resolve_franchise__(interaction) - if not _team: - await self.bot.send_notification(interaction, - 'Could not resolve this franchise from sprocket data!', - as_followup=True) - return - - dotenv.load_dotenv() - _channel_id = os.getenv(f"{_team['name'].upper()}_ROSTER") - if not _channel_id: - await self.bot.send_notification(interaction, - 'Roster channel has not been configured! Run /regrosterchannel!', - as_followup=True) - return - - _channel = next((x for x in interaction.guild.channels if x.id.__str__() == str(_channel_id)), None) - if not _channel: - await self.bot.send_notification(interaction, - 'Roster channel has not been found! Run /regrosterchannel!', - as_followup=True) - return - - if await self.bot.roster.post_roster(_team, - _channel): - await self.bot.send_notification(interaction, - 'Success!', - as_followup=True) - else: - await self.bot.send_notification(interaction, - 'Failure!', - as_followup=True) - - @app_commands.command(name='salary', - description='Get salary and extra data about yourself from provided sprocket data.') - @app_commands.default_permissions() - async def salary(self, - interaction: discord.Interaction): - await self.__local_lookup__(interaction) - - @commands.command(name='seasonstats', - description='Beta - Get season stats for a specific league.\n\tInclude league name. (e.g. ub.seasonstats master).\n\tNaming convention will be updated soon - Beta') - @app_commands.default_permissions() - async def seasonstats(self, - ctx: discord.ext.commands.Context, - league: str): - return - if not league: - return await self.bot.send_notification(ctx, 'You must specify a league when running this command.\n' - 'i.e.: ub.seasonstats master', True) - - await self.bot.franchise.post_season_stats_html(league.lower(), - ctx) - - @app_commands.command(name='showusage', - description='Show game usage stats for all league members of this franchise.') - @app_commands.default_permissions() - async def showusage(self, - interaction: discord.Interaction): - await interaction.response.defer() - _team = await self.__resolve_franchise__(interaction) - if not _team: - await self.bot.send_notification(interaction, - 'Could not resolve this franchise from sprocket data!', - as_followup=True) - return - - _players = [x for x in self.bot.sprocket.data['sprocket_players'] if x['franchise'] == _team['name']] - if not _players: - await self.bot.send_notification(interaction, - 'Could not get players for this franchise from sprocket!', - as_followup=True) - return - - _pl_players = [x for x in _players if x['skill_group'] == 'Premier League' and x['slot'] != 'NONE'] - _ml_players = [x for x in _players if x['skill_group'] == 'Master League' and x['slot'] != 'NONE'] - _cl_players = [x for x in _players if x['skill_group'] == 'Champion League' and x['slot'] != 'NONE'] - _al_players = [x for x in _players if x['skill_group'] == 'Academy League' and x['slot'] != 'NONE'] - _fl_players = [x for x in _players if x['skill_group'] == 'Foundation League' and x['slot'] != 'NONE'] - - embed = (discord.Embed( - color=discord.Color.from_str(_team['primary_color']), - title=f"**{_team['name']} Slot Usage Info**", - description='Data gathered by sprocket public data links.\n' - 'See more at [sprocket links](https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html)\n') - .set_footer(text=f'Generated: {self.bot.last_time}')) - embed.set_thumbnail(url=_team['logo_img_link']) - - embed.add_field(name='**Season Slot Allowances**', - value='`Doubles: 6 | Standard: 8 | Total: 12` ', - inline=False) - - self.__show_usage_league__(sorted(_pl_players, key=lambda _p: _p['slot']), - embed, - 'Premier', - SALARY_CAP_PL) - - self.__show_usage_league__(sorted(_ml_players, key=lambda _p: _p['slot']), - embed, - 'Master', - SALARY_CAP_ML) - - self.__show_usage_league__(sorted(_cl_players, key=lambda _p: _p['slot']), - embed, - 'Champion', - SALARY_CAP_CL) - - self.__show_usage_league__(sorted(_al_players, key=lambda _p: _p['slot']), - embed, - 'Academy', - SALARY_CAP_AL) - - self.__show_usage_league__(sorted(_fl_players, key=lambda _p: _p['slot']), - embed, - 'Foundation', - SALARY_CAP_FL) - - await interaction.followup.send(embed=embed) - - @app_commands.command(name='teameligibility', - description='Show team eligibility. Include league after command.\n\t(e.g. ub.teameligibility fl)') - @app_commands.describe(league='[PL, ML, CL, AL, FL]') - @app_commands.choices(league=[ - app_commands.Choice(name='PL', value='pl'), - app_commands.Choice(name='ML', value='ml'), - app_commands.Choice(name='CL', value='cl'), - app_commands.Choice(name='AL', value='al'), - app_commands.Choice(name='FL', value='fl') - ]) - @app_commands.default_permissions() - async def teameligibility(self, - interaction: discord.Interaction, - league: str): - if not league: - await self.bot.send_notification(interaction, - 'You must specify a league when running this command!') - return - _league_enum = get_league_enum_by_short_text(league) - if not _league_enum: - await self.bot.send_notification(interaction, - 'League not found. Please enter a valid league.') - return - _league_text = get_league_text(_league_enum) - - _team = await self.__resolve_franchise__(interaction) - if not _team: - await self.bot.send_notification(interaction, - 'Could not resolve this franchise from sprocket data!', - as_followup=True) - return - - _players = [x for x in self.bot.sprocket.data['sprocket_players'] if x['franchise'] == _team['name']] - if not _players: - await self.bot.send_notification(interaction, - 'Could not get players for this franchise from sprocket!', - as_followup=True) - return - - _league_players = sorted([x for x in _players if x['skill_group'] == _league_text and x['slot'] != 'NONE'], - key=lambda x: x['slot']) - if not _league_players: - await self.bot.send_notification(interaction, - 'An error has occurred.') - return - - await interaction.response.defer() - embed = self.bot.default_embed( - f"{_league_text} {_team['name']} Eligibility Information", - color=discord.Color.from_str(_team['primary_color'])) - embed.set_thumbnail(url=_team['logo_img_link']) - - ljust_limit = 8 - - for _p in _league_players: - embed.add_field(name=f"**{_p['name']}**", - value=f"`{'Role:'.ljust(ljust_limit)}` {_p['slot']}\n" - f"`{'Salary:'.ljust(ljust_limit)}` {_p['salary']}\n" - f"`{'Points:'.ljust(ljust_limit)}` {_p['current_scrim_points'].__str__()}\n" - f"`{'Until:'.ljust(ljust_limit)}` ~TBD~", - inline=True) - - await interaction.followup.send(embed=embed) - - @app_commands.command(name='teaminfo', - description='Get information about a team from the league!\n\tInclude team after command. (e.g. ub.teaminfo sabres)') - @app_commands.describe(_team_name='MLE Team to get info about.') - @app_commands.default_permissions() - async def teaminfo(self, - interaction: discord.Interaction, - _team_name: str): - - if not _team_name: - raise ReportableError('You must provide a team when running this command!') - - _team = next((x for x in self.bot.sprocket.data['sprocket_teams'] if x['name'].lower() == _team_name.lower()), - None) - if not _team: - raise ReportableError(f'Could not find team `{_team_name}` in sprocket data base!\nPlease try again!') - - embed = team.get_mle_franchise_embed(_team) - - await interaction.response.defer() - _team_players = team.get_defined_team_players(_team, - self.bot.sprocket.data) - if not _team_players: - raise ReportableError('Could not find players for franchise!') - - embed.add_field(name='**Franchise Manager**', - value=f"`{_team_players['fm']['name']}`" if _team_players['fm'] else "", - inline=False) - - embed.add_field(name='**General Manager**', - value=f"\n".join([f"`{gm['name']}`" for gm in _team_players['gms']]), - inline=False) - - if len(_team_players['agms']) != 0: - embed.add_field(name='**Assistant General Managers**', - value=f"\n".join([f"`{agm['name']}`" for agm in _team_players['agms']]), - inline=False) - - if len(_team_players['captains']) != 0: - embed.add_field(name='**Captains**', - value=f"\n".join([f"`{x['name']}`" for x in _team_players['captains']]), - inline=False) - - if len(_team_players['pr_supports']) != 0: - embed.add_field(name='**PR Supports**', - value=f"\n".join([f"`{x['name']}`" for x in _team_players['pr_supports']]), - inline=False) - - embed.add_field(name='**Roster**', value='**`[Top5/SalCap] [CanSign] League`**', inline=False) - - self.__team_info_league__(sorted(_team_players['pl_players'], key=lambda _p: _p['slot']), - embed, - 'Premier', - SALARY_CAP_PL) - - self.__team_info_league__(sorted(_team_players['ml_players'], key=lambda _p: _p['slot']), - embed, - 'Master', - SALARY_CAP_ML) - - self.__team_info_league__(sorted(_team_players['cl_players'], key=lambda _p: _p['slot']), - embed, - 'Champion', - SALARY_CAP_CL) - - self.__team_info_league__(sorted(_team_players['al_players'], key=lambda _p: _p['slot']), - embed, - 'Academy', - SALARY_CAP_AL) - - self.__team_info_league__(sorted(_team_players['fl_players'], key=lambda _p: _p['slot']), - embed, - 'Foundation', - SALARY_CAP_FL) - - await interaction.followup.send(embed=embed) - - @app_commands.command(name='updatesprocket', - description='Update internal information by probing sprocket for new data.') - @app_commands.default_permissions() - async def updatesprocket(self, - interaction: discord.Interaction): - await interaction.response.defer() - self.bot.sprocket.reset() - await self.bot.sprocket.run() - await self.bot.send_notification(interaction, - 'League-Sprocket update complete.', - as_followup=True) - - def __team_info_league__(self, - players: [], - embed: discord.Embed, - league_name: str, - salary_cap: float): - if not players: - return - - players_strings = '\n'.join( - [self.get_sprocket_player_team_info(player) for player in players if player is not None]) - - embed.add_field( - name=get_team_salary_string(players, - league_name, - salary_cap), - value=players_strings, - inline=False) - - def __show_usage_league__(self, - players: [], - embed: discord.Embed, - league_name: str, - salary_cap: float): - if not players: - return - - players_strings = '\n'.join( - [self.get_sprocket_usage_team_info(player) for player in players if player is not None]) - - embed.add_field( - name=league_name, - value=players_strings, - inline=False) - - -def get_players_salary_ceiling(top_sals: [{}]) -> float: - sal_ceiling = 0.0 - range_length = 5 if len(top_sals) >= 5 else len(top_sals) - for i in range(range_length): - sal_ceiling += top_sals[i]['salary'] - return sal_ceiling - - -def get_team_salary_string(players: [], - league_name: str, - salary_cap: float) -> str: - top_sals = sorted(players, - key=lambda p: p['salary'], - reverse=True) - sal_ceiling = get_players_salary_ceiling(top_sals) - _sign = '+' if sal_ceiling >= salary_cap else '-' - _signable_str = f"+{top_sals[-1]['salary'] + (salary_cap - sal_ceiling)}" if sal_ceiling <= salary_cap else "NONE" - return f'**`[{sal_ceiling} / {salary_cap}] [{_signable_str}]` {league_name}**' - - -def get_league_enum_by_short_text(league: str): - if not league: - return None - if league.lower() == 'pl': - _league_enum = LeagueEnum.Premier_League - elif league.lower() == 'ml': - _league_enum = LeagueEnum.Master_League - elif league.lower() == 'cl': - _league_enum = LeagueEnum.Champion_League - elif league.lower() == 'al': - _league_enum = LeagueEnum.Academy_League - elif league.lower() == 'fl': - _league_enum = LeagueEnum.Foundation_League - else: - _league_enum = None - return _league_enum diff --git a/MLEBot/mlebot.py b/MLEBot/mlebot.py new file mode 100644 index 0000000..a659a27 --- /dev/null +++ b/MLEBot/mlebot.py @@ -0,0 +1,90 @@ +"""Minor League E-Sports PyDiscoBot implimentation + """ +from __future__ import annotations + +import os +import discord +from discord.ext import commands as disco_commands + + +from pydiscobot import Bot + + +from .commands import Commands +from . import const +from .tasks.sprocket import Sprocket + + +class MLEBot(Bot): + """minor league esports bot + + Args: + Bot (_type_): parent bot + """ + + def __init__(self, + command_prefix: str | None, + bot_intents: discord.Intents | None, + command_cogs: list[disco_commands.Cog]): + command_cogs.extend(Commands) + super().__init__(command_prefix=command_prefix, + bot_intents=bot_intents, + command_cogs=command_cogs) + self._sprocket = Sprocket(self) + self._tasker.append(self._sprocket) + self._guild_ids: list[dict] = [] + + @property + def activity(self) -> discord.Activity: + return discord.Activity(type=discord.ActivityType.listening, + name='hot farts being generated in the atmosphere by alien lizard men.') + + @property + def guild_ids(self) -> list[dict]: + """all tracked MLE guilds + + Returns: + list[dict]: all tracked MLE guilds + """ + return self._guild_ids + + @property + def sprocket(self) -> Sprocket | None: + """get sprocket task + + Returns: + Sprocket | None: Sprocket task or None + """ + return self._sprocket + + async def _build_guilds(self): + self._guild_ids.clear() + for team in const.ALL_TEAMS: + try: + _id = os.getenv(team).upper() + self._guild_ids.append({'team': team, + 'id': _id}) + except AttributeError: + continue + + async def rebuild(self): + """rebuild list of known MLE guilds + """ + await self._build_guilds() + + async def on_ready(self, + suppress_task=False) -> None: + """on bot ready + + Args: + suppress_task (bool, optional): don't run periodic task. Defaults to False. + """ + if self.status.initialized: + self.logger.warning('already initialized!') + return + await super().on_ready(suppress_task) + await self._build_guilds() + + if self.ws: # if we are connected to a web-socket + # otherwise, this will raise... + await self.change_presence(activity=self.activity) diff --git a/MLEBot/roles.py b/MLEBot/roles.py deleted file mode 100644 index ce77c42..0000000 --- a/MLEBot/roles.py +++ /dev/null @@ -1,277 +0,0 @@ -#!/usr/bin/env python -""" Discord Roles Module for use in Minor League E-Sports -# Author: irox_rl -# Purpose: General Functions of Discord Roles -# Version 1.0.2 -""" - -# local imports # -from enums import LeagueEnum - -# non-local imports # -import copy -import discord -import os - -""" Constants -""" -Aviators = "Aviators" -Bears = "Bears" -Blizzard = "Blizzard" -Bulls = "Bulls" -Comets = "Comets" -Demolition = "Demolition" -Dodgers = "Dodgers" -Ducks = "Ducks" -Eclipse = "Eclipse" -Elite = "Elite" -Express = "Express" -Flames = "Flames" -Foxes = "Foxes" -Hawks = "Hawks" -Hive = "Hive" -Hurricanes = "Hurricanes" -Jets = "Jets" -Knights = "Knights" -Lightning = "Lightning" -Outlaws = "Outlaws" -Pandas = "Pandas" -Pirates = "Pirates" -Puffins = "Puffins" -Rhinos = "Rhinos" -Sabres = "Sabres" -Shadow = "Shadow" -Sharks = "Sharks" -Spartans = "Spartans" -Spectre = "Spectre" -Tyrants = "Tyrants" -Waivers = "Waivers" -Wizards = "Wizards" -Wolves = "Wolves" -SOCIAL_MEDIA = 'Social Media' - -# dotenv.load_dotenv('.env') -FRANCHISE_MANAGER = None -GENERAL_MANAGER_RL = None -GENERAL_MANAGER_TM = None -ASSISTANT_GENERAL_MANAGER_RL = None -ASSISTANT_GENERAL_MANAGER_TM = None -CAPTAIN = None -PREMIER_LEAGUE = None -MASTER_LEAGUE = None -CHAMPION_LEAGUE = None -ACADEMY_LEAGUE = None -FOUNDATION_LEAGUE = None -ROCKET_LEAGUE = None -PR_SUPPORT = None -FA = None -FP = None -Pend = None - -ALL_MLE_ROLES = [ - Aviators, - Bears, - Blizzard, - Bulls, - Comets, - Demolition, - Dodgers, - Ducks, - Eclipse, - Elite, - Express, - Flames, - Foxes, - Hawks, - Hive, - Hurricanes, - Jets, - Knights, - Lightning, - Outlaws, - Pandas, - Pirates, - Puffins, - Rhinos, - Sabres, - Shadow, - Sharks, - Spartans, - Spectre, - Tyrants, - Waivers, - Wizards, - Wolves, - FRANCHISE_MANAGER, - GENERAL_MANAGER_RL, - GENERAL_MANAGER_TM, - ASSISTANT_GENERAL_MANAGER_RL, - ASSISTANT_GENERAL_MANAGER_TM, - CAPTAIN, - PREMIER_LEAGUE, - MASTER_LEAGUE, - CHAMPION_LEAGUE, - ACADEMY_LEAGUE, - FOUNDATION_LEAGUE, - ROCKET_LEAGUE, - PR_SUPPORT, - FA, - FP, - Pend, -] - -ALL_TEAMS = [ - Aviators, - Bears, - Blizzard, - Bulls, - Comets, - Demolition, - Dodgers, - Ducks, - Eclipse, - Elite, - Express, - Flames, - Foxes, - Hawks, - Hive, - Hurricanes, - Jets, - Knights, - Lightning, - Outlaws, - Pandas, - Pirates, - Puffins, - Rhinos, - Sabres, - Shadow, - Sharks, - Spartans, - Spectre, - Tyrants, - Waivers, - Wizards, - Wolves, -] - -FRANCHISE_ROLES = [] -GENERAL_MGMT_ROLES = [] -CAPTAIN_ROLES = [] - -""" Globals -""" -social_media: discord.Role | None = None -franchise_manager: discord.Role | None = None -general_manager_rl: discord.Role | None = None -general_manager_tm: discord.Role | None = None -assistant_general_manager_rl: discord.Role | None = None -assistant_general_manager_tm: discord.Role | None = None -captain: discord.Role | None = None -premier: discord.Role | None = None -master: discord.Role | None = None -champion: discord.Role | None = None -academy: discord.Role | None = None -foundation: discord.Role | None = None - - -def init(guild: discord.Guild): - global social_media - global franchise_manager - global general_manager_rl - global general_manager_tm - global assistant_general_manager_rl - global assistant_general_manager_tm - global captain - global premier - global master - global champion - global academy - global foundation - global FRANCHISE_ROLES, GENERAL_MGMT_ROLES, CAPTAIN_ROLES - global FRANCHISE_MANAGER, GENERAL_MANAGER_RL, GENERAL_MANAGER_TM, ASSISTANT_GENERAL_MANAGER_RL, ASSISTANT_GENERAL_MANAGER_TM - global CAPTAIN, PREMIER_LEAGUE, MASTER_LEAGUE, CHAMPION_LEAGUE, ACADEMY_LEAGUE, FOUNDATION_LEAGUE - - FRANCHISE_MANAGER = os.getenv('ROLE_FM') - GENERAL_MANAGER_RL = os.getenv('ROLE_GM_RL') - GENERAL_MANAGER_TM = os.getenv('ROLE_GM_TM') - ASSISTANT_GENERAL_MANAGER_RL = os.getenv('ROLE_AGM_RL') - ASSISTANT_GENERAL_MANAGER_TM = os.getenv('ROLE_AGM_TM') - CAPTAIN = os.getenv('ROLE_CAPTAIN_RL') - PREMIER_LEAGUE = os.getenv('ROLE_PL') - MASTER_LEAGUE = os.getenv('ROLE_ML') - CHAMPION_LEAGUE = os.getenv('ROLE_CL') - ACADEMY_LEAGUE = os.getenv('ROLE_AL') - FOUNDATION_LEAGUE = os.getenv('ROLE_FL') - - social_media = get_role_by_name(guild, SOCIAL_MEDIA) - franchise_manager = get_role_by_name(guild, FRANCHISE_MANAGER) - general_manager_rl = get_role_by_name(guild, GENERAL_MANAGER_RL) - general_manager_tm = get_role_by_name(guild, GENERAL_MANAGER_TM) - assistant_general_manager_rl = get_role_by_name(guild, ASSISTANT_GENERAL_MANAGER_RL) - assistant_general_manager_tm = get_role_by_name(guild, ASSISTANT_GENERAL_MANAGER_TM) - captain = get_role_by_name(guild, CAPTAIN) - premier = get_role_by_name(guild, PREMIER_LEAGUE) - master = get_role_by_name(guild, MASTER_LEAGUE) - champion = get_role_by_name(guild, CHAMPION_LEAGUE) - academy = get_role_by_name(guild, ACADEMY_LEAGUE) - foundation = get_role_by_name(guild, FOUNDATION_LEAGUE) - - FRANCHISE_ROLES = [franchise_manager, - general_manager_rl, - general_manager_tm, - assistant_general_manager_rl, - assistant_general_manager_tm, - captain, - premier, - master, - champion, - academy, - foundation, - social_media] - - GENERAL_MGMT_ROLES = [franchise_manager, - general_manager_rl, - general_manager_tm, - assistant_general_manager_rl, - assistant_general_manager_tm] - - CAPTAIN_ROLES = copy.copy(GENERAL_MGMT_ROLES) - CAPTAIN_ROLES.append(captain) - - -def get_role_by_name(guild: discord.Guild, name: str) -> discord.Role | None: - return next((x for x in guild.roles if x.name == name), None) - - -def get_role_by_league(self, league: LeagueEnum): - match league: - case LeagueEnum.Premier_League: - return self.premier - case LeagueEnum.Master_League: - return self.master - case LeagueEnum.Champion_League: - return self.champion - case LeagueEnum.Academy_League: - return self.academy - case LeagueEnum.Foundation_League: - return self.foundation - - -def has_role(member: discord.user, *roles) -> bool: - return next((True for role in roles if role in member.roles), False) - - -def resolve_sprocket_league_role(sprocket_league: str) -> str | None: - if sprocket_league == 'FOUNDATION': - return FOUNDATION_LEAGUE - if sprocket_league == 'ACADEMY': - return ACADEMY_LEAGUE - if sprocket_league == 'CHAMPION': - return CHAMPION_LEAGUE - if sprocket_league == 'MASTER': - return MASTER_LEAGUE - if sprocket_league == 'PREMIER': - return PREMIER_LEAGUE - return None diff --git a/MLEBot/run.bat b/MLEBot/run.bat deleted file mode 100644 index 84b9f05..0000000 --- a/MLEBot/run.bat +++ /dev/null @@ -1,9 +0,0 @@ -@echo on - -call .\.venv\Scripts\activate.bat - -python -m pip install --upgrade PyDiscoBot - -python mle_bot.py - -PAUSE \ No newline at end of file diff --git a/MLEBot/services/__init__.py b/MLEBot/services/__init__.py new file mode 100644 index 0000000..7374131 --- /dev/null +++ b/MLEBot/services/__init__.py @@ -0,0 +1,13 @@ +"""MLE Bot Logical Services + """ + +from . import sprocket +from .sprocket import lookup_franchise, lookup_rl + +__version__ = '1.1.1' + +__all__ = ( + 'sprocket', + 'lookup_franchise', + 'lookup_rl', +) diff --git a/MLEBot/services/sprocket/__init__.py b/MLEBot/services/sprocket/__init__.py new file mode 100644 index 0000000..b9e5b7a --- /dev/null +++ b/MLEBot/services/sprocket/__init__.py @@ -0,0 +1,11 @@ +"""Sprocket related services + """ + +from .lookup import lookup_rl, lookup_franchise + +__version__ = '1.1.1' + +__all__ = ( + 'lookup_rl', + 'lookup_franchise' +) diff --git a/MLEBot/services/sprocket/lookup.py b/MLEBot/services/sprocket/lookup.py new file mode 100644 index 0000000..88d2eb2 --- /dev/null +++ b/MLEBot/services/sprocket/lookup.py @@ -0,0 +1,85 @@ +"""provide methods to distribute sprocket data, such as players or franchises + """ +from __future__ import annotations + + +import difflib + +from ... import const +from ...types import Member, SprocketLinks, PlayerRL, Franchise + + +def lookup_franchise(sprocket_data: SprocketLinks, + name: str | None = None, + discord_id: str | int | None = None) -> Franchise | None: + """lookup franchise from sprocket + + Args: + sprocket_data (SprocketLinks): sprocket data + discord_id (str | int | None): discord id of user + + Returns: + Franchise | None: _description_ + """ + if discord_id: + member: Member = lookup_rl(sprocket_data, + discord_id=discord_id) + if not member or not member.franchise: + return None + name = member.franchise['Franchise'] + + return sprocket_data.franchise(name) + + +def lookup_rl(sprocket_data: SprocketLinks, + name: str | None = None, + discord_id: str | int | None = None) -> Member | None: + """lookup a rocket league player from sprocket + + Args: + sprocket_data (SprocketLinks): sprocket data links supplied by bot + name (str): name of player to look up + + Returns: + Member | None: _description_ + """ + if not name and not discord_id: + return None + + if name: + member = _get_member(sprocket_data, name, try_match=True) + + elif discord_id: + member = sprocket_data.members.from_hash(str(discord_id)) + + else: + member = None + + if not member: + return None + + player = sprocket_data.players.from_hash(member[const.SPR_HK_PLAYERS]) + if not player: + return None + + return Member(member=member, + rl_player=PlayerRL(player=player, + tracker=sprocket_data.trackers.from_hash(member['mle_id'])), + franchise=sprocket_data.teams.from_hash(player['franchise'])) + + +def _get_member(sprocket_data: SprocketLinks, + name: str, + try_match: bool = False) -> dict: + members = sprocket_data.members.data + m = sprocket_data.members.from_secondary_hash(name) + if not m and try_match: + matches = difflib.get_close_matches(name, + [x['name'] for x in members], + 1) + if matches: + m = next( + (x for x in members if x['name'].lower() + == matches[0].lower()), + None) + return m diff --git a/MLEBot/sprocket_data_link.py b/MLEBot/sprocket_data_link.py deleted file mode 100644 index 9e7c3d1..0000000 --- a/MLEBot/sprocket_data_link.py +++ /dev/null @@ -1,34 +0,0 @@ -#!/usr/bin/env python -""" Sprocket Data Link -# Author: irox_rl -# Purpose: Individualized sprocket_data link object for sprocket database -# Version 1.0.2 -""" -# non-local imports # -import asyncio -import datetime -import requests - - -class SprocketDataLink: - def __init__(self, url_link: str): - self.url_link = url_link - self.last_time_updated: datetime.datetime | None = None - self.json_data = None - - def compress(self): - return { - 'json_data': self.json_data, - 'last_time_updated': self.last_time_updated, - } - - def decompress(self, pickle_data): - self.json_data = pickle_data['json_data'] - self.last_time_updated = pickle_data['last_time_updated'] - - async def data(self): - if not self.url_link: - raise ValueError('URL Link is empty, cannot fetch sprocket_data') - self.json_data = requests.get(f'{self.url_link}.json').json() - self.last_time_updated = datetime.datetime.now() - await asyncio.sleep(2) diff --git a/MLEBot/task_roster.py b/MLEBot/task_roster.py deleted file mode 100644 index 98db676..0000000 --- a/MLEBot/task_roster.py +++ /dev/null @@ -1,219 +0,0 @@ -#!/usr/bin/env python -""" Periodic Task - Role -# Author: irox_rl -# Purpose: Manage roles of a franchise and roster channel information -# Version 1.0.2 -""" - -from PyDiscoBot import channels, err - -# local imports # -from member import get_members_by_role -import roles - -# non-local imports # -import datetime -import discord -from discord.ext import commands -import os -import pickle -from typing import Callable - -IMG_STAFF = None -IMG_PREMIER = None -IMG_MASTER = None -IMG_CHAMPION = None -IMG_ACADEMY = None -IMG_FOUNDATION = None - - -class Task_Roster: - def __init__(self, - master_bot): - self.bot = master_bot - - def __get_league__(self, - _team: {}, - _team_players: {}, - _league: str, - _guild: discord.Guild) -> discord.Embed | None: - embed = (discord.Embed(color=discord.Color.from_str(_team['primary_color']), - title=f"**{_league}**") - .set_footer(text=f'Generated: {self.bot.last_time}')) - embed.set_thumbnail(url=_team['logo_img_link']) - _players = [x for x in _team_players if x['skill_group'] == _league and x['slot'] != 'NONE'] - - if len(_players) == 0: - return None - - _player_mentions: [str] = [] - for _player in _players: - _member = next((x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _player['member_id']), None) - if not _member: - _player_mentions.append(_player['name']) - continue - _discord_member = next((x for x in _guild.members if x.id.__str__() == str(_member['discord_id'])), None) - if not _discord_member: - _player_mentions.append(_player['name']) - continue - _player_mentions.append(_discord_member.mention) - - if _player_mentions: - embed.description = '\n'.join([x for x in _player_mentions if x is not None]) - return embed - else: - return None - - async def post_roster(self, - _team: {}, - _channel: discord.TextChannel) -> bool: - - success = await channels.clear_channel_messages(_channel, - 100) - if not success: - await err('Could not delete messages from roster channel!\n' - 'Please manually delete them and retry running the ub.runroster command again.') - return False - - _team_players = [x for x in self.bot.sprocket.data['sprocket_players'] if x['franchise'] == _team['name']] - if not _team_players: - await err('Could not find players for franchise!') - return False - - _fm = next((x for x in _team_players if x['Franchise Staff Position'] == 'Franchise Manager'), None) - _gms = [x for x in _team_players if x['Franchise Staff Position'] == 'General Manager'] - _agms = [x for x in _team_players if x['Franchise Staff Position'] == 'Assistant General Manager'] - _captains = [x for x in _team_players if x['Franchise Staff Position'] == 'Captain'] - _pr_supports = [x for x in _team_players if x['Franchise Staff Position'] == 'PR Support'] - - embed = (discord.Embed(color=discord.Color.from_str(_team['primary_color']), - title=f"**{_team['name']} Staff**") - .set_footer(text=f'Generated: {self.bot.last_time}')) - embed.set_thumbnail(url=_team['logo_img_link']) - - if _fm: - _fm_text: str = '' - _fm_member = next((x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _fm['member_id']), None) - if not _fm_member: - _fm_text = _fm['name'] - else: - _discord_mem = next((x for x in _channel.guild.members if x.id.__str__() == str(_fm_member['discord_id'])), None) - if not _discord_mem: - _fm_text = _fm['name'] - else: - _fm_text = _discord_mem.mention - embed.add_field(name='**Franchise Manager**', - value=_fm_text, - inline=True) - - if _gms: - _gm_text: [str] = [] - for _gm in _gms: - _gm_member = next((x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _gm['member_id']), None) - if not _gm_member: - _gm_text.append(_gm['name']) - else: - _discord_mem = next((x for x in _channel.guild.members if x.id.__str__() == str(_gm_member['discord_id'])), None) - if not _discord_mem: - _gm_text.append(_gm['name']) - else: - _gm_text.append(_discord_mem.mention) - embed.add_field(name='**General Managers**', - value='\n'.join(_gm_text), - inline=True) - - if _agms: - _agm_text: [str] = [] - for _agm in _agms: - _agm_member = next( - (x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _agm['member_id']), None) - if not _agm_member: - _agm_text.append(_agm['name']) - else: - _discord_mem = next( - (x for x in _channel.guild.members if x.id.__str__() == str(_agm_member['discord_id'])), None) - if not _discord_mem: - _agm_text.append(_agm['name']) - else: - _agm_text.append(_discord_mem.mention) - embed.add_field(name='**Assistant General Managers**', - value='\n'.join(_agm_text), - inline=True) - - if _captains: - _captains_text: [str] = [] - for _captain in _captains: - _captain_member = next( - (x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _captain['member_id']), - None) - if not _captain_member: - _captains_text.append(_captain['name']) - else: - _discord_mem = next( - (x for x in _channel.guild.members if x.id.__str__() == str(_captain_member['discord_id'])), None) - if not _discord_mem: - _captains_text.append(_captain['name']) - else: - _captains_text.append(_discord_mem.mention) - embed.add_field(name='**Captains**', - value='\n'.join(_captains_text), - inline=True) - - if _pr_supports: - _text: [str] = [] - for _pr_support in _pr_supports: - _pr_support_member = next( - (x for x in self.bot.sprocket.data['sprocket_members'] if x['member_id'] == _pr_support['member_id']), - None) - if not _pr_support_member: - _text.append(_pr_support['name']) - else: - _discord_mem = next( - (x for x in _channel.guild.members if x.id.__str__() == str(_pr_support_member['discord_id'])), - None) - if not _discord_mem: - _text.append(_pr_support['name']) - else: - _text.append(_discord_mem.mention) - embed.add_field(name='**PR Supports**', - value='\n'.join(_text), - inline=True) - - await _channel.send(embed=embed) - - emb = self.__get_league__(_team, - _team_players, - 'Premier League', - _channel.guild) - if emb: - await _channel.send(embed=emb) - - emb = self.__get_league__(_team, - _team_players, - 'Master League', - _channel.guild) - if emb: - await _channel.send(embed=emb) - - emb = self.__get_league__(_team, - _team_players, - 'Champion League', - _channel.guild) - if emb: - await _channel.send(embed=emb) - - emb = self.__get_league__(_team, - _team_players, - 'Academy League', - _channel.guild) - if emb: - await _channel.send(embed=emb) - - emb = self.__get_league__(_team, - _team_players, - 'Foundation League', - _channel.guild) - if emb: - await _channel.send(embed=emb) - # await channels.post_image(context, IMG_STAFF) if IMG_STAFF else None - return True diff --git a/MLEBot/task_sprocket.py b/MLEBot/task_sprocket.py deleted file mode 100644 index d8464fc..0000000 --- a/MLEBot/task_sprocket.py +++ /dev/null @@ -1,205 +0,0 @@ -#!/usr/bin/env python -""" Sprocket Periodic Task -# Author: irox_rl -# Purpose: Get MLE information hosted from sprocket for use in parsing sprocket_data -# Version 1.0.4 -""" - -from PyDiscoBot import err - -# local imports # -from sprocket_data_link import SprocketDataLink - -# non-local imports # -import datetime -import pickle - - -class Task_Sprocket: - def __init__(self, master_bot): - self.bot = master_bot - self.league_update_flag = False - self._members_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/members") - self._player_stats_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/player_stats") - self._players_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/players") - self._scrim_stats_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/scrim_stats") - self._teams_link = SprocketDataLink("https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/teams") - self._fixtures_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/schedules/fixtures") - self._match_groups_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/schedules/match_groups") - self._matches_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/schedules/matches") - self._trackers_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/trackers") - self._role_usages_link = SprocketDataLink( - "https://f004.backblazeb2.com/file/sprocket-artifacts/public/data/role_usages") - - self.on_updated = [] - - self._file_name = 'sprocket_data.pickle' - self._loaded = False - self._last_time_ran: datetime.datetime | None = None - self._next_run_time: datetime.datetime | None = None - - @property - def all_links_loaded(self) -> bool: - if (self._members_link.last_time_updated and - self._player_stats_link.last_time_updated and - self._players_link.last_time_updated and - self._scrim_stats_link.last_time_updated and - self._teams_link.last_time_updated and - self._fixtures_link.last_time_updated and - self._match_groups_link.last_time_updated and - self._matches_link.last_time_updated and - self._trackers_link.last_time_updated and - self._role_usages_link.last_time_updated): - return True - return False - - @property - def data(self) -> {}: - return {'sprocket_members': self._members_link.json_data, - 'sprocket_player_stats': self._player_stats_link.json_data, - 'sprocket_players': self._players_link.json_data, - 'sprocket_scrim_stats': self._scrim_stats_link.json_data, - 'sprocket_teams': self._teams_link.json_data, - 'sprocket_fixtures': self._fixtures_link.json_data, - 'sprocket_match_groups': self._match_groups_link.json_data, - 'sprocket_matches': self._matches_link.json_data, - 'sprocket_trackers': self._trackers_link.json_data, - 'role_usages': self._role_usages_link.json_data, - } - - @property - def members_link(self): - return self._player_stats_link - - @property - def player_stats_link(self): - return self._player_stats_link - - @property - def players_link(self): - return self._players_link - - @property - def role_usages_link(self): - return self._role_usages_link - - @property - def scrim_stats_link(self): - return self._scrim_stats_link - - @property - def teams_link(self): - return self._teams_link - - @property - def trackers_link(self): - return self._trackers_link - - @property - def fixtures_link(self): - return self._fixtures_link - - @property - def match_groups_link(self): - return self._match_groups_link - - @property - def matches_link(self): - return self._matches_link - - @property - def ready_to_update(self) -> bool: - return self._next_run_time <= datetime.datetime.now() - - def __get_next_run_time__(self): - self._last_time_ran = datetime.datetime.now() - if self._last_time_ran.hour < 6: - self._next_run_time = self._last_time_ran + datetime.timedelta(hours=(6 - self._last_time_ran.hour), - minutes=(0 - self._last_time_ran.minute), - seconds=(0 - self._last_time_ran.second)) - return - if self._last_time_ran.hour < 12: - self._next_run_time = self._last_time_ran + datetime.timedelta(hours=(12 - self._last_time_ran.hour), - minutes=(0 - self._last_time_ran.minute), - seconds=(0 - self._last_time_ran.second)) - return - if self._last_time_ran.hour < 18: - self._next_run_time = self._last_time_ran + datetime.timedelta(hours=(18 - self._last_time_ran.hour), - minutes=(0 - self._last_time_ran.minute), - seconds=(0 - self._last_time_ran.second)) - return - self._next_run_time = self._last_time_ran + datetime.timedelta(hours=(0 - self._last_time_ran.hour), - minutes=(0 - self._last_time_ran.minute), - seconds=(0 - self._last_time_ran.second), days=1) - return - - def load(self): - try: - with open(self._file_name, 'rb') as f: # Open save file - data = pickle.load(f) - self._members_link.decompress(data[0]) - self._player_stats_link.decompress(data[1]) - self._players_link.decompress(data[2]) - self._scrim_stats_link.decompress(data[3]) - self._teams_link.decompress(data[4]) - self._fixtures_link.decompress(data[5]) - self._match_groups_link.decompress(data[6]) - self._matches_link.decompress(data[7]) - self._trackers_link.decompress(data[8]) - self._role_usages_link.decompress(data[9]) - self._last_time_ran = data[10]['last_time_ran'] - self._next_run_time = data[10]['next_run_time'] - self._loaded = True - except (KeyError, FileNotFoundError, EOFError) as e: - self._next_run_time = datetime.datetime.now() - self._loaded = True - - def reset(self): - self._next_run_time = datetime.datetime.now() - - async def run(self): - if not self._loaded: - self.load() - if self.ready_to_update: - await self._members_link.data() - await self._player_stats_link.data() - await self._players_link.data() - await self._scrim_stats_link.data() - await self._teams_link.data() - await self._fixtures_link.data() - await self._match_groups_link.data() - await self._matches_link.data() - await self._trackers_link.data() - await self._role_usages_link.data() - else: - return - self.__get_next_run_time__() - self.save() - for callback in self.on_updated: - await callback(self.data) - await err('sprocket server links updated.') - - def save(self): - with open(self._file_name, 'wb') as f: - pickle.dump([self._members_link.compress(), - self._player_stats_link.compress(), - self._players_link.compress(), - self._scrim_stats_link.compress(), - self._teams_link.compress(), - self._fixtures_link.compress(), - self._match_groups_link.compress(), - self._matches_link.compress(), - self._trackers_link.compress(), - self._role_usages_link.compress(), - { - 'last_time_ran': self._last_time_ran, - 'next_run_time': self._next_run_time, - }], f) diff --git a/MLEBot/tasks/__init__.py b/MLEBot/tasks/__init__.py new file mode 100644 index 0000000..2a42a33 --- /dev/null +++ b/MLEBot/tasks/__init__.py @@ -0,0 +1,10 @@ +"""Minor League E-Sports Bot Tasks + """ + +from .sprocket import Sprocket + +__version__ = '1.1.1' + +__all__ = ( + 'Sprocket', +) diff --git a/MLEBot/tasks/sprocket.py b/MLEBot/tasks/sprocket.py new file mode 100644 index 0000000..310cb22 --- /dev/null +++ b/MLEBot/tasks/sprocket.py @@ -0,0 +1,193 @@ +"""Manage Sprocket data links for up-to-date information + """ +from __future__ import annotations + + +import datetime +import json +import threading + + +from pydiscobot import Task +from pydiscobot.types.err import BotNotLoaded + + +from ..types import SprocketLinks, UrlDataLink + + +class Sprocket(Task): + """sprocket bot task""" + + def __init__(self, parent): + super().__init__(parent) + + self._links = SprocketLinks() + self._loaded = False + self._updating = False + + self.on_updated: list[callable] = [] + + self._last_time_ran: datetime.datetime | None = None + self._next_run_time: datetime.datetime | None = None + + self.init() + + @property + def links(self) -> SprocketLinks: + """get all url data links for sprocket + + Returns: + SprocketLinks: dataclass of sprocket url datalinks + """ + if self.updating: + raise BotNotLoaded('Sprocket links are updating!') + return self._links + + @property + def iterlinks(self) -> list[UrlDataLink]: + """get iterable links + + Returns: + tuple[SprocketLinks]: sprocket data links + """ + return self.links.links() + + @property + def ready_to_update(self) -> bool: + """if sprocket has published, return True + sprocket publishes on 6 Hr intervals + starting @ 00:00 + + Returns: + bool: ready to update + """ + return self._next_run_time <= datetime.datetime.now() + + @property + def json_link(self) -> str: + """url link appended with .json for easy direct-to-file saving + + Returns: + str: string of url appended with .json + """ + return '.' + self.__class__.__name__ + '.json' + + @property + def updating(self) -> bool: + """links are currently fetching data + + Returns: + bool: updating + """ + return self._updating + + def _calc_next_fetch_time(self) -> None: + self.logger.info('calculating next run time...') + + def _generate(n: datetime.datetime, + hour: int): + return n + ( + datetime.timedelta(days=0, + hours=hour-n.hour, + minutes=15-n.minute, + seconds=0-n.second, + microseconds=0-n.microsecond)) + + now = datetime.datetime.now() + if now.hour < 6: + self._next_run_time = _generate(now, 6) + + elif now.hour < 12: + self._next_run_time = _generate(now, 12) + + elif now.hour < 18: + self._next_run_time = _generate(now, 18) + + else: + self._next_run_time = _generate(now, 24) + + self.logger.info('next run time -> %s', + self._next_run_time.strftime("%m/%d/%Y, %H:%M:%S")) + + async def _update(self): + threads = [threading.Thread(name='fetch', target=link.fetch) + for link in self.iterlinks] + + self._updating = True + # any calls to our 'links' attr here + # will raise an exception + + for t in threads: + t.start() + + for t in threads: + t.join() + + self._last_time_ran = datetime.datetime.now() + + self._links.compile_data() + + self._calc_next_fetch_time() + + self.save() + + self._updating = False + + for callback in self.on_updated: + await callback() + + await self.parent.notify('sprocket server links updated.') + + def load(self): + """load run times from artifacts file + """ + try: + self.logger.info('loading %s...', self.json_link) + with open(self.json_link, 'r', encoding='utf-8') as f: + data = json.load(f) + self._last_time_ran = datetime.datetime.strptime( + data['last_time_ran'], "%d/%m/%Y, %H:%M:%S") + self._next_run_time = datetime.datetime.strptime( + data['next_run_time'], "%d/%m/%Y, %H:%M:%S") + self._loaded = True + except (KeyError, FileNotFoundError, EOFError, json.JSONDecodeError, ValueError) as e: + self.logger.info('file load error on -> %s...%s', + self.json_link, e) + self._next_run_time = datetime.datetime.now() + self._loaded = True + + def reset(self): + """force run time request to now + """ + self._next_run_time = datetime.datetime.now() + + def init(self): + """initialize this sprocket task + """ + self.logger.info('initializing task...') + if self._loaded: + self.logger.warning('already loaded!') + return + for link in self.iterlinks: + link.init() + self._links.compile_data() + self.load() + + async def run(self): + """run this sprocket task + """ + if not self._loaded: + self.init() + if self.ready_to_update: + self.logger.info('updating links...') + await self._update() + + def save(self): + """save run time data to artifacts file + """ + self.logger.info('saving data to -> %s...', self.json_link) + with open(self.json_link, 'w', encoding='utf-8') as f: + json.dump({ + 'last_time_ran': self._last_time_ran.strftime("%d/%m/%Y, %H:%M:%S"), + 'next_run_time': self._next_run_time.strftime("%d/%m/%Y, %H:%M:%S") + }, f) diff --git a/MLEBot/team.py b/MLEBot/team.py deleted file mode 100644 index 9c22550..0000000 --- a/MLEBot/team.py +++ /dev/null @@ -1,685 +0,0 @@ -#!/usr/bin/env python -""" Minor League E-Sports Team -# Author: irox_rl -# Purpose: General Functions of a League Team -# Version 1.0.4 -""" - -from PyDiscoBot import channels, err - -# local imports # -from member import Member -from enums import LeagueEnum - -# non-local imports # -import os -import datetime -import discord -from discord.ext import commands -from html2image import Html2Image - -MLE_SEASON = 'Season 17' -EMOTE_CHECK_GREEN = ':white_check_mark:' -EMOTE_X_RED = ':x:' -EMOTE_SABRES_NO_BG_ID = '1002420732837511248' - - -class Team: - """ Minor League E-Sports Team Class\n - """ - - def __init__(self, - guild: discord.Guild, - franchise, - league: LeagueEnum) -> None: - """ Initialize method\n - **param master_bot**: reference to mle_bot Bot class that is running this repo\n - **param team_name**: string representation of this team's name (e.g. **'Sabres'**)\n - **param league**: enumeration of the league this team belongs to\n - All data is initialized to zero. Update method must be called with proper sprocket data to get actual statistics for this class\n - For additional information about Sprocket Data Sets and these dictionaries, see:\n - https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html - """ - self.guild = guild - self.franchise = franchise - self.league = league - self.players: [Member] = [] - self.channel: discord.TextChannel | None = None - self.message: discord.Message | None = None - self.message_id = None - self.played_matches: [{}] = None - self.sprocket_data: {} = None - self.standard_series_wins = 0 - self.standard_series_losses = 0 - self.standard_wins = 0 - self.standard_losses = 0 - self.doubles_series_wins = 0 - self.doubles_series_losses = 0 - self.doubles_wins = 0 - self.doubles_losses = 0 - self._sprocket_players: [{}] = [] - self._sprocket_members: [{}] = [] - - @property - def league_name(self) -> str: - return self.league.name.replace('_', ' ') - - @property - def sprocket_members(self): - return self._sprocket_members - - @property - def sprocket_players(self): - return self._sprocket_players - - def __get_emote_by_id__(self, emoji_id: str) -> discord.Emoji | None: - """ helper function to get guild emote by supplied ID\n - ***returns***: discord emote or None""" - return next((x for x in self.guild.emojis if x.id.__str__() == emoji_id), None) - - def __get_weekly_info__(self, game_mode: str, match_week: str) -> {}: - """ Helper function to get a dictionary describing the match of a specified game mode from a specific week\n - **param game_mode**: specified game mode that is being posted ('Standard' or 'Doubles')\n - **param match_week**: specified match_week that is being posted ('Match 1', e.g.)\n - **returns** dictionary describing the match\n - """ - sprocket_data = self.franchise.bot.sprocket.data - """ Using stored sprocket data, get match groups of this specified week / season - """ - match_group_weeks = [x for x in sprocket_data['sprocket_match_groups'] if - x['match_group_title'] == match_week and x['parent_group_title'] == os.getenv('SEASON')] - - """ Get the match from the specified week / specified mode of this season - """ - match_this_week = next((x for x in self.played_matches for y in match_group_weeks if - x['match_group_id'] == y['match_group_id'] and x['game_mode'] == game_mode), None) - - """ If we didn't get a match for this week, return an empty dictionary - """ - if not match_this_week: - return { - 'home_team': '', - 'away_team': '', - 'score': '', - 'home_color': '', - 'away_color': '', - 'home_url': '', - 'away_url': '', - } - - """ Gather both teams from the match - """ - team1 = next((x for x in sprocket_data['sprocket_teams'] if x['name'] == match_this_week['home']), None) - team2 = next((x for x in sprocket_data['sprocket_teams'] if x['name'] == match_this_week['away']), None) - - """ If BOTH teams weren't found, return an empty dictionary - """ - if (not team1) or (not team2): - return { - 'home_team': '', - 'away_team': '', - 'score': '', - 'home_color': '', - 'away_color': '', - 'home_url': '', - 'away_url': '', - } - - """ Get the scores - """ - hm_score = match_this_week['home_wins'] - away_score = match_this_week['away_wins'] - score_wk = f'{hm_score} - {away_score}' - - """ Assemble all the data into a dictionary and return - """ - return { - 'home_team': match_this_week['home'], - 'away_team': match_this_week['away'], - 'score': score_wk, - 'home_color': team1['primary_color'], - 'away_color': team2['secondary_color'], - 'home_url': team1['logo_img_link'], - 'away_url': team2['logo_img_link'], - } - - async def __post_quick_info_html__(self, ctx: discord.ext.commands.Context | discord.TextChannel | None = None): - """ Helper function to post quick info html to the quick info channel of this team\n - **`optional`param ctx**: specified context to send information to. If not supplied, the info is posted to the team's quick info channel.\n - **returns** None\n - """ - """ Parse all possible players based on role as defined by MLE - """ - playerA: Member | None = next((x for x in self.players if x.role == 'PLAYERA'), None) - playerB: Member | None = next((x for x in self.players if x.role == 'PLAYERB'), None) - playerC: Member | None = next((x for x in self.players if x.role == 'PLAYERC'), None) - playerD: Member | None = next((x for x in self.players if x.role == 'PLAYERD'), None) - playerE: Member | None = next((x for x in self.players if x.role == 'PLAYERE'), None) - playerF: Member | None = next((x for x in self.players if x.role == 'PLAYERF'), None) - playerG: Member | None = next((x for x in self.players if x.role == 'PLAYERG'), None) - playerH: Member | None = next((x for x in self.players if x.role == 'PLAYERH'), None) - playerA_name = next((x.mle_name for x in self.players if x.role == 'PLAYERA'), '') - playerB_name = next((x.mle_name for x in self.players if x.role == 'PLAYERB'), '') - playerC_name = next((x.mle_name for x in self.players if x.role == 'PLAYERC'), '') - playerD_name = next((x.mle_name for x in self.players if x.role == 'PLAYERD'), '') - playerE_name = next((x.mle_name for x in self.players if x.role == 'PLAYERE'), '') - playerF_name = next((x.mle_name for x in self.players if x.role == 'PLAYERF'), '') - playerG_name = next((x.mle_name for x in self.players if x.role == 'PLAYERG'), '') - playerH_name = next((x.mle_name for x in self.players if x.role == 'PLAYERH'), '') - - """ Can we fix this to not use my local stuff please... god i'm an idiot - """ - if playerA: - playerA_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerA.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerA_emote = None - if playerB: - playerB_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerB.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerB_emote = None - if playerC: - playerC_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerC.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerC_emote = None - if playerD: - playerD_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerD.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerD_emote = None - if playerE: - playerE_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerE.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerE_emote = None - if playerF: - playerF_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerF.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerF_emote = None - if playerG: - playerG_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerG.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerG_emote = None - if playerH: - playerH_emote = 'D:\Personal\SabresUtilityBot\checkmark.png' if playerH.schedule_confirmed else 'D:\Personal\SabresUtilityBot\\redex.png' - else: - playerH_emote = None - - hti = Html2Image(size=(615, 695)) - html_string = open('..\TeamQuickInfo.html').read().format(league=get_league_text(self.league), - std_series_wins=self.standard_series_wins, - std_series_losses=self.standard_series_losses, - std_game_wins=self.standard_wins, - std_game_losses=self.standard_losses, - dbl_series_wins=self.doubles_series_wins, - dbl_series_losses=self.doubles_series_losses, - dbl_game_wins=self.doubles_wins, - dbl_game_losses=self.doubles_losses, - player_a_mle_name=playerA_name, - player_a_emote=playerA_emote, - player_b_mle_name=playerB_name, - player_b_emote=playerB_emote, - player_c_mle_name=playerC_name, - player_c_emote=playerC_emote, - player_d_mle_name=playerD_name, - player_d_emote=playerD_emote, - player_e_mle_name=playerE_name, - player_e_emote=playerE_emote, - player_f_mle_name=playerF_name, - player_f_emote=playerF_emote, - player_g_mle_name=playerG_name, - player_g_emote=playerG_emote, - player_h_mle_name=playerH_name, - player_h_emote=playerH_emote, - team_img=self.__get_emote_by_id__( - EMOTE_SABRES_NO_BG_ID).url) - hti.screenshot(html_str=html_string, css_file='..\TeamQuickInfo.css', - save_as=f'{get_league_text(self.league)}.png') - - with open(f'{get_league_text(self.league)}.png', 'rb') as f: - if ctx: - # noinspection PyTypeChecker - await ctx.send(file=discord.File(f)) - # noinspection PyTypeChecker - await self.channel.send(file=discord.File(f)) - - async def post_season_stats_html(self, - game_mode: str, - ctx: discord.ext.commands.Context | discord.TextChannel | None = None): - """ Helper function to post season stats html to the quick info channel of this team (Standard or Doubles, individually)\n - **param game_mode**: specified game mode that is being posted ('Standard' or 'Doubles')\n - **`optional`param ctx**: specified context to send information to. If not supplied, the info is posted to the team's quick info channel.\n - **returns** None\n - """ - """ Get weekly information and store into a local dict - """ - await self.update() - wk = {} - for i in range(1, 14): - wk[f'{i}'] = self.__get_weekly_info__(game_mode, f'Match {i}') - - """ Temporary integers to hold info on html image size - Ideally, this should be placed into the .env file or something similar... Better sizing needs to happen - """ - width = 615 - height = 750 - - """ Create html 2 image object""" - hti = Html2Image(size=(width, height)) - - """ Create a formatted version of the template file - The dictionary above will fill out this file - """ - html_string = open(r'team/html/TeamWeeklyStats.html').read().format(league=get_league_text(self.league), - mode=game_mode, - home_team_wk_1=wk['1']['home_team'], - away_team_wk_1=wk['1']['away_team'], - home_team_wk_1_clr=wk['1']['home_color'], - away_team_wk_1_clr=wk['1']['away_color'], - home_team_wk_1_logo=wk['1']['home_url'], - away_team_wk_1_logo=wk['1']['away_url'], - score_wk_1=wk['1']['score'], - home_team_wk_2=wk['2']['home_team'], - away_team_wk_2=wk['2']['away_team'], - home_team_wk_2_clr=wk['2']['home_color'], - away_team_wk_2_clr=wk['2']['away_color'], - home_team_wk_2_logo=wk['2']['home_url'], - away_team_wk_2_logo=wk['2']['away_url'], - score_wk_2=wk['2']['score'], - home_team_wk_3=wk['3']['home_team'], - away_team_wk_3=wk['3']['away_team'], - home_team_wk_3_clr=wk['3']['home_color'], - away_team_wk_3_clr=wk['3']['away_color'], - home_team_wk_3_logo=wk['3']['home_url'], - away_team_wk_3_logo=wk['3']['away_url'], - score_wk_3=wk['3']['score'], - home_team_wk_4=wk['4']['home_team'], - away_team_wk_4=wk['4']['away_team'], - home_team_wk_4_clr=wk['4']['home_color'], - away_team_wk_4_clr=wk['4']['away_color'], - home_team_wk_4_logo=wk['4']['home_url'], - away_team_wk_4_logo=wk['4']['away_url'], - score_wk_4=wk['4']['score'], - home_team_wk_5=wk['5']['home_team'], - away_team_wk_5=wk['5']['away_team'], - home_team_wk_5_clr=wk['5']['home_color'], - away_team_wk_5_clr=wk['5']['away_color'], - home_team_wk_5_logo=wk['5']['home_url'], - away_team_wk_5_logo=wk['5']['away_url'], - score_wk_5=wk['5']['score'], - home_team_wk_6=wk['6']['home_team'], - away_team_wk_6=wk['6']['away_team'], - home_team_wk_6_clr=wk['6']['home_color'], - away_team_wk_6_clr=wk['6']['away_color'], - home_team_wk_6_logo=wk['6']['home_url'], - away_team_wk_6_logo=wk['6']['away_url'], - score_wk_6=wk['6']['score'], - home_team_wk_7=wk['7']['home_team'], - away_team_wk_7=wk['7']['away_team'], - home_team_wk_7_clr=wk['7']['home_color'], - away_team_wk_7_clr=wk['7']['away_color'], - home_team_wk_7_logo=wk['7']['home_url'], - away_team_wk_7_logo=wk['7']['away_url'], - score_wk_7=wk['7']['score'], - home_team_wk_8=wk['8']['home_team'], - away_team_wk_8=wk['8']['away_team'], - home_team_wk_8_clr=wk['8']['home_color'], - away_team_wk_8_clr=wk['8']['away_color'], - home_team_wk_8_logo=wk['8']['home_url'], - away_team_wk_8_logo=wk['8']['away_url'], - score_wk_8=wk['8']['score'], - home_team_wk_9=wk['9']['home_team'], - away_team_wk_9=wk['9']['away_team'], - home_team_wk_9_clr=wk['9']['home_color'], - away_team_wk_9_clr=wk['9']['away_color'], - home_team_wk_9_logo=wk['9']['home_url'], - away_team_wk_9_logo=wk['9']['away_url'], - score_wk_9=wk['9']['score'], - home_team_wk_10=wk['10']['home_team'], - away_team_wk_10=wk['10']['away_team'], - home_team_wk_10_clr=wk['10'][ - 'home_color'], - away_team_wk_10_clr=wk['10'][ - 'away_color'], - home_team_wk_10_logo=wk['10'][ - 'home_url'], - away_team_wk_10_logo=wk['10'][ - 'away_url'], - score_wk_10=wk['10']['score'], - home_team_wk_11=wk['11']['home_team'], - away_team_wk_11=wk['11']['away_team'], - home_team_wk_11_clr=wk['11'][ - 'home_color'], - away_team_wk_11_clr=wk['11'][ - 'away_color'], - home_team_wk_11_logo=wk['11'][ - 'home_url'], - away_team_wk_11_logo=wk['11'][ - 'away_url'], - score_wk_11=wk['11']['score'], - home_team_wk_12=wk['12']['home_team'], - away_team_wk_12=wk['12']['away_team'], - home_team_wk_12_clr=wk['12'][ - 'home_color'], - away_team_wk_12_clr=wk['12'][ - 'away_color'], - home_team_wk_12_logo=wk['12'][ - 'home_url'], - away_team_wk_12_logo=wk['12'][ - 'away_url'], - score_wk_12=wk['12']['score'], - home_team_wk_13=wk['13']['home_team'], - away_team_wk_13=wk['13']['away_team'], - home_team_wk_13_clr=wk['13'][ - 'home_color'], - away_team_wk_13_clr=wk['13'][ - 'away_color'], - home_team_wk_13_logo=wk['13'][ - 'home_url'], - away_team_wk_13_logo=wk['13'][ - 'away_url'], - score_wk_13=wk['13']['score'], - team_imgurl=self.franchise.bot.server_icon) - - hti.screenshot(html_str=html_string, css_file=r'team/html/TeamWeeklyStats.css', - save_as=f'{get_league_text(self.league)}weeklystats_{game_mode}.png') - - """ Open the newly created .png file and post it! - """ - with open(f'{get_league_text(self.league)}weeklystats_{game_mode}.png', 'rb') as f: - if ctx: - # noinspection PyTypeChecker - return await ctx.send(file=discord.File(f)) - # noinspection PyTypeChecker - await self.channel.send(file=discord.File(f)) - - def __process_matches__(self, sprocket_matches: [{}], - as_standard: bool) -> None: - """ Helper function to parse through supplied sprocket matches (Singles or Doubles, individually)\n - **param sprocket_matches**: dictionary of matches.json from sprocket (usually supplied by sprocket class)\n - **param as_standard**: bool used to determined mode where **True** is standard mode\n - **returns** None\n - For additional information about Sprocket Data Sets and these dictionaries, see:\n - https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html - """ - """ Set a string to match based on mode - """ - mode = "Standard" if as_standard else "Doubles" - - """ Parse matches that match the mode we're in (set by the string above)""" - for match in [x for x in sprocket_matches if x['game_mode'] == mode]: - """ Parse wins or losses based on winning team's name - Also, use the boolean mode to determine which stats to increase - """ - if match['winning_team'] == self.franchise.franchise_name: - if as_standard: - self.standard_series_wins += 1 - else: - self.doubles_series_wins += 1 - else: - if as_standard: - self.standard_series_losses += 1 - else: - self.doubles_series_losses += 1 - - """ Same as above but in-line - """ - if as_standard: - self.standard_wins += match['home_wins'] if self.franchise.franchise_name == match['home'] else match[ - 'away_wins'] - self.standard_losses += match['away_wins'] if self.franchise.franchise_name == match['home'] else match[ - 'home_wins'] - else: - self.doubles_wins += match['home_wins'] if self.franchise.franchise_name == match['home'] else match[ - 'away_wins'] - self.doubles_losses += match['away_wins'] if self.franchise.franchise_name == match['home'] else match[ - 'home_wins'] - - def __reset_match_data__(self) -> None: - """ Helper method to reset all match datas to 0\n - This method is only intended to be used internally by this class\n - To properly reset match data externally, run the **update** method - """ - self.standard_series_wins = 0 - self.standard_series_losses = 0 - self.standard_wins = 0 - self.standard_losses = 0 - self.doubles_series_wins = 0 - self.doubles_series_losses = 0 - self.doubles_wins = 0 - self.doubles_losses = 0 - - async def update_from_sprocket_data(self) -> None: - """ Update this team's information from supplied sprocket data\n - **param sprocket_data**: dictionary of .json data from sprocket (usually supplied by sprocket class)\n - **returns** None\n - For additional information about Sprocket Data Sets and these dictionaries, see:\n - https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html - """ - """ Begin by clearing out match data - """ - self.__reset_match_data__() - sprocket_data = self.franchise.bot.sprocket.data - - """ validate data - """ - if not sprocket_data: - return - if not sprocket_data['sprocket_matches']: - return - if not sprocket_data['sprocket_match_groups']: - return - - """ Get valid, played matches from supplied sprocket data - """ - self.get_played_matches(sprocket_matches=sprocket_data['sprocket_matches'], - sprocket_match_groups=sprocket_data['sprocket_match_groups']) - - """ If no played matches were found, do not continue the process - """ - if not self.played_matches: - return await err(f'Could not find valid, played matches for\n' - f'team {self.franchise.franchise_name}\n' - f'league {self.league}\n') - - """ Process standard series matches - """ - self.__process_matches__(sprocket_matches=self.played_matches, - as_standard=True) - - """ Process doubles series matches - """ - self.__process_matches__(sprocket_matches=self.played_matches, - as_standard=False) - - def add_member(self, - new_member: Member) -> bool: - """ Add a MLE member to this team's roster\n - **param member**: MLE Member to be added to this roster\n - **returns** Success status of add\n - """ - """ Validate the member's league is the same as this teams' - """ - if new_member.league != self.league: - return False - """ Validate that the member isn't already a part of our team - """ - if new_member in self.players: - return False - """ Add the member - """ - self.players.append(new_member) - new_member.update(self.franchise.bot.sprocket.data) - return True - - def build(self): - self._sprocket_players = [x for x in self.franchise.sprocket_players if x['skill_group'] == self.league_name] - self._sprocket_members = [] - for _player in self._sprocket_players: - _mem = next((x for x in self.franchise.sprocket_members if x['member_id'] == _player['member_id']), None) - if _mem: - self._sprocket_members.append(_mem) - _guild_member = next((x for x in self.franchise.guild.members if x.id == int(_mem['discord_id'])), None) - if _guild_member: - self.add_member(Member(_guild_member, - self.league)) - - async def build_quick_info_channel(self, - sprocket_data: {}) -> None: - """ Build quick info channel for this MLE team\n - Note: This method will return if no channel exists.\n - This function will clear the Quick Info channel messages (up to 100) and post various pieces of quick info\n - **param sprocket_data**: dictionary of .json data from sprocket (usually supplied by sprocket class)\n - **returns**: None\n - """ - """ If no channel exists, immediately return - """ - if not self.channel: - return - """ Clear out channel messages as much as we can - """ - await channels.clear_channel_messages(self.channel, 100) - - """ Send notification that an update is running - """ - await err(f'Running new quick info channel information.\n' - f'{self.franchise_name} - {self.league}') - - """ Send the team quick info html doc to the team's quick info channel - """ - await self.__post_quick_info_html__() - - """ Use helper function to send html doc of Standard matches to quick info channel - """ - await self.___post_season_stats_html__('Standard') - - """ Use helper function to send html doc of Doubles matches to quick info channel - """ - await self.___post_season_stats_html__('Doubles') - - def get_played_matches(self, sprocket_matches: {}, sprocket_match_groups: {}) -> [{}] or None: - """ Get played matches from sprocket data\n - **param sprocket_matches**: dictionary of matches.json from sprocket (usually supplied by sprocket class)\n - **param sprocket_match_groups**: dictionary of match_groups.json from sprocket (usually supplied by sprocket class)\n - **returns** dictionary of valid, played matches from the current MLE season\n - For additional information about Sprocket Data Sets and these dictionaries, see:\n - https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html - """ - - """ Get games that include this team as either home or away - param x: rocket league series played in MLE - """ - matches = [x for x in sprocket_matches if - ((x['home'] == self.franchise.franchise_name) | (x['away'] == self.franchise.franchise_name)) and - x['league'] == get_league_text(self.league)] - - """ Get valid match groups that occurred this current season - param x: match group hosted by sprocket that includes season data - """ - match_groups = [x for x in sprocket_match_groups if x['parent_group_title'] == os.getenv('SEASON')] - - """ Compare the two previous search results to come up with a final list of valid, played matches this season - param x: rocket league series played in MLE - param y: match group of series' played in MLE - """ - valid_season_matches = [x for x in matches for y in match_groups if - x['match_group_id'] == y['match_group_id']] - - """ Return only games that have been played - Games with the winning team marked as below have not been played, so these should not be included - param x: matches from this season that include our team - """ - self.played_matches = [x for x in valid_season_matches if - (x['winning_team'] != "Not Played / Data Unavailable")] - return self.played_matches - - async def get_updated_players(self) -> [Member]: - for player in self.players: - player.update(self.franchise.bot.sprocket.data) - return self.players - - def remove_member(self, _member: Member) -> bool: - if _member in self.players: - self.players.remove(_member) - return True - return False - - async def update(self): - """ Update MLE Team from supplied sprocket data\n - This method is a callback for the sprocket periodic data task\n - This method will **rebuild the team's quick info channel** (if it has one) - **param sprocket_data**: dictionary provided by Sprocket class of all sprocket public datasets\n - **returns**: None\n - For additional information about Sprocket Data Sets and these dictionaries, see:\n - https://f004.backblazeb2.com/file/sprocket-artifacts/public/pages/index.html - """ - """ Update self with the newly provided sprocket data - """ - await self.update_from_sprocket_data() - - """ Update players with the new sprocket data - """ - for player in self.players: - await player.update(self.sprocket_data) - - """ Finally, after everything has been updated, build the quick info channel - """ - await self.build_quick_info_channel(self.sprocket_data) - - -def get_league_text(league: LeagueEnum) -> str | None: - """ Get text representation of League enumeration """ - - match league: - case LeagueEnum.Premier_League: - return "Premier League" - case LeagueEnum.Master_League: - return "Master League" - case LeagueEnum.Champion_League: - return "Champion League" - case LeagueEnum.Academy_League: - return "Academy League" - case LeagueEnum.Foundation_League: - return "Foundation League" - - -def get_league_text_short(league) -> str | None: - """ Get shorthand string representation of League enumeration """ - - match league: - case LeagueEnum.Premier_League: - return "PL" - case LeagueEnum.Master_League: - return "ML" - case LeagueEnum.Champion_League: - return "CL" - case LeagueEnum.Academy_League: - return "AL" - case LeagueEnum.Foundation_League: - return "FL" - - -def get_defined_team_players(_team: {}, - _sprocket_data: {}): - if not _sprocket_data or not _team: - return None - _all = [x for x in _sprocket_data['sprocket_players'] if x['franchise'] == _team['name']] - return { - 'all': _all, - 'fm': next((x for x in _all if x['Franchise Staff Position'] == 'Franchise Manager'), None), - 'gms': [x for x in _all if x['Franchise Staff Position'] == 'General Manager'], - 'agms': [x for x in _all if x['Franchise Staff Position'] == 'Assistant General Manager'], - 'captains': [x for x in _all if x['Franchise Staff Position'] == 'Captain'], - 'pr_supports': [x for x in _all if x['Franchise Staff Position'] == 'PR Support'], - - 'pl_players': [x for x in _all if x['skill_group'] == 'Premier League' and x['slot'] != 'NONE'], - 'ml_players': [x for x in _all if x['skill_group'] == 'Master League' and x['slot'] != 'NONE'], - 'cl_players': [x for x in _all if x['skill_group'] == 'Champion League' and x['slot'] != 'NONE'], - 'al_players': [x for x in _all if x['skill_group'] == 'Academy League' and x['slot'] != 'NONE'], - 'fl_players': [x for x in _all if x['skill_group'] == 'Foundation League' and x['slot'] != 'NONE'], - } - - -def get_mle_franchise_embed(_team: {}) -> discord.Embed: - embed = (discord.Embed(color=discord.Color.from_str(_team['primary_color']), - title=f"{_team['name']} Roster") - .set_footer(text=f"Generated: {datetime.datetime.now().strftime('%c')}")) - embed.set_thumbnail(url=_team['logo_img_link']) - return embed diff --git a/MLEBot/team/html/TeamQuickInfo.css b/MLEBot/team/html/TeamQuickInfo.css deleted file mode 100644 index b839d4c..0000000 --- a/MLEBot/team/html/TeamQuickInfo.css +++ /dev/null @@ -1,166 +0,0 @@ -body { -background: #250000; -background: -moz-linear-gradient(left, #250000 0%, #7B4409 50%, #A25C07 100%); -background: -webkit-linear-gradient(left, #250000 0%, #7B4409 50%, #A25C07 100%); -background: linear-gradient(to right, #250000 0%, #7B4409 50%, #A25C07 100%); -} - -h1 { -text-align: left; -color: white; -font-family: Arial Black, Gadget, sans-serif; -font-size: small; -} - -series_banner_div { -position: relative; -top: -1px; -background: #1c1c1c; -border: 1px solid #000000; -color: white; -height: 102%; -width: 122px; -font-family: Verdana Black, Verdana, monospace; -font-size: small; -line-height: 30px; -text-align: center; -text-shadow: 4px 4px 4px black; -white-space: nowrap; -} - -game_mode_div { -position: relative; -top: -3px; -text-align: left; -color: white; -font-family: Verdana Black, Verdana, monospace; -font-size: small; -text-shadow: 4px 4px 4px black; -padding-top: 10px; -padding-bottom: 10px; -width: 80px; -white-space: nowrap; -} - -win_loss_div { -position: relative; -top: -1px; -border: 1px solid #000000; -height: 102%; -width: 122px; -font-family: Verdana, Verdana, monospace; -font-size: small; -line-height: 30px; -text-align: center; -color: white; -white-space: nowrap; -} - -long_banner_div { -height: 30px; -width: 575px; -margin: 5px; -margin-bottom: 10px; -padding-left: 10px; -padding-bottom: 1px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -short_banner_div { -height: 30px; -width: 328px; -margin: 5px; -margin-bottom: 10px; -padding-left: 10px; -padding-bottom: 1px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -img.scheduler_img { -position: relative; -margin: auto; -top: -12px; -left: 0; -right: 0; -bottom: 0; -width: 50px; -height: 50px; -} - -img_div { -line-height: -10px; -height: 28px; -width: 75px; -margin-top: 4px; -display: flex; -} - -player { -height: 30px; -width: 260px; -margin: 5px; -margin-bottom: 10px; -padding-top: 0px; -padding-left: 10px; -padding-right: 5px; -padding-bottom: 5px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -player_role { -text-align: left; -color: white; -font-family: Arial Black, Gadget, sans-serif; -font-size: small; -padding-top: 10px; -padding-bottom: 10px; -} - -mle_name { -width: 110px; -text-align: center; -color: white; -font-family: Arial, Gadget, sans-serif; -font-size: small; -padding-top: 10px; -padding-bottom: 10px; -} - -#shadowbox { -justify-content: center; -align-items: center; -height: fit-contents; --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -background: #1c1c1c; -align-items: right; -padding-top: 5px; -padding-right: 10px; -padding-bottom: 10px; -padding-left: 0px; -border-radius: 10px 10px 10px 10px; -} \ No newline at end of file diff --git a/MLEBot/team/html/TeamQuickInfo.html b/MLEBot/team/html/TeamQuickInfo.html deleted file mode 100644 index b9eb961..0000000 --- a/MLEBot/team/html/TeamQuickInfo.html +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - -
-
-
-

- {league:} -

-
- -
- - - - Series Wins - Series Losses - Game Wins - Game Losses - - - - - Standard - - - {std_series_wins:} - - - {std_series_losses:} - - - {std_game_wins:} - - - {std_game_losses:} - - - - - - Doubles - - - {dbl_series_wins:} - - - {dbl_series_losses:} - - - {dbl_game_wins:} - - - {dbl_game_losses:} - - - - - Role - - Name - - Scheduler - - - - PLAYERA - - - {player_a_mle_name:} - - - - - - - - PLAYERB - - - {player_b_mle_name:} - - - - - - - - PLAYERC - - - {player_c_mle_name:} - - - - - - - - PLAYERD - - - {player_d_mle_name:} - - - - - - - - PLAYERE - - - {player_e_mle_name:} - - - - - - - - PLAYERF - - - {player_f_mle_name:} - - - - - - - - PLAYERG - - - {player_g_mle_name:} - - - - - - - - PLAYERH - - - {player_h_mle_name:} - - - - - -
- - - \ No newline at end of file diff --git a/MLEBot/team/html/TeamWeeklyStats.css b/MLEBot/team/html/TeamWeeklyStats.css deleted file mode 100644 index f77d30d..0000000 --- a/MLEBot/team/html/TeamWeeklyStats.css +++ /dev/null @@ -1,240 +0,0 @@ -body { -background: #250000; -background: -moz-linear-gradient(left, #250000 0%, #7B4409 50%, #A25C07 100%); -background: -webkit-linear-gradient(left, #250000 0%, #7B4409 50%, #A25C07 100%); -background: linear-gradient(to right, #250000 0%, #7B4409 50%, #A25C07 100%); -} - -h1 { -text-align: left; -color: white; -font-family: Arial Black, Gadget, sans-serif; -font-size: small; -} - -h2 { -color: lightgrey; -font-family: Verdana Black, sans-serif; -text-shadow: 4px 4px 1px black; font-size: white-space: nowrap; -} - -top_banner_sub { -position: relative; -top: -1px; -background: #1c1c1c; -border: 1px solid #000000; -color: white; -height: 102%; -width: 150px; -font-family: Verdana Black, Verdana, monospace; -font-size: small; -line-height: 30px; -text-align: center; -text-shadow: 4px 4px 4px black; -white-space: nowrap; -} - -week_div { -position: relative; -top: -1px; -border: 1px solid #000000; -height: 102%; -width: 122px; -font-family: Verdana Black, Verdana, monospace; -font-size: small; -line-height: 30px; -text-align: center; -color: white; -white-space: nowrap; -} - -home_div1 { -padding-left: 15px; -padding-right: 15px; -} - -home_div2 { -width: 91px; -} - -away_div1 { -width: 91px; -} - -away_div2 { -padding-left: 15px; -} - -score_div { -width: 100%; -} - -light_bg_short_div { -position: relative; -top: -1px; -border: 1px solid #000000; -height: 102%; -width: 150px; -font-family: Verdana Black, Verdana, monospace; -font-size: 18px; -text-shadow: 2px 2px 4px black; -line-height: 30px; -text-align: center; -color: lightgrey; -white-space: nowrap; -display: flex; -flex-direction: row; -} - -game_mode_div { -position: relative; -top: -3px; -text-align: left; -color: white; -font-family: Verdana Black, Verdana, monospace; -font-size: small; -text-shadow: 4px 4px 4px black; -padding-top: 10px; -padding-bottom: 10px; -width: 80px; -white-space: nowrap; -} - -win_loss_div { -position: relative; -top: -1px; -border: 1px solid #000000; -height: 102%; -width: 122px; -font-family: Verdana, Verdana, monospace; -font-size: small; -line-height: 30px; -text-align: center; -color: white; -white-space: nowrap; -} - -long_banner_div { -height: 30px; -width: 575px; -margin: 5px; -margin-bottom: 10px; -padding-bottom: 1px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -medium_banner_div { -height: 30px; -width: 580px; -margin: 5px; -margin-bottom: 10px; -padding-bottom: 1px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -short_banner_div { -height: 30px; -width: 328px; -margin: 5px; -margin-bottom: 10px; -padding-left: 0px; -padding-bottom: 1px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -img.scheduler_img { -position: relative; -margin: auto; -top: -12px; -left: 0; -right: 0; -bottom: 0; -width: 50px; -height: 50px; -} - -img_div { -line-height: -10px; -height: 28px; -width: 75px; -margin-top: 4px; -display: flex; -} - -player { -height: 30px; -width: 260px; -margin: 5px; -margin-bottom: 10px; -padding-top: 0px; -padding-left: 10px; -padding-right: 5px; -padding-bottom: 5px; -border-radius: 10px 10px 10px 10px; -background: #1C1C1C; -background: -moz-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: -webkit-linear-gradient(left, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); -background: linear-gradient(to right, #1C1C1C 0%, #2A2A2A 50%, #373737 100%); --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -display: flex; -flex-direction: row; -} - -player_role { -text-align: left; -color: white; -font-family: Arial Black, Gadget, sans-serif; -font-size: small; -padding-top: 10px; -padding-bottom: 10px; -} - -mle_name { -width: 110px; -text-align: center; -color: white; -font-family: Arial, Gadget, sans-serif; -font-size: small; -padding-top: 10px; -padding-bottom: 10px; -} - -#shadowbox { -justify-content: center; -align-items: center; -height: fit-contents; --webkit-box-shadow: 5px 5px 15px 5px #000000; -box-shadow: 5px 5px 15px 5px #000000; -background: #1c1c1c; -align-items: right; -padding-top: 5px; -padding-right: 10px; -padding-bottom: 10px; -padding-left: 0px; -border-radius: 10px 10px 10px 10px; -} \ No newline at end of file diff --git a/MLEBot/team/html/TeamWeeklyStats.html b/MLEBot/team/html/TeamWeeklyStats.html deleted file mode 100644 index 3832f5a..0000000 --- a/MLEBot/team/html/TeamWeeklyStats.html +++ /dev/null @@ -1,361 +0,0 @@ - - - - - - -
-
-
-
-

- {league:} -

-

- {mode:} -

-
-
- -
- - Week - Home - Score - Away - - - - Week 1 - - - - - - {home_team_wk_1:} - - - - - {score_wk_1:} - - - - - {away_team_wk_1:} - - - - - - - - Week 2 - - - - - - {home_team_wk_2:} - - - - - {score_wk_2:} - - - - - {away_team_wk_2:} - - - - - - - - Week 3 - - - - - - {home_team_wk_3:} - - - - - {score_wk_3:} - - - - - {away_team_wk_3:} - - - - - - - - Week 4 - - - - - - {home_team_wk_4:} - - - - - {score_wk_4:} - - - - - {away_team_wk_4:} - - - - - - - - Week 5 - - - - - - {home_team_wk_5:} - - - - - {score_wk_5:} - - - - - {away_team_wk_5:} - - - - - - - - Week 6 - - - - - - {home_team_wk_6:} - - - - - {score_wk_6:} - - - - - {away_team_wk_6:} - - - - - - - - Week 7 - - - - - - {home_team_wk_7:} - - - - - {score_wk_7:} - - - - - {away_team_wk_7:} - - - - - - - - Week 8 - - - - - - {home_team_wk_8:} - - - - - {score_wk_8:} - - - - - {away_team_wk_8:} - - - - - - - - Week 9 - - - - - - {home_team_wk_9:} - - - - - {score_wk_9:} - - - - - {away_team_wk_9:} - - - - - - - - Week 10 - - - - - - {home_team_wk_10:} - - - - - {score_wk_10:} - - - - - {away_team_wk_10:} - - - - - - - - Week 11 - - - - - - {home_team_wk_11:} - - - - - {score_wk_11:} - - - - - {away_team_wk_11:} - - - - - - - - Week 12 - - - - - - {home_team_wk_12:} - - - - - {score_wk_12:} - - - - - {away_team_wk_12:} - - - - - - - - Week 13 - - - - - - {home_team_wk_13:} - - - - - {score_wk_13:} - - - - - {away_team_wk_13:} - - - - - - -
- - - \ No newline at end of file diff --git a/MLEBot/team/imgs/ACADEMY.png b/MLEBot/team/imgs/ACADEMY.png deleted file mode 100644 index 29e5538..0000000 Binary files a/MLEBot/team/imgs/ACADEMY.png and /dev/null differ diff --git a/MLEBot/team/imgs/CHAMPION.png b/MLEBot/team/imgs/CHAMPION.png deleted file mode 100644 index 9d6cdfb..0000000 Binary files a/MLEBot/team/imgs/CHAMPION.png and /dev/null differ diff --git a/MLEBot/team/imgs/FOUNDATION.png b/MLEBot/team/imgs/FOUNDATION.png deleted file mode 100644 index 57b9907..0000000 Binary files a/MLEBot/team/imgs/FOUNDATION.png and /dev/null differ diff --git a/MLEBot/team/imgs/MASTER.png b/MLEBot/team/imgs/MASTER.png deleted file mode 100644 index 482f5f5..0000000 Binary files a/MLEBot/team/imgs/MASTER.png and /dev/null differ diff --git a/MLEBot/team/imgs/STAFF.png b/MLEBot/team/imgs/STAFF.png deleted file mode 100644 index b845608..0000000 Binary files a/MLEBot/team/imgs/STAFF.png and /dev/null differ diff --git a/MLEBot/types/__init__.py b/MLEBot/types/__init__.py new file mode 100644 index 0000000..c51c0a9 --- /dev/null +++ b/MLEBot/types/__init__.py @@ -0,0 +1,29 @@ +"""Minor League E-Sports Types (Classes) + """ + +from .enums import LeagueEnum + +from .sprocket import ( + Franchise, + Member, + PlayerRL, + TeamRocketLeague, + TeamTrackmania, + MLEFranchiseTeam, + SprocketLinks +) +from .url_datalink import UrlDataLink + +__version__ = '1.1.4' + +__all__ = ( + 'Franchise', + 'TeamRocketLeague', + 'TeamTrackmania', + 'Member', + 'MLEFranchiseTeam', + 'PlayerRL', + 'LeagueEnum', + 'SprocketLinks', + 'UrlDataLink', +) diff --git a/MLEBot/types/enums.py b/MLEBot/types/enums.py new file mode 100644 index 0000000..9736ddd --- /dev/null +++ b/MLEBot/types/enums.py @@ -0,0 +1,14 @@ +""" MLE League Enumeration Class + """ + +from enum import Enum + + +class LeagueEnum(Enum): + """ MLE League Enumeration Class + """ + PREMIER_LEAGUE = 1 + MASTER_LEAGUE = 2 + CHAMPION_LEAGUE = 3 + ACADEMY_LEAGUE = 4 + FOUNDATION_LEAGUE = 5 diff --git a/MLEBot/types/sprocket.py b/MLEBot/types/sprocket.py new file mode 100644 index 0000000..f67e55d --- /dev/null +++ b/MLEBot/types/sprocket.py @@ -0,0 +1,436 @@ +"""Minor League E-Sports Franchise and Team Dataclasses + """ +from __future__ import annotations + + +from dataclasses import dataclass, field +from typing import Self + + +from .url_datalink import UrlDataLink +from .. import const + + +@dataclass +class PlayerRL: + """MLE Sprocket Rocket League 'Player' + """ + + player: dict | None = None + tracker: dict | None = None + usage: dict | None = None + + @property + def eligibility_date(self) -> str: + """scrim point elgibility date + + Returns: + str: eligible until: + """ + return self.player["Eligible Until"] + + @property + def eligible(self) -> bool: + """eligible to play + + Returns: + bool: eligible + """ + return self.scrim_points >= 30 + + @property + def franchise(self) -> str: + """player franchise + + Returns: + str: franchise + """ + return self.player["franchise"] + + @property + def league(self) -> str: + """player league (skill group) + + Returns: + str: skill group + """ + return self.skill_group + + @property + def name(self) -> str: + """player name + + Returns: + str: name + """ + return self.player["name"] + + @property + def salary(self) -> str: + """player salary + + Returns: + str: salary + """ + return self.player["salary"] + + @property + def scrim_points(self) -> int: + """scrim points + + Returns: + str: current scrim points + """ + return int(self.player["current_scrim_points"]) + + @property + def skill_group(self) -> str: + """skill group + + Returns: + str: skill group + """ + return self.player["skill_group"] + + @property + def slot(self) -> str: + """player slot + + Returns: + str: slot + """ + return self.player["slot"] + + @property + def staff_position(self) -> str: + """franchise staff position + + Returns: + str: franchise staff position + """ + return self.player["Franchise Staff Position"] + + @property + def usage_doubles(self) -> int: + """player league usages + doubles + + Returns: + str: doubles usage + """ + return int(self.usage["doubles_uses"]) + + @property + def usage_standard(self) -> int: + """player league usages + standard + + Returns: + int: standard usage + """ + return int(self.usage["standard_uses"]) + + @property + def usage_total(self) -> int: + """player league usages + total + + Returns: + int: total usage + """ + return int(self.usage['total_uses']) + + +@dataclass +class Member: + """MLE Sprocket 'Member' + """ + member: dict | None = None + rl_player: PlayerRL = field(default_factory=PlayerRL()) + franchise: dict | None = None + + @property + def mle_id(self) -> str: + """member mle id + + Returns: + str: mle id + """ + return self.member['mle_id'] + + @property + def name(self) -> str: + """member name + + Returns: + str: name + """ + return self.member['name'] + + +class MLEFranchiseTeam(list[PlayerRL]): + """sub team for MLE franchises (think 'ML' or 'AL') + inherits list properties, used to add functions + """ + + def slot_sorted_players(self): + """get all players sorted by slot + """ + return sorted(self, + key=lambda x: x.slot) + + @classmethod + def from_sprocket_data(cls, + players: list[dict], + trackers, + usages: list[dict]) -> Self: + """compile list of PlayerRL from provided sprocket data + consider hashing usages with role-league-team is the key or something? + + Args: + players (list[dict]): sprocket players dict + trackers (UrlDataLink): trackers link + usages (list[dict]): usages dict + + Returns: + Self: this class appended with a list of PlayerRL for this franchise + """ + team = cls() + team.extend([PlayerRL(x, + trackers.from_hash(x['member_id']), + next((y for y in usages if y['role'] == x['slot']), None)) + for x in players]) + return team + + +@dataclass +class TeamRocketLeague: + """Minor League E-Sports Rocket League Franchise Team + """ + pl: MLEFranchiseTeam + ml: MLEFranchiseTeam + cl: MLEFranchiseTeam + al: MLEFranchiseTeam + fl: MLEFranchiseTeam + + def all_players(self): + """get all players for this team + + Returns: + list[PlayerRL]: all players + """ + return self.pl + self.ml + self.cl + self.al + self.fl + + def by_skill_group(self, + skill_group: str) -> MLEFranchiseTeam: + """get franchise team by skill group string + + Args: + skill_group (str): sprocket const skill group + + Returns: + MLEFranchiseTeam: team + """ + match skill_group: + case const.SPR_SG_PL: + return self.pl + case const.SPR_SG_ML: + return self.ml + case const.SPR_SG_CL: + return self.cl + case const.SPR_SG_AL: + return self.al + case const.SPR_SG_FL: + return self.fl + case _: + return None + + +@dataclass +class TeamTrackmania: + """Minor League E-Sports Trackmania Franchise Team + """ + cl: MLEFranchiseTeam + al: MLEFranchiseTeam + + def all_players(self): + """get all players for this team + + Returns: + list[PlayerRL]: all players + """ + return self.cl + self.al + + +@dataclass +class Franchise: + """Minor League E-Sports Franchise Dataclass + """ + players_rl: TeamRocketLeague + players_tm: TeamTrackmania + franchise_meta: dict + players_rl_meta: dict + players_tm_meta: dict + fm: dict + gms: list[dict] + agms: list[dict] + captains: list[dict] + pr: dict + + @classmethod + def from_sprocket_links(cls, + meta_data: dict, + sprocket_data) -> Self: + """compile franchise from sprocket links data + + Args: + meta_data (dict): sprocket dict of the franchise to create + sprocket_data (SprocketLinks): SprocketLinks class + + Returns: + Self: Franchise class + """ + + # do data gathering here to keep the local def easy to read + # gather rl data + p_rl = [x for x in sprocket_data.players.data if x['franchise'] == meta_data['Franchise']] + p_pl = [x for x in p_rl if x['skill_group'] == const.SPR_SG_PL and x['slot'] != 'NONE'] + p_ml = [x for x in p_rl if x['skill_group'] == const.SPR_SG_ML and x['slot'] != 'NONE'] + p_cl = [x for x in p_rl if x['skill_group'] == const.SPR_SG_CL and x['slot'] != 'NONE'] + p_al = [x for x in p_rl if x['skill_group'] == const.SPR_SG_AL and x['slot'] != 'NONE'] + p_fl = [x for x in p_rl if x['skill_group'] == const.SPR_SG_FL and x['slot'] != 'NONE'] + + # usage data + u_rl = [x for x in sprocket_data.usages.data if x['team_name'] == meta_data['Franchise']] + u_pl = [x for x in u_rl if x['league'].lower() in const.SPR_SG_PL.lower()] + u_ml = [x for x in u_rl if x['league'].lower() in const.SPR_SG_ML.lower()] + u_cl = [x for x in u_rl if x['league'].lower() in const.SPR_SG_CL.lower()] + u_al = [x for x in u_rl if x['league'].lower() in const.SPR_SG_AL.lower()] + u_fl = [x for x in u_rl if x['league'].lower() in const.SPR_SG_FL.lower()] + + rl_team = TeamRocketLeague( + pl=MLEFranchiseTeam.from_sprocket_data(p_pl, + sprocket_data.trackers, + u_pl), + ml=MLEFranchiseTeam.from_sprocket_data(p_ml, + sprocket_data.trackers, + u_ml), + cl=MLEFranchiseTeam.from_sprocket_data(p_cl, + sprocket_data.trackers, + u_cl), + al=MLEFranchiseTeam.from_sprocket_data(p_al, + sprocket_data.trackers, + u_al), + fl=MLEFranchiseTeam.from_sprocket_data(p_fl, + sprocket_data.trackers, + u_fl), + ) + + # gather tm data + p_tm = [] + + tm_team = TeamTrackmania( + cl=MLEFranchiseTeam([]), + al=MLEFranchiseTeam([]) + ) + + # compile + return cls( + players_rl=rl_team, + players_tm=tm_team, + players_rl_meta=p_rl, + players_tm_meta=p_tm, + franchise_meta=meta_data, + fm=next((x for x in p_rl if x['Franchise Staff Position'] == 'Franchise Manager'), None), + gms=[x for x in p_rl if x['Franchise Staff Position'] == 'General Manager'], + agms=[x for x in p_rl if x['Franchise Staff Position'] == 'Assistant General Manager'], + captains=[x for x in p_rl if x['Franchise Staff Position'] == 'Captain'], + pr=[x for x in p_rl if x['Franchise Staff Position'] == 'PR Support'], + ) + + @property + def name(self) -> str: + """franchise name + + Returns: + str: franchise name + """ + return self.franchise_meta['Franchise'] + + def all_players(self) -> list[PlayerRL]: + """get all players + + Returns: + list[PlayerRL]: all players + """ + return self.players_rl.all_players().extend(self.players_tm.all_players()) + + def get_skill_group(self, + team: list[PlayerRL]) -> str | None: + """cheap and easy get of skill group + + Args: + team (list[PlayerRL]): team of players + + Returns: + str: skill group + """ + if len(team) != 0: + return team[0].player['skill_group'] + return None + + +class SprocketLinks: + """sprocket data links + """ + + def __init__(self): + self.members: UrlDataLink = UrlDataLink('members', const.SPR_DL_MEMBERS, const.SPR_HK_MEMBERS, + const.SPR_HK_MEMBERS_SECONDARY) + self.players: UrlDataLink = UrlDataLink( + 'players', const.SPR_DL_PLAYERS, const.SPR_HK_PLAYERS) + self.player_stats: UrlDataLink = UrlDataLink( + 'player_stats', const.SPR_DL_PLA_STATS, const.SPR_HK_PLA_STATS) + self.scrims: UrlDataLink = UrlDataLink( + 'scrims', const.SPR_DL_SCRIMS, const.SPR_HK_SCRIMS) + self.teams: UrlDataLink = UrlDataLink( + 'teams', const.SPR_DL_TEAMS, const.SPR_HK_TEAMS) + self.fixtures: UrlDataLink = UrlDataLink( + 'fixtures', const.SPR_DL_FIXT, const.SPR_HK_FIXT) + self.match_grps: UrlDataLink = UrlDataLink( + 'match_groups', const.SPR_DL_MAT_GRP, const.SPR_HK_MAT_GRP) + self.matches: UrlDataLink = UrlDataLink( + 'matches', const.SPR_DL_MATCHES, const.SPR_HK_MATCHES) + self.trackers: UrlDataLink = UrlDataLink( + 'trackers', const.SPR_DL_TRACKER, const.SPR_HK_TRACKER) + self.usages: UrlDataLink = UrlDataLink( + 'usages', const.SPR_DL_USAGE, const.SPR_HK_USAGE) + + self._franchises: dict = {} + + def compile_data(self) -> None: + """compile data (usually directly after updating all links) + """ + if not self.teams.data: + return + + for f in self.teams.data: + self._franchises[f['Franchise'].lower()] = Franchise.from_sprocket_links(f, + self) + + def franchise(self, + name: str) -> franchise.Franchise | None: + """get franchise by name + + Args: + name (str): franchise name + + Returns: + (Franchise | None): franchise or none + """ + return self._franchises.get(name.lower(), None) + + def links(self): + """get iterable links from this sprocket links class + """ + return [getattr(self, x) for x in dir(self) if isinstance(getattr(self, x), UrlDataLink)] diff --git a/MLEBot/types/test_types.py b/MLEBot/types/test_types.py new file mode 100644 index 0000000..682d8c3 --- /dev/null +++ b/MLEBot/types/test_types.py @@ -0,0 +1,20 @@ +"""test types for MLE Bot + """ +from __future__ import annotations + +import unittest +from .. import const +from ..types import UrlDataLink + + +class TestUrlDataLink(unittest.TestCase): + """test class url datalink + """ + + def test_build(self): + """test data link builds + """ + link = UrlDataLink('unit-test-link', + const.SPR_DL_TEAMS, + const.SPR_HK_TEAMS) + self.assertIsNotNone(link) diff --git a/MLEBot/types/url_datalink.py b/MLEBot/types/url_datalink.py new file mode 100644 index 0000000..ea7aa60 --- /dev/null +++ b/MLEBot/types/url_datalink.py @@ -0,0 +1,197 @@ +"""url data link to grab json data from remote server and store it + """ +from __future__ import annotations + + +import json +from logging import Logger +from typing import Any +import requests + + +from pydiscobot import log + + +URL_REQ_TIMEOUT = 30.0 + + +class UrlDataLink: + """url data link to grab json data from remote server and store it + currently supports 2 hashes. + could extend to any amount, but not currently needed, so i'll worry about that logic later + """ + + def __init__(self, + name: str, + url_link: str, + hash_key: str | None = None, + second_hash_key: str | None = None): + self._data = None + self._hash_data = None + self._secondary_hash_data = None + self._name = name + self._url = url_link + self._hash_key = hash_key + self._secondary_hash_key = second_hash_key + self._initialized: bool = False + self._logger = log.logger(self._name) + + @property + def data(self) -> any: + """get stored data + + Returns: + any: stored data + """ + return self._data + + @data.setter + def data(self, value: dict) -> None: + """set stored data + also, process hash info + + Args: + value (dict): dictionary to store into data + """ + self._process_data(value) + + @property + def json_link(self) -> str: + """url link appended with .json for easy direct-to-file saving + + Returns: + str: string of url appended with .json + """ + return '.' + self._name + '.json' + + @property + def logger(self) -> Logger: + return self._logger + + def _clear(self) -> None: + self._data = None + self._hash_data = None + self._secondary_hash_data = None + + def _process_data(self, + data: dict) -> None: + """hash data for quick lookup + """ + self._clear() + + self._data = data + + if not self._hash_key: + return + + self._hash_data = {} + for key in data: + self._hash_data[key[self._hash_key]] = key + + if not self._secondary_hash_key: + return + + self._secondary_hash_data = {} + for key in data: + self._secondary_hash_data[key[self._secondary_hash_key]] = key + + def compress(self) -> dict: + """compress data to dict + + Returns: + dict: dict describing this link + """ + return { + 'data': self._data, + } + + def decompress(self, data: dict): + """restore compressed data back to link + + Args: + data (dict): data to restore into link + """ + try: + self.data = data['data'] + except (json.decoder.JSONDecodeError, ValueError, KeyError) as e: + self.logger.warning('load failure...%s - %s', self._name, e) + self._clear() + + def fetch(self): + """fetch data from url + + Raises: + ValueError: URL Link is empty or corrupt. + """ + if not self._url: + raise ValueError('URL Link is empty, cannot fetch data') + + self.logger.info('fetching %s...', self._url) + self.data = requests.get(self._url, + timeout=URL_REQ_TIMEOUT).json() + self.save() + + def from_hash(self, + key: str) -> Any: + """get data from hash storage, or none if not stored + + Args: + key (str): key value to lookup + + Raises: + ValueError: no data for key was found (data[key] == NotFound) + + Returns: + Any: data from hash table + """ + if not self._hash_data: + raise ValueError('no hash data to retrieve!') + + return self._hash_data.get(key, None) + + def from_secondary_hash(self, + key: str) -> Any: + """get data from hash storage, or none if not stored + + Args: + key (str): key value to lookup + + Raises: + ValueError: no data for key was found (data[key] == NotFound) + + Returns: + Any: data from hash table + """ + if not self._secondary_hash_data: + raise ValueError('no hash data to retrieve!') + + return self._secondary_hash_data.get(key, None) + + def init(self): + """initialize + """ + if self._initialized: + return + try: + self.load() + except (FileNotFoundError, EOFError): + pass + finally: + self._initialized = True + + def save(self): + """save data to artifacts dir + """ + self.logger.info('saving %s...', self.json_link) + with open(self.json_link, 'w', encoding='utf-8') as f: + json.dump(self.compress(), f) + + def load(self): + """load data from artifacts dir + """ + self.logger.info('loading %s...', self.json_link) + with open(self.json_link, 'r', encoding='utf-8') as f: + try: + self.decompress(json.load(f)) + except json.decoder.JSONDecodeError as e: + self.logger.warning('failed to load file -> %s', e) diff --git a/pyproject.toml b/pyproject.toml index c92d18d..485fd0f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "MLEBot" -version = "1.1.0" +version = "1.1.2" authors = [ { name="Brian LaFond", email="Brian.L.LaFond@gmail.com" }, ] @@ -29,11 +29,10 @@ classifiers = [ "Programming Language :: Python :: 3.11", ] dependencies = [ - "PyDiscoBot>=1.0.4", + "PyDiscoBot>=1.1.1", "discord.py", "python-dotenv", "requests", - "html2image" ] license = {file = "LICENSE"} diff --git a/requirements.txt b/requirements.txt index a4984ff..f7d32a9 100644 Binary files a/requirements.txt and b/requirements.txt differ