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