mirror of
https://github.com/insertapp/mmolbbot.git
synced 2025-07-01 14:27:03 +00:00
Initial Commit
This commit is contained in:
parent
fe70a525f8
commit
dc18eb5b8c
9 changed files with 678 additions and 0 deletions
178
.dockerignore
Normal file
178
.dockerignore
Normal file
|
@ -0,0 +1,178 @@
|
|||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
http_cache
|
||||
.direnv
|
||||
*.db
|
||||
.envrc
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
||||
# 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
|
4
.gitignore
vendored
4
.gitignore
vendored
|
@ -2,6 +2,10 @@
|
|||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
http_cache
|
||||
.direnv
|
||||
*.db
|
||||
.envrc
|
||||
|
||||
# C extensions
|
||||
*.so
|
||||
|
|
8
README.md
Normal file
8
README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
# MMOLBBOT
|
||||
## Not affliated with mmolb in any way I just couldn't think of a better name
|
||||
The bot is user-installable so the commands can either be added to a guild or your account
|
||||
Invite: https://discord.com/oauth2/authorize?client_id=1365149865444905121
|
||||
# Building
|
||||
inside the folder
|
||||
`docker build . -t mmolbbot`
|
||||
`docker run mmolbbot -e TOKEN=<Token> -e OWNER_GUILD=<guild with management commands>`
|
62
bot.py
Normal file
62
bot.py
Normal file
|
@ -0,0 +1,62 @@
|
|||
import nextcord
|
||||
from nextcord.ext import application_checks, commands
|
||||
from nextcord import SlashOption
|
||||
from dotenv import load_dotenv
|
||||
from random import randint
|
||||
import aiohttp
|
||||
import aiosqlite as sqlite3
|
||||
import traceback
|
||||
from urllib.parse import urlparse
|
||||
import os
|
||||
import logging
|
||||
import sys
|
||||
|
||||
load_dotenv()
|
||||
bot = commands.Bot()
|
||||
|
||||
|
||||
|
||||
@bot.event
|
||||
async def on_ready():
|
||||
global db
|
||||
global cur
|
||||
db = await sqlite3.connect("/data/mmolb.db")
|
||||
cur = await db.cursor()
|
||||
res = await cur.execute("SELECT name FROM sqlite_master WHERE name='liveupdate'")
|
||||
if await res.fetchone() is None:
|
||||
await cur.execute("CREATE TABLE liveupdate(serverid INTEGER, userid INTEGER, channelid INTEGER, messageid INTEGER, gameid TEXT, offset INTEGER)")
|
||||
await db.commit()
|
||||
bot.db = db
|
||||
bot.cur = cur
|
||||
bot.load_extension('cogs.liveupdate')
|
||||
bot.load_extension('cogs.team')
|
||||
bot.add_all_application_commands()
|
||||
#await bot.sync_all_application_commands()
|
||||
|
||||
@bot.slash_command(
|
||||
name="managecog",
|
||||
description="manage cogs",
|
||||
guild_ids=[int(os.getenv("OWNER_GUILD"))],
|
||||
)
|
||||
@application_checks.is_owner()
|
||||
async def managecog(interaction: nextcord.Interaction,
|
||||
action: str = SlashOption(choices=["load", "unload", "reload","sync"]),
|
||||
cog: str = SlashOption(required=False)
|
||||
):
|
||||
errors = ""
|
||||
try:
|
||||
if action == "load":
|
||||
bot.load_extension(cog)
|
||||
elif action == "unload":
|
||||
bot.unload_extension(cog)
|
||||
elif action == "reload":
|
||||
bot.reload_extension(cog)
|
||||
elif action == "sync":
|
||||
bot.add_all_application_commands()
|
||||
await bot.sync_all_application_commands()
|
||||
await interaction.response.send_message(f"Done!" + (f"\n{errors}" if errors != "" else ""), ephemeral=True)
|
||||
|
||||
except Exception as e:
|
||||
await interaction.response.send_message(e, ephemeral=True)
|
||||
|
||||
bot.run(os.getenv("TOKEN"))
|
98
cogs/liveupdate.py
Normal file
98
cogs/liveupdate.py
Normal file
|
@ -0,0 +1,98 @@
|
|||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import requests
|
||||
import asyncio
|
||||
import nextcord
|
||||
import aiosqlite as sqlite3
|
||||
from nextcord.ext import commands, application_checks, tasks
|
||||
from nextcord import TextInputStyle, IntegrationType
|
||||
|
||||
|
||||
class liveupdate(commands.Cog):
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
|
||||
@nextcord.slash_command(
|
||||
name="liveupdates",
|
||||
description="Get live updates on a game",
|
||||
integration_types=[
|
||||
IntegrationType.user_install,
|
||||
IntegrationType.guild_install,
|
||||
],
|
||||
contexts=[
|
||||
nextcord.InteractionContextType.guild,
|
||||
nextcord.InteractionContextType.bot_dm,
|
||||
nextcord.InteractionContextType.private_channel,
|
||||
],
|
||||
force_global=True,
|
||||
)
|
||||
async def liveupdatecreate(self, interaction: nextcord.Interaction, gameid: str):
|
||||
data = requests.get(f"https://mmolb.com/api/game/{gameid}").json()
|
||||
await interaction.response.send_message(f"{data["AwayTeamName"]} {data["AwayTeamEmoji"]} **{data["EventLog"][-1]["away_score"]}** vs {data["HomeTeamName"]} {data["HomeTeamEmoji"]} **{data["EventLog"][-1]["home_score"]}**")
|
||||
message = await interaction.original_message()
|
||||
await self.bot.cur.execute(f"""
|
||||
INSERT INTO liveupdate VALUES
|
||||
({interaction.guild_id}, {interaction.user.id}, {interaction.channel_id}, {message.id}, "{gameid}", {len(data["EventLog"])})
|
||||
""")
|
||||
await self.bot.db.commit()
|
||||
await self.updatelivegames.start()
|
||||
|
||||
|
||||
@nextcord.slash_command(
|
||||
name="liveupdatesdelete",
|
||||
description="Delete a subscribed update",
|
||||
integration_types=[
|
||||
IntegrationType.user_install,
|
||||
IntegrationType.guild_install,
|
||||
],
|
||||
contexts=[
|
||||
nextcord.InteractionContextType.guild,
|
||||
nextcord.InteractionContextType.bot_dm,
|
||||
nextcord.InteractionContextType.private_channel,
|
||||
],
|
||||
force_global=True,
|
||||
)
|
||||
async def liveupdatedelete(self, interaction: nextcord.Interaction, messageid: float):
|
||||
await self.bot.cur.execute(f"""
|
||||
DELETE from liveupdate WHERE messageid = {messageid}
|
||||
""")
|
||||
await self.bot.db.commit()
|
||||
await interaction.response.send_message("stopped updates for message") #TODO This will be a button
|
||||
|
||||
@tasks.loop(seconds=10.0)
|
||||
async def updatelivegames(self):
|
||||
res = await self.bot.cur.execute("SELECT serverid,userid,channelid,messageid,gameid,offset FROM liveupdate")
|
||||
res = await res.fetchall()
|
||||
for [serverid,userid,channelid,messageid,gameid,offset] in res:
|
||||
channel = self.bot.get_channel(channelid)
|
||||
message = await channel.fetch_message(messageid)
|
||||
data = requests.get(f"https://mmolb.com/api/game/{gameid}/live?after={offset}").json()
|
||||
if len(data["entries"]) > 0:
|
||||
splitstr = message.content.split("\n")
|
||||
splitstr.pop(0)
|
||||
while len(splitstr)>(5-len(data["entries"])):
|
||||
splitstr.pop(0)
|
||||
print(splitstr)
|
||||
basedata = requests.get(f"https://mmolb.com/api/game/{gameid}").json()
|
||||
finalstr = f"{basedata["AwayTeamName"]} {basedata["AwayTeamEmoji"]} **{data["entries"][-1]["away_score"]}** vs {basedata["HomeTeamName"]} {basedata["HomeTeamEmoji"]} **{data["entries"][-1]["home_score"]}**"
|
||||
for i in splitstr:
|
||||
finalstr += f"\n{i}"
|
||||
for i in data["entries"]:
|
||||
finalstr += f"\n{i['message']}"
|
||||
await self.bot.cur.execute(f"""
|
||||
UPDATE liveupdate set offset = {offset+1} WHERE messageid = '{messageid}'
|
||||
""") #Could do this for every meessage subscribed to the game but since the messages go one by one... maybe I should change that
|
||||
await self.bot.db.commit()
|
||||
if i["event"] == "Recordkeeping":
|
||||
await self.bot.cur.execute(f"""
|
||||
DELETE from liveupdate WHERE messageid = {messageid}
|
||||
""")
|
||||
await self.bot.db.commit()
|
||||
await message.edit(finalstr)
|
||||
|
||||
def setup(bot: commands.Bot):
|
||||
bot.add_cog(liveupdate(bot))
|
305
cogs/team.py
Normal file
305
cogs/team.py
Normal file
|
@ -0,0 +1,305 @@
|
|||
import hashlib
|
||||
import json
|
||||
from pathlib import Path
|
||||
from datetime import datetime, timedelta, timezone
|
||||
import requests
|
||||
import re
|
||||
import asyncio
|
||||
import nextcord
|
||||
from nextcord.ext import commands, application_checks
|
||||
from nextcord import TextInputStyle, IntegrationType
|
||||
|
||||
#all of this code is by evilscientist3, full credit to them
|
||||
HTTP_CACHE_DIR = Path("http_cache")
|
||||
|
||||
statdict = {
|
||||
"BA":(0.4, 0.35, 0.3, 0.25, 0.2, 0.15),
|
||||
"OBP":(0.475, 0.425, 0.375, 0.325, 0.275, 0.225),
|
||||
"SLG":(0.55, 0.52, 0.46, 0.4, 0.36, 0.33),
|
||||
"OPS":(1.0, 0.9, 0.8, 0.7, 0.6, 0.5),
|
||||
"ERA":(2.75, 3.25, 3.75, 4.75, 5.5, 6.5),
|
||||
"WHIP":(1, 1.1, 1.25, 1.4, 1.5, 1.6)
|
||||
}
|
||||
|
||||
# whether ascending is GOOD (True) or BAD (False) for figure of merit
|
||||
NGUdict = {
|
||||
"BA":True,
|
||||
"OBP":True,
|
||||
"SLG":True,
|
||||
"OPS":True,
|
||||
"ERA":False,
|
||||
"WHIP":False
|
||||
}
|
||||
|
||||
# sig figs for each stat
|
||||
sfdict = {
|
||||
"BA":3,
|
||||
"OBP":3,
|
||||
"SLG":3,
|
||||
"OPS":3,
|
||||
"ERA":2,
|
||||
"WHIP":2
|
||||
}
|
||||
|
||||
def eval_stat(stat, val):
|
||||
|
||||
# modifier for if descending is better. in this case logic is reversed; most easily accounted for by multiplying bounds and values by -1
|
||||
# why bother keeping descending values' bounds positive, you ask? for human readability and ease of modification, I say
|
||||
mod = 1 if NGUdict[stat] else -1
|
||||
|
||||
# set of ANSI colour codes. best to worst, one more than bounds
|
||||
colours = [32, 72, 36, 30, 33, 31, 31]
|
||||
|
||||
# multiply bounds to account for descending
|
||||
bounds = [mod*i for i in statdict[stat]]
|
||||
mval = mod*val
|
||||
|
||||
# flag for colour being set, as an "else" condition to the entire loop
|
||||
cset = 0
|
||||
|
||||
# main loop - checks quality in descending order
|
||||
for i, j in enumerate(bounds):
|
||||
if mval > j:
|
||||
colour = colours[i]
|
||||
cset = 1
|
||||
break
|
||||
# check if value is below all bounds
|
||||
if cset == 0:
|
||||
colour = colours[-1]
|
||||
|
||||
# return stat string in correct colour
|
||||
return f"\033[38;5;{colour}m{val:.{sfdict[stat]}f}\033[0m"
|
||||
|
||||
def stable_str_hash(in_val: str) -> str:
|
||||
return hex(int(hashlib.md5(in_val.encode("utf-8")).hexdigest(), 16))[2:]
|
||||
|
||||
def get_json(url: str) -> dict:
|
||||
HTTP_CACHE_DIR.mkdir(exist_ok=True)
|
||||
|
||||
cache = {}
|
||||
cache_file_path = HTTP_CACHE_DIR / f"{stable_str_hash(url)}.json"
|
||||
try:
|
||||
with open(cache_file_path) as cache_file:
|
||||
cache = json.load(cache_file)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
|
||||
# Return from cache if the cache entry is less than 5 minutes old
|
||||
now = datetime.now(timezone.utc)
|
||||
if (
|
||||
url in cache and
|
||||
"__archived_at" in cache[url] and
|
||||
cache[url]["__archived_at"] > (now - timedelta(minutes=5)).isoformat()
|
||||
):
|
||||
return cache[url]
|
||||
|
||||
data = requests.get(url).json()
|
||||
cache[url] = data
|
||||
cache[url]["__archived_at"] = now.isoformat()
|
||||
with open(cache_file_path, "w") as cache_file:
|
||||
json.dump(cache, cache_file)
|
||||
|
||||
return data
|
||||
|
||||
def teamstats(MY_TEAM_ID):
|
||||
finalstr = ""
|
||||
team_obj = get_json(f"https://mmolb.com/api/team/{MY_TEAM_ID}")
|
||||
|
||||
for player in team_obj["Players"]:
|
||||
player_obj = get_json(f"https://mmolb.com/api/player/{player['PlayerID']}")
|
||||
|
||||
try:
|
||||
# I'm pretty sure IDs are lexicographically ordered, so we want the
|
||||
# maximum value to get stats for the latest season
|
||||
stats_obj = player_obj["Stats"][max(player_obj["Stats"].keys())]
|
||||
except ValueError:
|
||||
finalstr += (f"No stats for {player["FirstName"]} {player["LastName"]} \n")
|
||||
continue
|
||||
|
||||
singles = stats_obj.get("singles", 0)
|
||||
doubles = stats_obj.get("doubles", 0)
|
||||
triples = stats_obj.get("triples", 0)
|
||||
home_runs = stats_obj.get("home_runs", 0)
|
||||
hits = singles + doubles + triples + home_runs
|
||||
bb = stats_obj.get("walked", 0)
|
||||
hbp = stats_obj.get("hit_by_pitch", 0)
|
||||
earned_runs = stats_obj.get("earned_runs", 0)
|
||||
hits_allowed = stats_obj.get("hits_allowed", 0)
|
||||
walks = stats_obj.get("walks", 0)
|
||||
|
||||
try:
|
||||
ab = stats_obj["at_bats"]
|
||||
except KeyError:
|
||||
ba_str = None
|
||||
else:
|
||||
ba = hits / ab
|
||||
ba_str = f"BA: {eval_stat('BA', ba)} ({ab} AB)"
|
||||
|
||||
try:
|
||||
pa = stats_obj["plate_appearances"]
|
||||
ab = stats_obj["at_bats"]
|
||||
except KeyError:
|
||||
ops_str = None
|
||||
else:
|
||||
obp = (hits + bb + hbp) / pa
|
||||
slg = (singles + 2 * doubles + 3 * triples + 4 * home_runs) / ab
|
||||
ops = obp + slg
|
||||
pa_str = dot_format(pa)
|
||||
ops_str = f"OBP: {eval_stat('OBP', obp)}, SLG: {eval_stat('SLG', slg)}, OPS: {eval_stat('OPS', ops)} ({pa_str} PA)"
|
||||
|
||||
try:
|
||||
ip = stats_obj["outs"] / 3
|
||||
except KeyError:
|
||||
era_str = None
|
||||
else:
|
||||
era = 9 * earned_runs / ip
|
||||
whip = (walks + hits_allowed) / ip
|
||||
ip_str = dot_format(ip)
|
||||
era_str = f"ERA {eval_stat('ERA', era)} WHIP {eval_stat('WHIP', whip)} ({ip_str} IP)"
|
||||
|
||||
stats_str = ", ".join(s for s in [ba_str, ops_str, era_str] if s is not None)
|
||||
if stats_str:
|
||||
finalstr += (f"{player["Position"]} {player["FirstName"]} {player["LastName"]} ")
|
||||
finalstr += "" * (30 - (len(player["Position"]) + len(player["FirstName"]) + len(player["LastName"])))
|
||||
finalstr += f"{stats_str}\n"
|
||||
|
||||
return finalstr
|
||||
|
||||
|
||||
def dot_format(in_val: float) -> str:
|
||||
ip_whole = int(in_val)
|
||||
ip_remainder = int((in_val - ip_whole) / 3 * 10)
|
||||
if ip_remainder == 0:
|
||||
ip_str = f"{ip_whole}"
|
||||
else:
|
||||
ip_str = f"{ip_whole}.{ip_remainder}"
|
||||
return ip_str
|
||||
#-------
|
||||
|
||||
class TeamView(nextcord.ui.View):
|
||||
def __init__(self):
|
||||
super().__init__(timeout=None)
|
||||
|
||||
@nextcord.ui.button(
|
||||
label="Players", style=nextcord.ButtonStyle.green, custom_id="team:players"
|
||||
)
|
||||
async def getplayersbutton(self, button: nextcord.ui.Button, interaction: nextcord.Interaction):
|
||||
await interaction.response.defer(ephemeral=True)
|
||||
loop = asyncio.get_event_loop()
|
||||
ogmsg = interaction.message.embeds
|
||||
embed = ogmsg[0]
|
||||
stats = await loop.run_in_executor(None, teamstats,embed.fields[0].value)
|
||||
splistats = [stats[i:i+1900] for i in range(0, len(stats), 1900)] #TODO make better as in don't do cutoff
|
||||
for i in splistats:
|
||||
await interaction.followup.send(f"```ansi\n{i}```",ephemeral=True)
|
||||
|
||||
class team(commands.Cog):
|
||||
|
||||
def __init__(self, bot: commands.Bot):
|
||||
self.bot = bot
|
||||
|
||||
|
||||
@nextcord.slash_command(
|
||||
name="greaterteam",
|
||||
description="Get information about a greater leauge team",
|
||||
integration_types=[
|
||||
IntegrationType.user_install,
|
||||
IntegrationType.guild_install,
|
||||
],
|
||||
contexts=[
|
||||
nextcord.InteractionContextType.guild,
|
||||
nextcord.InteractionContextType.bot_dm,
|
||||
nextcord.InteractionContextType.private_channel,
|
||||
],
|
||||
force_global=True,
|
||||
)
|
||||
async def greaterteam(self, interaction: nextcord.Interaction, teamid: str = nextcord.SlashOption(
|
||||
name = "team",
|
||||
description = "The greater leauge team",
|
||||
choices = {
|
||||
"Seattle Shine" : "6805db0cac48194de3cd40a2",
|
||||
"Chicago Seers": "6805db0cac48194de3cd40b5",
|
||||
"Atlanta Tree Frogs": "6805db0cac48194de3cd40ee",
|
||||
"Baltimore Lady Beetles": "6805db0cac48194de3cd407c",
|
||||
"St. Louis Archers": "6805db0cac48194de3cd400a",
|
||||
"Anaheim Angles": "6805db0cac48194de3cd401d",
|
||||
"Miami Merfolk": "6805db0cac48194de3cd4101",
|
||||
"Washington Baseball Team": "6805db0cac48194de3cd3ff7",
|
||||
"Dallas Instruments": "6805db0cac48194de3cd4114",
|
||||
"Roswell Weather Balloons": "6805db0cac48194de3cd40c8",
|
||||
"Toronto Northern Lights": "6805db0cac48194de3cd4043",
|
||||
"Kansas City Stormchasers": "6805db0cac48194de3cd4069",
|
||||
"Philadelphia Phantasms": "6805db0cac48194de3cd40db",
|
||||
"Durhamshire Badgers": "6805db0cac48194de3cd4056",
|
||||
"Boston Street Sweepers": "6805db0cac48194de3cd408f",
|
||||
"Brooklyn Scooter Dodgers": "6805db0cac48194de3cd4030"
|
||||
}
|
||||
)):
|
||||
await interaction.response.defer()
|
||||
data = requests.get(f"https://mmolb.com/api/team/{teamid}").json()
|
||||
color = tuple(int(data["Color"][i:i+2], 16) for i in (0, 2, 4))
|
||||
embed = nextcord.Embed(title=f"{data["Location"]} {data["Name"]} {data["Emoji"]}", description=f"{data["Motto"]}", colour = nextcord.Color.from_rgb(color[0], color[1], color[2]))
|
||||
embed.add_field(name="Team ID", value=f"{teamid}", inline=False)
|
||||
embed.add_field(name="Wins", value=f"{data["Record"]["Regular Season"]["Wins"]}", inline=True)
|
||||
embed.add_field(name="Losses", value=f"{data["Record"]["Regular Season"]["Losses"]}", inline=True)
|
||||
embed.add_field(name="Run Differential", value=f"{data["Record"]["Regular Season"]["RunDifferential"]}", inline=True)
|
||||
embed.add_field(name="Augments", value=f"{data["Augments"]}", inline=True)
|
||||
embed.add_field(name="Champtionships", value=f"{data["Championships"]}", inline=True)
|
||||
await interaction.edit_original_message(embed=embed,view=TeamView())
|
||||
|
||||
@nextcord.slash_command(
|
||||
name="lesserteam",
|
||||
description="Get information about a lesser leauge team",
|
||||
integration_types=[
|
||||
IntegrationType.user_install,
|
||||
IntegrationType.guild_install,
|
||||
],
|
||||
contexts=[
|
||||
nextcord.InteractionContextType.guild,
|
||||
nextcord.InteractionContextType.bot_dm,
|
||||
nextcord.InteractionContextType.private_channel,
|
||||
],
|
||||
force_global=True,
|
||||
)
|
||||
async def lesserteam(self, interaction: nextcord.Interaction, teamid: str = nextcord.SlashOption(
|
||||
name = "team",
|
||||
description = "The lesser leauge team"
|
||||
)):
|
||||
await interaction.response.defer()
|
||||
data = requests.get(f"https://mmolb.com/api/team/{teamid}").json()
|
||||
color = tuple(int(data["Color"][i:i+2], 16) for i in (0, 2, 4))
|
||||
embed = nextcord.Embed(title=f"{data["Location"]} {data["Name"]} {data["Emoji"]}", description=f"{data["Motto"]}", colour = nextcord.Color.from_rgb(color[0], color[1], color[2]))
|
||||
embed.add_field(name="Team ID", value=f"{teamid}", inline=False)
|
||||
embed.add_field(name="Wins", value=f"{data["Record"]["Regular Season"]["Wins"]}", inline=True)
|
||||
embed.add_field(name="Losses", value=f"{data["Record"]["Regular Season"]["Losses"]}", inline=True)
|
||||
embed.add_field(name="Run Differential", value=f"{data["Record"]["Regular Season"]["RunDifferential"]}", inline=True)
|
||||
embed.add_field(name="Augments", value=f"{data["Augments"]}", inline=True)
|
||||
embed.add_field(name="Champtionships", value=f"{data["Championships"]}", inline=True)
|
||||
await interaction.edit_original_message(embed=embed,view=TeamView())
|
||||
|
||||
|
||||
@nextcord.slash_command(
|
||||
name="teamstats",
|
||||
description="Get a teams stats",
|
||||
integration_types=[
|
||||
IntegrationType.user_install,
|
||||
IntegrationType.guild_install,
|
||||
],
|
||||
contexts=[
|
||||
nextcord.InteractionContextType.guild,
|
||||
nextcord.InteractionContextType.bot_dm,
|
||||
nextcord.InteractionContextType.private_channel,
|
||||
],
|
||||
force_global=True,
|
||||
)
|
||||
async def teamstats(self, interaction: nextcord.Interaction, teamid: str):
|
||||
await interaction.response.defer()
|
||||
loop = asyncio.get_event_loop()
|
||||
stats = await loop.run_in_executor(None, teamstats,teamid)
|
||||
splistats = [stats[i:i+1900] for i in range(0, len(stats), 1900)] #TODO make better as in don't do cutoff
|
||||
await interaction.edit_original_message(content=f"```ansi\n{splistats[0]}```")
|
||||
splistats.pop(0)
|
||||
for i in splistats:
|
||||
await interaction.followup.send(f"```ansi\n{i}```")
|
||||
def setup(bot: commands.Bot):
|
||||
bot.add_cog(team(bot))
|
7
dockerfile
Normal file
7
dockerfile
Normal file
|
@ -0,0 +1,7 @@
|
|||
FROM python:3.13
|
||||
|
||||
WORKDIR /mmolbbot
|
||||
COPY . /mmolbbot
|
||||
RUN pip install -U --timeout 10000 -r requirements.txt
|
||||
|
||||
CMD ["python", "bot.py"]
|
5
requirements.txt
Normal file
5
requirements.txt
Normal file
|
@ -0,0 +1,5 @@
|
|||
nextcord
|
||||
requests
|
||||
aiosqlite
|
||||
aiohttp
|
||||
python-dotenv
|
11
shell.nix
Normal file
11
shell.nix
Normal file
|
@ -0,0 +1,11 @@
|
|||
{ pkgs ? import <nixpkgs> {}}:
|
||||
|
||||
pkgs.mkShell {
|
||||
packages = [
|
||||
pkgs.python312
|
||||
pkgs.python312Packages.pip
|
||||
pkgs.python312Packages.python-dotenv
|
||||
pkgs.python312Packages.venvShellHook
|
||||
];
|
||||
venvDir = "./.venv";
|
||||
}
|
Loading…
Reference in a new issue