python-mytnb 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.
- python_mytnb-0.1.0/.github/workflows/ci.yml +32 -0
- python_mytnb-0.1.0/.github/workflows/publish.yml +23 -0
- python_mytnb-0.1.0/.gitignore +27 -0
- python_mytnb-0.1.0/LICENSE +21 -0
- python_mytnb-0.1.0/PKG-INFO +165 -0
- python_mytnb-0.1.0/README.md +134 -0
- python_mytnb-0.1.0/pyproject.toml +84 -0
- python_mytnb-0.1.0/src/mytnb/__init__.py +26 -0
- python_mytnb-0.1.0/src/mytnb/__main__.py +5 -0
- python_mytnb-0.1.0/src/mytnb/auth.py +67 -0
- python_mytnb-0.1.0/src/mytnb/cli.py +379 -0
- python_mytnb-0.1.0/src/mytnb/client/__init__.py +3 -0
- python_mytnb-0.1.0/src/mytnb/client/auth.py +159 -0
- python_mytnb-0.1.0/src/mytnb/client/client.py +239 -0
- python_mytnb-0.1.0/src/mytnb/client/config.py +35 -0
- python_mytnb-0.1.0/src/mytnb/client/legacy.py +120 -0
- python_mytnb-0.1.0/src/mytnb/client/rest.py +117 -0
- python_mytnb-0.1.0/src/mytnb/crypto.py +142 -0
- python_mytnb-0.1.0/src/mytnb/exceptions.py +47 -0
- python_mytnb-0.1.0/src/mytnb/models.py +236 -0
- python_mytnb-0.1.0/tests/test_auth.py +53 -0
- python_mytnb-0.1.0/tests/test_cli.py +89 -0
- python_mytnb-0.1.0/tests/test_client.py +472 -0
- python_mytnb-0.1.0/tests/test_crypto.py +116 -0
- python_mytnb-0.1.0/tests/test_models.py +284 -0
- python_mytnb-0.1.0/uv.lock +733 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [dev, main]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [dev, main]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
- uses: astral-sh/setup-uv@v5
|
|
15
|
+
with:
|
|
16
|
+
enable-cache: true
|
|
17
|
+
- run: uv sync --extra dev
|
|
18
|
+
- run: uv run pylint src/mytnb/ tests/
|
|
19
|
+
- run: uv run ruff check src/mytnb/ tests/
|
|
20
|
+
|
|
21
|
+
test:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
strategy:
|
|
24
|
+
matrix:
|
|
25
|
+
python-version: ["3.10", "3.11", "3.12", "3.13"]
|
|
26
|
+
steps:
|
|
27
|
+
- uses: actions/checkout@v4
|
|
28
|
+
- uses: astral-sh/setup-uv@v5
|
|
29
|
+
with:
|
|
30
|
+
enable-cache: true
|
|
31
|
+
- run: uv sync --extra dev
|
|
32
|
+
- run: uv run pytest tests/ -v
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
|
|
8
|
+
jobs:
|
|
9
|
+
publish:
|
|
10
|
+
name: Build and publish to PyPI
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
permissions:
|
|
13
|
+
id-token: write
|
|
14
|
+
contents: read
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
17
|
+
- uses: astral-sh/setup-uv@fac544c07dec837d0ccb6301d7b5580bf5edae39 # v8.2.0
|
|
18
|
+
with:
|
|
19
|
+
enable-cache: true
|
|
20
|
+
- name: Build package
|
|
21
|
+
run: uv build
|
|
22
|
+
- name: Publish to PyPI
|
|
23
|
+
uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Python
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
*.egg-info/
|
|
6
|
+
*.egg
|
|
7
|
+
dist/
|
|
8
|
+
build/
|
|
9
|
+
*.whl
|
|
10
|
+
.pytest_cache/
|
|
11
|
+
|
|
12
|
+
# Virtual environments
|
|
13
|
+
.venv/
|
|
14
|
+
venv/
|
|
15
|
+
env/
|
|
16
|
+
|
|
17
|
+
# IDE
|
|
18
|
+
.vscode/
|
|
19
|
+
.idea/
|
|
20
|
+
*.swp
|
|
21
|
+
*.swo
|
|
22
|
+
|
|
23
|
+
# Reverse-engineering artifacts
|
|
24
|
+
resources/
|
|
25
|
+
|
|
26
|
+
# Config with credentials
|
|
27
|
+
mytnb.json
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Latiff Danieyal
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: python-mytnb
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Python library to interface with the myTNB API (Tenaga Nasional Berhad)
|
|
5
|
+
Project-URL: Repository, https://github.com/danieyal/python-mytnb
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Development Status :: 4 - Beta
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
11
|
+
Classifier: Operating System :: OS Independent
|
|
12
|
+
Classifier: Programming Language :: Python :: 3
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Requires-Dist: click>=8.3.3
|
|
20
|
+
Requires-Dist: cryptography>=42.0
|
|
21
|
+
Requires-Dist: httpx>=0.27
|
|
22
|
+
Requires-Dist: pydantic>=2.0
|
|
23
|
+
Requires-Dist: rich>=15.0.0
|
|
24
|
+
Requires-Dist: tls-client>=1.0
|
|
25
|
+
Provides-Extra: dev
|
|
26
|
+
Requires-Dist: pylint>=3.0; extra == 'dev'
|
|
27
|
+
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
|
|
28
|
+
Requires-Dist: pytest>=7.0; extra == 'dev'
|
|
29
|
+
Requires-Dist: ruff>=0.6; extra == 'dev'
|
|
30
|
+
Description-Content-Type: text/markdown
|
|
31
|
+
|
|
32
|
+
# python-mytnb
|
|
33
|
+
|
|
34
|
+
A Python library to interface with the myTNB (Tenaga Nasional Berhad) API for electricity account management and usage monitoring in Malaysia.
|
|
35
|
+
|
|
36
|
+
## Installation
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
uv add python-mytnb
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Or with pip:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
pip install python-mytnb
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Quick Start
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
import asyncio
|
|
52
|
+
from mytnb import MyTNBClient
|
|
53
|
+
|
|
54
|
+
async def main():
|
|
55
|
+
client = await MyTNBClient.login("user@example.com", "your-password")
|
|
56
|
+
|
|
57
|
+
async with client:
|
|
58
|
+
# Get smart meter usage & billing history
|
|
59
|
+
usage = await client.get_account_usage_smart("220123456789")
|
|
60
|
+
|
|
61
|
+
# Monthly billing breakdown
|
|
62
|
+
for month in usage.by_month.months:
|
|
63
|
+
print(f"{month.month} {month.year}: {month.usage_total} kWh — RM {month.amount_total}")
|
|
64
|
+
|
|
65
|
+
# Bill history & due amount
|
|
66
|
+
history = await client.get_bill_history("220123456789")
|
|
67
|
+
due = await client.get_account_due_amount("220123456789")
|
|
68
|
+
|
|
69
|
+
asyncio.run(main())
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
## CLI
|
|
73
|
+
|
|
74
|
+
Pass credentials directly:
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
mytnb --email user@example.com --password yourpass usage 220123456789
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or via environment variables:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
export MYTNB_EMAIL=user@example.com
|
|
84
|
+
export MYTNB_PASSWORD=yourpass
|
|
85
|
+
mytnb usage 220123456789
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
All commands:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
mytnb login # Test login, show user info
|
|
92
|
+
mytnb usage <account> # Monthly usage & billing summary
|
|
93
|
+
mytnb usage --daily <account> # Daily usage breakdown
|
|
94
|
+
mytnb usage --json <account> # Full usage data as JSON
|
|
95
|
+
mytnb current-usage <account> # Simplified current usage summary
|
|
96
|
+
mytnb due-amount <account> # Outstanding balance
|
|
97
|
+
mytnb bill-history <account> # Payment history
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Global options: `--debug` for full tracebacks, `--version`.
|
|
101
|
+
|
|
102
|
+
Use a config file instead of flags (`--config <path>`, `mytnb.json`, or `~/.config/mytnb/config.json`):
|
|
103
|
+
|
|
104
|
+
```bash
|
|
105
|
+
mytnb init-config # Generate a starter config file
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
## API Architecture
|
|
109
|
+
|
|
110
|
+
myTNB uses two API backends, both handled transparently by this library:
|
|
111
|
+
|
|
112
|
+
| Backend | Domain | Auth | Used for |
|
|
113
|
+
| ----------- | --------------------- | ------------------------------------------- | ----------------------------------- |
|
|
114
|
+
| REST | `api.mytnb.com.my` | JWT + API key | Account listing, eligibility checks |
|
|
115
|
+
| Legacy ASMX | `mytnbapp.tnb.com.my` | Encrypted payloads (AES-256-CBC + RSA-OAEP) | Usage data, billing, services |
|
|
116
|
+
|
|
117
|
+
Request encryption for the ASMX API is automatic — just pass plaintext parameters.
|
|
118
|
+
|
|
119
|
+
## Data Models
|
|
120
|
+
|
|
121
|
+
| Model | Description |
|
|
122
|
+
| --------------- | ---------------------------------------------------- |
|
|
123
|
+
| `AccountUsage` | Full usage response: metrics, monthly and daily data |
|
|
124
|
+
| `UsageMetric` | Current/average usage (kWh) |
|
|
125
|
+
| `CostMetric` | Current/projected cost (RM) |
|
|
126
|
+
| `BillingMonth` | Monthly billing record with tariff blocks |
|
|
127
|
+
| `DailyUsage` | Daily consumption and cost |
|
|
128
|
+
| `TariffBlock` | Tariff pricing block details |
|
|
129
|
+
| `SMRAccount` | Smart Meter Reading eligibility status |
|
|
130
|
+
| `BREligibility` | Bill rendering opt-in status |
|
|
131
|
+
|
|
132
|
+
## Geographic Restrictions
|
|
133
|
+
|
|
134
|
+
The myTNB API only accepts connections from **Malaysian IP addresses** and blocks most VPN services. If you get a `GeoBlockedError` (HTTP 403), make sure you are connecting from a Malaysian network without a VPN.
|
|
135
|
+
|
|
136
|
+
## Error Handling
|
|
137
|
+
|
|
138
|
+
```python
|
|
139
|
+
from mytnb.exceptions import MyTNBError, APIError, AuthenticationError, GeoBlockedError
|
|
140
|
+
|
|
141
|
+
try:
|
|
142
|
+
client = await MyTNBClient.login("user@example.com", "password")
|
|
143
|
+
async with client:
|
|
144
|
+
usage = await client.get_account_usage_smart("220123456789")
|
|
145
|
+
except GeoBlockedError:
|
|
146
|
+
print("Blocked — connect from a Malaysian IP without VPN")
|
|
147
|
+
except AuthenticationError:
|
|
148
|
+
print("Invalid email or password")
|
|
149
|
+
except APIError as e:
|
|
150
|
+
print(f"API error {e.error_code}: {e.display_message}")
|
|
151
|
+
except MyTNBError as e:
|
|
152
|
+
print(f"Error: {e}")
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Development
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
uv sync --extra dev
|
|
159
|
+
uv run pytest
|
|
160
|
+
uv run pylint src/mytnb/
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
## License
|
|
164
|
+
|
|
165
|
+
MIT
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
# python-mytnb
|
|
2
|
+
|
|
3
|
+
A Python library to interface with the myTNB (Tenaga Nasional Berhad) API for electricity account management and usage monitoring in Malaysia.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv add python-mytnb
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Or with pip:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install python-mytnb
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Quick Start
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import asyncio
|
|
21
|
+
from mytnb import MyTNBClient
|
|
22
|
+
|
|
23
|
+
async def main():
|
|
24
|
+
client = await MyTNBClient.login("user@example.com", "your-password")
|
|
25
|
+
|
|
26
|
+
async with client:
|
|
27
|
+
# Get smart meter usage & billing history
|
|
28
|
+
usage = await client.get_account_usage_smart("220123456789")
|
|
29
|
+
|
|
30
|
+
# Monthly billing breakdown
|
|
31
|
+
for month in usage.by_month.months:
|
|
32
|
+
print(f"{month.month} {month.year}: {month.usage_total} kWh — RM {month.amount_total}")
|
|
33
|
+
|
|
34
|
+
# Bill history & due amount
|
|
35
|
+
history = await client.get_bill_history("220123456789")
|
|
36
|
+
due = await client.get_account_due_amount("220123456789")
|
|
37
|
+
|
|
38
|
+
asyncio.run(main())
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## CLI
|
|
42
|
+
|
|
43
|
+
Pass credentials directly:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
mytnb --email user@example.com --password yourpass usage 220123456789
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Or via environment variables:
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
export MYTNB_EMAIL=user@example.com
|
|
53
|
+
export MYTNB_PASSWORD=yourpass
|
|
54
|
+
mytnb usage 220123456789
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
All commands:
|
|
58
|
+
|
|
59
|
+
```
|
|
60
|
+
mytnb login # Test login, show user info
|
|
61
|
+
mytnb usage <account> # Monthly usage & billing summary
|
|
62
|
+
mytnb usage --daily <account> # Daily usage breakdown
|
|
63
|
+
mytnb usage --json <account> # Full usage data as JSON
|
|
64
|
+
mytnb current-usage <account> # Simplified current usage summary
|
|
65
|
+
mytnb due-amount <account> # Outstanding balance
|
|
66
|
+
mytnb bill-history <account> # Payment history
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Global options: `--debug` for full tracebacks, `--version`.
|
|
70
|
+
|
|
71
|
+
Use a config file instead of flags (`--config <path>`, `mytnb.json`, or `~/.config/mytnb/config.json`):
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
mytnb init-config # Generate a starter config file
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
## API Architecture
|
|
78
|
+
|
|
79
|
+
myTNB uses two API backends, both handled transparently by this library:
|
|
80
|
+
|
|
81
|
+
| Backend | Domain | Auth | Used for |
|
|
82
|
+
| ----------- | --------------------- | ------------------------------------------- | ----------------------------------- |
|
|
83
|
+
| REST | `api.mytnb.com.my` | JWT + API key | Account listing, eligibility checks |
|
|
84
|
+
| Legacy ASMX | `mytnbapp.tnb.com.my` | Encrypted payloads (AES-256-CBC + RSA-OAEP) | Usage data, billing, services |
|
|
85
|
+
|
|
86
|
+
Request encryption for the ASMX API is automatic — just pass plaintext parameters.
|
|
87
|
+
|
|
88
|
+
## Data Models
|
|
89
|
+
|
|
90
|
+
| Model | Description |
|
|
91
|
+
| --------------- | ---------------------------------------------------- |
|
|
92
|
+
| `AccountUsage` | Full usage response: metrics, monthly and daily data |
|
|
93
|
+
| `UsageMetric` | Current/average usage (kWh) |
|
|
94
|
+
| `CostMetric` | Current/projected cost (RM) |
|
|
95
|
+
| `BillingMonth` | Monthly billing record with tariff blocks |
|
|
96
|
+
| `DailyUsage` | Daily consumption and cost |
|
|
97
|
+
| `TariffBlock` | Tariff pricing block details |
|
|
98
|
+
| `SMRAccount` | Smart Meter Reading eligibility status |
|
|
99
|
+
| `BREligibility` | Bill rendering opt-in status |
|
|
100
|
+
|
|
101
|
+
## Geographic Restrictions
|
|
102
|
+
|
|
103
|
+
The myTNB API only accepts connections from **Malaysian IP addresses** and blocks most VPN services. If you get a `GeoBlockedError` (HTTP 403), make sure you are connecting from a Malaysian network without a VPN.
|
|
104
|
+
|
|
105
|
+
## Error Handling
|
|
106
|
+
|
|
107
|
+
```python
|
|
108
|
+
from mytnb.exceptions import MyTNBError, APIError, AuthenticationError, GeoBlockedError
|
|
109
|
+
|
|
110
|
+
try:
|
|
111
|
+
client = await MyTNBClient.login("user@example.com", "password")
|
|
112
|
+
async with client:
|
|
113
|
+
usage = await client.get_account_usage_smart("220123456789")
|
|
114
|
+
except GeoBlockedError:
|
|
115
|
+
print("Blocked — connect from a Malaysian IP without VPN")
|
|
116
|
+
except AuthenticationError:
|
|
117
|
+
print("Invalid email or password")
|
|
118
|
+
except APIError as e:
|
|
119
|
+
print(f"API error {e.error_code}: {e.display_message}")
|
|
120
|
+
except MyTNBError as e:
|
|
121
|
+
print(f"Error: {e}")
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
## Development
|
|
125
|
+
|
|
126
|
+
```bash
|
|
127
|
+
uv sync --extra dev
|
|
128
|
+
uv run pytest
|
|
129
|
+
uv run pylint src/mytnb/
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## License
|
|
133
|
+
|
|
134
|
+
MIT
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "python-mytnb"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Python library to interface with the myTNB API (Tenaga Nasional Berhad)"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"License :: OSI Approved :: MIT License",
|
|
16
|
+
"Operating System :: OS Independent",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Programming Language :: Python :: 3.10",
|
|
19
|
+
"Programming Language :: Python :: 3.11",
|
|
20
|
+
"Programming Language :: Python :: 3.12",
|
|
21
|
+
"Programming Language :: Python :: 3.13",
|
|
22
|
+
"Topic :: Software Development :: Libraries",
|
|
23
|
+
]
|
|
24
|
+
dependencies = [
|
|
25
|
+
"httpx>=0.27",
|
|
26
|
+
"pydantic>=2.0",
|
|
27
|
+
"cryptography>=42.0",
|
|
28
|
+
"tls_client>=1.0",
|
|
29
|
+
"click>=8.3.3",
|
|
30
|
+
"rich>=15.0.0",
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
[project.optional-dependencies]
|
|
34
|
+
dev = [
|
|
35
|
+
"pytest>=7.0",
|
|
36
|
+
"pytest-asyncio>=0.21",
|
|
37
|
+
"pylint>=3.0",
|
|
38
|
+
"ruff>=0.6",
|
|
39
|
+
]
|
|
40
|
+
|
|
41
|
+
[project.scripts]
|
|
42
|
+
mytnb = "mytnb.cli:main"
|
|
43
|
+
|
|
44
|
+
[project.urls]
|
|
45
|
+
Repository = "https://github.com/danieyal/python-mytnb"
|
|
46
|
+
|
|
47
|
+
[tool.hatch.build.targets.wheel]
|
|
48
|
+
packages = ["src/mytnb"]
|
|
49
|
+
|
|
50
|
+
[tool.pylint.main]
|
|
51
|
+
load-plugins = []
|
|
52
|
+
jobs = 0
|
|
53
|
+
|
|
54
|
+
[tool.pylint.messages_control]
|
|
55
|
+
disable = [
|
|
56
|
+
"missing-module-docstring",
|
|
57
|
+
"missing-class-docstring",
|
|
58
|
+
"missing-function-docstring",
|
|
59
|
+
"too-few-public-methods",
|
|
60
|
+
"too-many-arguments",
|
|
61
|
+
"too-many-instance-attributes",
|
|
62
|
+
"too-many-return-statements",
|
|
63
|
+
"too-many-branches",
|
|
64
|
+
"too-many-locals",
|
|
65
|
+
"line-too-long",
|
|
66
|
+
"fixme",
|
|
67
|
+
"broad-exception-caught",
|
|
68
|
+
"import-error",
|
|
69
|
+
]
|
|
70
|
+
|
|
71
|
+
[tool.pylint.format]
|
|
72
|
+
max-line-length = 120
|
|
73
|
+
|
|
74
|
+
[tool.pylint.basic]
|
|
75
|
+
good-names = ["i", "j", "k", "v", "e", "f", "d", "ui", "di"]
|
|
76
|
+
|
|
77
|
+
[tool.ruff]
|
|
78
|
+
line-length = 120
|
|
79
|
+
|
|
80
|
+
[tool.ruff.lint]
|
|
81
|
+
select = ["E", "F", "I", "N", "W"]
|
|
82
|
+
|
|
83
|
+
[tool.ruff.lint.isort]
|
|
84
|
+
known-first-party = ["mytnb"]
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"""Python library for the myTNB API."""
|
|
2
|
+
|
|
3
|
+
from mytnb.client import MyTNBClient
|
|
4
|
+
from mytnb.crypto import EncryptedPayload, encrypt_request
|
|
5
|
+
from mytnb.models import (
|
|
6
|
+
AccountUsage,
|
|
7
|
+
BillingMonth,
|
|
8
|
+
CostMetric,
|
|
9
|
+
DailyUsage,
|
|
10
|
+
Metric,
|
|
11
|
+
TariffBlock,
|
|
12
|
+
UsageMetric,
|
|
13
|
+
)
|
|
14
|
+
|
|
15
|
+
__all__ = [
|
|
16
|
+
"MyTNBClient",
|
|
17
|
+
"EncryptedPayload",
|
|
18
|
+
"encrypt_request",
|
|
19
|
+
"AccountUsage",
|
|
20
|
+
"BillingMonth",
|
|
21
|
+
"CostMetric",
|
|
22
|
+
"DailyUsage",
|
|
23
|
+
"Metric",
|
|
24
|
+
"TariffBlock",
|
|
25
|
+
"UsageMetric",
|
|
26
|
+
]
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"""Authentication handling for myTNB API."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass
|
|
10
|
+
class DeviceInfo:
|
|
11
|
+
"""Device information sent with API requests."""
|
|
12
|
+
|
|
13
|
+
device_id: str
|
|
14
|
+
app_version: str = "4.0.2"
|
|
15
|
+
os_type: str = "2" # 1=Android, 2=iOS
|
|
16
|
+
os_version: str = "18.0"
|
|
17
|
+
device_desc: str = "EN"
|
|
18
|
+
version_code: str = "1425"
|
|
19
|
+
|
|
20
|
+
def to_dict(self) -> dict:
|
|
21
|
+
return {
|
|
22
|
+
"deviceId": self.device_id,
|
|
23
|
+
"appVersion": self.app_version,
|
|
24
|
+
"osType": self.os_type,
|
|
25
|
+
"osVersion": self.os_version,
|
|
26
|
+
"deviceDesc": self.device_desc,
|
|
27
|
+
"versionCode": self.version_code,
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
@dataclass
|
|
32
|
+
class UserInfo:
|
|
33
|
+
"""User information for authenticated requests."""
|
|
34
|
+
|
|
35
|
+
user_name: str
|
|
36
|
+
user_id: str
|
|
37
|
+
display_name: str = ""
|
|
38
|
+
role_id: str = "16"
|
|
39
|
+
language: str = "EN"
|
|
40
|
+
|
|
41
|
+
def to_dict(self) -> dict:
|
|
42
|
+
return {
|
|
43
|
+
"RoleId": self.role_id,
|
|
44
|
+
"UserId": self.user_id,
|
|
45
|
+
"UserName": self.user_name,
|
|
46
|
+
"Lang": self.language,
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class Credentials:
|
|
52
|
+
"""API credentials for myTNB."""
|
|
53
|
+
|
|
54
|
+
# REST API (api.mytnb.com.my)
|
|
55
|
+
api_key: str
|
|
56
|
+
authorization_token: str
|
|
57
|
+
# Optional bearer token for some endpoints
|
|
58
|
+
bearer_token: Optional[str] = None
|
|
59
|
+
# Channel API key (static JWT)
|
|
60
|
+
channel_api_key: Optional[str] = None
|
|
61
|
+
|
|
62
|
+
# Legacy API (mytnbapp.tnb.com.my)
|
|
63
|
+
secure_key: Optional[str] = None
|
|
64
|
+
|
|
65
|
+
# User context
|
|
66
|
+
user_info: Optional[UserInfo] = None
|
|
67
|
+
device_info: Optional[DeviceInfo] = None
|