34 Commits

Author SHA1 Message Date
herel e11eb6943b feat: add club selection support and improve mobile API integration 2025-08-18 11:26:56 +02:00
herel f3d272a0b6 fix: update get_login return type to allow None values 2025-08-18 09:56:11 +02:00
herel 4a8fe64177 refactor: simplify AgeGroup enum and enhance normalization
Remove redundant ELIT variants from AgeGroup enum and implement case/spelling tolerant normalization function. The new normalize_age_group function handles case-insensitive matching and spelling variations (Elite/ELIT/Elit) while mapping to canonical enum values.

This reduces the enum from 15 to 13 entries while maintaining full backward compatibility.
2025-08-15 07:50:26 +02:00
herel e9eb9c9dc8 docs: add and format AGENTS.md file
Adding documentation file for agentic coding agents with proper markdown formatting to pass linting checks.
2025-08-14 12:56:32 +02:00
herel c4e206b6c3 feat: update AgeGroup class to include all age groups from schedule.json
Added missing age group enums based on the schedule.json file to ensure proper parsing of all scheduled events.
2025-08-14 12:54:29 +02:00
herel aa42af6b1f refactor: improve JSON handling and API reliability
- Add robust JSON sanitization function to handle malformed API responses
- Select club after login for proper session initialization
- Enhance API request headers for better compatibility
- Add JSON parsing fallback with sanitization for malformed files
- Add poetry plugin requirement for export functionality
2025-08-14 12:50:13 +02:00
herel 333b89194b feat: add healthcheck 2025-03-13 11:29:30 +01:00
herel e5b58b8db2 chore: change title and aligment 2025-03-13 08:51:18 +01:00
herel 263e546c78 fix: sort players by number 2025-03-12 11:59:22 +01:00
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
5 changed files with 54 additions and 322 deletions
+9 -112
View File
@@ -1,9 +1,9 @@
# myice # myice
## introduction ## intro
With this tool, you can fetch kids' schedules from MyIce and generate Avec tout ça on va aller chercher sur MyIce les planning des gamins et générer
the PDFs you need. les pdf qu'on veut
## install ## install
@@ -19,102 +19,17 @@ with [pipx](https://pipx.pypa.io/stable/installation/):
pipx install --extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ myice pipx install --extra-index-url https://gitea.parano.ch/api/packages/herel/pypi/simple/ myice
``` ```
## configuration ## récupérer le schedule
Create a `myice.ini` file with your credentials:
```ini
[default]
username = your_email@example.com
password = your_password
userid = 12345
token = your_api_token
club_id = 172
```
You can also create multiple sections for different users:
```ini
[default]
username = user1@example.com
password = password1
userid = 12345
token = token1
club_id = 172
[second_kid]
username = charlie@example.com
password = password2
userid = 67890
token = token2
club_id = 186
```
Note: The `userid`, `token`, and `club_id` fields are optional. If you don't
have them, the tool can fetch them automatically using the mobile-login
command (see below).
To fetch the `token` and `club_id` using mobile-login:
```shell
myice mobile-login
```
This will output the necessary information that you can add to your `myice.ini` file.
## web interface
The tool includes a web interface that can be accessed by running the web API server:
```shell
uv run fastapi run myice/webapi.py
```
Then open your browser at `http://localhost:8000`. The web interface allows you to:
- Select between different configured accounts
- View upcoming games and practices
- See detailed information about events including player rosters
The web API provides the following endpoints:
- `/schedule` - Get the schedule for a specific account
- `/game/{game_id}` - Get details for a specific game
- `/accounts` - Get a list of available accounts
- `/health` - Health check endpoint
All endpoints (except `/health`) require an Authorization header with a Bearer token.
For development purposes, you can use `abc` as the token.
## mobile functions
The tool includes several mobile API functions that interact with MyIce's
mobile API:
- `myice mobile-login` - Authenticate and get authentication tokens
- `myice mobile` - Fetch game data using the mobile API
- `myice mobile-game {game_id}` - Get detailed information about a specific game
These functions can be useful for debugging or when the regular web
interface is not available.
## fetch schedule
```shell ```shell
myice schedule -o schedule.json myice schedule -o schedule.json
``` ```
To use a specific configuration section:
```shell
myice schedule -o schedule.json --config-section isaac
```
## data ## data
### listing ### listing
To fetch events for U13 Elite for example: Pour récupérer les event des U13 Elite par example:
```shell ```shell
myice search "U13 (Elite)" myice search "U13 (Elite)"
@@ -140,11 +55,11 @@ practice: On-Ice Patinoire des Vernets - Patinoire Extérieure
game: Saison HC Ajoie game: Saison HC Ajoie
``` ```
And we retrieve the match roster: Et on récupère la convoc du match:
### match ### match
To get the roster for the match against Ajoie, the ID is 117015: alors pour avoir la convocation du match contre Ajoie, l'id c'est 117015:
```shell ```shell
myice game 117015 myice game 117015
@@ -152,15 +67,9 @@ Opening file game_117015.pdf
``` ```
To use a specific configuration section: ### entraînement
```shell et pour la convoc d'un entraînement:
myice game 117015 --config-section isaac
```
### practice
And for a practice roster:
```shell ```shell
myice practice 561855 myice practice 561855
@@ -168,12 +77,6 @@ Opening file practice_561855.pdf
``` ```
To use a specific configuration section:
```shell
myice practice 561855 --config-section isaac
```
### AI ### AI
```text ```text
@@ -183,9 +86,3 @@ To use a specific configuration section:
> et les u13 a ? > 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. < 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.
``` ```
To use a specific configuration section:
```shell
myice ai --config-section isaac
```
+5 -53
View File
@@ -16,12 +16,6 @@
<div id="apikeyContainer" class="mb-3"></div> <div id="apikeyContainer" class="mb-3"></div>
<div id="eventFilters" style="display: none;"> <div id="eventFilters" style="display: none;">
<div class="mb-3">
<label for="account" class="form-label">Sélectionner un compte</label>
<select id="account" class="form-select">
<option value="default">Compte par défaut</option>
</select>
</div>
<div class="mb-3"> <div class="mb-3">
<label for="agegroup" class="form-label">Filtrer par groupe d'âge</label> <label for="agegroup" class="form-label">Filtrer par groupe d'âge</label>
<select id="agegroup" class="form-select"> <select id="agegroup" class="form-select">
@@ -52,7 +46,6 @@
document.addEventListener("DOMContentLoaded", function () { document.addEventListener("DOMContentLoaded", function () {
const apikeyContainer = document.getElementById("apikeyContainer"); const apikeyContainer = document.getElementById("apikeyContainer");
const eventFilters = document.getElementById("eventFilters"); const eventFilters = document.getElementById("eventFilters");
const accountSelect = document.getElementById("account");
const agegroupSelect = document.getElementById("agegroup"); const agegroupSelect = document.getElementById("agegroup");
const eventList = document.getElementById("eventList"); const eventList = document.getElementById("eventList");
const fetchButton = document.getElementById("fetchEvents"); const fetchButton = document.getElementById("fetchEvents");
@@ -61,20 +54,16 @@
let storedApiKey = localStorage.getItem("apikey"); let storedApiKey = localStorage.getItem("apikey");
let lastFetchedEvents = []; let lastFetchedEvents = [];
let storedAccount = localStorage.getItem("account") || "default";
function renderApiKeyInput() { function renderApiKeyInput() {
if (storedApiKey) { if (storedApiKey) {
apikeyContainer.innerHTML = `<button id="disconnect" class="btn btn-danger">Déconnecter</button>`; apikeyContainer.innerHTML = `<button id="disconnect" class="btn btn-danger">Déconnecter</button>`;
document.getElementById("disconnect").addEventListener("click", () => { document.getElementById("disconnect").addEventListener("click", () => {
localStorage.removeItem("apikey"); localStorage.removeItem("apikey");
localStorage.removeItem("account");
location.reload(); location.reload();
}); });
eventFilters.style.display = "block"; eventFilters.style.display = "block";
// Load available accounts from server or use predefined ones fetchEvents(storedApiKey);
updateAccountOptions();
fetchEvents(storedApiKey, storedAccount);
} else { } else {
apikeyContainer.innerHTML = ` apikeyContainer.innerHTML = `
<label for="apikey" class="form-label">API Key</label> <label for="apikey" class="form-label">API Key</label>
@@ -101,58 +90,22 @@
} }
} }
function updateAccountOptions() {
// Fetch available accounts from the server
fetch(`${apiBaseUrl}/accounts`, {
headers: { "Authorization": `Bearer ${storedApiKey}` }
})
.then(response => response.json())
.then(accounts => {
accountSelect.innerHTML = '';
accounts.forEach(account => {
const option = document.createElement("option");
option.value = account.name;
option.textContent = account.label;
if (account.name === storedAccount) {
option.selected = true;
}
accountSelect.appendChild(option);
});
})
.catch(error => {
console.error("Erreur lors du chargement des comptes:", error);
// Fallback to default options
accountSelect.innerHTML = `
<option value="default" ${storedAccount === "default" ? "selected" : ""}>Compte par défaut</option>
`;
});
}
renderApiKeyInput(); renderApiKeyInput();
accountSelect.addEventListener("change", () => {
const selectedAccount = accountSelect.value;
localStorage.setItem("account", selectedAccount);
if (storedApiKey) {
fetchEvents(storedApiKey, selectedAccount);
}
});
fetchButton.addEventListener("click", () => { fetchButton.addEventListener("click", () => {
if (!storedApiKey) { if (!storedApiKey) {
alert("Veuillez entrer une clé API"); alert("Veuillez entrer une clé API");
return; return;
} }
const selectedAccount = accountSelect.value; fetchEvents(storedApiKey);
fetchEvents(storedApiKey, selectedAccount);
}); });
agegroupSelect.addEventListener("change", () => { agegroupSelect.addEventListener("change", () => {
displayEvents(lastFetchedEvents); displayEvents(lastFetchedEvents);
}); });
function fetchEvents(apiKey, account) { function fetchEvents(apiKey) {
fetch(`${apiBaseUrl}/schedule?account=${account}`, { fetch(`${apiBaseUrl}/schedule`, {
headers: { "Authorization": `Bearer ${apiKey}` } headers: { "Authorization": `Bearer ${apiKey}` }
}) })
.then(response => response.json()) .then(response => response.json())
@@ -203,8 +156,7 @@
} }
function fetchEventDetails(eventId) { function fetchEventDetails(eventId) {
const selectedAccount = accountSelect.value; fetch(`${apiBaseUrl}/game/${eventId}`, {
fetch(`${apiBaseUrl}/game/${eventId}?account=${selectedAccount}`, {
headers: { "Authorization": `Bearer ${storedApiKey}` } headers: { "Authorization": `Bearer ${storedApiKey}` }
}) })
.then(response => response.json()) .then(response => response.json())
+33 -99
View File
@@ -165,7 +165,6 @@ def save_cookies(file: str = "cookies.txt"):
def get_login( def get_login(
local_file: str = "myice.ini", local_file: str = "myice.ini",
config_section: str = "default",
) -> tuple[str, str, int | None, str | None, int | None]: ) -> tuple[str, str, int | None, str | None, int | None]:
config = configparser.ConfigParser() config = configparser.ConfigParser()
config.read( config.read(
@@ -175,29 +174,22 @@ def get_login(
local_file, local_file,
] ]
) )
if config_section in config.sections(): if "default" in config.sections():
selected_config = config[config_section] default_config = config["default"]
elif "default" in config.sections():
# Fallback to default section if specified section doesn't exist username = default_config.get("username")
selected_config = config["default"] password = default_config.get("password")
print( userid = default_config.getint("userid")
f"Warning: Section '{config_section}' not found, using 'default' section", token = default_config.get("token")
file=sys.stderr, club_id = default_config.getint("club_id")
) if not username or not password:
print(
"Error: please configure username/password in ini file", file=sys.stderr
)
sys.exit(1)
else: else:
print("Error: please configure username/password in ini file", file=sys.stderr) print("Error: please configure username/password in ini file", file=sys.stderr)
sys.exit(1) sys.exit(1)
username = selected_config.get("username")
password = selected_config.get("password")
userid = selected_config.getint("userid", fallback=None)
token = selected_config.get("token", fallback=None)
club_id = selected_config.getint("club_id", fallback=None)
if not username or not password:
print("Error: please configure username/password in ini file", file=sys.stderr)
sys.exit(1)
return username, password, userid, token, club_id return username, password, userid, token, club_id
@@ -210,14 +202,10 @@ def select_club(club_id: int = 172):
r.raise_for_status() r.raise_for_status()
def do_login(config_section: str = "default"): def do_login():
global session global session
global userid global userid
username, password, userid_tmp, token, club_id = get_login( username, password, userid, token, club_id = get_login()
config_section=config_section
)
if userid_tmp is not None:
userid = userid_tmp
r = session.get("https://app.myice.hockey/", headers={"User-Agent": user_agent}) r = session.get("https://app.myice.hockey/", headers={"User-Agent": user_agent})
r.raise_for_status() r.raise_for_status()
form_data = { form_data = {
@@ -237,8 +225,7 @@ def do_login(config_section: str = "default"):
) )
r.raise_for_status() r.raise_for_status()
# select the club we want # select the club we want
if club_id: select_club(club_id)
select_club(club_id)
def get_userid(): def get_userid():
@@ -262,18 +249,15 @@ def get_userid():
def wrapper_session(func): def wrapper_session(func):
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
global session, userid global session, userid
# Extract config_section from kwargs if present
config_section = kwargs.get("config_section", "default")
session = requests.Session() session = requests.Session()
# session.verify = False # session.verify = False
session.cookies = load_cookies() session.cookies = load_cookies()
session.cookies.clear_expired_cookies() session.cookies.clear_expired_cookies()
if not session.cookies.get("mih_v3_cookname"): if not session.cookies.get("mih_v3_cookname"):
print("login...", file=sys.stderr) print("login...", file=sys.stderr)
do_login(config_section=config_section) do_login()
save_cookies() save_cookies()
_, _, userid, _, _ = get_login(config_section=config_section) _, _, userid, _, _ = get_login()
if not userid: if not userid:
print("get userid...", file=sys.stderr) print("get userid...", file=sys.stderr)
userid = get_userid() userid = get_userid()
@@ -283,7 +267,7 @@ def wrapper_session(func):
@wrapper_session @wrapper_session
def get_schedule(num_days: int, config_section: str = "default") -> str: def get_schedule(num_days: int) -> str:
global session global session
global userid global userid
assert session and userid assert session and userid
@@ -314,7 +298,7 @@ def get_schedule(num_days: int, config_section: str = "default") -> str:
@wrapper_session @wrapper_session
def game_pdf(gameid: int, outfile: Path, config_section: str = "default"): def game_pdf(gameid: int, outfile: Path):
global session, userid global session, userid
assert session and userid assert session and userid
r = session.get( r = session.get(
@@ -331,7 +315,7 @@ def game_pdf(gameid: int, outfile: Path, config_section: str = "default"):
@wrapper_session @wrapper_session
def practice_pdf(gameid: int, outfile: Path, config_section: str = "default"): def practice_pdf(gameid: int, outfile: Path):
global session, userid global session, userid
assert session and userid assert session and userid
r = session.get( r = session.get(
@@ -356,17 +340,11 @@ def schedule(
), ),
] = None, ] = None,
num_days: Annotated[int, typer.Option("--days")] = 7, num_days: Annotated[int, typer.Option("--days")] = 7,
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
): ):
""" """
Fetch schedule as json Fetch schedule as json
""" """
schedule = get_schedule(num_days, config_section=config_section) schedule = get_schedule(num_days)
# Sanitize the JSON response using our proven approach # Sanitize the JSON response using our proven approach
sanitized_schedule = sanitize_json_response(schedule) sanitized_schedule = sanitize_json_response(schedule)
if outfile: if outfile:
@@ -405,12 +383,6 @@ def extract_players(pdf_file: Path) -> List[str]:
def get_game_pdf( def get_game_pdf(
game_id: Annotated[int, typer.Argument(help="ID of game to gen pdf for")], game_id: Annotated[int, typer.Argument(help="ID of game to gen pdf for")],
open_file: Annotated[bool, typer.Option("--open", "-o")] = False, open_file: Annotated[bool, typer.Option("--open", "-o")] = False,
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
): ):
""" """
Genate the pdf for the game invitation Genate the pdf for the game invitation
@@ -419,7 +391,7 @@ def get_game_pdf(
output_filename = f"game_{game_id}.pdf" output_filename = f"game_{game_id}.pdf"
else: else:
output_filename = tempfile.NamedTemporaryFile().name output_filename = tempfile.NamedTemporaryFile().name
game_pdf(game_id, Path(output_filename), config_section=config_section) game_pdf(game_id, Path(output_filename))
if open_file: if open_file:
os_open(output_filename) os_open(output_filename)
else: else:
@@ -431,18 +403,12 @@ def get_game_pdf(
@app.command("practice") @app.command("practice")
def get_practice_pdf( def get_practice_pdf(
game_id: Annotated[int, typer.Argument(help="ID of practice to gen pdf for")], game_id: Annotated[int, typer.Argument(help="ID of practice to gen pdf for")],
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
): ):
""" """
Genate the pdf for the practice invitation Genate the pdf for the practice invitation
""" """
output_filename = f"practice_{game_id}.pdf" output_filename = f"practice_{game_id}.pdf"
practice_pdf(game_id, Path(output_filename), config_section=config_section) practice_pdf(game_id, Path(output_filename))
os_open(output_filename) os_open(output_filename)
@@ -456,12 +422,6 @@ def parse_schedule(
schedule_file: Annotated[ schedule_file: Annotated[
Path, typer.Option(help="schedule json file to parse") Path, typer.Option(help="schedule json file to parse")
] = Path("schedule.json"), ] = Path("schedule.json"),
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
): ):
""" """
Parse schedule.json to look for specific games or practices Parse schedule.json to look for specific games or practices
@@ -511,12 +471,6 @@ def check_with_ai(
schedule_file: Annotated[ schedule_file: Annotated[
Path, typer.Option(help="schedule json file to parse") Path, typer.Option(help="schedule json file to parse")
] = Path("schedule.json"), ] = Path("schedule.json"),
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
): ):
""" """
Search through the schedule with natural language using Infomaniak LLM API Search through the schedule with natural language using Infomaniak LLM API
@@ -569,10 +523,10 @@ mobile_headers = {
} }
def mobile_login(config_section: str = "default"): def mobile_login():
import base64 import base64
username, password, _, token, club_id = get_login(config_section=config_section) username, password, _, token, club_id = get_login()
if token and club_id: if token and club_id:
return {"id": 0, "token": token, "id_club": club_id} return {"id": 0, "token": token, "id_club": club_id}
@@ -604,37 +558,23 @@ def refresh_data():
"https://app.myice.hockey/api/mobilerest/refreshdata", "https://app.myice.hockey/api/mobilerest/refreshdata",
headers=mobile_headers, headers=mobile_headers,
data=f"token={userdata['token']}&id_club={userdata['id_club']}&language=FR", data=f"token={userdata['token']}&id_club={userdata['id_club']}&language=FR",
# verify=False, verify=False,
) as r: ) as r:
r.raise_for_status() r.raise_for_status()
return r.json() return r.json()
@app.command("mobile-login") @app.command("mobile-login")
def do_mobile_login( def do_mobile_login():
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
):
global userdata global userdata
userdata = mobile_login(config_section=config_section) userdata = mobile_login()
print(json.dumps(userdata, indent=2)) print(json.dumps(userdata, indent=2))
@app.command("mobile") @app.command("mobile")
def mobile( def mobile():
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
):
global userdata global userdata
userdata = mobile_login(config_section=config_section) userdata = mobile_login()
games = [x for x in refresh_data().get("club_games")] games = [x for x in refresh_data().get("club_games")]
print(json.dumps(games, indent=2)) print(json.dumps(games, indent=2))
@@ -643,15 +583,9 @@ def mobile(
def mobile_game( def mobile_game(
game_id: Annotated[int, typer.Argument(help="game id")], game_id: Annotated[int, typer.Argument(help="game id")],
raw: Annotated[bool, typer.Option(help="display raw output")] = False, raw: Annotated[bool, typer.Option(help="display raw output")] = False,
config_section: Annotated[
str,
typer.Option(
"--config-section", "-c", help="Configuration section to use from INI file"
),
] = "default",
): ):
global userdata global userdata
userdata = mobile_login(config_section=config_section) userdata = mobile_login()
# data = refresh_data() # data = refresh_data()
with requests.post( with requests.post(
@@ -667,7 +601,7 @@ def mobile_game(
"language=FR", "language=FR",
] ]
), ),
# verify=False, verify=False,
) as r: ) as r:
data = r.json()["eventData"] data = r.json()["eventData"]
players = data["convocation"]["available"] players = data["convocation"]["available"]
+6 -57
View File
@@ -74,86 +74,35 @@ async def favico():
@app.get("/schedule") @app.get("/schedule")
async def schedule( async def schedule(
headers: Annotated[AuthHeaders, Header()], headers: Annotated[AuthHeaders, Header()],
account: str = "default",
): ):
if not headers.authorized(): if not headers.authorized():
raise HTTPException(401, detail="get out") raise HTTPException(401, detail="get out")
username, password, userid, existing_token, club_id = myice.get_login( username, password, userid, existing_token = myice.get_login()
config_section=account
)
if existing_token: if existing_token:
myice.userdata = { myice.userdata = {
"id": userid, "id": userid,
"id_club": club_id or 186, "id_club": 186,
"token": existing_token, "token": existing_token,
} }
else: else:
myice.userdata = myice.mobile_login(config_section=account) myice.userdata = myice.mobile_login()
return myice.refresh_data()["club_games"] return myice.refresh_data()["club_games"]
@app.get("/accounts")
async def accounts(
headers: Annotated[AuthHeaders, Header()],
):
if not headers.authorized():
raise HTTPException(401, detail="get out")
# Import configparser to read the available sections
import configparser
from pathlib import Path
config = configparser.ConfigParser()
config_files = [
Path("~/.config/myice.ini").expanduser(),
Path("myice.ini"),
]
# Read all config files
for config_file in config_files:
if config_file.exists():
try:
config.read(config_file, encoding="utf-8")
except Exception:
# Try without specifying encoding
config.read(config_file)
# Get all sections (accounts) from the config
accounts = []
for section in config.sections():
if section != "DEFAULT": # Skip DEFAULT section
# Capitalize first letter for display
label = (
section[0].upper() + section[1:]
if len(section) > 1
else section.upper()
)
accounts.append({"name": section, "label": label})
# If no accounts found, return default
if not accounts:
accounts = [{"name": "default", "label": "Default"}]
return accounts
@app.get("/game/{game_id}") @app.get("/game/{game_id}")
async def game( async def game(
headers: Annotated[AuthHeaders, Header()], headers: Annotated[AuthHeaders, Header()],
game_id: int, game_id: int,
account: str = "default",
): ):
username, password, userid, existing_token, club_id = myice.get_login( username, password, userid, existing_token = myice.get_login()
config_section=account
)
if existing_token: if existing_token:
myice.userdata = { myice.userdata = {
"id": userid, "id": userid,
"id_club": club_id or 186, "id_club": 186,
"token": existing_token, "token": existing_token,
} }
else: else:
myice.userdata = myice.mobile_login(config_section=account) myice.userdata = myice.mobile_login()
# data = refresh_data() # data = refresh_data()
with requests.post( with requests.post(
+1 -1
View File
@@ -1,6 +1,6 @@
[project] [project]
name = "myice" name = "myice"
version = "v0.4.0" version = "v0.3.2"
description = "myice parsing" description = "myice parsing"
authors = [ authors = [
{ name = "Rene Luria", "email" = "<rene@luria.ch>"}, { name = "Rene Luria", "email" = "<rene@luria.ch>"},