Metadata-Version: 2.5
Name: attendance42lyon
Version: 0.1.2
Summary: Local HTTP API and CLI for the 42 Lyon attendance intranet
Project-URL: Homepage, https://codeberg.org/Z-OxO/attendance-lyon-api/
Project-URL: Issues, https://codeberg.org/Z-OxO/attendance-lyon-api/issues
Author: jbenhass
License-Expression: MIT
License-File: LICENSE
Keywords: 42,attendance,cli,daemon
Classifier: Environment :: Console
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Utilities
Requires-Python: >=3.13
Requires-Dist: fastapi>=0.141.1
Requires-Dist: httpx>=0.28.1
Requires-Dist: keyring>=25.7.0
Requires-Dist: platformdirs>=4.11.3
Requires-Dist: playwright>=1.62.0
Requires-Dist: pydantic>=2.13.4
Requires-Dist: pywin32>=312; sys_platform == 'win32'
Requires-Dist: uvicorn>=0.52.3
Description-Content-Type: text/markdown

# attendance42lyon

Local HTTP API for the 42 Lyon attendance intranet.

[![PyPI](https://img.shields.io/pypi/v/attendance42lyon)](https://pypi.org/project/attendance42lyon/)
[![Python](https://img.shields.io/pypi/pyversions/attendance42lyon)](https://pypi.org/project/attendance42lyon/)
[![License](https://img.shields.io/pypi/l/attendance42lyon)](LICENSE)

The intranet gives you a web page. This gives you `curl`.

A small daemon holds your session and exposes your attendance data on
`127.0.0.1:8420`, so a waybar module, a GNOME extension or a shell script can
read your hours without reimplementing the login.

```console
$ curl -s -H "Authorization: Bearer $(cat ~/.config/attendance42/token)" \
    127.0.0.1:8420/v1/month | jq '{worked, requiredSoFar, balanceSoFar}'
{
  "worked": 82740,
  "requiredSoFar": 302400,
  "balanceSoFar": -219660
}
```

All durations are **integer seconds**.

## Install

```bash
uv tool install attendance42lyon
attendance install
```

`attendance install` writes a `--user` systemd unit, starts it, and waits for
the daemon to answer. It needs a graphical session: signing in opens a real
browser window.

```console
$ attendance install
· /home/you/.local/bin/attendance-daemon
✓ attendance42.service running on 127.0.0.1:8420
```

Then, once your session expires:

```console
$ attendance login
→ browser opened, sign in there
✓ signed in
```

That is the whole CLI — `install`, `login`, `uninstall`. Reading data is the
API's job.

## Endpoints

| Route | Returns |
|---|---|
| `GET /healthz` | liveness, no authentication |
| `GET /v1/month` | current month |
| `GET /v1/months/{YYYY-MM}` | a given month |
| `POST /v1/login` | opens the browser, replies `202` immediately |

Everything except `/healthz` needs `Authorization: Bearer <token>`.

Full reference, including every field and error state: [docs/API.md](docs/API.md).

## Interactive docs

The daemon serves its own OpenAPI schema. With the service running:

- <http://127.0.0.1:8420/docs> — Swagger UI, with an **Authorize** button. Paste
  the token once and you can fire real requests from the browser.
- <http://127.0.0.1:8420/redoc> — the same schema, laid out for reading.
- <http://127.0.0.1:8420/openapi.json> — the raw spec, to generate a client.

Written reference: [docs/API.md](docs/API.md).

## Credentials and storage

**Your 42 password never reaches this tool.** Signing in happens on the real 42
page, in a real browser. What comes back is a session cookie.

That cookie is stored in your **system keyring** — KWallet, GNOME Keyring, or
whatever implements the Secret Service API on your desktop. It is encrypted at
rest by the keyring, unlocked with your session.

If no keyring is available, the SDK falls back to
`~/.local/state/attendance-42/state.json` at mode `0600`. On Linux that file is
**plaintext**, protected by file permissions alone. If your desktop has no
keyring, know that this is the trade-off.

The API token is a separate thing, and is not in the keyring: the daemon
generates it on first start in `~/.config/attendance42/token`, mode `0600`, as
plaintext. It has to be readable without unlocking anything, since the daemon
starts with your session. It is not your 42 password — it only proves a local
client is you, because anything on the machine can reach the loopback port.

It is read once at startup. To rotate it, delete the file and restart the
service; every client then needs the new value.

## How the session works

The daemon never opens a browser on its own. When your 42 session dies it
answers `503` with a state a client can act on:

```json
{ "state": "unauthenticated", "hint": "POST /v1/login" }
```

A GNOME extension shows a button, waybar shows a marker, you decide when to sign
in. `POST /v1/login` then opens Chrome, you authenticate on the real 42 page, and
the daemon keeps the cookie in your keyring. **The tool never sees your
password.**

While a login is in progress, reads answer `503 logging_in` instead of blocking
for three minutes.

## Requirements

- Linux with systemd `--user`
- Python 3.13+
- **X11.** Wayland is untested for now — the unit imports `DISPLAY` and
  `XAUTHORITY` from the session. Chrome runs under XWayland, so it may well
  work, but nothing has been verified. Reports welcome.
- A system keyring (KWallet, GNOME Keyring, …)
- Google Chrome, for the login window

## Troubleshooting

**`attendance install` fails on `/healthz`** — the daemon started and died. The
last journal lines are printed with the error; a missing dependency is the usual
cause.

**`{"state": "unauthorized"}`** — the *local* token is wrong. Check that your
client reads `~/.config/attendance42/token`, and that `$XDG_CONFIG_HOME` is the
same for the daemon and for you.

**`{"state": "unauthenticated"}`** — the *42* session expired. Run
`attendance login`.

**Nothing on port 8420 after a `uv tool upgrade`** — re-run `attendance install`
to refresh the unit.

## Contributing

```bash
git clone https://codeberg.org/Z-OxO/attendance-lyon-api
cd attendance-lyon-api
uv sync
uv run python -m attendance42lyon.daemon
```

Issues and patches: <https://codeberg.org/Z-OxO/attendance-lyon-api/issues>

## License

MIT. Not affiliated with 42 or 42 Lyon.
