From e3ee17cc11cde9dd677980cb9aa27d4365d38796 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C3=ABl=20Arnauts?= Date: Sat, 27 Nov 2021 16:23:29 +0100 Subject: [PATCH] Code cleanup, bugfixing, improvements --- .env.example | 3 +- .gitattributes | 3 +- .pylintrc | 1 + Makefile | 3 +- README.md | 2 - addon.xml | 4 +- requirements.txt | 1 + .../resource.language.en_gb/strings.po | 191 +--------------- .../resource.language.nl_nl/strings.po | 216 ++---------------- resources/lib/addon.py | 37 +-- resources/lib/modules/authentication.py | 42 +++- resources/lib/modules/catalog.py | 18 +- resources/lib/modules/iptvmanager.py | 4 +- resources/lib/modules/menu.py | 16 +- resources/lib/modules/player.py | 44 +--- resources/lib/modules/search.py | 4 +- resources/lib/service.py | 3 - resources/lib/vtmgo/__init__.py | 4 +- resources/lib/vtmgo/exceptions.py | 13 -- resources/lib/vtmgo/vtmgo.py | 91 -------- resources/lib/vtmgo/vtmgoauth.py | 78 +++---- resources/lib/vtmgo/vtmgostream.py | 2 +- scripts/check_for_unused_translations.py | 34 +++ scripts/update_translations.py | 40 ++++ tests/__init__.py | 15 +- tests/test_api.py | 35 +-- tests/test_auth.py | 34 +-- tests/test_iptvmanager.py | 2 - tests/test_routing.py | 5 + 29 files changed, 227 insertions(+), 718 deletions(-) create mode 100755 scripts/check_for_unused_translations.py create mode 100755 scripts/update_translations.py diff --git a/.env.example b/.env.example index c0e8ac7d..1e33a904 100644 --- a/.env.example +++ b/.env.example @@ -1,5 +1,4 @@ -ADDON_DEVICE_CODE= -ADDON_PROFILE= +ADDON_TOKEN= KODI_HOME=tests/home KODI_INTERACTIVE=0 diff --git a/.gitattributes b/.gitattributes index f52271bf..2a43aaf7 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,9 +1,10 @@ .env.example export-ignore .github/ export-ignore -tests/ export-ignore .gitattributes export-ignore .gitignore export-ignore .pylintrc export-ignore codecov.yml export-ignore Makefile export-ignore requirements.txt export-ignore +scripts/ export-ignore +tests/ export-ignore diff --git a/.pylintrc b/.pylintrc index af7ab972..087266d8 100644 --- a/.pylintrc +++ b/.pylintrc @@ -22,3 +22,4 @@ disable= super-with-arguments, # Python 2.7 compatibility raise-missing-from, # Python 2.7 compatibility + consider-using-f-string, # Python 2.7 compatibility diff --git a/Makefile b/Makefile index 4783482e..044d5806 100644 --- a/Makefile +++ b/Makefile @@ -30,8 +30,9 @@ check-pylint: check-translations: @printf ">>> Running translation checks\n" @$(foreach lang,$(languages), \ - msgcmp resources/language/resource.language.$(lang)/strings.po resources/language/resource.language.en_gb/strings.po; \ + msgcmp --use-untranslated resources/language/resource.language.$(lang)/strings.po resources/language/resource.language.en_gb/strings.po; \ ) + @scripts/check_for_unused_translations.py check-addon: clean build @printf ">>> Running addon checks\n" diff --git a/README.md b/README.md index e6c12e33..83c73273 100644 --- a/README.md +++ b/README.md @@ -21,9 +21,7 @@ De volgende features worden ondersteund: * Bekijk en speel rechtstreeks vanaf de TV gids * Doorzoek de catalogus * Bekijk YouTube-filmpjes van enkele DPG Media kanalen -* ~~Gebruikersprofielen (VTM GO of VTM GO Kids)~~ * Integratie met [IPTV Manager](https://github.com/add-ons/service.iptv.manager) -* ~~Integratie met Kodi bibliotheek~~ ## Screenshots diff --git a/addon.xml b/addon.xml index e8537652..73e5341e 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -23,7 +23,7 @@ Deze add-on wordt niet ondersteund door DPG Media en wordt aangeboden 'as is', zonder enige garantie. De VTM GO naam, VTM GO logo en de kanaallogo's zijn eigendom van DPG Media en worden gebruikt in overeenstemming met de fair use policy. all GPL-3.0-only - v2.0 (XXXX-XX-XX) + v1.3 (XXXX-XX-XX) - Rewrite authentication, update API and remove broken features. https://github.com/add-ons/plugin.video.vtm.go diff --git a/requirements.txt b/requirements.txt index b476add6..c780ced0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -8,5 +8,6 @@ python-dateutil pysocks requests git+git://github.com/dagwieers/kodi-plugin-routing.git@setup#egg=routing +mock sakee win-inet-pton; platform_system=="Windows" \ No newline at end of file diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po index 3c2d2b53..e2aed771 100644 --- a/resources/language/resource.language.en_gb/strings.po +++ b/resources/language/resource.language.en_gb/strings.po @@ -5,15 +5,7 @@ msgstr "" "Language: en\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -### MENUS -msgctxt "#30001" -msgid "A-Z" -msgstr "" - -msgctxt "#30002" -msgid "Alphabetically sorted list of programs" -msgstr "" - +# MENUS msgctxt "#30003" msgid "Movies" msgstr "" @@ -46,22 +38,10 @@ msgctxt "#30010" msgid "Search the VTM GO catalogue" msgstr "" -msgctxt "#30011" -msgid "Switch Profile ([B]{profile}[/B])" -msgstr "" - -msgctxt "#30012" -msgid "Select a different profile to use" -msgstr "" - msgctxt "#30013" msgid "TV guide" msgstr "" -msgctxt "#30014" -msgid "Browse the TV Guide" -msgstr "" - msgctxt "#30015" msgid "Recommendations" msgstr "" @@ -95,11 +75,7 @@ msgid "Show the VTM GO recommendations for Kids" msgstr "" -### SUBMENUS -msgctxt "#30051" -msgid "Watch [B]{channel}[/B]" -msgstr "" - +# SUBMENUS msgctxt "#30052" msgid "Watch live [B]{channel}[/B]" msgstr "" @@ -112,21 +88,8 @@ msgctxt "#30054" msgid "Browse the TV Guide for [B]{channel}[/B]" msgstr "" -msgctxt "#30055" -msgid "Catalog for [B]{channel}[/B]" -msgstr "" - -msgctxt "#30056" -msgid "Browse the Catalog for [B]{channel}[/B]" -msgstr "" - -msgctxt "#30057" -msgid "Select Profile" -msgstr "" - - -### CONTEXT MENU +# CONTEXT MENU msgctxt "#30100" msgid "Add to My List" msgstr "" @@ -143,32 +106,12 @@ msgctxt "#30103" msgid "Watch Live" msgstr "" -msgctxt "#30104" -msgid "TV Guide" -msgstr "" - msgctxt "#30105" msgid "Play from Catalog" msgstr "" -### CODE -msgctxt "#30200" -msgid "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed." -msgstr "" - -msgctxt "#30201" -msgid "[B][COLOR lightblue]Kids zone[/COLOR][/B]" -msgstr "" - -msgctxt "#30202" -msgid "Credentials are correct!" -msgstr "" - -msgctxt "#30203" -msgid "Your credentials are not valid!" -msgstr "" - +# CODE msgctxt "#30204" msgid "All seasons" msgstr "" @@ -213,20 +156,12 @@ msgctxt "#30214" msgid "[B]Next:[/B] {start} - {end}\n» {title}" msgstr "" -msgctxt "#30215" -msgid "Browse the TV-Guide of [B]{channel}[/B]" -msgstr "" - msgctxt "#30216" msgid "All ages" msgstr "" -### Dates -msgctxt "#30300" -msgid "2 days ago" -msgstr "" - +# Dates msgctxt "#30301" msgid "Yesterday" msgstr "" @@ -239,28 +174,12 @@ msgctxt "#30303" msgid "Tomorrow" msgstr "" -msgctxt "#30304" -msgid "In 2 days" -msgstr "" - -### MESSAGES +# MESSAGES msgctxt "#30701" msgid "You need to configure your credentials before you can access the content of VTM GO. Do you want to enter them now?" msgstr "" -msgctxt "#30702" -msgid "Unknown error while logging in: {code}" -msgstr "" - -msgctxt "#30703" -msgid "Your account has no profiles defined. Please login on vtm.be/vtmgo and create a profile." -msgstr "" - -msgctxt "#30704" -msgid "This video may be geo-blocked, or your account is already being used currently." -msgstr "" - msgctxt "#30705" msgid "The VTM GO Service has been updated, but this add-on hasn't been modified yet for these changes. Please check for an update." msgstr "" @@ -293,18 +212,6 @@ msgctxt "#30713" msgid "The requested video was not found in the guide." msgstr "" -msgctxt "#30714" -msgid "Local metadata is cleared." -msgstr "" - -msgctxt "#30715" -msgid "Updating metadata..." -msgstr "" - -msgctxt "#30716" -msgid "Updating metadata ({index}/{total})..." -msgstr "" - msgctxt "#30717" msgid "This program is not available in the VTM GO catalogue." msgstr "" @@ -314,35 +221,7 @@ msgid "The Manifest proxy is not running. Please restart Kodi." msgstr "" -### SETTINGS -msgctxt "#30800" -msgid "Credentials" -msgstr "" - -msgctxt "#30801" -msgid "Credentials" -msgstr "" - -msgctxt "#30803" -msgid "Email address" -msgstr "" - -msgctxt "#30805" -msgid "Password" -msgstr "" - -msgctxt "#30809" -msgid "Profile" -msgstr "" - -msgctxt "#30810" -msgid "Always use the current profile" -msgstr "" - -msgctxt "#30811" -msgid "Current profile" -msgstr "" - +# SETTINGS msgctxt "#30820" msgid "Interface" msgstr "" @@ -359,22 +238,6 @@ msgctxt "#30826" msgid "Show Continue watching" msgstr "" -msgctxt "#30827" -msgid "Metadata" -msgstr "" - -msgctxt "#30829" -msgid "Periodically refresh metadata in the background" -msgstr "" - -msgctxt "#30831" -msgid "Update local metadata now…" -msgstr "" - -msgctxt "#30833" -msgid "Clear local metadata…" -msgstr "" - msgctxt "#30840" msgid "Playback" msgstr "" @@ -431,46 +294,6 @@ msgctxt "#30869" msgid "IPTV Manager settings…" msgstr "" -msgctxt "#30870" -msgid "Kodi Library" -msgstr "" - -msgctxt "#30871" -msgid "Indexing" -msgstr "" - -msgctxt "#30872" -msgid "Add video locations to the Kodi Library… [COLOR gray](See README.md)[/COLOR]" -msgstr "" - -msgctxt "#30873" -msgid "Index the following movies" -msgstr "" - -msgctxt "#30874" -msgid "Index the following series" -msgstr "" - -msgctxt "#30875" -msgid "Full catalog" -msgstr "" - -msgctxt "#30876" -msgid "Items on 'My List' only" -msgstr "" - -msgctxt "#30877" -msgid "Maintenance" -msgstr "" - -msgctxt "#30878" -msgid "Refresh Library…" -msgstr "" - -msgctxt "#30879" -msgid "Clean Library…" -msgstr "" - msgctxt "#30880" msgid "Expert" msgstr "" diff --git a/resources/language/resource.language.nl_nl/strings.po b/resources/language/resource.language.nl_nl/strings.po index 19563c7c..6f146fa3 100644 --- a/resources/language/resource.language.nl_nl/strings.po +++ b/resources/language/resource.language.nl_nl/strings.po @@ -1,20 +1,13 @@ +# msgid "" msgstr "" +"Language: nl\n" "MIME-Version: 1.0\n" "Content-Type: text/plain; charset=UTF-8\n" "Content-Transfer-Encoding: 8bit\n" -"Language: nl\n" "Plural-Forms: nplurals=2; plural=(n != 1);\n" -### MENUS -msgctxt "#30001" -msgid "A-Z" -msgstr "A-Z" - -msgctxt "#30002" -msgid "Alphabetically sorted list of programs" -msgstr "Volledige lijst van programma's" - +# MENUS msgctxt "#30003" msgid "Movies" msgstr "Films" @@ -47,22 +40,10 @@ msgctxt "#30010" msgid "Search the VTM GO catalogue" msgstr "Doorzoek de VTM catalogus" -msgctxt "#30011" -msgid "Switch Profile ([B]{profile}[/B])" -msgstr "Verwissel profiel ([B]{profile}[/B])" - -msgctxt "#30012" -msgid "Select a different profile to use" -msgstr "Selecteer een ander profiel om te gebruiken" - msgctxt "#30013" msgid "TV guide" msgstr "Tv-gids" -msgctxt "#30014" -msgid "Browse the TV Guide" -msgstr "Doorblader de tv-gids" - msgctxt "#30015" msgid "Recommendations" msgstr "Aanbevelingen" @@ -95,12 +76,7 @@ msgctxt "#30022" msgid "Show the VTM GO recommendations for Kids" msgstr "Bekijk de VTM GO aanbevelingen voor kinderen" - -### SUBMENUS -msgctxt "#30051" -msgid "Watch [B]{channel}[/B]" -msgstr "Kijk [B]{channel}[/B]" - +# SUBMENUS msgctxt "#30052" msgid "Watch live [B]{channel}[/B]" msgstr "Kijk live [B]{channel}[/B]" @@ -113,21 +89,7 @@ msgctxt "#30054" msgid "Browse the TV Guide for [B]{channel}[/B]" msgstr "Doorblader de tv-gids voor [B]{channel}[/B]" -msgctxt "#30055" -msgid "Catalog for [B]{channel}[/B]" -msgstr "Catalogus voor [B]{channel}[/B]" - -msgctxt "#30056" -msgid "Browse the Catalog for [B]{channel}[/B]" -msgstr "Doorblader de catalogus voor [B]{channel}[/B]" - -msgctxt "#30057" -msgid "Select Profile" -msgstr "Selecteer profiel" - - -### CONTEXT MENU - +# CONTEXT MENU msgctxt "#30100" msgid "Add to My List" msgstr "Toevoegen aan mijn lijst" @@ -144,32 +106,11 @@ msgctxt "#30103" msgid "Watch Live" msgstr "Kijk live" -msgctxt "#30104" -msgid "TV Guide" -msgstr "Tv-gids" - msgctxt "#30105" msgid "Play from Catalog" msgstr "Afspelen uit catalogus" - -### CODE -msgctxt "#30200" -msgid "Using a SOCKS proxy requires the PySocks library (script.module.pysocks) installed." -msgstr "De PySocks library (script.module.pysocks) is nodig voor het gebruik van een SOCKS proxy." - -msgctxt "#30201" -msgid "[B][COLOR lightblue]Kids zone[/COLOR][/B]" -msgstr "[B][COLOR lightblue]Kids zone[/COLOR][/B]" - -msgctxt "#30202" -msgid "Credentials are correct!" -msgstr "De VTM GO inloggegevens werken perfect!" - -msgctxt "#30203" -msgid "Your credentials are not valid!" -msgstr "De VTM GO inloggegevens zijn onjuist!" - +# CODE msgctxt "#30204" msgid "All seasons" msgstr "Alle seizoenen" @@ -207,27 +148,26 @@ msgid "[B]{days} more days[/B] remaining" msgstr "Nog [B]{days} dagen[/B] beschikbaar" msgctxt "#30213" -msgid "[B]Now:[/B] {start} - {end}\n» {title}" -msgstr "[B]Nu:[/B] {start} - {end}\n» {title}" +msgid "" +"[B]Now:[/B] {start} - {end}\n" +"» {title}" +msgstr "" +"[B]Nu:[/B] {start} - {end}\n" +"» {title}" msgctxt "#30214" -msgid "[B]Next:[/B] {start} - {end}\n» {title}" -msgstr "[B]Straks:[/B] {start} - {end}\n» {title}" - -msgctxt "#30215" -msgid "Browse the TV-Guide of [B]{channel}[/B]" -msgstr "Doorblader de tv-gids van [B]{channel}[/B]" +msgid "" +"[B]Next:[/B] {start} - {end}\n" +"» {title}" +msgstr "" +"[B]Straks:[/B] {start} - {end}\n" +"» {title}" msgctxt "#30216" msgid "All ages" msgstr "Alle leeftijden" - -### Dates -msgctxt "#30300" -msgid "2 days ago" -msgstr "Eergisteren" - +# Dates msgctxt "#30301" msgid "Yesterday" msgstr "Gisteren" @@ -240,28 +180,11 @@ msgctxt "#30303" msgid "Tomorrow" msgstr "Morgen" -msgctxt "#30304" -msgid "In 2 days" -msgstr "Overmorgen" - - -### MESSAGES +# MESSAGES msgctxt "#30701" msgid "You need to configure your credentials before you can access the content of VTM GO. Do you want to enter them now?" msgstr "U moet uw inloggegevens configureren voordat u VTM GO kan gebruiken. Wilt u dit nu doen?" -msgctxt "#30702" -msgid "Unknown error while logging in: {code}" -msgstr "Onbekende fout bij het inloggen: {code}" - -msgctxt "#30703" -msgid "Your account has no profiles defined. Please login on vtm.be/vtmgo and create a profile." -msgstr "Je account heeft geen profielen. Gelieve in te loggen op vtm.be/vtmgo en een profiel aan te maken." - -msgctxt "#30704" -msgid "This video may be geo-blocked, or your account is already being used currently." -msgstr "Deze video werd wellicht geografisch geblokkeerd, of je account is reeds in gebruik." - msgctxt "#30705" msgid "The VTM GO Service has been updated, but this add-on hasn't been modified yet for these changes. Please check for an update." msgstr "De VTM GO Service is geüpdate, maar deze add-on is helaas nog niet aangepast hiervoor. Gelieve te controleren of er een update beschikbaar is." @@ -294,18 +217,6 @@ msgctxt "#30713" msgid "The requested video was not found in the guide." msgstr "De gevraagde video werd niet gevonden in de tv-gids." -msgctxt "#30714" -msgid "Local metadata is cleared." -msgstr "De lokale metadata is verwijderd." - -msgctxt "#30715" -msgid "Updating metadata..." -msgstr "Vernieuwen metadata..." - -msgctxt "#30716" -msgid "Updating metadata ({index}/{total})..." -msgstr "Vernieuwen metadata ({index}/{total})..." - msgctxt "#30717" msgid "This program is not available in the VTM GO catalogue." msgstr "Dit programma is niet beschikbaar in de VTM GO catalogus." @@ -314,35 +225,7 @@ msgctxt "#30718" msgid "The Manifest proxy is not running. Please restart Kodi." msgstr "De Manifest-proxy is niet gestart. Gelieve Kodi te herstarten." -### SETTINGS -msgctxt "#30800" -msgid "Credentials" -msgstr "Inloggegevens" - -msgctxt "#30801" -msgid "Credentials" -msgstr "Inloggegevens" - -msgctxt "#30803" -msgid "Email address" -msgstr "E-mailadres" - -msgctxt "#30805" -msgid "Password" -msgstr "Wachtwoord" - -msgctxt "#30809" -msgid "Profile" -msgstr "Profile" - -msgctxt "#30810" -msgid "Always use the current profile" -msgstr "Altijd het huidige profiel gebruiken" - -msgctxt "#30811" -msgid "Current profile" -msgstr "Huidig profiel" - +# SETTINGS msgctxt "#30820" msgid "Interface" msgstr "Interface" @@ -359,22 +242,6 @@ msgctxt "#30826" msgid "Show Continue watching" msgstr "Toon 'Verder kijken' menu" -msgctxt "#30827" -msgid "Metadata" -msgstr "Metadata" - -msgctxt "#30829" -msgid "Periodically refresh metadata in the background" -msgstr "Vernieuw de metadata automatisch in de achtergrond" - -msgctxt "#30831" -msgid "Update local metadata now…" -msgstr "De lokale metadata nu vernieuwen…" - -msgctxt "#30833" -msgid "Clear local metadata…" -msgstr "De lokale metadata verwijderen…" - msgctxt "#30840" msgid "Playback" msgstr "Afspelen" @@ -395,7 +262,6 @@ msgctxt "#30844" msgid "Maximum bandwidth (kbps)" msgstr "Maximale bandbreedte (kbps)" - msgctxt "#30860" msgid "Integrations" msgstr "Integraties" @@ -432,46 +298,6 @@ msgctxt "#30869" msgid "IPTV Manager settings…" msgstr "IPTV Manager instellingen…" -msgctxt "#30870" -msgid "Kodi Library" -msgstr "Kodi-bibliotheek" - -msgctxt "#30871" -msgid "Indexing" -msgstr "Indexeren" - -msgctxt "#30872" -msgid "Add video locations to the Kodi Library… [COLOR gray](See README.md)[/COLOR]" -msgstr "Videolocaties toevoegen aan de Kodi-bibliotheek… [COLOR gray](Zie README.md)[/COLOR]" - -msgctxt "#30873" -msgid "Index the following movies" -msgstr "Indexeer de volgende films" - -msgctxt "#30874" -msgid "Index the following series" -msgstr "Indexeer de volgende series" - -msgctxt "#30875" -msgid "Full catalog" -msgstr "Volledige catalogus" - -msgctxt "#30876" -msgid "Items on 'My List' only" -msgstr "Enkel items op 'Mijn lijst'" - -msgctxt "#30877" -msgid "Maintenance" -msgstr "Onderhoud" - -msgctxt "#30878" -msgid "Refresh Library…" -msgstr "Verniew bibliotheek…" - -msgctxt "#30879" -msgid "Clean Library…" -msgstr "Bibliotheek opkuisen…" - msgctxt "#30880" msgid "Expert" msgstr "Expert" diff --git a/resources/lib/addon.py b/resources/lib/addon.py index d3b0adc1..0df75701 100644 --- a/resources/lib/addon.py +++ b/resources/lib/addon.py @@ -8,7 +8,6 @@ import routing from resources.lib import kodilogging, kodiutils -from resources.lib.vtmgo.exceptions import NoLoginException from resources.lib.vtmgo.vtmgoauth import VtmGoAuth routing = routing.Plugin() # pylint: disable=invalid-name @@ -20,13 +19,9 @@ def index(): """ Show the profile selection, or go to the main menu. """ auth = VtmGoAuth(kodiutils.get_tokens_path()) - - try: - # Check if we have valid tokens and show the main menu if we do. - auth.get_tokens() + if auth.get_tokens(): show_main_menu() - except NoLoginException: - # We are not authorized, start the authorization flow. + else: show_login_menu() @@ -79,34 +74,6 @@ def show_tvguide_detail(channel=None, date=None): TvGuide().show_tvguide_detail(channel, date) -# @routing.route('/catalog') -# def show_catalog(): -# """ Show the catalog """ -# from resources.lib.modules.catalog import Catalog -# Catalog().show_catalog() -# -# -# @routing.route('/catalog/all') -# def show_catalog_all(): -# """ Show a category in the catalog """ -# from resources.lib.modules.catalog import Catalog -# Catalog().show_catalog_category() -# -# -# @routing.route('/catalog/by-category/') -# def show_catalog_category(category): -# """ Show a category in the catalog """ -# from resources.lib.modules.catalog import Catalog -# Catalog().show_catalog_category(category) - - -# @routing.route('/catalog/by-channel/') -# def show_catalog_channel(channel): -# """ Show a category in the catalog """ -# from resources.lib.modules.catalog import Catalog -# Catalog().show_catalog_channel(channel) - - @routing.route('/catalog/program/') def show_catalog_program(program): """ Show a program from the catalog """ diff --git a/resources/lib/modules/authentication.py b/resources/lib/modules/authentication.py index c43c46f3..a54b5e7e 100644 --- a/resources/lib/modules/authentication.py +++ b/resources/lib/modules/authentication.py @@ -4,6 +4,8 @@ from __future__ import absolute_import, division, unicode_literals import logging +from datetime import datetime, timedelta +from time import sleep from resources.lib import kodiutils from resources.lib.vtmgo.vtmgoauth import VtmGoAuth @@ -20,27 +22,43 @@ def __init__(self): def login(self): """ Start the authorisation flow. """ - # Start the authorization auth_info = self._auth.authorize() + progress_dialog = kodiutils.progress( + message="Go to {url} on another device and enter [B]{code}[/B] to login on this device.".format( + url=auth_info.get('verification_uri'), + code=auth_info.get('user_code'))) + progress_dialog.update(0) + # Check the authorization until it succeeds or the user cancels. - while True: - result = kodiutils.yesno_dialog( - message="Go to {url} and enter [B]{code}[/B] to login on this device.".format( - url=auth_info.get('verification_uri'), - code=auth_info.get('user_code')), - yeslabel="Yes Refresh", - nolabel="No Cancel" - ) - - if not result: - # User has cancelled + delay = auth_info.get('interval') + expiry = auth_info.get('expires_in') + time_start = datetime.now() + time_end = time_start + timedelta(seconds=expiry) + while datetime.now() < time_end: + # Update progress + progress_dialog.update(int((datetime.now() - time_start).seconds * 100 / expiry)) + + # Check if the users has cancelled the login flow + if progress_dialog.iscanceled(): + progress_dialog.close() return # Check if we are authorized now check = self._auth.authorize_check() if check: + progress_dialog.close() + kodiutils.notification("Sucessfully logged in") kodiutils.redirect(kodiutils.url_for('show_main_menu')) + return + + # Sleep a bit + sleep(delay) + + # Close progress indicator + progress_dialog.close() + + kodiutils.ok_dialog(message="The login code has expired. Please try again.") def clear_tokens(self): """ Clear the authentication tokens """ diff --git a/resources/lib/modules/catalog.py b/resources/lib/modules/catalog.py index fe37baa9..b96c506e 100644 --- a/resources/lib/modules/catalog.py +++ b/resources/lib/modules/catalog.py @@ -22,14 +22,14 @@ class Catalog: def __init__(self): """ Initialise object """ auth = VtmGoAuth(kodiutils.get_tokens_path()) - self._vtm_go = VtmGo(auth.get_tokens()) + self._api = VtmGo(auth.get_tokens()) def show_program(self, program): """ Show a program from the catalog :type program: str """ try: - program_obj = self._vtm_go.get_program(program, cache=CACHE_PREVENT) # Use CACHE_PREVENT since we want fresh data + program_obj = self._api.get_program(program, cache=CACHE_PREVENT) # Use CACHE_PREVENT since we want fresh data except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the VTM GO catalogue. kodiutils.end_of_directory() @@ -97,7 +97,7 @@ def show_program_season(self, program, season): :type season: int """ try: - program_obj = self._vtm_go.get_program(program) # Use CACHE_AUTO since the data is just refreshed in show_program + program_obj = self._api.get_program(program) # Use CACHE_AUTO since the data is just refreshed in show_program except UnavailableException: kodiutils.ok_dialog(message=kodiutils.localize(30717)) # This program is not available in the VTM GO catalogue. kodiutils.end_of_directory() @@ -121,7 +121,7 @@ def show_recommendations(self, storefront): :type storefront: str """ try: - results = self._vtm_go.get_storefront(storefront) + results = self._api.get_storefront(storefront) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize(30705)) # The VTM GO Service has been updated... return @@ -160,7 +160,7 @@ def show_recommendations_category(self, storefront, category): :type category: str """ try: - result = self._vtm_go.get_storefront_category(storefront, category) + result = self._api.get_storefront_category(storefront, category) except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize(30705)) # The VTM GO Service has been updated... return @@ -186,7 +186,7 @@ def show_recommendations_category(self, storefront, category): def show_mylist(self): """ Show the items in "My List" """ try: - mylist = self._vtm_go.get_mylist() + mylist = self._api.get_mylist() except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize(30705)) # The VTM GO Service has been updated... return @@ -208,7 +208,7 @@ def mylist_add(self, video_type, content_id): :type video_type: str :type content_id: str """ - self._vtm_go.add_mylist(video_type, content_id) + self._api.add_mylist(video_type, content_id) kodiutils.end_of_directory() def mylist_del(self, video_type, content_id): @@ -216,14 +216,14 @@ def mylist_del(self, video_type, content_id): :type video_type: str :type content_id: str """ - self._vtm_go.del_mylist(video_type, content_id) + self._api.del_mylist(video_type, content_id) kodiutils.end_of_directory() kodiutils.container_refresh() def show_continuewatching(self): """ Show the items in "Continue Watching" """ try: - category = self._vtm_go.get_storefront_category(STOREFRONT_MAIN, 'continue-watching') + category = self._api.get_storefront_category(STOREFRONT_MAIN, 'continue-watching') except ApiUpdateRequired: kodiutils.ok_dialog(message=kodiutils.localize(30705)) # The VTM GO Service has been updated... return diff --git a/resources/lib/modules/iptvmanager.py b/resources/lib/modules/iptvmanager.py index 4bcfdb77..2e3fcd25 100644 --- a/resources/lib/modules/iptvmanager.py +++ b/resources/lib/modules/iptvmanager.py @@ -78,7 +78,7 @@ def send_epg(self): channels = self._vtm_go_epg.get_epgs(date) for channel in channels: # Lookup channel data in our own CHANNELS dict - channel_data = next((c for c in CHANNELS.values() if c.get('epg') == channel.key), None) + channel_data = next((c for c in list(CHANNELS.values()) if c.get('epg') == channel.key), None) if not channel_data: _LOGGER.warning('Skipping EPG for %s since we don\'t know this channel', channel.key) continue @@ -86,7 +86,7 @@ def send_epg(self): key = channel_data.get('iptv_id') # Create channel in dict if it doesn't exists - if key not in results.keys(): + if key not in list(results.keys()): results[key] = [] results[key].extend([ diff --git a/resources/lib/modules/menu.py b/resources/lib/modules/menu.py index 130002cc..afb1c7a3 100644 --- a/resources/lib/modules/menu.py +++ b/resources/lib/modules/menu.py @@ -7,10 +7,8 @@ from resources.lib import kodiutils from resources.lib.modules import CHANNELS -from resources.lib.vtmgo import STOREFRONT_KIDS, STOREFRONT_MAIN, STOREFRONT_MOVIES, STOREFRONT_SERIES, Episode, Movie, \ - Program +from resources.lib.vtmgo import STOREFRONT_KIDS, STOREFRONT_MAIN, STOREFRONT_MOVIES, STOREFRONT_SERIES, Episode, Movie, Program from resources.lib.vtmgo.vtmgo import CONTENT_TYPE_MOVIE, CONTENT_TYPE_PROGRAM -from resources.lib.vtmgo.vtmgoauth import VtmGoAuth _LOGGER = logging.getLogger(__name__) @@ -20,14 +18,12 @@ class Menu: def __init__(self): """ Initialise object """ - self._auth = VtmGoAuth(kodiutils.get_tokens_path()) - def show_mainmenu(self): + @staticmethod + def show_mainmenu(): """ Show the main menu """ listing = [] - account = self._auth.get_tokens() - listing.append(kodiutils.TitleItem( title=kodiutils.localize(30007), # TV Channels path=kodiutils.url_for('show_channels'), @@ -194,8 +190,7 @@ def generate_titleitem(cls, item, progress=False): 'title': item.name, 'plot': cls.format_plot(item), 'studio': CHANNELS.get(item.channel, {}).get('studio_icon'), - 'mpaa': ', '.join(item.legal) if hasattr(item, 'legal') and item.legal else kodiutils.localize(30216), - # All ages + 'mpaa': ', '.join(item.legal) if hasattr(item, 'legal') and item.legal else kodiutils.localize(30216), # All ages } prop_dict = {} @@ -262,9 +257,6 @@ def generate_titleitem(cls, item, progress=False): 'year': item.year, 'season': len(item.seasons), }) - prop_dict.update({ - 'hash': item.content_hash, - }) return kodiutils.TitleItem( title=item.name, diff --git a/resources/lib/modules/player.py b/resources/lib/modules/player.py index 15a0a6ab..65148821 100644 --- a/resources/lib/modules/player.py +++ b/resources/lib/modules/player.py @@ -7,7 +7,7 @@ from resources.lib import kodiutils from resources.lib.kodiplayer import KodiPlayer -from resources.lib.vtmgo.exceptions import NoLoginException, StreamGeoblockedException, StreamUnavailableException, UnavailableException +from resources.lib.vtmgo.exceptions import StreamGeoblockedException, StreamUnavailableException, UnavailableException from resources.lib.vtmgo.vtmgo import VtmGo from resources.lib.vtmgo.vtmgoauth import VtmGoAuth from resources.lib.vtmgo.vtmgostream import VtmGoStream @@ -20,14 +20,9 @@ class Player: def __init__(self): """ Initialise object """ - try: - auth = VtmGoAuth(kodiutils.get_tokens_path()) - except NoLoginException: - auth = None - - tokens = auth.get_tokens() - self._vtm_go = VtmGo(tokens) - self._vtm_go_stream = VtmGoStream(tokens) + auth = VtmGoAuth(kodiutils.get_tokens_path()) + self._api = VtmGo(auth.get_tokens()) + self._stream = VtmGoStream(auth.get_tokens()) def play_or_live(self, category, item, channel): """ Ask to play the requested item or switch to the live channel @@ -51,10 +46,6 @@ def play(self, category, item): :type category: string :type item: string """ - # if not self._check_credentials(): - # kodiutils.end_of_directory() - # return - # Check if inputstreamhelper is correctly installed if not self._check_inputstream(): kodiutils.end_of_directory() @@ -62,7 +53,7 @@ def play(self, category, item): try: # Get stream information - resolved_stream = self._vtm_go_stream.get_stream(category, item) + resolved_stream = self._stream.get_stream(category, item) except StreamGeoblockedException: kodiutils.ok_dialog(heading=kodiutils.localize(30709), message=kodiutils.localize(30710)) # This video is geo-blocked... @@ -94,7 +85,7 @@ def play(self, category, item): info_dict.update({'mediatype': 'movie'}) # Get details - movie_details = self._vtm_go.get_movie(item) + movie_details = self._api.get_movie(item) if movie_details: info_dict.update({ 'plot': movie_details.description, @@ -106,9 +97,9 @@ def play(self, category, item): info_dict.update({'mediatype': 'episode'}) # There is no direct API to get episode details, so we go through the cached program details - program = self._vtm_go.get_program(resolved_stream.program_id) + program = self._api.get_program(resolved_stream.program_id) if program: - episode_details = self._vtm_go.get_episode_from_program(program, item) + episode_details = self._api.get_episode_from_program(program, item) if episode_details: info_dict.update({ 'plot': episode_details.description, @@ -117,7 +108,7 @@ def play(self, category, item): }) # Lookup the next episode - next_episode_details = self._vtm_go.get_next_episode_from_program(program, episode_details.season, episode_details.number) + next_episode_details = self._api.get_next_episode_from_program(program, episode_details.season, episode_details.number) # Create the data for Up Next if next_episode_details: @@ -159,7 +150,7 @@ def play(self, category, item): else: url = resolved_stream.url - license_key = self._vtm_go_stream.create_license_key(resolved_stream.license_url) + license_key = self._stream.create_license_key(resolved_stream.license_url) # Play this item kodiutils.play(url, license_key, resolved_stream.title, {}, info_dict, prop_dict, stream_dict) @@ -176,21 +167,6 @@ def play(self, category, item): _LOGGER.debug("Sending Up Next data: %s", upnext_data) self.send_upnext(upnext_data) - # @staticmethod - # def _check_credentials(): - # """ Check if the user has credentials """ - # if kodiutils.has_credentials(): - # return True - # - # # You need to configure your credentials before you can access the content of VTM GO. - # confirm = kodiutils.yesno_dialog(message=kodiutils.localize(30701)) - # if confirm: - # kodiutils.open_settings() - # if kodiutils.has_credentials(): - # return True - # - # return False - @staticmethod def _check_inputstream(): """ Check if inputstreamhelper and inputstream.adaptive are fine. diff --git a/resources/lib/modules/search.py b/resources/lib/modules/search.py index 94d189b0..6e450e76 100644 --- a/resources/lib/modules/search.py +++ b/resources/lib/modules/search.py @@ -19,7 +19,7 @@ class Search: def __init__(self): """ Initialise object """ auth = VtmGoAuth(kodiutils.get_tokens_path()) - self._vtm_go = VtmGo(auth.get_tokens()) + self._api = VtmGo(auth.get_tokens()) def show_search(self, query=None): """ Shows the search dialog @@ -34,7 +34,7 @@ def show_search(self, query=None): # Do search try: - items = self._vtm_go.do_search(query) + items = self._api.do_search(query) except Exception as ex: # pylint: disable=broad-except kodiutils.notification(message=str(ex)) kodiutils.end_of_directory() diff --git a/resources/lib/service.py b/resources/lib/service.py index 57582fae..db47eabd 100644 --- a/resources/lib/service.py +++ b/resources/lib/service.py @@ -4,13 +4,11 @@ from __future__ import absolute_import, division, unicode_literals import logging -from time import time from xbmc import Monitor, Player, getInfoLabel from resources.lib import kodilogging, kodiutils from resources.lib.modules.proxy import Proxy -from resources.lib.vtmgo.exceptions import NoLoginException _LOGGER = logging.getLogger(__name__) @@ -47,7 +45,6 @@ def run(self): _LOGGER.debug('Service stopped') - class PlayerMonitor(Player): """ A custom Player object to check subtitles """ diff --git a/resources/lib/vtmgo/__init__.py b/resources/lib/vtmgo/__init__.py index e25e46ec..8bfe5f5a 100644 --- a/resources/lib/vtmgo/__init__.py +++ b/resources/lib/vtmgo/__init__.py @@ -141,7 +141,7 @@ class Program: """ Defines a Program """ def __init__(self, program_id=None, name=None, description=None, year=None, poster=None, thumb=None, fanart=None, seasons=None, - geoblocked=None, channel=None, legal=None, my_list=None, content_hash=None): + geoblocked=None, channel=None, legal=None, my_list=None): """ :type program_id: str :type name: str @@ -155,7 +155,6 @@ def __init__(self, program_id=None, name=None, description=None, year=None, post :type channel: str :type legal: str :type my_list: bool - :type content_hash: str """ self.program_id = program_id self.name = name @@ -169,7 +168,6 @@ def __init__(self, program_id=None, name=None, description=None, year=None, post self.channel = channel self.legal = legal self.my_list = my_list - self.content_hash = content_hash def __repr__(self): return "%r" % self.__dict__ diff --git a/resources/lib/vtmgo/exceptions.py b/resources/lib/vtmgo/exceptions.py index 7efe20d5..1ea43f1d 100644 --- a/resources/lib/vtmgo/exceptions.py +++ b/resources/lib/vtmgo/exceptions.py @@ -4,11 +4,6 @@ from __future__ import absolute_import, division, unicode_literals - -class NotAuthorizedException(Exception): - """ Is thrown when this device is not authorized. """ - - class NoLoginException(Exception): """ Is thrown when the user need to follow the authorization flow. """ @@ -21,14 +16,6 @@ class InvalidLoginException(Exception): """ Is thrown when the credentials are invalid. """ -class LoginErrorException(Exception): - """ Is thrown when we could not login. """ - - def __init__(self, code): - super(LoginErrorException, self).__init__() - self.code = code - - class UnavailableException(Exception): """ Is thrown when an item is unavailable. """ diff --git a/resources/lib/vtmgo/vtmgo.py b/resources/lib/vtmgo/vtmgo.py index 6dec7d61..1daf9122 100644 --- a/resources/lib/vtmgo/vtmgo.py +++ b/resources/lib/vtmgo/vtmgo.py @@ -3,13 +3,11 @@ from __future__ import absolute_import, division, unicode_literals -import hashlib import json import logging from resources.lib import kodiutils from resources.lib.vtmgo import API_ANDROID_ENDPOINT, API_ENDPOINT, Category, Episode, LiveChannel, LiveChannelEpg, Movie, Program, Season, util -from resources.lib.vtmgo.vtmgoauth import AccountStorage _LOGGER = logging.getLogger(__name__) @@ -193,50 +191,6 @@ def get_live_channel(self, key): channels = self.get_live_channels() return next(c for c in channels if c.key == key) - # def get_categories(self): - # """ Get a list of all the categories. - # :rtype list[Category] - # """ - # response = util.http_get(API_ENDPOINT + '/%s/catalog/filters' % self._mode(), - # token=self._tokens.access_token if self._tokens else None, - # profile=self._tokens.profile if self._tokens else None) - # info = json.loads(response.text) - # - # categories = [] - # for item in info.get('catalogFilters', []): - # categories.append(Category( - # category_id=item.get('id'), - # title=item.get('title'), - # )) - # - # return categories - - # def get_items(self, category=None, content_filter=None, cache=CACHE_ONLY): - # """ Get a list of all the items in a category. - # - # :type category: str - # :type content_filter: class - # :type cache: int - # :rtype list[resources.lib.vtmgo.Movie | resources.lib.vtmgo.Program] - # """ - # # Fetch from API - # response = util.http_get(API_ENDPOINT + '/%s/catalog' % self._mode(), - # params={'pageSize': 2000, 'filter': quote(category) if category else None}, - # token=self._tokens.access_token if self._tokens else None, - # profile=self._tokens.profile if self._tokens else None) - # info = json.loads(response.text) - # content = info.get('pagedTeasers', {}).get('content', []) - # - # items = [] - # for item in content: - # if item.get('target', {}).get('type') == CONTENT_TYPE_MOVIE and content_filter in [None, Movie]: - # items.append(self._parse_movie_teaser(item, cache=cache)) - # - # elif item.get('target', {}).get('type') == CONTENT_TYPE_PROGRAM and content_filter in [None, Program]: - # items.append(self._parse_program_teaser(item, cache=cache)) - # - # return items - def get_movie(self, movie_id, cache=CACHE_AUTO): """ Get the details of the specified movie. :type movie_id: str @@ -300,10 +254,6 @@ def get_program(self, program_id, cache=CACHE_AUTO): channel = self._parse_channel(program.get('channelLogoUrl')) - # Calculate a hash value of the ids of all episodes - program_hash = hashlib.md5() - program_hash.update(program.get('id').encode()) - seasons = {} for item_season in program.get('seasons', []): episodes = {} @@ -328,7 +278,6 @@ def get_program(self, program_id, cache=CACHE_AUTO): progress=item_episode.get('playerPositionSeconds', 0), watched=item_episode.get('doneWatching', False), ) - program_hash.update(item_episode.get('id').encode()) seasons[item_season.get('index')] = Season( number=item_season.get('index'), @@ -348,7 +297,6 @@ def get_program(self, program_id, cache=CACHE_AUTO): seasons=seasons, channel=channel, legal=program.get('legalIcons'), - content_hash=program_hash.hexdigest().upper(), # my_list=program.get('addedToMyList'), # Don't use addedToMyList, since we might have cached this info ) @@ -421,45 +369,6 @@ def get_episode(self, episode_id): next_episode=next_episode, ) - def get_mylist_ids(self): - """ Returns the IDs of the contents of My List """ - # Try to fetch from cache - items = kodiutils.get_cache(['mylist_id'], 300) # 5 minutes ttl - if items: - return items - - # Fetch from API - response = util.http_get(API_ENDPOINT + '/%s/my-list' % (self._mode()), - token=self._tokens.access_token, - profile=self._tokens.profile) - - # Result can be empty - result = json.loads(response.text) if response.text else [] - - items = [item.get('target', {}).get('id') for item in result.get('teasers', [])] - - kodiutils.set_cache(['mylist_id'], items) - return items - - # def get_catalog_ids(self): - # """ Returns the IDs of the contents of the Catalog """ - # # Try to fetch from cache - # items = kodiutils.get_cache(['catalog_id'], 300) # 5 minutes ttl - # if items: - # return items - # - # # Fetch from API - # response = util.http_get(API_ENDPOINT + '/%s/catalog' % self._mode(), - # params={'pageSize': 2000, 'filter': None}, - # token=self._tokens.access_token if self._tokens else None, - # profile=self._tokens.profile if self._tokens else None) - # info = json.loads(response.text) - # - # items = [item.get('target', {}).get('id') for item in info.get('pagedTeasers', {}).get('content', [])] - # - # kodiutils.set_cache(['catalog_id'], items) - # return items - def do_search(self, search): """ Do a search in the full catalog. :type search: str diff --git a/resources/lib/vtmgo/vtmgoauth.py b/resources/lib/vtmgo/vtmgoauth.py index 152f9681..549312e4 100644 --- a/resources/lib/vtmgo/vtmgoauth.py +++ b/resources/lib/vtmgo/vtmgoauth.py @@ -3,10 +3,10 @@ from __future__ import absolute_import, division, unicode_literals -import uuid import json import logging import os +import uuid from requests import HTTPError @@ -19,11 +19,6 @@ # The package is named pyjwt in Kodi 18: https://github.com/lottaboost/script.module.pyjwt/pull/1 import pyjwt as jwt -try: # Python 3 - from urllib.parse import parse_qs, urlparse -except ImportError: # Python 2 - from urlparse import parse_qs, urlparse - _LOGGER = logging.getLogger(__name__) @@ -36,9 +31,6 @@ class AccountStorage: profile = '' product = '' - # Credentials hash - hash = '' - def is_valid_token(self): """ Validate the JWT to see if it's still valid. @@ -54,18 +46,11 @@ def is_valid_token(self): algorithms=['HS256'], options={'verify_signature': False, 'verify_aud': False}) - # # Check issued at datetime - # # VTM GO updated the JWT token format on 2021-05-03T12:00:00+00:00, older JWT tokens became invalid - # update = dateutil.parser.parse('2021-05-03T12:00:00+00:00') - # iat = datetime.fromtimestamp(decoded_jwt.get('iat'), tz=dateutil.tz.gettz('Europe/Brussels')) - # if iat < update: - # _LOGGER.debug('JWT issued at %s is too old', iat.isoformat()) - # return False - # Check expiration time + from datetime import datetime + import dateutil.parser import dateutil.tz - from datetime import datetime exp = datetime.fromtimestamp(decoded_jwt.get('exp'), tz=dateutil.tz.gettz('Europe/Brussels')) now = datetime.now(dateutil.tz.UTC) if exp < now: @@ -93,6 +78,11 @@ def __init__(self, token_path): self._account = AccountStorage() self._load_cache() + def set_token(self, access_token): + """ Sets an auth token """ + self._account.access_token = access_token + self._save_cache() + def authorize(self): """ Start the authorization flow. """ response = util.http_post('https://login2.vtm.be/device/authorize', form={ @@ -132,24 +122,33 @@ def authorize_check(self): def get_tokens(self): """ Check if we have a token based on our device code. """ - if not self._account.id_token: - raise NoLoginException - # Return our current token if it is still valid. - if self._account.is_valid_token(): + if self._account.is_valid_token() and self._account.profile and self._account.product: return self._account - # Fetch an actual token we can use - response = util.http_post('https://lfvp-api.dpgmedia.net/vtmgo/tokens', data={ - 'device': { - 'id': str(uuid.uuid4()), # TODO: should we reuse this id? - 'name': '' # Show we announce ourselves as Kodi? - }, - 'idToken': self._account.id_token, - }) + if self._account.access_token: + # We can refresh our old token so it's valid again + response = util.http_post('https://lfvp-api.dpgmedia.net/vtmgo/tokens/refresh', data={ + 'lfvpToken': self._account.access_token, + }) - auth_info = json.loads(response.text) - self._account.access_token = auth_info.get('lfvpToken') + # Get JWT from reply + self._account.access_token = json.loads(response.text).get('lfvpToken') + + elif self._account.id_token: + # Fetch an actual token we can use + response = util.http_post('https://lfvp-api.dpgmedia.net/vtmgo/tokens', data={ + 'device': { + 'id': str(uuid.uuid4()), # TODO: should we reuse this id? + 'name': '' # Show we announce ourselves as Kodi? + }, + 'idToken': self._account.id_token, + }) + + self._account.access_token = json.loads(response.text).get('lfvpToken') + + else: + return None # We always use the main profile profiles = self.get_profiles() @@ -182,22 +181,9 @@ def get_profiles(self, products='VTM_GO,VTM_GO_KIDS'): def logout(self): """ Clear the session tokens. """ - self._account.access_token = None + self._account.__dict__ = {} # pylint: disable=attribute-defined-outside-init self._save_cache() - # def _android_refesh(self): - # - # # We can refresh our old token so it's valid again - # response = util.http_post('https://lfvp-api.dpgmedia.net/vtmgo/tokens/refresh', data={ - # 'lfvpToken': self._account.access_token, - # }) - # - # # Get JWT from reply - # self._account.access_token = json.loads(response.text).get('lfvpToken') - # self._save_cache() - # - # return self._account - def _load_cache(self): """ Load tokens from cache """ try: diff --git a/resources/lib/vtmgo/vtmgostream.py b/resources/lib/vtmgo/vtmgostream.py index 3debd3df..73941558 100644 --- a/resources/lib/vtmgo/vtmgostream.py +++ b/resources/lib/vtmgo/vtmgostream.py @@ -214,7 +214,7 @@ def _delay_webvtt_timing(match, ad_breaks): # time format: seconds.fraction or seconds ad_break_start = timedelta(milliseconds=ad_break.get('start') * 1000) ad_break_duration = timedelta(milliseconds=ad_break.get('duration') * 1000) - if ad_break_start < sub_timings[0]: + if ad_break_start <= sub_timings[0]: for idx, item in enumerate(sub_timings): sub_timings[idx] += ad_break_duration for idx, item in enumerate(sub_timings): diff --git a/scripts/check_for_unused_translations.py b/scripts/check_for_unused_translations.py new file mode 100755 index 00000000..bae1f27b --- /dev/null +++ b/scripts/check_for_unused_translations.py @@ -0,0 +1,34 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- +""" Quick and dirty way to check if all translations might be used. """ + +from __future__ import absolute_import, division, print_function, unicode_literals + +# pylint: disable=invalid-name,superfluous-parens + +import subprocess +import sys + +import polib + +error = 0 + +# Load all python code from git +code = subprocess.check_output(['git', 'grep', '', '--', 'resources/*.py', 'resources/settings.xml', 'addon.xml']).decode('utf-8') + +# Load po file +po = polib.pofile('resources/language/resource.language.en_gb/strings.po') +for entry in po: + # Skip empty translations + if entry.msgid == '': + continue + + # Extract msgctxt + msgctxt = entry.msgctxt.lstrip('#') + + if msgctxt not in code: + print('No usage found for translation:') + print(entry) + error = 1 + +sys.exit(error) diff --git a/scripts/update_translations.py b/scripts/update_translations.py new file mode 100755 index 00000000..60a9cce6 --- /dev/null +++ b/scripts/update_translations.py @@ -0,0 +1,40 @@ +#!/usr/bin/env python +# -*- coding: utf-8 -*- + +# pylint: disable=missing-docstring,no-self-use,wrong-import-order,wrong-import-position,invalid-name + +import sys +from glob import glob + +import polib + +original_file = 'resources/language/resource.language.en_gb/strings.po' +original = polib.pofile(original_file, wrapwidth=0) + +for translated_file in glob('resources/language/resource.language.*/strings.po'): + # Skip original file + if translated_file == original_file: + continue + print('Updating %s...' % translated_file) + + # Load po-files + translated = polib.pofile(translated_file, wrapwidth=0) + + for entry in original: + # Find a translation + translation = translated.find(entry.msgctxt, 'msgctxt') + + if translation and entry.msgid == translation.msgid: + entry.msgstr = translation.msgstr + + original.metadata = translated.metadata + + if sys.platform.startswith('win'): + # On Windows save the file keeping the Linux return character + with open(translated_file, 'wb') as _file: + content = str(original).encode('utf-8') + content = content.replace(b'\r\n', b'\n') + _file.write(content) + else: + # Save it now over the translation + original.save(translated_file) diff --git a/tests/__init__.py b/tests/__init__.py index 6d99080d..e5c32a29 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -8,9 +8,8 @@ import os import sys -import xbmcaddon - -from resources.lib import kodilogging +from resources.lib import kodilogging, kodiutils +from resources.lib.vtmgo.vtmgoauth import VtmGoAuth try: # Python 3 from http.client import HTTPConnection @@ -29,10 +28,6 @@ # Set credentials based on environment data # Use the .env file with Pipenv to make this work nicely during development -ADDON = xbmcaddon.Addon() -if os.environ.get('ADDON_USERNAME'): - ADDON.setSetting('username', os.environ.get('ADDON_USERNAME')) -if os.environ.get('ADDON_PASSWORD'): - ADDON.setSetting('password', os.environ.get('ADDON_PASSWORD')) -if os.environ.get('ADDON_PROFILE'): - ADDON.setSetting('profile', os.environ.get('ADDON_PROFILE')) +if os.environ.get('ADDON_TOKEN'): + AUTH = VtmGoAuth(kodiutils.get_tokens_path()) + AUTH.set_token(os.environ.get('ADDON_TOKEN')) diff --git a/tests/test_api.py b/tests/test_api.py index 772bba5a..72a7a250 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -11,7 +11,7 @@ from resources.lib import kodiutils from resources.lib.modules.player import Player -from resources.lib.vtmgo import Movie, Program, Category +from resources.lib.vtmgo import Category from resources.lib.vtmgo import STOREFRONT_MAIN, STOREFRONT_MOVIES, STOREFRONT_SERIES from resources.lib.vtmgo.vtmgo import VtmGo from resources.lib.vtmgo.vtmgoauth import VtmGoAuth @@ -33,27 +33,6 @@ def test_get_config(self): config = self.api.get_config() self.assertTrue(config) - # def test_catalog(self): - # items = self.api.get_items() - # self.assertTrue(items) - # - # # Movies - # movie = next(a for a in items if isinstance(a, Movie) and not a.geoblocked) - # info = self.api.get_movie(movie.movie_id) - # self.assertTrue(info) - # self.player.play('movies', info.movie_id) - # - # # Programs - # program = next(a for a in items if isinstance(a, Program) and not a.geoblocked) - # info = self.api.get_program(program.program_id) - # self.assertTrue(info) - # - # season = list(info.seasons.values())[0] - # episode = list(season.episodes.values())[0] - # info = self.api.get_episode(episode.episode_id) - # self.assertTrue(info) - # self.player.play('episodes', info.episode_id) - def test_recommendations(self): results = self.api.get_storefront(STOREFRONT_MAIN) self.assertIsInstance(results, list) @@ -72,10 +51,6 @@ def test_mylist(self): mylist = self.api.get_mylist() self.assertIsInstance(mylist, list) - # def test_continuewatching(self): - # result = self.api.get_storefront_category(STOREFRONT_MAIN, 'continue-watching') - # self.assertIsInstance(result.content, list) - def test_search(self): results = self.api.do_search('telefacts') self.assertIsInstance(results, list) @@ -85,14 +60,6 @@ def test_live(self): self.assertTrue(channel) self.player.play('channels', channel.channel_id) - def test_mylist_ids(self): - mylist = self.api.get_mylist_ids() - self.assertIsInstance(mylist, list) - - # def test_catalog_ids(self): - # mylist = self.api.get_catalog_ids() - # self.assertIsInstance(mylist, list) - if __name__ == '__main__': unittest.main() diff --git a/tests/test_auth.py b/tests/test_auth.py index e83fdf20..a2e838a5 100644 --- a/tests/test_auth.py +++ b/tests/test_auth.py @@ -11,33 +11,23 @@ from resources.lib.vtmgo.vtmgoauth import VtmGoAuth -# @unittest.skipUnless(kodiutils.get_setting('username') and kodiutils.get_setting('password'), 'Skipping since we have no credentials.') class TestAuth(unittest.TestCase): """ Tests for VTM GO Auth """ + def test_authorization(self): + auth = VtmGoAuth(kodiutils.get_tokens_path()) + auth_info = auth.authorize() + self.assertIsInstance(auth_info, dict) + self.assertIsNotNone(auth_info.get('user_code')) + self.assertIsNotNone(auth_info.get('device_code')) + self.assertIsNotNone(auth_info.get('interval')) + self.assertIsNotNone(auth_info.get('verification_uri')) + self.assertIsNotNone(auth_info.get('expires_in')) + def test_login(self): auth = VtmGoAuth(kodiutils.get_tokens_path()) - a = auth.get_tokens() - print(a) - - # account = auth.get_tokens() - # self.assertIsInstance(account, AccountStorage) - # self.assertIsNotNone(account.access_token) - # - # profiles = auth.get_profiles() - # self.assertIsInstance(profiles[0], Profile) - - # def test_errors(self): - # with self.assertRaises(NoLoginException): - # VtmGoAuth(None, None, None, None, token_path=kodiutils.get_tokens_path()) - # - # with self.assertRaises(InvalidLoginException): - # VtmGoAuth(self._random_email(), 'test', 'VTM', None, token_path=kodiutils.get_tokens_path()) - # - # @staticmethod - # def _random_email(domain='gmail.com'): - # """ Generate a random e-mail address. """ - # return '%s@%s' % (''.join(random.choice(string.ascii_letters) for i in range(12)), domain) + tokens = auth.get_tokens() + self.assertTrue(tokens.is_valid_token()) if __name__ == '__main__': diff --git a/tests/test_iptvmanager.py b/tests/test_iptvmanager.py index c0f41f26..ef39bc5e 100644 --- a/tests/test_iptvmanager.py +++ b/tests/test_iptvmanager.py @@ -10,8 +10,6 @@ import socket import unittest -from resources.lib import kodiutils - _LOGGER = logging.getLogger(__name__) diff --git a/tests/test_routing.py b/tests/test_routing.py index 128333d1..315b2df6 100644 --- a/tests/test_routing.py +++ b/tests/test_routing.py @@ -9,6 +9,7 @@ import unittest import xbmc +from mock import patch from resources.lib import addon from resources.lib import kodiutils @@ -41,6 +42,10 @@ def tearDown(self): def test_index(self): routing.run([routing.url_for(addon.index), '0', '']) + def test_login_menu(self): + with patch('xbmcgui.DialogProgress.iscanceled', return_value=True): + routing.run([routing.url_for(addon.show_login_menu), '0', '']) + def test_main_menu(self): routing.run([routing.url_for(addon.show_main_menu), '0', ''])