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.
Files changed (31) hide show
  1. telekom_iptv_remote-0.1.0/PKG-INFO +153 -0
  2. telekom_iptv_remote-0.1.0/README.md +146 -0
  3. telekom_iptv_remote-0.1.0/pyproject.toml +28 -0
  4. telekom_iptv_remote-0.1.0/setup.cfg +4 -0
  5. telekom_iptv_remote-0.1.0/setup.py +6 -0
  6. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/__init__.py +45 -0
  7. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/codec.py +792 -0
  8. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/device.py +21 -0
  9. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/epg.py +84 -0
  10. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/keys.py +77 -0
  11. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/remote.py +224 -0
  12. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/__init__.py +11 -0
  13. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/__init__.py +7 -0
  14. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/channel.py +45 -0
  15. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/group.py +28 -0
  16. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/epg/program.py +49 -0
  17. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/__init__.py +19 -0
  18. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/activity.py +45 -0
  19. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/app.py +26 -0
  20. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/box_channel.py +31 -0
  21. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/capabilities.py +41 -0
  22. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/genre.py +29 -0
  23. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/now_playing.py +58 -0
  24. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/types/remote/paired_device.py +30 -0
  25. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/utils/__init__.py +21 -0
  26. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote/utils/parsing.py +69 -0
  27. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/PKG-INFO +153 -0
  28. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/SOURCES.txt +29 -0
  29. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/dependency_links.txt +1 -0
  30. telekom_iptv_remote-0.1.0/src/telekom_iptv_remote.egg-info/top_level.txt +1 -0
  31. 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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env python3
2
+ # Shim so `pip install -e .` works on older pip/setuptools too.
3
+ # All real metadata lives in pyproject.toml.
4
+ from setuptools import setup
5
+
6
+ setup()
@@ -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
+ ]