initial import
This commit is contained in:
5
.gitignore
vendored
Normal file
5
.gitignore
vendored
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
myice.ini
|
||||||
|
*.pdf
|
||||||
|
schedule.json
|
||||||
|
.envrc
|
||||||
|
cookies.txt
|
||||||
63
.pre-commit-config.yaml
Normal file
63
.pre-commit-config.yaml
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
---
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v3.2.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: check-case-conflict
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-executables-have-shebangs
|
||||||
|
- id: check-merge-conflict
|
||||||
|
- id: check-yaml
|
||||||
|
- id: check-added-large-files
|
||||||
|
- id: detect-private-key
|
||||||
|
- repo: https://github.com/astral-sh/ruff-pre-commit
|
||||||
|
# Ruff version.
|
||||||
|
rev: v0.6.8
|
||||||
|
hooks:
|
||||||
|
# Run the linter.
|
||||||
|
- id: ruff
|
||||||
|
args: [--fix]
|
||||||
|
# Run the formatter.
|
||||||
|
- id: ruff-format
|
||||||
|
args: [--diff, --target-version, py312]
|
||||||
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
|
rev: v1.11.2
|
||||||
|
hooks:
|
||||||
|
- id: mypy
|
||||||
|
exclude: ^(docs/|example-plugin/)
|
||||||
|
args: [--ignore-missing-imports]
|
||||||
|
additional_dependencies: [types-requests]
|
||||||
|
- repo: https://github.com/adrienverge/yamllint.git
|
||||||
|
rev: v1.35.1
|
||||||
|
hooks:
|
||||||
|
- id: yamllint
|
||||||
|
args: [--strict]
|
||||||
|
|
||||||
|
- repo: https://github.com/markdownlint/markdownlint
|
||||||
|
rev: v0.12.0
|
||||||
|
hooks:
|
||||||
|
- id: markdownlint
|
||||||
|
exclude: "^.github|(^docs/_sidebar\\.md$)"
|
||||||
|
|
||||||
|
- repo: https://github.com/shellcheck-py/shellcheck-py
|
||||||
|
rev: v0.10.0.1
|
||||||
|
hooks:
|
||||||
|
- id: shellcheck
|
||||||
|
args: ["--severity=error"]
|
||||||
|
exclude: "^.git"
|
||||||
|
files: "\\.sh$"
|
||||||
|
|
||||||
|
- repo: https://github.com/golangci/misspell
|
||||||
|
rev: v0.6.0
|
||||||
|
hooks:
|
||||||
|
- id: misspell
|
||||||
|
args: ["-i", "charactor"]
|
||||||
|
- repo: https://github.com/python-poetry/poetry
|
||||||
|
rev: "1.8.0"
|
||||||
|
hooks:
|
||||||
|
- id: poetry-check
|
||||||
|
- id: poetry-lock
|
||||||
|
- id: poetry-install
|
||||||
59
README.md
Normal file
59
README.md
Normal file
@@ -0,0 +1,59 @@
|
|||||||
|
# myice
|
||||||
|
|
||||||
|
## récupérer le schedule
|
||||||
|
|
||||||
|
```shell
|
||||||
|
./myice.py schedule -o schedule.json
|
||||||
|
```
|
||||||
|
|
||||||
|
## data
|
||||||
|
|
||||||
|
Pour récupérer les event des U13 Elite par exemple:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
❯ jq -r '.[] | select(.agegroup == "U13 (Elite)") | [.date, .id_event, .event, .title] | join(" - ")' schedule.json
|
||||||
|
2024-11-01 - 561896 - - U13 (Elite)
|
||||||
|
Spécial
|
||||||
|
Shooting Box Groupe #3
|
||||||
|
2024-11-01 - 561904 - - U13 (Elite)
|
||||||
|
Off-Ice
|
||||||
|
Patinoire des Vernets - Patinoire Extérieure
|
||||||
|
2024-11-01 - 561855 - - U13 (Elite)
|
||||||
|
On-Ice
|
||||||
|
Patinoire des Vernets - Patinoire Extérieure
|
||||||
|
2024-11-04 - 576653 - - U13 (Elite)
|
||||||
|
Off-Ice
|
||||||
|
Patinoire des Vernets - Patinoire Extérieure
|
||||||
|
2024-11-04 - 572066 - - U13 (Elite)
|
||||||
|
On-Ice
|
||||||
|
Patinoire des Vernets - Patinoire Extérieure
|
||||||
|
2024-11-05 - 576652 - - U13 (Elite)
|
||||||
|
Off-Ice
|
||||||
|
Patinoire des Vernets - Patinoire Extérieure
|
||||||
|
2024-11-05 - 572068 - - U13 (Elite)
|
||||||
|
On-Ice
|
||||||
|
Patinoire des Vernets - Patinoire Extérieure
|
||||||
|
2024-11-02 - 117015 - Jeu - U13 (Elite)
|
||||||
|
Saison
|
||||||
|
HC Ajoie
|
||||||
|
```
|
||||||
|
|
||||||
|
### match
|
||||||
|
|
||||||
|
alors pour avoir la convocation du match contre Ajoie, l'id c'est 117015:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
❯ ./get_schedule.py game 117015
|
||||||
|
Opening file game_117015.pdf
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
### entraînement
|
||||||
|
|
||||||
|
et pour la convoc d'un entraînement:
|
||||||
|
|
||||||
|
```shell
|
||||||
|
❯ ./myice.py practice 561855
|
||||||
|
Opening file practice_561855.pdf
|
||||||
|
|
||||||
|
```
|
||||||
0
myice/__init__.py
Normal file
0
myice/__init__.py
Normal file
237
myice/myice.py
Executable file
237
myice/myice.py
Executable file
@@ -0,0 +1,237 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
"""
|
||||||
|
Tool to work with My Ice Hockey schedules
|
||||||
|
"""
|
||||||
|
|
||||||
|
import configparser
|
||||||
|
import datetime
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
from typing import Annotated
|
||||||
|
import requests
|
||||||
|
import typer
|
||||||
|
|
||||||
|
user_agent = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:131.0) Gecko/20100101 Firefox/131.0"
|
||||||
|
|
||||||
|
app = typer.Typer(no_args_is_help=True)
|
||||||
|
session: requests.Session
|
||||||
|
userid: int
|
||||||
|
|
||||||
|
|
||||||
|
def load_cookies(file: str = "cookies.txt") -> requests.cookies.RequestsCookieJar:
|
||||||
|
cookie_jar_file = Path(file)
|
||||||
|
cj_dict = {}
|
||||||
|
if cookie_jar_file.exists():
|
||||||
|
with cookie_jar_file.open("rb") as f:
|
||||||
|
cj_dict = json.load(f)
|
||||||
|
return requests.cookies.cookiejar_from_dict(cj_dict)
|
||||||
|
|
||||||
|
|
||||||
|
def save_cookies():
|
||||||
|
with open("cookies.txt", "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]:
|
||||||
|
config = configparser.ConfigParser()
|
||||||
|
config.read(
|
||||||
|
[
|
||||||
|
Path("~/.config/myice.ini").expanduser(),
|
||||||
|
"myice.ini",
|
||||||
|
local_file,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
if "default" in config.sections():
|
||||||
|
default_config = config["default"]
|
||||||
|
|
||||||
|
username = default_config.get("username")
|
||||||
|
password = default_config.get("password")
|
||||||
|
userid = default_config.getint("userid")
|
||||||
|
if not username or not password:
|
||||||
|
print("Error: please configure username/password in ini file")
|
||||||
|
sys.exit(1)
|
||||||
|
else:
|
||||||
|
print("Error: please configure username/password in ini file")
|
||||||
|
sys.exit(1)
|
||||||
|
return username, password, userid
|
||||||
|
|
||||||
|
|
||||||
|
def do_login():
|
||||||
|
global session
|
||||||
|
global userid
|
||||||
|
username, password, userid = get_login()
|
||||||
|
r = session.get("https://app.myice.hockey/", headers={"User-Agent": user_agent})
|
||||||
|
r.raise_for_status()
|
||||||
|
form_data = {
|
||||||
|
"login_email": username,
|
||||||
|
"login_password": password,
|
||||||
|
"sublogin": 1,
|
||||||
|
"login_submit": "",
|
||||||
|
}
|
||||||
|
r = session.post(
|
||||||
|
"https://app.myice.hockey/classes/process.php",
|
||||||
|
data=form_data,
|
||||||
|
headers={
|
||||||
|
"Referer": "https://app.myice.hockey/",
|
||||||
|
"Origin": "https://app.myice.hockey",
|
||||||
|
"User-Agent": user_agent,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
|
||||||
|
|
||||||
|
def get_userid():
|
||||||
|
global session
|
||||||
|
r = session.get(
|
||||||
|
"https://app.myice.hockey/players/clubschedule/",
|
||||||
|
headers={
|
||||||
|
"User-Agent": user_agent,
|
||||||
|
"Referer": "https://app.myice.hockey/classes/process.php",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
for line in r.text.splitlines():
|
||||||
|
m = re.search(r"^\s+userid: '(?P<userid>[0-9]+)'", line)
|
||||||
|
if m:
|
||||||
|
userid = m.group("userid")
|
||||||
|
break
|
||||||
|
return userid
|
||||||
|
|
||||||
|
|
||||||
|
def wrapper_session(func):
|
||||||
|
def wrapper(*args, **kwargs):
|
||||||
|
global session, userid
|
||||||
|
session = requests.Session()
|
||||||
|
session.cookies = load_cookies()
|
||||||
|
session.cookies.clear_expired_cookies()
|
||||||
|
if not session.cookies.get("mih_v3_cookname"):
|
||||||
|
print("login...")
|
||||||
|
do_login()
|
||||||
|
save_cookies()
|
||||||
|
_, _, userid = get_login()
|
||||||
|
if not userid:
|
||||||
|
print("get userid...")
|
||||||
|
userid = get_userid()
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
|
||||||
|
return wrapper
|
||||||
|
|
||||||
|
|
||||||
|
@wrapper_session
|
||||||
|
def get_schedule() -> 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)
|
||||||
|
r = session.post(
|
||||||
|
"https://app.myice.hockey/inc/processclubplanning.php",
|
||||||
|
data={
|
||||||
|
"type": "fetchmy",
|
||||||
|
"userid": userid,
|
||||||
|
"type_location": ["*"],
|
||||||
|
"start": date_start.strftime("%Y-%m-%d"),
|
||||||
|
"end": date_end.strftime("%Y-%m-%d"),
|
||||||
|
},
|
||||||
|
headers={
|
||||||
|
"User-Agent": user_agent,
|
||||||
|
"Referer": "https://app.myice.hockey/players/clubschedule/",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
return r.text
|
||||||
|
|
||||||
|
|
||||||
|
@wrapper_session
|
||||||
|
def game_pdf(gameid: int, outfile: Path):
|
||||||
|
global session, userid
|
||||||
|
assert session and userid
|
||||||
|
r = session.get(
|
||||||
|
f"https://app.myice.hockey/pdfexports/playerplatgamepdfgenerator.php?id={gameid}",
|
||||||
|
headers={
|
||||||
|
"User-Agent": user_agent,
|
||||||
|
"Referer": "https://app.myice.hockey/inc/processclubplanning.php",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
with outfile.open("wb") as fd:
|
||||||
|
for chunk in r.iter_content(chunk_size=1024 * 1024 * 4):
|
||||||
|
fd.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
@wrapper_session
|
||||||
|
def practice_pdf(gameid: int, outfile: Path):
|
||||||
|
global session, userid
|
||||||
|
assert session and userid
|
||||||
|
r = session.get(
|
||||||
|
f"https://app.myice.hockey/pdfexports/playerplatpracticepdf.php?practice={gameid}",
|
||||||
|
headers={
|
||||||
|
"User-Agent": user_agent,
|
||||||
|
"Referer": "https://app.myice.hockey/inc/processclubplanning.php",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
r.raise_for_status()
|
||||||
|
with outfile.open("wb") as fd:
|
||||||
|
for chunk in r.iter_content(chunk_size=1024 * 1024 * 4):
|
||||||
|
fd.write(chunk)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command()
|
||||||
|
def schedule(
|
||||||
|
outfile: Annotated[
|
||||||
|
Path | None,
|
||||||
|
typer.Option(
|
||||||
|
"--outfile", "-o", help="file to write result to, or stdout if none"
|
||||||
|
),
|
||||||
|
] = None,
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Fetch schedule as json
|
||||||
|
"""
|
||||||
|
schedule = get_schedule()
|
||||||
|
if outfile:
|
||||||
|
with outfile.open("w") as f:
|
||||||
|
f.write(schedule)
|
||||||
|
else:
|
||||||
|
print("Schedule:", file=sys.stderr)
|
||||||
|
print(schedule)
|
||||||
|
|
||||||
|
|
||||||
|
def open(file: str) -> None:
|
||||||
|
if os.uname().sysname == "Linux":
|
||||||
|
os.system(f"xdg-open {file}")
|
||||||
|
else:
|
||||||
|
print(f"Opening file {file}", file=sys.stderr)
|
||||||
|
os.system(f"open {file}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.command("game")
|
||||||
|
def get_game_pdf(
|
||||||
|
game_id: Annotated[int, typer.Argument(help="ID of game to gen pdf for")],
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Genate the pdf for the game invitation
|
||||||
|
"""
|
||||||
|
output_filename = f"game_{game_id}.pdf"
|
||||||
|
game_pdf(game_id, Path(output_filename))
|
||||||
|
open(output_filename)
|
||||||
|
|
||||||
|
|
||||||
|
@app.command("practice")
|
||||||
|
def get_practice_pdf(
|
||||||
|
game_id: Annotated[int, typer.Argument(help="ID of practice to gen pdf for")],
|
||||||
|
):
|
||||||
|
"""
|
||||||
|
Genate the pdf for the practice invitation
|
||||||
|
"""
|
||||||
|
output_filename = f"practice_{game_id}.pdf"
|
||||||
|
practice_pdf(game_id, Path(output_filename))
|
||||||
|
open(output_filename)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
app()
|
||||||
1028
poetry.lock
generated
Normal file
1028
poetry.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
22
pyproject.toml
Normal file
22
pyproject.toml
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
[tool.poetry]
|
||||||
|
name = "myice"
|
||||||
|
version = "0.1.0"
|
||||||
|
description = ""
|
||||||
|
authors = ["Rene Luria <rene.luria@infomaniak.com>"]
|
||||||
|
readme = "README.md"
|
||||||
|
|
||||||
|
[tool.poetry.dependencies]
|
||||||
|
python = "^3.12"
|
||||||
|
requests = "^2.32.3"
|
||||||
|
typer = {extras = ["all"], version = "^0.12.5"}
|
||||||
|
|
||||||
|
|
||||||
|
[tool.poetry.group.dev.dependencies]
|
||||||
|
ipykernel = "^6.29.5"
|
||||||
|
|
||||||
|
[build-system]
|
||||||
|
requires = ["poetry-core"]
|
||||||
|
build-backend = "poetry.core.masonry.api"
|
||||||
|
|
||||||
|
[tool.poetry.scripts]
|
||||||
|
myice = 'myice.myice:app'
|
||||||
Reference in New Issue
Block a user