myorlen-api 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- myorlen_api-0.1.0/.claude/settings.local.json +13 -0
- myorlen_api-0.1.0/.env.example +7 -0
- myorlen_api-0.1.0/.github/workflows/ci.yml +34 -0
- myorlen_api-0.1.0/.github/workflows/publish.yml +41 -0
- myorlen_api-0.1.0/.gitignore +23 -0
- myorlen_api-0.1.0/.mcp.json +9 -0
- myorlen_api-0.1.0/CHANGELOG.md +27 -0
- myorlen_api-0.1.0/CLAUDE.md +78 -0
- myorlen_api-0.1.0/LICENSE +9 -0
- myorlen_api-0.1.0/PKG-INFO +240 -0
- myorlen_api-0.1.0/README.md +205 -0
- myorlen_api-0.1.0/myorlen/__init__.py +27 -0
- myorlen_api-0.1.0/myorlen/_helpers.py +130 -0
- myorlen_api-0.1.0/myorlen/client.py +347 -0
- myorlen_api-0.1.0/myorlen/exceptions.py +22 -0
- myorlen_api-0.1.0/myorlen/mcp_server.py +271 -0
- myorlen_api-0.1.0/myorlen/models.py +70 -0
- myorlen_api-0.1.0/myorlen/py.typed +0 -0
- myorlen_api-0.1.0/myorlen/sync.py +77 -0
- myorlen_api-0.1.0/pyproject.toml +61 -0
- myorlen_api-0.1.0/scripts/smoke_test.py +72 -0
- myorlen_api-0.1.0/tests/__init__.py +0 -0
- myorlen_api-0.1.0/tests/conftest.py +43 -0
- myorlen_api-0.1.0/tests/fixtures/agreements.json +20 -0
- myorlen_api-0.1.0/tests/fixtures/balance.json +5 -0
- myorlen_api-0.1.0/tests/fixtures/invoices.json +35 -0
- myorlen_api-0.1.0/tests/fixtures/login_response.json +6 -0
- myorlen_api-0.1.0/tests/fixtures/ppg_list.json +21 -0
- myorlen_api-0.1.0/tests/fixtures/user_data.json +7 -0
- myorlen_api-0.1.0/tests/test_client.py +132 -0
- myorlen_api-0.1.0/tests/test_exceptions.py +31 -0
- myorlen_api-0.1.0/tests/test_helpers.py +236 -0
- myorlen_api-0.1.0/tests/test_mcp_server.py +319 -0
- myorlen_api-0.1.0/tests/test_models.py +68 -0
- myorlen_api-0.1.0/uv.lock +1189 -0
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
# OrlenID credentials (shared Orlen Group account — takes priority if both pairs are set)
|
|
2
|
+
ORLENID_USERNAME=you@example.com
|
|
3
|
+
ORLENID_PASSWORD=your-orlenid-password
|
|
4
|
+
|
|
5
|
+
# Native myORLEN eBOK credentials (formerly PGNiG)
|
|
6
|
+
# MYORLEN_USERNAME=you@example.com
|
|
7
|
+
# MYORLEN_PASSWORD=your-myorlen-password
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
test:
|
|
11
|
+
name: Test (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
|
|
14
|
+
strategy:
|
|
15
|
+
fail-fast: false
|
|
16
|
+
matrix:
|
|
17
|
+
python-version: ["3.12", "3.13"]
|
|
18
|
+
|
|
19
|
+
steps:
|
|
20
|
+
- uses: actions/checkout@v4
|
|
21
|
+
|
|
22
|
+
- name: Install uv
|
|
23
|
+
uses: astral-sh/setup-uv@v4
|
|
24
|
+
with:
|
|
25
|
+
enable-cache: true
|
|
26
|
+
|
|
27
|
+
- name: Install Python ${{ matrix.python-version }}
|
|
28
|
+
run: uv python install ${{ matrix.python-version }}
|
|
29
|
+
|
|
30
|
+
- name: Install dependencies
|
|
31
|
+
run: uv sync --group dev
|
|
32
|
+
|
|
33
|
+
- name: Run tests
|
|
34
|
+
run: uv run pytest -v
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
name: Build and publish
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
environment: pypi
|
|
13
|
+
|
|
14
|
+
permissions:
|
|
15
|
+
id-token: write # required for Trusted Publisher OIDC
|
|
16
|
+
contents: read
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0 # required by hatch-vcs to derive version from git tags
|
|
22
|
+
|
|
23
|
+
- name: Install uv
|
|
24
|
+
uses: astral-sh/setup-uv@v4
|
|
25
|
+
with:
|
|
26
|
+
enable-cache: true
|
|
27
|
+
|
|
28
|
+
- name: Install Python
|
|
29
|
+
run: uv python install 3.12
|
|
30
|
+
|
|
31
|
+
- name: Install dependencies
|
|
32
|
+
run: uv sync --group dev
|
|
33
|
+
|
|
34
|
+
- name: Run tests
|
|
35
|
+
run: uv run pytest -v
|
|
36
|
+
|
|
37
|
+
- name: Build
|
|
38
|
+
run: uv build
|
|
39
|
+
|
|
40
|
+
- name: Publish to PyPI
|
|
41
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
# Python-generated files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[oc]
|
|
4
|
+
build/
|
|
5
|
+
dist/
|
|
6
|
+
wheels/
|
|
7
|
+
*.egg-info
|
|
8
|
+
|
|
9
|
+
# Virtual environments
|
|
10
|
+
.venv
|
|
11
|
+
|
|
12
|
+
# Secrets
|
|
13
|
+
.env
|
|
14
|
+
|
|
15
|
+
# HAR capture files (contain session tokens and credentials)
|
|
16
|
+
*.har
|
|
17
|
+
|
|
18
|
+
# Downloaded invoices
|
|
19
|
+
*.pdf
|
|
20
|
+
|
|
21
|
+
# Editor swap files
|
|
22
|
+
*.swp
|
|
23
|
+
*.swo
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0] - 2026-03-29
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- Async client (`MyOrlenClient`) with direct myORLEN eBOK login (`/auth/login`)
|
|
14
|
+
- OrlenID federated login (`use_orlenid=True`) — headless Keycloak form submission via `oid-ws.orlen.pl`
|
|
15
|
+
- Sync wrapper (`MyOrlenClientSync`) with a dedicated event loop for use in non-async contexts
|
|
16
|
+
- Transparent re-login on token expiry (tokens last ~1 hour; up to 2 silent re-login attempts)
|
|
17
|
+
- `get_balance()` — current balance and amount to pay
|
|
18
|
+
- `get_invoices(page, page_size)` — paginated invoice list with gas consumption data (`WearM3`, `WearKWH`)
|
|
19
|
+
- `get_invoice_pdf(invoice_number)` — download invoice PDF as raw bytes
|
|
20
|
+
- Cached account data at login: `user_info`, `agreements`, `metering_points`
|
|
21
|
+
- Typed models: `Address`, `MeteringPoint`, `Agreement`, `UserInfo`, `Balance`, `Invoice`
|
|
22
|
+
- Exception hierarchy: `MyOrlenError`, `MyOrlenAuthError`, `MyOrlenForbiddenError`, `MyOrlenNotFoundError`, `MyOrlenAPIError`
|
|
23
|
+
- MCP server (`myorlen.mcp_server`) exposing four tools: `get_account_info`, `get_balance`, `get_invoices`, `download_invoice`
|
|
24
|
+
- `mcp` optional dependency group: `pip install myorlen-api[mcp]`
|
|
25
|
+
- `myorlen-mcp` script entry point
|
|
26
|
+
- Credential auto-detection: `ORLENID_USERNAME`/`ORLENID_PASSWORD` (OrlenID, preferred) or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD` (native)
|
|
27
|
+
- PEP 561 `py.typed` marker
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# myorlen-api
|
|
2
|
+
|
|
3
|
+
Python client for the myORLEN eBOK self-care portal at `https://ebok.myorlen.pl` (gas accounts, formerly PGNiG).
|
|
4
|
+
|
|
5
|
+
## Project structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
myorlen/
|
|
9
|
+
├── client.py # Async MyOrlenClient
|
|
10
|
+
├── sync.py # MyOrlenClientSync (blocking wrapper)
|
|
11
|
+
├── models.py # Dataclasses: Address, MeteringPoint, Agreement, UserInfo, Balance, Invoice
|
|
12
|
+
├── exceptions.py # MyOrlenError hierarchy
|
|
13
|
+
├── _helpers.py # Response parsers, generate_device_id, token_expires_at
|
|
14
|
+
├── mcp_server.py # MCP server (requires myorlen-api[mcp])
|
|
15
|
+
└── __init__.py # Public exports
|
|
16
|
+
scripts/
|
|
17
|
+
└── smoke_test.py # End-to-end test, reads env vars
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
## Running
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
uv sync
|
|
24
|
+
MYORLEN_USERNAME=... MYORLEN_PASSWORD=... uv run python scripts/smoke_test.py
|
|
25
|
+
ORLENID_USERNAME=... ORLENID_PASSWORD=... uv run python scripts/smoke_test.py
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Authentication
|
|
29
|
+
|
|
30
|
+
### Direct login (MYORLEN_USERNAME / MYORLEN_PASSWORD)
|
|
31
|
+
- `POST /auth/login?api-version=3.0` with JSON body containing `identificator`, `accessPin`, `DeviceId`
|
|
32
|
+
- Returns `Token` + `DateExpirationUtc` directly in response body
|
|
33
|
+
- All subsequent requests use `AuthToken: {token}` header (not `Authorization: Bearer`)
|
|
34
|
+
- All requests include `?api-version=3.0`
|
|
35
|
+
- Server returns HTTP 401 + `Code: 1025 "Soft blocked."` for bad credentials
|
|
36
|
+
|
|
37
|
+
### OrlenID login (ORLENID_USERNAME / ORLENID_PASSWORD)
|
|
38
|
+
Headless OIDC flow — no browser required:
|
|
39
|
+
1. `POST /auth/oid/init-login` → server generates PKCE server-side, returns `RedirectUrl` to Keycloak
|
|
40
|
+
2. `GET RedirectUrl` (oid-ws.orlen.pl) → Keycloak login form; accumulates session cookies
|
|
41
|
+
3. `POST` credentials to form action with `Content-Type: application/x-www-form-urlencoded` → 302 to callback
|
|
42
|
+
4. `GET /auth/oid/after-login-callback?code=...` → server exchanges code, stores token for DeviceId
|
|
43
|
+
5. `GET /auth/get-auth-token?deviceId=...` → returns the token
|
|
44
|
+
|
|
45
|
+
## Known gotchas
|
|
46
|
+
|
|
47
|
+
### aiohttp session-level Content-Type override
|
|
48
|
+
The session is created with default `Content-Type: application/json`. For the Keycloak form POST,
|
|
49
|
+
this must be explicitly overridden per-request:
|
|
50
|
+
```python
|
|
51
|
+
session.post(form_action, data={...}, headers={"Content-Type": "application/x-www-form-urlencoded"})
|
|
52
|
+
```
|
|
53
|
+
Without this, aiohttp sends the wrong content type, Keycloak silently re-renders the form (HTTP 200)
|
|
54
|
+
instead of redirecting, and login fails with no obvious error.
|
|
55
|
+
|
|
56
|
+
### OIDC callback URL contains IdP domain in query parameter
|
|
57
|
+
The successful redirect from Keycloak looks like:
|
|
58
|
+
```
|
|
59
|
+
https://ebok.myorlen.pl/auth/oid/after-login-callback?state=...&iss=https%3A%2F%2Foid-ws.orlen.pl%2F...&code=...
|
|
60
|
+
```
|
|
61
|
+
Checking `"oid-ws.orlen.pl" in location` is a false positive. Use `location.startswith(_BASE_URL)` instead.
|
|
62
|
+
|
|
63
|
+
### Token expiry
|
|
64
|
+
Tokens expire after ~1 hour (`DateExpirationUtc`). No refresh endpoint exists — re-login on expiry.
|
|
65
|
+
Up to `_MAX_RELOGIN_ATTEMPTS = 2` transparent re-logins before raising `MyOrlenAuthError`.
|
|
66
|
+
|
|
67
|
+
### MCP server credentials
|
|
68
|
+
Credential env vars (`ORLENID_USERNAME`/`ORLENID_PASSWORD` or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD`)
|
|
69
|
+
are not listed in `.mcp.json` — the subprocess inherits them from the shell environment. This avoids
|
|
70
|
+
Claude Code's "missing environment variables" diagnostic, which fires for any `${VAR}` reference
|
|
71
|
+
whose var is unset. Export the pair you need in `~/.bashrc` / `~/.zshrc` before starting Claude Code.
|
|
72
|
+
|
|
73
|
+
## Response format
|
|
74
|
+
All API responses wrap data in:
|
|
75
|
+
```json
|
|
76
|
+
{"Code": 0, "Message": null, "TokenExpireDate": "...", ...data fields...}
|
|
77
|
+
```
|
|
78
|
+
`Code != 0` means an error. The `check_response_code()` helper raises `MyOrlenAPIError` for these.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pawel Rapkiewicz
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: myorlen-api
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Async/sync Python client for the myORLEN eBOK (formerly PGNiG) self-care portal
|
|
5
|
+
Project-URL: Homepage, https://github.com/vincentto13/myorlen-api
|
|
6
|
+
Project-URL: Source, https://github.com/vincentto13/myorlen-api
|
|
7
|
+
Project-URL: Bug Tracker, https://github.com/vincentto13/myorlen-api/issues
|
|
8
|
+
Author-email: Pawel Rapkiewicz <pawel.rapkiewicz@gmail.com>
|
|
9
|
+
License: MIT License
|
|
10
|
+
|
|
11
|
+
Copyright (c) 2026 Pawel Rapkiewicz
|
|
12
|
+
|
|
13
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
14
|
+
|
|
15
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
16
|
+
|
|
17
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Keywords: api-client,gas,myorlen,orlen,pgnig,poland,utility
|
|
20
|
+
Classifier: Development Status :: 4 - Beta
|
|
21
|
+
Classifier: Framework :: AsyncIO
|
|
22
|
+
Classifier: Intended Audience :: Developers
|
|
23
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
24
|
+
Classifier: Programming Language :: Python :: 3
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
26
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
27
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
28
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
29
|
+
Classifier: Typing :: Typed
|
|
30
|
+
Requires-Python: >=3.12
|
|
31
|
+
Requires-Dist: aiohttp>=3.9
|
|
32
|
+
Provides-Extra: mcp
|
|
33
|
+
Requires-Dist: mcp>=1.0.0; extra == 'mcp'
|
|
34
|
+
Description-Content-Type: text/markdown
|
|
35
|
+
|
|
36
|
+
# myorlen-api
|
|
37
|
+
|
|
38
|
+
Python client library for the [myORLEN eBOK](https://ebok.myorlen.pl) self-care portal (gas accounts, formerly PGNiG).
|
|
39
|
+
Reverse-engineered from browser traffic. Supports both async and sync usage.
|
|
40
|
+
|
|
41
|
+
## Features
|
|
42
|
+
|
|
43
|
+
- Direct myORLEN eBOK login and OrlenID federated login (shared Orlen Group account)
|
|
44
|
+
- Automatic re-login on token expiry (tokens last ~1 hour, no refresh endpoint)
|
|
45
|
+
- Fetch account balance
|
|
46
|
+
- List invoices with pagination
|
|
47
|
+
- Download invoice PDFs
|
|
48
|
+
- MCP server for Claude and other AI assistants
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
### From PyPI
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install myorlen-api
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### From source (with [uv](https://docs.astral.sh/uv/))
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
git clone https://github.com/vincentto13/myorlen-api.git
|
|
62
|
+
cd myorlen-api
|
|
63
|
+
uv sync
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Configuration
|
|
67
|
+
|
|
68
|
+
Set credentials for one of the following:
|
|
69
|
+
|
|
70
|
+
| Env vars | Login mode | Notes |
|
|
71
|
+
|---|---|---|
|
|
72
|
+
| `ORLENID_USERNAME` + `ORLENID_PASSWORD` | OrlenID (`oid-ws.orlen.pl`) | Shared across Orlen Group services. Takes priority if both pairs are set. |
|
|
73
|
+
| `MYORLEN_USERNAME` + `MYORLEN_PASSWORD` | Native myORLEN eBOK | Use if you have a standalone account (formerly PGNiG). |
|
|
74
|
+
|
|
75
|
+
## Usage
|
|
76
|
+
|
|
77
|
+
### Async
|
|
78
|
+
|
|
79
|
+
```python
|
|
80
|
+
from myorlen import MyOrlenClient
|
|
81
|
+
|
|
82
|
+
# Native myORLEN eBOK credentials
|
|
83
|
+
async with MyOrlenClient("user@example.com", "password") as client:
|
|
84
|
+
...
|
|
85
|
+
|
|
86
|
+
# OrlenID credentials (shared Orlen Group account)
|
|
87
|
+
async with MyOrlenClient("user@example.com", "orlenid-password", use_orlenid=True) as client:
|
|
88
|
+
print(client.user_info.first_name, client.user_info.last_name)
|
|
89
|
+
|
|
90
|
+
for agreement in client.agreements:
|
|
91
|
+
print(agreement.agreement_number, "active:", agreement.active)
|
|
92
|
+
|
|
93
|
+
for mp in client.metering_points:
|
|
94
|
+
print(mp.ppg_id, mp.tariff, mp.address)
|
|
95
|
+
|
|
96
|
+
balance = await client.get_balance()
|
|
97
|
+
print(balance.value, "PLN to pay:", balance.amount_to_pay, "PLN")
|
|
98
|
+
|
|
99
|
+
invoices = await client.get_invoices(page=1, page_size=12)
|
|
100
|
+
for inv in invoices:
|
|
101
|
+
print(inv.invoice_number, inv.issue_date, inv.amount, "PLN paid:", inv.is_paid)
|
|
102
|
+
|
|
103
|
+
# Download a PDF
|
|
104
|
+
pdf = await client.get_invoice_pdf(invoices[0].invoice_number)
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Sync
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
from myorlen import MyOrlenClientSync
|
|
111
|
+
|
|
112
|
+
with MyOrlenClientSync("user@example.com", "orlenid-password", use_orlenid=True) as client:
|
|
113
|
+
balance = client.get_balance()
|
|
114
|
+
invoices = client.get_invoices()
|
|
115
|
+
pdf = client.get_invoice_pdf(invoices[0].invoice_number)
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
## Development
|
|
119
|
+
|
|
120
|
+
### MCP server — local setup with Claude Code
|
|
121
|
+
|
|
122
|
+
**1. Install the MCP extra**
|
|
123
|
+
|
|
124
|
+
```bash
|
|
125
|
+
uv sync --extra mcp
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
**2. Export your credentials**
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# OrlenID (recommended — works across all Orlen Group services)
|
|
132
|
+
export ORLENID_USERNAME=you@example.com
|
|
133
|
+
export ORLENID_PASSWORD=your-orlenid-password
|
|
134
|
+
|
|
135
|
+
# — or — native myORLEN eBOK credentials
|
|
136
|
+
export MYORLEN_USERNAME=you@example.com
|
|
137
|
+
export MYORLEN_PASSWORD=your-myorlen-password
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Persist in `~/.bashrc` / `~/.zshrc` so they're always available.
|
|
141
|
+
|
|
142
|
+
**3. Verify the server starts**
|
|
143
|
+
|
|
144
|
+
```bash
|
|
145
|
+
uv run python -m myorlen.mcp_server
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
The process should start and wait for MCP input on stdin (no output is normal — that's correct stdio behaviour). Press `Ctrl+C` to stop.
|
|
149
|
+
|
|
150
|
+
**4. Connect Claude Code**
|
|
151
|
+
|
|
152
|
+
The repo includes a `.mcp.json` that points Claude Code at the server automatically.
|
|
153
|
+
Open Claude Code from this project directory — it will pick up `.mcp.json` and prompt you to approve the server on first use.
|
|
154
|
+
|
|
155
|
+
Check the connection inside a Claude Code session:
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
/mcp
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
You should see `myorlen` listed as connected with 4 tools.
|
|
162
|
+
|
|
163
|
+
### Running the smoke test (live API)
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
# OrlenID credentials
|
|
167
|
+
ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python scripts/smoke_test.py
|
|
168
|
+
|
|
169
|
+
# Native myORLEN credentials
|
|
170
|
+
MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python scripts/smoke_test.py
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## MCP Server
|
|
174
|
+
|
|
175
|
+
The library ships an [MCP](https://modelcontextprotocol.io) server that exposes your myORLEN gas account
|
|
176
|
+
as tools for Claude and other MCP-compatible AI assistants.
|
|
177
|
+
|
|
178
|
+
### Install
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
pip install myorlen-api[mcp]
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
### Available tools
|
|
185
|
+
|
|
186
|
+
| Tool | Description |
|
|
187
|
+
|---|---|
|
|
188
|
+
| `get_account_info` | User profile, agreements and metering points (cached, no network request) |
|
|
189
|
+
| `get_balance` | Current balance and amount to pay |
|
|
190
|
+
| `get_invoices` | Paginated invoice list with gas consumption data |
|
|
191
|
+
| `download_invoice` | Download a PDF invoice — saves to a temp file and returns the path |
|
|
192
|
+
|
|
193
|
+
### Run standalone
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# OrlenID credentials
|
|
197
|
+
ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python -m myorlen.mcp_server
|
|
198
|
+
|
|
199
|
+
# Native myORLEN credentials
|
|
200
|
+
MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python -m myorlen.mcp_server
|
|
201
|
+
```
|
|
202
|
+
|
|
203
|
+
### Claude Desktop configuration
|
|
204
|
+
|
|
205
|
+
Add to `~/.config/claude/claude_desktop_config.json`:
|
|
206
|
+
|
|
207
|
+
```json
|
|
208
|
+
{
|
|
209
|
+
"mcpServers": {
|
|
210
|
+
"myorlen": {
|
|
211
|
+
"command": "uv",
|
|
212
|
+
"args": ["run", "--project", "/path/to/myorlen-api", "python", "-m", "myorlen.mcp_server"],
|
|
213
|
+
"env": {
|
|
214
|
+
"ORLENID_USERNAME": "you@example.com",
|
|
215
|
+
"ORLENID_PASSWORD": "your-orlenid-password"
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
Use `ORLENID_USERNAME`/`ORLENID_PASSWORD` for OrlenID, or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD` for native login. If both are set, OrlenID takes priority.
|
|
223
|
+
|
|
224
|
+
> **Note:** Tokens expire after ~1 hour. The client re-logs in transparently up to 2 times before raising an error — no manual restart needed in most cases.
|
|
225
|
+
|
|
226
|
+
### Example prompts
|
|
227
|
+
|
|
228
|
+
Ask Claude naturally:
|
|
229
|
+
|
|
230
|
+
- *"What's my gas balance?"*
|
|
231
|
+
- *"Show me my last 3 gas invoices"*
|
|
232
|
+
- *"Do I have any unpaid gas bills?"*
|
|
233
|
+
- *"What's my gas consumption for the last invoice?"*
|
|
234
|
+
- *"Download the latest invoice"*
|
|
235
|
+
|
|
236
|
+
## Acknowledgements
|
|
237
|
+
|
|
238
|
+
This library was built with the help of [Claude](https://claude.ai) (Anthropic's AI assistant).
|
|
239
|
+
Claude assisted with reverse-engineering the authentication flow from browser HAR captures,
|
|
240
|
+
designing the library architecture, and implementing the async/sync client and MCP server.
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# myorlen-api
|
|
2
|
+
|
|
3
|
+
Python client library for the [myORLEN eBOK](https://ebok.myorlen.pl) self-care portal (gas accounts, formerly PGNiG).
|
|
4
|
+
Reverse-engineered from browser traffic. Supports both async and sync usage.
|
|
5
|
+
|
|
6
|
+
## Features
|
|
7
|
+
|
|
8
|
+
- Direct myORLEN eBOK login and OrlenID federated login (shared Orlen Group account)
|
|
9
|
+
- Automatic re-login on token expiry (tokens last ~1 hour, no refresh endpoint)
|
|
10
|
+
- Fetch account balance
|
|
11
|
+
- List invoices with pagination
|
|
12
|
+
- Download invoice PDFs
|
|
13
|
+
- MCP server for Claude and other AI assistants
|
|
14
|
+
|
|
15
|
+
## Installation
|
|
16
|
+
|
|
17
|
+
### From PyPI
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install myorlen-api
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
### From source (with [uv](https://docs.astral.sh/uv/))
|
|
24
|
+
|
|
25
|
+
```bash
|
|
26
|
+
git clone https://github.com/vincentto13/myorlen-api.git
|
|
27
|
+
cd myorlen-api
|
|
28
|
+
uv sync
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Configuration
|
|
32
|
+
|
|
33
|
+
Set credentials for one of the following:
|
|
34
|
+
|
|
35
|
+
| Env vars | Login mode | Notes |
|
|
36
|
+
|---|---|---|
|
|
37
|
+
| `ORLENID_USERNAME` + `ORLENID_PASSWORD` | OrlenID (`oid-ws.orlen.pl`) | Shared across Orlen Group services. Takes priority if both pairs are set. |
|
|
38
|
+
| `MYORLEN_USERNAME` + `MYORLEN_PASSWORD` | Native myORLEN eBOK | Use if you have a standalone account (formerly PGNiG). |
|
|
39
|
+
|
|
40
|
+
## Usage
|
|
41
|
+
|
|
42
|
+
### Async
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from myorlen import MyOrlenClient
|
|
46
|
+
|
|
47
|
+
# Native myORLEN eBOK credentials
|
|
48
|
+
async with MyOrlenClient("user@example.com", "password") as client:
|
|
49
|
+
...
|
|
50
|
+
|
|
51
|
+
# OrlenID credentials (shared Orlen Group account)
|
|
52
|
+
async with MyOrlenClient("user@example.com", "orlenid-password", use_orlenid=True) as client:
|
|
53
|
+
print(client.user_info.first_name, client.user_info.last_name)
|
|
54
|
+
|
|
55
|
+
for agreement in client.agreements:
|
|
56
|
+
print(agreement.agreement_number, "active:", agreement.active)
|
|
57
|
+
|
|
58
|
+
for mp in client.metering_points:
|
|
59
|
+
print(mp.ppg_id, mp.tariff, mp.address)
|
|
60
|
+
|
|
61
|
+
balance = await client.get_balance()
|
|
62
|
+
print(balance.value, "PLN to pay:", balance.amount_to_pay, "PLN")
|
|
63
|
+
|
|
64
|
+
invoices = await client.get_invoices(page=1, page_size=12)
|
|
65
|
+
for inv in invoices:
|
|
66
|
+
print(inv.invoice_number, inv.issue_date, inv.amount, "PLN paid:", inv.is_paid)
|
|
67
|
+
|
|
68
|
+
# Download a PDF
|
|
69
|
+
pdf = await client.get_invoice_pdf(invoices[0].invoice_number)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
### Sync
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
from myorlen import MyOrlenClientSync
|
|
76
|
+
|
|
77
|
+
with MyOrlenClientSync("user@example.com", "orlenid-password", use_orlenid=True) as client:
|
|
78
|
+
balance = client.get_balance()
|
|
79
|
+
invoices = client.get_invoices()
|
|
80
|
+
pdf = client.get_invoice_pdf(invoices[0].invoice_number)
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
## Development
|
|
84
|
+
|
|
85
|
+
### MCP server — local setup with Claude Code
|
|
86
|
+
|
|
87
|
+
**1. Install the MCP extra**
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
uv sync --extra mcp
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
**2. Export your credentials**
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
# OrlenID (recommended — works across all Orlen Group services)
|
|
97
|
+
export ORLENID_USERNAME=you@example.com
|
|
98
|
+
export ORLENID_PASSWORD=your-orlenid-password
|
|
99
|
+
|
|
100
|
+
# — or — native myORLEN eBOK credentials
|
|
101
|
+
export MYORLEN_USERNAME=you@example.com
|
|
102
|
+
export MYORLEN_PASSWORD=your-myorlen-password
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Persist in `~/.bashrc` / `~/.zshrc` so they're always available.
|
|
106
|
+
|
|
107
|
+
**3. Verify the server starts**
|
|
108
|
+
|
|
109
|
+
```bash
|
|
110
|
+
uv run python -m myorlen.mcp_server
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
The process should start and wait for MCP input on stdin (no output is normal — that's correct stdio behaviour). Press `Ctrl+C` to stop.
|
|
114
|
+
|
|
115
|
+
**4. Connect Claude Code**
|
|
116
|
+
|
|
117
|
+
The repo includes a `.mcp.json` that points Claude Code at the server automatically.
|
|
118
|
+
Open Claude Code from this project directory — it will pick up `.mcp.json` and prompt you to approve the server on first use.
|
|
119
|
+
|
|
120
|
+
Check the connection inside a Claude Code session:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
/mcp
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
You should see `myorlen` listed as connected with 4 tools.
|
|
127
|
+
|
|
128
|
+
### Running the smoke test (live API)
|
|
129
|
+
|
|
130
|
+
```bash
|
|
131
|
+
# OrlenID credentials
|
|
132
|
+
ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python scripts/smoke_test.py
|
|
133
|
+
|
|
134
|
+
# Native myORLEN credentials
|
|
135
|
+
MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python scripts/smoke_test.py
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
## MCP Server
|
|
139
|
+
|
|
140
|
+
The library ships an [MCP](https://modelcontextprotocol.io) server that exposes your myORLEN gas account
|
|
141
|
+
as tools for Claude and other MCP-compatible AI assistants.
|
|
142
|
+
|
|
143
|
+
### Install
|
|
144
|
+
|
|
145
|
+
```bash
|
|
146
|
+
pip install myorlen-api[mcp]
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
### Available tools
|
|
150
|
+
|
|
151
|
+
| Tool | Description |
|
|
152
|
+
|---|---|
|
|
153
|
+
| `get_account_info` | User profile, agreements and metering points (cached, no network request) |
|
|
154
|
+
| `get_balance` | Current balance and amount to pay |
|
|
155
|
+
| `get_invoices` | Paginated invoice list with gas consumption data |
|
|
156
|
+
| `download_invoice` | Download a PDF invoice — saves to a temp file and returns the path |
|
|
157
|
+
|
|
158
|
+
### Run standalone
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
# OrlenID credentials
|
|
162
|
+
ORLENID_USERNAME=you@example.com ORLENID_PASSWORD=orlenid-secret uv run python -m myorlen.mcp_server
|
|
163
|
+
|
|
164
|
+
# Native myORLEN credentials
|
|
165
|
+
MYORLEN_USERNAME=you@example.com MYORLEN_PASSWORD=myorlen-secret uv run python -m myorlen.mcp_server
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### Claude Desktop configuration
|
|
169
|
+
|
|
170
|
+
Add to `~/.config/claude/claude_desktop_config.json`:
|
|
171
|
+
|
|
172
|
+
```json
|
|
173
|
+
{
|
|
174
|
+
"mcpServers": {
|
|
175
|
+
"myorlen": {
|
|
176
|
+
"command": "uv",
|
|
177
|
+
"args": ["run", "--project", "/path/to/myorlen-api", "python", "-m", "myorlen.mcp_server"],
|
|
178
|
+
"env": {
|
|
179
|
+
"ORLENID_USERNAME": "you@example.com",
|
|
180
|
+
"ORLENID_PASSWORD": "your-orlenid-password"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
Use `ORLENID_USERNAME`/`ORLENID_PASSWORD` for OrlenID, or `MYORLEN_USERNAME`/`MYORLEN_PASSWORD` for native login. If both are set, OrlenID takes priority.
|
|
188
|
+
|
|
189
|
+
> **Note:** Tokens expire after ~1 hour. The client re-logs in transparently up to 2 times before raising an error — no manual restart needed in most cases.
|
|
190
|
+
|
|
191
|
+
### Example prompts
|
|
192
|
+
|
|
193
|
+
Ask Claude naturally:
|
|
194
|
+
|
|
195
|
+
- *"What's my gas balance?"*
|
|
196
|
+
- *"Show me my last 3 gas invoices"*
|
|
197
|
+
- *"Do I have any unpaid gas bills?"*
|
|
198
|
+
- *"What's my gas consumption for the last invoice?"*
|
|
199
|
+
- *"Download the latest invoice"*
|
|
200
|
+
|
|
201
|
+
## Acknowledgements
|
|
202
|
+
|
|
203
|
+
This library was built with the help of [Claude](https://claude.ai) (Anthropic's AI assistant).
|
|
204
|
+
Claude assisted with reverse-engineering the authentication flow from browser HAR captures,
|
|
205
|
+
designing the library architecture, and implementing the async/sync client and MCP server.
|