telekom-iptv-remote 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.
- telekom_iptv_remote-0.1.0/PKG-INFO +153 -0
- telekom_iptv_remote-0.1.0/README.md +146 -0
- telekom_iptv_remote-0.1.0/pyproject.toml +28 -0
- telekom_iptv_remote-0.1.0/setup.cfg +4 -0
- telekom_iptv_remote-0.1.0/setup.py +6 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/__init__.py +45 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/codec.py +792 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/device.py +21 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/epg.py +84 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/keys.py +77 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/remote.py +224 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/__init__.py +11 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/__init__.py +7 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/channel.py +45 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/group.py +28 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/program.py +49 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/__init__.py +19 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/activity.py +45 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/app.py +26 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/box_channel.py +31 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/capabilities.py +41 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/genre.py +29 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/now_playing.py +58 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/paired_device.py +30 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/utils/__init__.py +21 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/utils/parsing.py +69 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/PKG-INFO +153 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/SOURCES.txt +29 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/dependency_links.txt +1 -0
- telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/top_level.txt +1 -0
- telekom_iptv_remote-0.1.0/tests/test_capture.py +74 -0
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: telekom-iptv-remote
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Client for the Magyar Telekom (Microsoft Mediaroom) set-top-box remote and EPG
|
|
5
|
+
Requires-Python: >=3.9
|
|
6
|
+
Description-Content-Type: text/markdown
|
|
7
|
+
|
|
8
|
+
# Telekom IPTV remote and EPG
|
|
9
|
+
|
|
10
|
+
Reverse-engineered from the Magyar Telekom Műsorújság Android app
|
|
11
|
+
(`tmobile.hu.android.epgmiab`, v4.2.359). Stdlib only, no dependencies.
|
|
12
|
+
|
|
13
|
+
Two independent subsystems:
|
|
14
|
+
|
|
15
|
+
| Subsystem | Transport | Auth | Needs the box? |
|
|
16
|
+
|-----------|-----------|------|----------------|
|
|
17
|
+
| Local remote | HTTP to STB `:53208`, custom CS64/BV4 crypto | 8-hex code, then cid/key | yes |
|
|
18
|
+
| Cloud EPG | XML over HTTPS, `smartapp.telekom.hu` | UserID `0` (public) or PIN-linked | no |
|
|
19
|
+
|
|
20
|
+
## Package layout
|
|
21
|
+
|
|
22
|
+
```
|
|
23
|
+
src/telekom_iptv_remote/ reusable library (stdlib only)
|
|
24
|
+
__init__.py exports Remote, Device, Key, Epg
|
|
25
|
+
codec.py crypto engine (Codec), ported from Java with readable names
|
|
26
|
+
device.py Device dataclass (ip, cid, key, port, seq, name)
|
|
27
|
+
remote.py Remote: pair(), send_key(), tune(), unpair(), info(), activity(),
|
|
28
|
+
capabilities(), channels(), apps(), devices(), genres()
|
|
29
|
+
epg.py Epg: channels(), groups(), schedules(), register_pin()
|
|
30
|
+
keys.py Key enum (53 buttons)
|
|
31
|
+
utils/ shared XML parsing and coercion helpers
|
|
32
|
+
types/
|
|
33
|
+
epg/ typed EPG models: Channel, Program, ChannelGroup
|
|
34
|
+
remote/ typed box models: NowPlaying, Activity, BoxChannel,
|
|
35
|
+
Capabilities, App, PairedDevice, Genre
|
|
36
|
+
examples/
|
|
37
|
+
pairing.py one-time pairing, prints clientId and clientKey
|
|
38
|
+
epg.py channels and schedules (no box needed)
|
|
39
|
+
remote_commands.py send commands using a saved clientId and clientKey
|
|
40
|
+
state.py dump everything the box reports (state, now-playing,
|
|
41
|
+
channels, devices, apps, genres)
|
|
42
|
+
tests/
|
|
43
|
+
test_capture.py byte-exact regression test against a real captured pairing
|
|
44
|
+
decompiled-reference/ jadx output for the crypto classes
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
A separate HomeAssistant integration package depends on this one.
|
|
48
|
+
|
|
49
|
+
## Library usage
|
|
50
|
+
|
|
51
|
+
```python
|
|
52
|
+
from telekom_iptv_remote import Remote, Device, Key, Epg
|
|
53
|
+
|
|
54
|
+
# one-time pairing (enter the 8-hex code shown on the TV)
|
|
55
|
+
remote = Remote.pair("192.168.1.50", "1a2b3c4d")
|
|
56
|
+
print(remote.device.cid, remote.device.key) # persistent clientId and clientKey
|
|
57
|
+
|
|
58
|
+
# later: reuse the credentials, no re-pairing
|
|
59
|
+
remote = Remote(
|
|
60
|
+
Device(
|
|
61
|
+
"192.168.1.50",
|
|
62
|
+
cid="12345678-90ab-cdef-1234-567890abcdef",
|
|
63
|
+
key="0123456789ABCDEF",
|
|
64
|
+
volume_min=0, # the box's volume scale (default 1..25); configure per device
|
|
65
|
+
volume_max=25,
|
|
66
|
+
)
|
|
67
|
+
)
|
|
68
|
+
remote.send_key(Key.VOLUME_UP) # True if the box accepted it
|
|
69
|
+
remote.set_volume(12) # absolute volume: reads current, sends the up/down steps
|
|
70
|
+
remote.tune("8") # by logical channel number (LCN)
|
|
71
|
+
remote.tune("206", epg_id=True) # or by EPG id, resolved to the LCN via op=channels
|
|
72
|
+
|
|
73
|
+
# read live box state, returns typed models from telekom_iptv_remote.types.remote
|
|
74
|
+
act = remote.activity() # Activity: volume, muted, channel (LCN), awake, streaming
|
|
75
|
+
print(act.volume, act.muted, act.channel, act.awake)
|
|
76
|
+
now = remote.info() # NowPlaying: current program
|
|
77
|
+
print(now.title, now.start_time, now.end_time, now.tune, now.station_epg_id)
|
|
78
|
+
cap = remote.capabilities() # Capabilities: device_type, client_version, commands
|
|
79
|
+
chans = remote.channels() # list[BoxChannel], maps LCN to EPG id (paginated)
|
|
80
|
+
|
|
81
|
+
# cloud EPG (no box needed), returns typed models from telekom_iptv_remote.types.epg
|
|
82
|
+
epg = Epg()
|
|
83
|
+
channels = epg.channels() # list[Channel] (ch.id, ch.name, ch.rank:int, ch.group_ids)
|
|
84
|
+
groups = epg.groups() # list[ChannelGroup]
|
|
85
|
+
guide = epg.schedules(["206", "369"]) # dict[str, list[Program]]
|
|
86
|
+
for program in guide["206"]: # program.start_time: datetime, program.channel_id: str
|
|
87
|
+
print(program.title, program.start_time, program.end_time, program.channel_id)
|
|
88
|
+
user_id = epg.register_pin("1234") # optional: personal UserID for recordings
|
|
89
|
+
|
|
90
|
+
# Each method is one fetch. Models carry raw ids (Channel.group_ids, Program.channel_id).
|
|
91
|
+
# Resolving those to objects and any caching are the caller's responsibility.
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Examples
|
|
95
|
+
|
|
96
|
+
Runnable from a checkout (each adds `src/` to `sys.path` via `_bootstrap`):
|
|
97
|
+
|
|
98
|
+
```bash
|
|
99
|
+
python3 examples/pairing.py 192.168.1.50 1A2B3C4D # one-time, prints cid/key
|
|
100
|
+
python3 examples/epg.py # channels and today's guide
|
|
101
|
+
python3 examples/remote_commands.py <ip> <cid> <key> # send a command or run the demo
|
|
102
|
+
python3 examples/state.py <ip> <cid> <key> # dump all box state (read-only)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Install it as a dependency instead:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install -e . # then: from telekom_iptv_remote import Remote, Device, Key, Epg
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Protocol notes
|
|
112
|
+
|
|
113
|
+
**Pairing.** `op=pair&key=<code>&name=&tags=` encrypted with bootstrap cid
|
|
114
|
+
`E7AAEC8C-…` and key `code+code`. The box replies with XML holding your persistent
|
|
115
|
+
`cid` and `key`.
|
|
116
|
+
|
|
117
|
+
**Commands.** `op=remotekey&key=<name>` and `op=tune&s=<ch>&type=channel`, encrypted
|
|
118
|
+
with your cid and key, POSTed to `http://<ip>:53208/companion?hash=…&cid=…`.
|
|
119
|
+
|
|
120
|
+
**State read-back.** The box supports many Mediaroom ops the app never uses.
|
|
121
|
+
`op=activity` returns live state: volume, mute, channel, awake or standby, streaming,
|
|
122
|
+
recording. `op=info` returns the current program. `op=channels` returns the box
|
|
123
|
+
channel list, which maps LCN to EPG id, paginated 30 per page via `&index=N`.
|
|
124
|
+
`op=capabilities` returns device info and the full command set. `op=apps`,
|
|
125
|
+
`op=devices`, and `op=genres` return the installed apps, paired companions, and the
|
|
126
|
+
genre tree. The library wraps these as `activity()`, `info()`, `channels()`,
|
|
127
|
+
`capabilities()`, `apps()`, `devices()`, and `genres()`.
|
|
128
|
+
|
|
129
|
+
**op=open.** `open()` raises `NotImplementedError` because the box's URL format is
|
|
130
|
+
unknown. An app urn returns 404, and the app has the op but never calls it.
|
|
131
|
+
|
|
132
|
+
**Ops that need parameters.** `op=diags` returns 404 (listed in capabilities but
|
|
133
|
+
unsupported on this box). `op=audio`, `op=sound`, `op=state`, `op=subtitles`,
|
|
134
|
+
`op=devicename`, and `op=listen` return 400 without parameters, whose format is
|
|
135
|
+
unknown. `op=listen` is probably a notification or subscribe channel worth working
|
|
136
|
+
out, so state updates could be pushed instead of polled.
|
|
137
|
+
|
|
138
|
+
**Crypto** (`codec.Codec`). A CBC-MAC and a CS64 word hash derive a per-message
|
|
139
|
+
nonce. An RC4-style stream (`StreamCipher`) encrypts the body. An 8-byte MAC trailer
|
|
140
|
+
authenticates it. The URL `hash` is a separate keyed digest over seq, length, ip, and
|
|
141
|
+
cid bytes. `seq` is always 0, and the cid is byte-reordered like .NET
|
|
142
|
+
`Guid.ToByteArray()`. Every class in `codec.py` maps one-to-one to a Java class, with
|
|
143
|
+
a mapping table at the top of the file.
|
|
144
|
+
|
|
145
|
+
## Verified
|
|
146
|
+
|
|
147
|
+
- `python3 tests/test_capture.py` matches a real captured pairing byte for byte:
|
|
148
|
+
request body, URL hash, response decrypt, and credential parse.
|
|
149
|
+
- EPG checked live against `smartapp.telekom.hu`.
|
|
150
|
+
- Local remote checked live: `send_key` returns HTTP 204 from a real box.
|
|
151
|
+
- State read-back checked live: `activity()` returned `volume=21, channel=8,
|
|
152
|
+
muted=False`, and `info()` returned the current program with parsed start and end
|
|
153
|
+
times.
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Telekom IPTV remote and EPG
|
|
2
|
+
|
|
3
|
+
Reverse-engineered from the Magyar Telekom Műsorújság Android app
|
|
4
|
+
(`tmobile.hu.android.epgmiab`, v4.2.359). Stdlib only, no dependencies.
|
|
5
|
+
|
|
6
|
+
Two independent subsystems:
|
|
7
|
+
|
|
8
|
+
| Subsystem | Transport | Auth | Needs the box? |
|
|
9
|
+
|-----------|-----------|------|----------------|
|
|
10
|
+
| Local remote | HTTP to STB `:53208`, custom CS64/BV4 crypto | 8-hex code, then cid/key | yes |
|
|
11
|
+
| Cloud EPG | XML over HTTPS, `smartapp.telekom.hu` | UserID `0` (public) or PIN-linked | no |
|
|
12
|
+
|
|
13
|
+
## Package layout
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
src/telekom_iptv_remote/ reusable library (stdlib only)
|
|
17
|
+
__init__.py exports Remote, Device, Key, Epg
|
|
18
|
+
codec.py crypto engine (Codec), ported from Java with readable names
|
|
19
|
+
device.py Device dataclass (ip, cid, key, port, seq, name)
|
|
20
|
+
remote.py Remote: pair(), send_key(), tune(), unpair(), info(), activity(),
|
|
21
|
+
capabilities(), channels(), apps(), devices(), genres()
|
|
22
|
+
epg.py Epg: channels(), groups(), schedules(), register_pin()
|
|
23
|
+
keys.py Key enum (53 buttons)
|
|
24
|
+
utils/ shared XML parsing and coercion helpers
|
|
25
|
+
types/
|
|
26
|
+
epg/ typed EPG models: Channel, Program, ChannelGroup
|
|
27
|
+
remote/ typed box models: NowPlaying, Activity, BoxChannel,
|
|
28
|
+
Capabilities, App, PairedDevice, Genre
|
|
29
|
+
examples/
|
|
30
|
+
pairing.py one-time pairing, prints clientId and clientKey
|
|
31
|
+
epg.py channels and schedules (no box needed)
|
|
32
|
+
remote_commands.py send commands using a saved clientId and clientKey
|
|
33
|
+
state.py dump everything the box reports (state, now-playing,
|
|
34
|
+
channels, devices, apps, genres)
|
|
35
|
+
tests/
|
|
36
|
+
test_capture.py byte-exact regression test against a real captured pairing
|
|
37
|
+
decompiled-reference/ jadx output for the crypto classes
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
A separate HomeAssistant integration package depends on this one.
|
|
41
|
+
|
|
42
|
+
## Library usage
|
|
43
|
+
|
|
44
|
+
```python
|
|
45
|
+
from telekom_iptv_remote import Remote, Device, Key, Epg
|
|
46
|
+
|
|
47
|
+
# one-time pairing (enter the 8-hex code shown on the TV)
|
|
48
|
+
remote = Remote.pair("192.168.1.50", "1a2b3c4d")
|
|
49
|
+
print(remote.device.cid, remote.device.key) # persistent clientId and clientKey
|
|
50
|
+
|
|
51
|
+
# later: reuse the credentials, no re-pairing
|
|
52
|
+
remote = Remote(
|
|
53
|
+
Device(
|
|
54
|
+
"192.168.1.50",
|
|
55
|
+
cid="12345678-90ab-cdef-1234-567890abcdef",
|
|
56
|
+
key="0123456789ABCDEF",
|
|
57
|
+
volume_min=0, # the box's volume scale (default 1..25); configure per device
|
|
58
|
+
volume_max=25,
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
remote.send_key(Key.VOLUME_UP) # True if the box accepted it
|
|
62
|
+
remote.set_volume(12) # absolute volume: reads current, sends the up/down steps
|
|
63
|
+
remote.tune("8") # by logical channel number (LCN)
|
|
64
|
+
remote.tune("206", epg_id=True) # or by EPG id, resolved to the LCN via op=channels
|
|
65
|
+
|
|
66
|
+
# read live box state, returns typed models from telekom_iptv_remote.types.remote
|
|
67
|
+
act = remote.activity() # Activity: volume, muted, channel (LCN), awake, streaming
|
|
68
|
+
print(act.volume, act.muted, act.channel, act.awake)
|
|
69
|
+
now = remote.info() # NowPlaying: current program
|
|
70
|
+
print(now.title, now.start_time, now.end_time, now.tune, now.station_epg_id)
|
|
71
|
+
cap = remote.capabilities() # Capabilities: device_type, client_version, commands
|
|
72
|
+
chans = remote.channels() # list[BoxChannel], maps LCN to EPG id (paginated)
|
|
73
|
+
|
|
74
|
+
# cloud EPG (no box needed), returns typed models from telekom_iptv_remote.types.epg
|
|
75
|
+
epg = Epg()
|
|
76
|
+
channels = epg.channels() # list[Channel] (ch.id, ch.name, ch.rank:int, ch.group_ids)
|
|
77
|
+
groups = epg.groups() # list[ChannelGroup]
|
|
78
|
+
guide = epg.schedules(["206", "369"]) # dict[str, list[Program]]
|
|
79
|
+
for program in guide["206"]: # program.start_time: datetime, program.channel_id: str
|
|
80
|
+
print(program.title, program.start_time, program.end_time, program.channel_id)
|
|
81
|
+
user_id = epg.register_pin("1234") # optional: personal UserID for recordings
|
|
82
|
+
|
|
83
|
+
# Each method is one fetch. Models carry raw ids (Channel.group_ids, Program.channel_id).
|
|
84
|
+
# Resolving those to objects and any caching are the caller's responsibility.
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Examples
|
|
88
|
+
|
|
89
|
+
Runnable from a checkout (each adds `src/` to `sys.path` via `_bootstrap`):
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
python3 examples/pairing.py 192.168.1.50 1A2B3C4D # one-time, prints cid/key
|
|
93
|
+
python3 examples/epg.py # channels and today's guide
|
|
94
|
+
python3 examples/remote_commands.py <ip> <cid> <key> # send a command or run the demo
|
|
95
|
+
python3 examples/state.py <ip> <cid> <key> # dump all box state (read-only)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Install it as a dependency instead:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pip install -e . # then: from telekom_iptv_remote import Remote, Device, Key, Epg
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Protocol notes
|
|
105
|
+
|
|
106
|
+
**Pairing.** `op=pair&key=<code>&name=&tags=` encrypted with bootstrap cid
|
|
107
|
+
`E7AAEC8C-…` and key `code+code`. The box replies with XML holding your persistent
|
|
108
|
+
`cid` and `key`.
|
|
109
|
+
|
|
110
|
+
**Commands.** `op=remotekey&key=<name>` and `op=tune&s=<ch>&type=channel`, encrypted
|
|
111
|
+
with your cid and key, POSTed to `http://<ip>:53208/companion?hash=…&cid=…`.
|
|
112
|
+
|
|
113
|
+
**State read-back.** The box supports many Mediaroom ops the app never uses.
|
|
114
|
+
`op=activity` returns live state: volume, mute, channel, awake or standby, streaming,
|
|
115
|
+
recording. `op=info` returns the current program. `op=channels` returns the box
|
|
116
|
+
channel list, which maps LCN to EPG id, paginated 30 per page via `&index=N`.
|
|
117
|
+
`op=capabilities` returns device info and the full command set. `op=apps`,
|
|
118
|
+
`op=devices`, and `op=genres` return the installed apps, paired companions, and the
|
|
119
|
+
genre tree. The library wraps these as `activity()`, `info()`, `channels()`,
|
|
120
|
+
`capabilities()`, `apps()`, `devices()`, and `genres()`.
|
|
121
|
+
|
|
122
|
+
**op=open.** `open()` raises `NotImplementedError` because the box's URL format is
|
|
123
|
+
unknown. An app urn returns 404, and the app has the op but never calls it.
|
|
124
|
+
|
|
125
|
+
**Ops that need parameters.** `op=diags` returns 404 (listed in capabilities but
|
|
126
|
+
unsupported on this box). `op=audio`, `op=sound`, `op=state`, `op=subtitles`,
|
|
127
|
+
`op=devicename`, and `op=listen` return 400 without parameters, whose format is
|
|
128
|
+
unknown. `op=listen` is probably a notification or subscribe channel worth working
|
|
129
|
+
out, so state updates could be pushed instead of polled.
|
|
130
|
+
|
|
131
|
+
**Crypto** (`codec.Codec`). A CBC-MAC and a CS64 word hash derive a per-message
|
|
132
|
+
nonce. An RC4-style stream (`StreamCipher`) encrypts the body. An 8-byte MAC trailer
|
|
133
|
+
authenticates it. The URL `hash` is a separate keyed digest over seq, length, ip, and
|
|
134
|
+
cid bytes. `seq` is always 0, and the cid is byte-reordered like .NET
|
|
135
|
+
`Guid.ToByteArray()`. Every class in `codec.py` maps one-to-one to a Java class, with
|
|
136
|
+
a mapping table at the top of the file.
|
|
137
|
+
|
|
138
|
+
## Verified
|
|
139
|
+
|
|
140
|
+
- `python3 tests/test_capture.py` matches a real captured pairing byte for byte:
|
|
141
|
+
request body, URL hash, response decrypt, and credential parse.
|
|
142
|
+
- EPG checked live against `smartapp.telekom.hu`.
|
|
143
|
+
- Local remote checked live: `send_key` returns HTTP 204 from a real box.
|
|
144
|
+
- State read-back checked live: `activity()` returned `volume=21, channel=8,
|
|
145
|
+
muted=False`, and `info()` returned the current program with parsed start and end
|
|
146
|
+
times.
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "telekom-iptv-remote"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Client for the Magyar Telekom (Microsoft Mediaroom) set-top-box remote and EPG"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.9"
|
|
11
|
+
dependencies = [] # stdlib only
|
|
12
|
+
|
|
13
|
+
[tool.setuptools.packages.find]
|
|
14
|
+
where = ["src"]
|
|
15
|
+
include = ["telekom_iptv_remote*"]
|
|
16
|
+
|
|
17
|
+
[tool.ruff]
|
|
18
|
+
line-length = 100
|
|
19
|
+
target-version = "py39"
|
|
20
|
+
|
|
21
|
+
[tool.ruff.lint]
|
|
22
|
+
# pycodestyle errors, pyflakes, and import sorting. Line length and other
|
|
23
|
+
# formatting concerns are owned by `ruff format`, not the linter.
|
|
24
|
+
select = ["E4", "E7", "E9", "F", "I"]
|
|
25
|
+
|
|
26
|
+
[tool.ruff.lint.per-file-ignores]
|
|
27
|
+
"tests/*" = ["E402"] # sys.path shim runs before imports
|
|
28
|
+
"examples/*" = ["E402"]
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Telekom (Microsoft Mediaroom) set-top-box remote + EPG client.
|
|
3
|
+
|
|
4
|
+
from telekom_iptv_remote import Remote, Device, Key, Epg
|
|
5
|
+
|
|
6
|
+
remote = Remote.pair("192.168.1.50", "1a2b3c4d")
|
|
7
|
+
remote.send_key(Key.VOLUME_UP)
|
|
8
|
+
|
|
9
|
+
epg = Epg()
|
|
10
|
+
print(epg.channels())
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
from .device import Device
|
|
14
|
+
from .epg import Epg
|
|
15
|
+
from .keys import ALL_KEYS, Key
|
|
16
|
+
from .remote import PairingError, Remote
|
|
17
|
+
from .types import Channel, ChannelGroup, Program
|
|
18
|
+
from .types.remote import (
|
|
19
|
+
Activity,
|
|
20
|
+
App,
|
|
21
|
+
BoxChannel,
|
|
22
|
+
Capabilities,
|
|
23
|
+
Genre,
|
|
24
|
+
NowPlaying,
|
|
25
|
+
PairedDevice,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"Remote",
|
|
30
|
+
"Device",
|
|
31
|
+
"Key",
|
|
32
|
+
"Epg",
|
|
33
|
+
"PairingError",
|
|
34
|
+
"ALL_KEYS",
|
|
35
|
+
"Channel",
|
|
36
|
+
"Program",
|
|
37
|
+
"ChannelGroup",
|
|
38
|
+
"NowPlaying",
|
|
39
|
+
"Activity",
|
|
40
|
+
"BoxChannel",
|
|
41
|
+
"Capabilities",
|
|
42
|
+
"App",
|
|
43
|
+
"PairedDevice",
|
|
44
|
+
"Genre",
|
|
45
|
+
]
|