25 Commits

Author SHA1 Message Date
herel f439fc9595 fix: add favicon to docker and load events immediately 2025-03-12 11:53:55 +01:00
herel b2932685b6 fix: webapi schedule uses mobile api endpoint 2025-03-12 11:46:20 +01:00
herel 7089e6df6d fix: favicon and some stuff 2025-03-12 11:15:45 +01:00
herel 488b2316f0 feat: add webapi, lower python version requirement and add a Dockerfile 2025-03-12 10:52:11 +01:00
herel 31da987f81 chore: poetry 2.0.0 2025-01-13 09:13:53 +01:00
herel 6de4f5f9d8 feat: add mobile methods 2025-01-13 09:06:39 +01:00
herel 7d65a4aee7 feat: extract players from pdf 2025-01-08 14:06:35 +01:00
herel ab10d3cc57 chore: v0.3.0 2024-11-27 14:33:50 +01:00
herel 2c0bf905da feat: add new category and days to schedule argument
🛠️ myice.py -> Added new enum value 'u111' to AgeGroup, modified get_schedule function to accept num_days parameter, added num_days option to schedule function.
2024-11-27 14:32:51 +01:00
herel 831b3fe38f Here is a brief summary of the changes:
🛠️ poetry.lock -> Updated rl-ai-tools version from 1.10.0 to 1.10.1
🛠️ pyproject.toml -> Updated myice version from v0.2.4 to v0.2.5
2024-11-04 14:46:10 +01:00
herel 7d996ca9a9 doc: update 2024-11-04 08:41:01 +01:00
herel 1bcecf1274 Here is a brief summary of the changes:
🛠️ myice.py -> Modified get_schedule function and updated AI system prompt
🛠️ poetry.lock -> Updated rich package version from 13.9.3 to 13.9.4 and rl-ai-tools package version from 1.9.0 to 1.10.0
🛠️ pyproject.toml -> Updated project version from v0.2.0 to v0.2.4
2024-11-04 08:37:56 +01:00
herel 6f3c7bf8d2 🛠️ pyproject.toml -> Updated version from v0.1.6 to v0.2.0 2024-11-01 15:24:14 +01:00
herel 2c67596d38 🛠️ myice/myice.py -> Added new AI functionality using Infomaniak LLM API
🛠️ poetry.lock -> Added rl-ai-tools package dependency
🛠️ pyproject.toml -> Updated dependencies and added rl-ai-tools source
2024-11-01 15:23:51 +01:00
herel a39899ebb1 chore: myice.py -> Added file parameter to save_cookies function and used Path object for file handling. 2024-11-01 14:02:10 +01:00
herel 320ec291e6 🟢 pyproject.toml: Added Poetry project configuration file
🛠️ pyproject.toml -> Updated version from 'v0.1.5' to 'v0.1.6'
2024-11-01 13:18:38 +01:00
herel 85f84b540a 🟢 README.md -> added installation instructions and introduction section
🛠️ .mdlrc -> set style path
🛠️ mdl.rb -> created a new ruby script
🔴 RECUPE\_SCHEDULE.sh -> removed shell script
2024-11-01 13:15:35 +01:00
herel 7939414995 🛠️ myice.py -> updated error messages to redirect to stderr
🛠️ myice.py -> changed login and get\_userid prints to write to stderr
🔴 pyproject.toml -> removed version v0.1.3
🟢 pyproject.toml -> added version v0.1.5
2024-11-01 11:23:05 +01:00
herel b2baa07371 🛠️ myice/myice.py -> updated function get_login by simplifying config reading; renamed functions open, get_game_pdf, get_practice_pdf to os_open, get_game_pdf_os, get_practice_pdf_os respectively
🔴 pyproject.toml -> removed version 'v0.1.2', now at 'v0.1.3'
2024-11-01 11:17:26 +01:00
herel 56e7ba92e8 🛠️ myice/myice.py -> added EventType enum and updated search command to support optional event type filtering
- Added `EventType` enum with two values: 'game' and 'practice'.
- Updated `parse_schedule` function to accept an optional `event_type_filter` argument which filters displayed events based on their type.
- Changed `age_group` parameter in `parse_schedule` to allow `None`.
2024-11-01 11:06:10 +01:00
herel a3e1d9ccbf 🛠️ pyproject.toml -> updated project dependencies and version number from 0.1.0 to v0.1.1
🛠️ poetry.lock -> content-hash changed from e11a99... to 32eb77...
2024-11-01 10:53:58 +01:00
herel 3b2351efc6 🟢 LICENSE.txt (New MIT license added)
🛠️ pyproject.toml -> Updated poetry dependencies, authors email changed, added license info
2024-11-01 10:41:39 +01:00
herel e313b824d7 🟢 README.md: Updated command to retrieve schedule and added new commands for searching events by age group and retrieving match details
🛠️ myice/myice.py: Implemented a function to parse the schedule JSON file based on given age groups, improved error handling, enhanced formatting when printing results, added a new command `myice search`
2024-11-01 10:38:43 +01:00
herel 11bbfdfc10 chore: add from Python.gitignore 2024-11-01 10:07:04 +01:00
herel 1644523d40 initial import 2024-11-01 09:59:45 +01:00
12 changed files with 1818 additions and 249 deletions
+1
View File
@@ -0,0 +1 @@
style "#{File.dirname(__FILE__)}/mdl.rb"
+2 -2
View File
@@ -29,7 +29,7 @@ repos:
- id: mypy
exclude: ^(docs/|example-plugin/)
args: [--ignore-missing-imports]
additional_dependencies: [types-requests]
additional_dependencies: [types-requests, PyPDF2]
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
hooks:
@@ -56,7 +56,7 @@ repos:
- id: misspell
args: ["-i", "charactor"]
- repo: https://github.com/python-poetry/poetry
rev: "1.8.0"
rev: "2.0.0"
hooks:
- id: poetry-check
- id: poetry-lock
+20
View File
@@ -0,0 +1,20 @@
FROM python:3.11
RUN install -o www-data -g www-data -d -m 0755 /var/www
USER www-data
RUN curl -sSL https://install.python-poetry.org | python3 -
ENV PATH=/var/www/.local/bin:/usr/local/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
COPY README.md pyproject.toml poetry.lock docker-entrypoint.sh index.html favicon.ico /var/www/
COPY myice /var/www/myice
WORKDIR /var/www
RUN poetry install && . $(poetry env info -p)
EXPOSE 8000
ENTRYPOINT [ "/var/www/docker-entrypoint.sh" ]
+30 -1
View File
@@ -1,5 +1,24 @@
# myice
## intro
Avec tout ça on va aller chercher sur MyIce les planning des gamins et générer
les pdf qu'on veut
## install
with [uv](https://docs.astral.sh/uv/getting-started/installation/):
```shell
uv tool install --extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ myice
```
with [pipx](https://pipx.pypa.io/stable/installation/):
```shell
pipx install --extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ myice
```
## récupérer le schedule
```shell
@@ -10,7 +29,7 @@ myice schedule -o schedule.json
### listing
Pour récupérer les event des U13 Elite par exemple:
Pour récupérer les event des U13 Elite par example:
```shell
myice search "U13 (Elite)"
@@ -57,3 +76,13 @@ et pour la convoc d'un entraînement:
Opening file practice_561855.pdf
```
### AI
```text
myice ai
> prochain match u13 top ?
< Le prochain match de l'équipe U13 Top se déroulera le dimanche 10 novembre 2024 contre HC Ajoie à la Raffeisen Arena de Porrentruy. Le match débutera à 14h00 et se terminera à 16h15.
> et les u13 a ?
< Le prochain match de l'équipe U13 A se déroulera le samedi 9 novembre 2024 contre HC Vallorbe à P. du Frézillon, 1337 Vallorbe VD. Le match débutera à 13h00 et se terminera à 15h00. Le prochain match à domicile de l'équipe U13 A se déroulera le dimanche 10 novembre 2024 contre CP Meyrin à Les Vernets, Glace extérieure, 1227 Les Acacias GE. Le match débutera à 13h00 et se terminera à 15h00.
```
+3
View File
@@ -0,0 +1,3 @@
#!/bin/sh
exec /var/www/.local/bin/poetry run fastapi run myice/webapi.py
BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

+184
View File
@@ -0,0 +1,184 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Événements - Jeux</title>
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
</head>
<body>
<div class="container mt-4">
<h1 class="mb-4">Événements - Jeux</h1>
<div id="apikeyContainer" class="mb-3"></div>
<div id="eventFilters" style="display: none;">
<div class="mb-3">
<label for="agegroup" class="form-label">Filtrer par groupe d'âge</label>
<select id="agegroup" class="form-select">
<option value="">Tous</option>
</select>
</div>
<button id="fetchEvents" class="btn btn-primary mb-3">Charger les événements</button>
</div>
<div id="eventList" class="row"></div>
<!-- Modal pour afficher les détails d'un événement -->
<div class="modal fade" id="eventDetailsModal" tabindex="-1" aria-labelledby="eventDetailsLabel"
aria-hidden="true">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title" id="eventDetailsLabel">Détails de l'événement</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body" id="eventDetailsContent">Chargement...</div>
</div>
</div>
</div>
</div>
<script>
document.addEventListener("DOMContentLoaded", function () {
const apikeyContainer = document.getElementById("apikeyContainer");
const eventFilters = document.getElementById("eventFilters");
const agegroupSelect = document.getElementById("agegroup");
const eventList = document.getElementById("eventList");
const fetchButton = document.getElementById("fetchEvents");
const eventDetailsContent = document.getElementById("eventDetailsContent");
const apiBaseUrl = window.location.origin;
let storedApiKey = localStorage.getItem("apikey");
let lastFetchedEvents = [];
function renderApiKeyInput() {
if (storedApiKey) {
apikeyContainer.innerHTML = `<button id="disconnect" class="btn btn-danger">Déconnecter</button>`;
document.getElementById("disconnect").addEventListener("click", () => {
localStorage.removeItem("apikey");
location.reload();
});
eventFilters.style.display = "block";
fetchEvents(storedApiKey);
} else {
apikeyContainer.innerHTML = `
<label for="apikey" class="form-label">API Key</label>
<input type="text" id="apikey" class="form-control" placeholder="Entrez votre API Key">
<button id="validateApiKey" class="btn btn-success mt-2">Valider</button>
`;
eventFilters.style.display = "none";
document.getElementById("validateApiKey").addEventListener("click", saveApiKey);
document.getElementById("apikey").addEventListener("keypress", function (event) {
if (event.key === "Enter") {
saveApiKey();
}
});
}
}
function saveApiKey() {
const key = document.getElementById("apikey").value;
if (key) {
localStorage.setItem("apikey", key);
location.reload();
} else {
alert("Veuillez entrer une clé API valide.");
}
}
renderApiKeyInput();
fetchButton.addEventListener("click", () => {
if (!storedApiKey) {
alert("Veuillez entrer une clé API");
return;
}
fetchEvents(storedApiKey);
});
agegroupSelect.addEventListener("change", () => {
displayEvents(lastFetchedEvents);
});
function fetchEvents(apiKey) {
fetch(`${apiBaseUrl}/schedule`, {
headers: { "Authorization": `Bearer ${apiKey}` }
})
.then(response => response.json())
.then(data => {
lastFetchedEvents = data.filter(event => event.event === "Jeu");
updateAgeGroupOptions(lastFetchedEvents);
displayEvents(lastFetchedEvents);
})
.catch(error => console.error("Erreur lors du chargement des événements:", error));
}
function updateAgeGroupOptions(events) {
let agegroups = new Set(events.map(event => `${event.agegroup} ${event.name}`.trim()));
agegroupSelect.innerHTML = '<option value="">Tous</option>';
agegroups.forEach(group => {
const option = document.createElement("option");
option.value = group;
option.textContent = group;
agegroupSelect.appendChild(option);
});
}
function displayEvents(events) {
eventList.innerHTML = "";
let selectedAgegroup = agegroupSelect.value;
let filteredEvents = events.filter(event => event.event === "Jeu" && (selectedAgegroup === "" || `${event.agegroup} ${event.name}` === selectedAgegroup));
if (filteredEvents.length === 0) {
eventList.innerHTML = "<p class='text-muted'>Aucun événement 'Jeu' trouvé.</p>";
return;
}
filteredEvents.forEach(event => {
const eventCard = document.createElement("div");
eventCard.className = "col-md-4 mb-3";
eventCard.innerHTML = `
<div class="card" style="border-left: 5px solid ${event.color}" data-id="${event.id_event}">
<div class="card-body">
<h5 class="card-title">${event.title}</h5>
<p class="card-text"><strong>Lieu:</strong> ${event.place}</p>
<p class="card-text"><strong>Heure:</strong> ${event.start} - ${event.end}</p>
</div>
</div>
`;
eventCard.addEventListener("click", () => fetchEventDetails(event.id_event));
eventList.appendChild(eventCard);
});
}
function fetchEventDetails(eventId) {
fetch(`${apiBaseUrl}/game/${eventId}`, {
headers: { "Authorization": `Bearer ${storedApiKey}` }
})
.then(response => response.json())
.then(data => {
eventDetailsContent.innerHTML = `
<h5>${data.title}</h5>
<p><strong>Type:</strong> ${data.type}</p>
<p><strong>Lieu:</strong> ${data.place}</p>
<p><strong>Heure:</strong> ${data.time_start} - ${data.time_end}</p>
<h6>Joueurs convoqués:</h6>
<ul>${data.convocation.available.map(player => {
let number = player.number ? player.number : "N/A";
let position = player.position ? player.position : "N/A";
return `<li>${number} - ${player.fname} ${player.lname} (${position}, ${player.dob})</li>`;
}).join('')}</ul>
`;
new bootstrap.Modal(document.getElementById('eventDetailsModal')).show();
})
.catch(error => console.error("Erreur lors du chargement des détails de l'événement:", error));
}
});
</script>
</body>
</html>
+2
View File
@@ -0,0 +1,2 @@
all
rule 'MD013', :ignore_code_blocks => true
+251 -23
View File
@@ -9,11 +9,16 @@ import json
import os
import re
import sys
import tempfile
from enum import Enum
from pathlib import Path
from typing import Annotated
from typing import List, Tuple
import PyPDF2
import requests
import typer
from rich import print
from rl_ai_tools import utils # type: ignore
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0"
@@ -29,6 +34,12 @@ class AgeGroup(str, Enum):
u13p = "U13 (Prép)"
u15t = "U15 (Top)"
u15a = "U15 (A)"
u111 = "U11 (1)"
class EventType(str, Enum):
game = "game"
practice = "practice"
def load_cookies(file: str = "cookies.txt") -> requests.cookies.RequestsCookieJar:
@@ -40,12 +51,13 @@ def load_cookies(file: str = "cookies.txt") -> requests.cookies.RequestsCookieJa
return requests.cookies.cookiejar_from_dict(cj_dict)
def save_cookies():
with open("cookies.txt", "w") as f:
def save_cookies(file: str = "cookies.txt"):
cookie_jar_file = Path(file)
with cookie_jar_file.open("w") as f:
f.write(json.dumps(requests.utils.dict_from_cookiejar(session.cookies)))
def get_login(local_file: str = "myice.ini") -> tuple[str, str, int]:
def get_login(local_file: str = "myice.ini") -> tuple[str, str, int, str]:
config = configparser.ConfigParser()
config.read(
[
@@ -60,19 +72,22 @@ def get_login(local_file: str = "myice.ini") -> tuple[str, str, int]:
username = default_config.get("username")
password = default_config.get("password")
userid = default_config.getint("userid")
token = default_config.get("token")
if not username or not password:
print("Error: please configure username/password in ini file")
print(
"Error: please configure username/password in ini file", file=sys.stderr
)
sys.exit(1)
else:
print("Error: please configure username/password in ini file")
print("Error: please configure username/password in ini file", file=sys.stderr)
sys.exit(1)
return username, password, userid
return username, password, userid, token
def do_login():
global session
global userid
username, password, userid = get_login()
username, password, userid, token = get_login()
r = session.get("https://app.myice.hockey/", headers={"User-Agent": user_agent})
r.raise_for_status()
form_data = {
@@ -115,15 +130,16 @@ def wrapper_session(func):
def wrapper(*args, **kwargs):
global session, userid
session = requests.Session()
# session.verify = False
session.cookies = load_cookies()
session.cookies.clear_expired_cookies()
if not session.cookies.get("mih_v3_cookname"):
print("login...")
print("login...", file=sys.stderr)
do_login()
save_cookies()
_, _, userid = get_login()
_, _, userid, _ = get_login()
if not userid:
print("get userid...")
print("get userid...", file=sys.stderr)
userid = get_userid()
return func(*args, **kwargs)
@@ -131,13 +147,13 @@ def wrapper_session(func):
@wrapper_session
def get_schedule() -> str:
def get_schedule(num_days: int) -> str:
global session
global userid
assert session and userid
now = datetime.datetime.now()
date_start = now + datetime.timedelta(days=1)
date_end = date_start + datetime.timedelta(days=7)
date_start = now
date_end = date_start + datetime.timedelta(days=num_days)
r = session.post(
"https://app.myice.hockey/inc/processclubplanning.php",
data={
@@ -198,20 +214,20 @@ def schedule(
"--outfile", "-o", help="file to write result to, or stdout if none"
),
] = None,
num_days: Annotated[int, typer.Option("--days")] = 7,
):
"""
Fetch schedule as json
"""
schedule = get_schedule()
schedule = get_schedule(num_days)
if outfile:
with outfile.open("w") as f:
f.write(schedule)
else:
print("Schedule:", file=sys.stderr)
print(schedule)
def open(file: str) -> None:
def os_open(file: str) -> None:
if os.uname().sysname == "Linux":
os.system(f"xdg-open {file}")
else:
@@ -219,16 +235,42 @@ def open(file: str) -> None:
os.system(f"open {file}")
def extract_players(pdf_file: Path) -> List[str]:
reader = PyPDF2.PdfReader(pdf_file)
page = reader.pages[0]
players = []
def visitor_body(text, cm, tm, fontDict, fontSize):
(x, y) = (tm[4], tm[5])
# print(tm, text)
if x > 79 and x < 80 and y < 741.93:
# and y < 741.93 and y > 741.93 - 585.18:
players.append(text)
page.extract_text(visitor_text=visitor_body)
return players
@app.command("game")
def get_game_pdf(
game_id: Annotated[int, typer.Argument(help="ID of game to gen pdf for")],
open_file: Annotated[bool, typer.Option("--open", "-o")] = False,
):
"""
Genate the pdf for the game invitation
"""
output_filename = f"game_{game_id}.pdf"
if open_file:
output_filename = f"game_{game_id}.pdf"
else:
output_filename = tempfile.NamedTemporaryFile().name
game_pdf(game_id, Path(output_filename))
open(output_filename)
if open_file:
os_open(output_filename)
else:
players = extract_players(Path(output_filename))
print("Players:")
print("\n".join(players))
@app.command("practice")
@@ -240,12 +282,16 @@ def get_practice_pdf(
"""
output_filename = f"practice_{game_id}.pdf"
practice_pdf(game_id, Path(output_filename))
open(output_filename)
os_open(output_filename)
@app.command("search")
def parse_schedule(
age_group: Annotated[AgeGroup, typer.Argument(...)],
age_group: Annotated[AgeGroup | None, typer.Option(...)] = None,
event_type_filter: Annotated[
EventType | None,
typer.Option("--type", help="Only display events of this type"),
] = None,
schedule_file: Annotated[
Path, typer.Option(help="schedule json file to parse")
] = Path("schedule.json"),
@@ -255,9 +301,22 @@ def parse_schedule(
"""
with schedule_file.open("r") as f:
data = json.load(f)
for event in [x for x in data if x["agegroup"] == age_group]:
# print(json.dumps(event, indent=2))
raw_title = event["title"].removeprefix(age_group + "\n")
# age_group filter
if age_group:
events = [x for x in data if x["agegroup"] == age_group]
else:
events = [x for x in data if x["agegroup"] in AgeGroup]
# event_type filter
if event_type_filter:
if event_type_filter.value == EventType.game:
events = [x for x in events if "event" in x and x["event"] == "Jeu"]
else:
events = [x for x in events if "event" not in x or x["event"] == "Jeu"]
for event in events:
if age_group:
raw_title = event["title"].removeprefix(age_group + "\n")
else:
raw_title = event["title"]
title = " ".join(raw_title.split("\n"))
start = datetime.datetime.fromisoformat(event["start"])
start_fmt = start.strftime("%H:%M")
@@ -273,5 +332,174 @@ def parse_schedule(
print(f"{event_type}: {title}\n")
@app.command("ai")
def check_with_ai(
schedule_file: Annotated[
Path, typer.Option(help="schedule json file to parse")
] = Path("schedule.json"),
):
"""
Search through the schedule with natural language using Infomaniak LLM API
"""
if not utils.init():
sys.exit(1)
with schedule_file.open("r") as f:
schedule_data = json.load(f)
schedule_data = [x for x in schedule_data if x["agegroup"] in AgeGroup]
for event in schedule_data:
event["team"] = event["agegroup"].replace("(", "").replace(")", "")
del event["agegroup"]
when = datetime.datetime.now().strftime("%d-%m-%Y et il est %H:%M")
system = "\n".join(
[
"Tu es une IA connaissant bien les données suivantes, qui décrivent les match et entraînements d'équipes de hockey sur glace.",
f"aujourd'hui, nous sommes le {when}"
"attention: ce qu'il y a entre parenthèse après la catégorie est une catégorie à part entière, example, u13 a = U13 (A) et ça correspond aux agegroup",
"assure-toi de ne pas confondre les catégories d'age dans tes réponses. ne donne pas une réponse pour la mauvaise équipe",
"ne confond pas top, elite, a, prép",
"```json",
json.dumps(schedule_data),
"```",
],
)
history: List[Tuple[str, str]] = []
while True:
try:
question = input("> ")
except EOFError:
break
answer = utils.llm_inference(
question, history, system=system, model="mixtral8x22b"
)
print("<", answer)
history.append((question, answer))
mobile_headers = {
"Accept": "application/json, text/plain, */*",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/605.1.15 (KHTML, like Gecko) Mobile/15E148",
"Sec-Fetch-Site": "cross-site",
"Accept-Language": "fr-FR,fr;q=0.9",
"Sec-Fetch-Mode": "cors",
"Origin": "null",
"Sec-Fetch-Dest": "empty",
"Content-Type": "application/x-www-form-urlencoded",
}
def mobile_login():
import base64
username, password, userid, token = get_login()
with requests.post(
"https://app.myice.hockey/api/mobilerest/login",
headers=mobile_headers,
data=f"login_email={base64.b64encode(username.encode()).decode()}&login_password={base64.b64encode(password.encode()).decode()}&language=FR&v=2",
# verify=False,
) as r:
r.raise_for_status()
return {
"id": r.json()["userid"],
"token": r.json()["userinfo"]["token"],
"id_club": r.json()["userinfo"]["id_club"],
}
userdata = {
"id": 0,
"id_club": 0,
"token": "",
}
def refresh_data():
with requests.post(
"https://app.myice.hockey/api/mobilerest/refreshdata",
headers=mobile_headers,
data=f"token={userdata['token']}&id_club=186&language=FR",
# verify=False,
) as r:
r.raise_for_status()
return r.json()
@app.command("mobile")
def mobile(
token: Annotated[str, typer.Option(envvar="MYICE_TOKEN")] = "",
id_club: Annotated[int, typer.Option(envvar="MYICE_CLUB")] = 0,
):
global userdata
if not token:
userdata = mobile_login()
else:
userdata = {
"id": 0,
"id_club": id_club,
"token": token,
}
print(json.dumps(refresh_data(), indent=2))
@app.command("mobile-game")
def mobile_game(
game_id: Annotated[int, typer.Argument(help="game id")],
token: Annotated[str, typer.Option(envvar="MYICE_TOKEN")] = "",
id_club: Annotated[int, typer.Option(envvar="MYICE_CLUB")] = 0,
raw: Annotated[bool, typer.Option(help="display raw output")] = False,
):
global userdata
username, password, userid, existing_token = get_login()
if token:
userdata = {
"id": 0,
"id_club": 186,
"token": token,
}
else:
if existing_token:
userdata = {
"id": userid,
"id_club": 186,
"token": existing_token,
}
else:
userdata = mobile_login()
# data = refresh_data()
with requests.post(
"https://app.myice.hockey/api/mobilerest/getevent",
headers=mobile_headers,
data="&".join(
[
f"token={userdata['token']}",
f"id_event={game_id}",
"type=games",
f"id_player={userdata['id']}",
f"id_club={userdata['id_club']}",
"language=FR",
]
),
# verify=False,
) as r:
data = r.json()["eventData"]
players = data["convocation"]["available"]
if raw:
print(data)
print(
f"Game {game_id}: {data['title']} ({data['type']}, {data['is_away']}) at {data['time_start']}"
)
print(f"{len(players)} players")
for player in sorted(
players,
key=lambda x: (
x["position"] if x["position"] else "none",
x["dob"],
),
):
print(
f"[{player['position']}] {player['fname']} {player['lname']} ({player['dob']})"
)
if __name__ == "__main__":
app()
+94
View File
@@ -0,0 +1,94 @@
import requests
from typing import Annotated
from fastapi import FastAPI, Header, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import FileResponse
from pydantic import BaseModel
from . import myice
origins = ["*"]
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
class AuthHeaders(BaseModel):
Authorization: str
def token(self) -> str:
return self.Authorization.split(" ")[1]
def authorized(self) -> bool:
if self.token() == "abc":
return True
return False
@app.get("/")
async def home():
return FileResponse("index.html")
@app.get("/favicon.ico")
async def favico():
return FileResponse("favicon.ico")
@app.get("/schedule")
async def schedule(
headers: Annotated[AuthHeaders, Header()],
):
if not headers.authorized():
raise HTTPException(401, detail="get out")
username, password, userid, existing_token = myice.get_login()
if existing_token:
myice.userdata = {
"id": userid,
"id_club": 186,
"token": existing_token,
}
else:
myice.userdata = myice.mobile_login()
return myice.refresh_data()["club_games"]
@app.get("/game/{game_id}")
async def game(
headers: Annotated[AuthHeaders, Header()],
game_id: int,
):
username, password, userid, existing_token = myice.get_login()
if existing_token:
myice.userdata = {
"id": userid,
"id_club": 186,
"token": existing_token,
}
else:
myice.userdata = myice.mobile_login()
# data = refresh_data()
with requests.post(
"https://app.myice.hockey/api/mobilerest/getevent",
headers=myice.mobile_headers,
data="&".join(
[
f"token={myice.userdata['token']}",
f"id_event={game_id}",
"type=games",
f"id_player={myice.userdata['id']}",
f"id_club={myice.userdata['id_club']}",
"language=FR",
]
),
# verify=False,
) as r:
data = r.json()["eventData"]
return data
Generated
+1207 -215
View File
File diff suppressed because it is too large Load Diff
+24 -8
View File
@@ -1,23 +1,39 @@
[tool.poetry]
[project]
name = "myice"
version = "0.1.0"
version = "v0.3.2"
description = "myice parsing"
authors = ["Rene Luria <rene@luria.ch>"]
authors = [
{ name = "Rene Luria", "email" = "<rene@luria.ch>"},
]
license = "MIT"
readme = "README.md"
requires-python = ">=3.11"
dependencies = [
"requests (>=2.32.3,<2.33.0)",
"typer (>=0.15.1,<0.16.0)",
"pypdf2 (>=3.0.1)",
"rl-ai-tools >=1.9.0",
"fastapi[standard] (>=0.115.11,<0.116.0)",
]
[tool.poetry.dependencies]
python = "^3.12"
requests = "^2.32.3"
typer = {extras = ["all"], version = "^0.12.5"}
rl-ai-tools = { source = "infomaniak" }
[tool.poetry.group.dev.dependencies]
ipykernel = "^6.29.5"
types-requests = "^2.32.0.20241016"
mypy = "^1.15.0"
[[tool.poetry.source]]
name = "infomaniak"
url = "https://pypi.purple.infomaniak.ch"
priority = "supplemental"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
[tool.poetry.scripts]
[projectscripts]
myice = 'myice.myice:app'