diff --git a/README.md b/README.md index 46238dd..6b4c979 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,9 @@ What it adds: - **Login from the Movistar home network**, no password. The home-network login gives a token for Movistar's new servers, which is traded once for a classic token; the classic servers renew it every 10 days. It needs the account number (taken from an existing login, or set in the settings) and an already registered device. - **Takes over the login of plugin.video.movistarplus** on first run (same registered device, so no new device is added to the account). - **Hides channels it can't play**: Movistar marks football and premium sports with `QMWatermark`; LALIGA and Liga de Campeones refuse their manifest, while M+ Deportes, DAZN and DAZN Baloncesto play and are kept (`resources/lib/kodimov.py`). -- **Extra free channels** in the TV guide list: TVG2 (10) and TeleVigo (155). -- **The TV guide list stays current**: re-exported every 6 hours, numbered with Movistar's own dial numbers. +- **Extra free channels** in the TV guide list: TVG2 (10) and TeleVigo (15). +- **The TV guide stays current**: channel list and programme guide re-exported every 6 hours, numbered with Movistar's own dial numbers. Listings come from Movistar (with catch-up links) for its channels and from the public EPG_dobleM guide for the extras. +- Fixes the programme guide export of upstream 1.0.0 (the amazon.tv guide is a flat list). - Its own proxy port (57031), so it can run next to the original. --- diff --git a/__pycache__/service.cpython-315.pyc b/__pycache__/service.cpython-315.pyc index 8ac43a4..f4f5e96 100644 Binary files a/__pycache__/service.cpython-315.pyc and b/__pycache__/service.cpython-315.pyc differ diff --git a/addon.xml b/addon.xml index dcc4869..2083ba7 100644 --- a/addon.xml +++ b/addon.xml @@ -1,7 +1,7 @@ @@ -19,9 +19,10 @@ Movistar+ for Kodi, with automatic login from the Movistar home network Movistar+ para Kodi, con inicio de sesión automático desde la red de Movistar - Fork of Paco8's Movistar+ add-on (plugin.video.movistarplus). Logs in by itself from the Movistar home network, keeps the channel list and numbers up to date, hides the channels that need stronger DRM than Kodi has, and adds free Galician channels (TVG2, TeleVigo) to the TV guide. Requires a Movistar+ subscription. - Fork del addon Movistar+ de Paco8 (plugin.video.movistarplus). Inicia sesión solo desde la red de Movistar, mantiene la lista y los números de los canales al día, oculta los canales que necesitan una protección que Kodi no tiene y añade canales gallegos gratuitos (TVG2, TeleVigo) a la guía. Se necesita estar abonado a Movistar+. - 1.0.0+kodimov.1: first KodiMov+ release, based on Movistar+ 1.0.0. + Fork of Paco8's Movistar+ add-on (plugin.video.movistarplus). Logs in by itself from the Movistar home network, keeps the channel list and numbers up to date, hides the channels that need stronger DRM than Kodi has, adds free Galician channels (TVG2, TeleVigo) and writes a programme guide for every channel. Requires a Movistar+ subscription. + Fork del addon Movistar+ de Paco8 (plugin.video.movistarplus). Inicia sesión solo desde la red de Movistar, mantiene la lista y los números de los canales al día, oculta los canales que necesitan una protección que Kodi no tiene añade canales gallegos gratuitos (TVG2, TeleVigo) y genera la guía de programación de todos los canales. Se necesita estar abonado a Movistar+. + 1.0.0+kodimov.2: programme guide for every channel (Movistar + TVG2/TeleVigo), TeleVigo on 15, silent background refresh. +1.0.0+kodimov.1: first KodiMov+ release, based on Movistar+ 1.0.0. This addon is not officially commissioned/supported by Movistar+. All product names, logos, and trademarks mentioned in this project are property of their respective owners. Este addon no ha sido realizado ni apoyado por Movistar+. Todos los nombres de productos, logotipos y marcas registradas mencionados en este proyecto son propiedad de sus respectivos dueños. all diff --git a/resources/lib/__pycache__/kodimov.cpython-315.pyc b/resources/lib/__pycache__/kodimov.cpython-315.pyc index 303f641..475e7de 100644 Binary files a/resources/lib/__pycache__/kodimov.cpython-315.pyc and b/resources/lib/__pycache__/kodimov.cpython-315.pyc differ diff --git a/resources/lib/__pycache__/movistar.cpython-315.pyc b/resources/lib/__pycache__/movistar.cpython-315.pyc index d6c3a0e..12217c9 100644 Binary files a/resources/lib/__pycache__/movistar.cpython-315.pyc and b/resources/lib/__pycache__/movistar.cpython-315.pyc differ diff --git a/resources/lib/__pycache__/plugin.cpython-315.pyc b/resources/lib/__pycache__/plugin.cpython-315.pyc index 7ee219e..c785eb5 100644 Binary files a/resources/lib/__pycache__/plugin.cpython-315.pyc and b/resources/lib/__pycache__/plugin.cpython-315.pyc differ diff --git a/resources/lib/kodimov.py b/resources/lib/kodimov.py index a82391d..997db50 100644 --- a/resources/lib/kodimov.py +++ b/resources/lib/kodimov.py @@ -33,14 +33,17 @@ WATERMARKED_BUT_PLAYABLE = { # Free channels that Movistar+ doesn't carry. 'chno' must not clash with a # Movistar dial number. EXTRA_CHANNELS = [ - {'id': 'TVG2', 'name': 'TVG2', 'chno': 10, 'group': 'Galicia', + {'id': 'TVG2', 'name': 'TVG2', 'chno': 10, 'group': 'Galicia', 'epg_id': 'TVG2', 'logo': 'https://i.imgur.com/yFy6gxg.png', 'url': 'https://crtvg-tvg2.flumotion.cloud/playlist.m3u8'}, - {'id': 'TeleVigo', 'name': 'TeleVigo', 'chno': 155, 'group': 'Galicia', + {'id': 'TeleVigo', 'name': 'TeleVigo', 'chno': 15, 'group': 'Galicia', 'epg_id': 'TELEVIGO', 'logo': 'https://i.imgur.com/4iVmlFa.png', 'url': 'https://stream-us-east-1.getpublica.com/playlist.m3u8?network_id=12687'}, ] +# Programme listings for the extra channels (Movistar's guide covers the rest). +EXTRA_EPG_URL = 'https://raw.githubusercontent.com/davidmuma/EPG_dobleM/master/guiafanart_color.xml.gz' + AUTOLOGIN_URL = 'https://soter-pf.svp.video.telefonicaservices.com/service/login/autologin?deviceId={device}&deviceType=webplayer&_={ts}' SOTER_INITDATA_URL = 'https://soter-pf.sve.video.telefonicaservices.com/service/login/initData?legacy=true' @@ -167,3 +170,33 @@ def write_if_changed(filename, content): f.write(content) os.replace(tmp, filename) if hasattr(os, 'replace') else shutil.move(tmp, filename) return True + + +def extra_channels_xmltv(session): + """Returns the and elements for the extra channels, + taken from a public XMLTV guide and renamed to their tvg-id.""" + import gzip + import xml.etree.ElementTree as ET + wanted = dict((c['epg_id'], c) for c in EXTRA_CHANNELS if c.get('epg_id')) + out = [] + for c in EXTRA_CHANNELS: + out.append('\n {name}\n \n\n'.format(**c)) + try: + r = session.get(EXTRA_EPG_URL, timeout=(10, 120)) + r.raise_for_status() + found = 0 + for _, el in ET.iterparse(gzip.GzipFile(fileobj=io.BytesIO(r.content))): + if el.tag == 'programme': + c = wanted.get(el.get('channel')) + if c: + el.set('channel', c['id']) + for icon in el.findall('icon'): el.remove(icon) # large fan art + out.append(ET.tostring(el, encoding='unicode') + '\n') + found += 1 + el.clear() + elif el.tag == 'channel': + el.clear() + LOG('extra_channels_xmltv: {} programmes'.format(found)) + except Exception as e: + LOG('extra_channels_xmltv: failed: {}'.format(e)) + return out diff --git a/resources/lib/movistar.py b/resources/lib/movistar.py index a5e9e2d..595c5ee 100644 --- a/resources/lib/movistar.py +++ b/resources/lib/movistar.py @@ -540,7 +540,16 @@ class Movistar(object): epg = {} if 'error' in data: return epg + # The amazon.tv guide is one flat list of programmes; webplayer's is + # grouped by channel. Group the flat one the same way. + if data and isinstance(data[0], dict): + groups = {} + for p in data: + groups.setdefault(p['Canal']['CodCadenaTv'], []).append(p) + data = list(groups.values()) + for ch in data: + if not ch: continue id = ch[0]['Canal']['CodCadenaTv'] if not id in epg: epg[id] = [] for p in ch: @@ -1537,9 +1546,10 @@ class Movistar(object): res.append(' {}\n'.format(c['name'])) res.append(' \n'); res.append('\n') + if self.add_extra_channels: + res.extend(kodimov.extra_channels_xmltv(self.net.session)) res.append('\n') - with io.open(filename, 'w', encoding='utf-8', newline='') as handle: - handle.write(''.join(res)) + kodimov.write_if_changed(filename, ''.join(res)) def save_credentials(self, username, password): from .b64 import encode_base64 diff --git a/resources/lib/plugin.py b/resources/lib/plugin.py index c7691a9..6bd0b26 100644 --- a/resources/lib/plugin.py +++ b/resources/lib/plugin.py @@ -776,23 +776,26 @@ def export_channels_now(): if m.export_channels_to_m3u8(channels_filename, addon.getSettingBool('only_subscribed')): LOG('export_channels_now: channel list changed') -def export_epg_now(): +def export_epg_now(quiet=False): + # quiet: background refresh from the service, no pop-ups while watching TV + def notify(message): + if not quiet: show_notification(message, xbmcgui.NOTIFICATION_INFO) def report_progress(message): - show_notification(addon.getLocalizedString(30314).format(message), xbmcgui.NOTIFICATION_INFO) + notify(addon.getLocalizedString(30314).format(message)) if not m.logged: return only_subscribed = addon.getSettingBool('only_subscribed') channels_filename = os.path.join(profile_dir, 'channels.m3u8') epg_filename = os.path.join(profile_dir, 'epg.xml') - show_notification(addon.getLocalizedString(30310), xbmcgui.NOTIFICATION_INFO) + notify(addon.getLocalizedString(30310)) m.export_channels_to_m3u8(channels_filename, only_subscribed) if True: #not addon.getSettingBool('use_external_epg'): - show_notification(addon.getLocalizedString(30311), xbmcgui.NOTIFICATION_INFO) + notify(addon.getLocalizedString(30311)) m.export_epg_to_xml(epg_filename, addon.getSettingInt('export_days'), report_progress, only_subscribed) - show_notification(addon.getLocalizedString(30313), xbmcgui.NOTIFICATION_INFO) + notify(addon.getLocalizedString(30313)) def to_wishlist(params): @@ -886,7 +889,7 @@ def router(paramstring): elif params['action'] == 'create_iptv_settings': create_iptv_settings() elif params['action'] == 'export_epg_now': - export_epg_now() + export_epg_now(quiet=params.get('quiet') == '1') elif params['action'] == 'to_wishlist': to_wishlist(params) elif 'iptv' in params['action']: diff --git a/resources/settings.xml b/resources/settings.xml index 44293c7..9b76087 100644 --- a/resources/settings.xml +++ b/resources/settings.xml @@ -62,7 +62,7 @@ --> - + diff --git a/service.py b/service.py index 9e29564..958c9b6 100644 --- a/service.py +++ b/service.py @@ -38,14 +38,8 @@ if __name__ == '__main__': proxy.start() LOG('proxy_address: {}'.format(proxy.proxy_address)) - if addon.getSettingBool('export_epg'): - try: - export_epg() - except: - pass - - # Refreshes the TV guide channel list (new channels, new numbers) every - # 6 hours. Opening the add-on also renews the 10-day Movistar login. + # Refreshes the TV guide channel list (new channels, new numbers) and the + # programme guide a minute after Kodi starts and then every 6 hours. Opening the add-on also renews the 10-day Movistar login. next_export = time.time() + 60 monitor = xbmc.Monitor() while not monitor.abortRequested(): @@ -53,7 +47,9 @@ if __name__ == '__main__': break if time.time() >= next_export: next_export = time.time() + 6 * 3600 - if addon.getSettingBool('export_channels') and not addon.getSettingBool('export_epg'): + if addon.getSettingBool('export_epg'): # channels + programme guide + xbmc.executebuiltin('RunPlugin(plugin://plugin.video.kodimovplus/?action=export_epg_now&quiet=1)') + elif addon.getSettingBool('export_channels'): xbmc.executebuiltin('RunPlugin(plugin://plugin.video.kodimovplus/?action=export_channels_now)') proxy.stop()