diff --git a/.gitignore b/.gitignore
index c791ff5..d4c0972 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,4 @@
# Byte-compiled / optimized / DLL files
__pycache__/
-*.py[cod]
\ No newline at end of file
+*.py[cod]
+.idea/
\ No newline at end of file
diff --git a/README.md b/README.md
index 81ff312..89a42c3 100644
--- a/README.md
+++ b/README.md
@@ -1,27 +1,54 @@
-# Viaplay for Kodi #
-This is a Kodi add-on that allows you to stream content from Viaplay in Kodi.
+[![GitHub release](https://img.shields.io/github/v/release/Mariusz89B/plugin.video.viaplay.svg)](https://github.com/Mariusz89B/plugin.video.viaplay/releases)
+[![GitHub downloads](https://img.shields.io/github/downloads/Mariusz89B/plugin.video.viaplay/total.svg)](https://github.com/Mariusz89B/plugin.video.viaplay)
+[![License: GPLv3](https://img.shields.io/badge/license-GPLv3-red.svg)](https://www.gnu.org/licenses/gpl-3.0.html)
+[![License: MIT](https://img.shields.io/badge/license-MIT-brightgreen.svg)](https://opensource.org/licenses/MIT)
+[![Kodi](https://img.shields.io/badge/platform-Kodi-lightgrey.svg)](https://kodi.tv/)
-## Disclaimer ##
-This add-on is unoffical and is not endorsed or supported by Viaplay in any way. Any trademarks used belong to their owning companies and organisations.
+# Support us!
-## Dependencies: ##
-This add-on is available in the official Kodi repository and all dependencies will be installed automatically when installed from there. However, if you're installing straight from git, please make sure you've got the following modules installed:
- * script.module.requests >= 2.9.1 (http://mirrors.kodi.tv/addons/krypton/script.module.requests/)
- * script.module.iso8601 (http://mirrors.kodi.tv/addons/krypton/script.module.iso8601/)
- * script.module.inputstreamhelper >= 0.3.3 (http://mirrors.kodi.tv/addons/krypton/script.module.inputstreamhelper/)
- * script.module.routing >= 0.2.0 (http://mirrors.kodi.tv/addons/krypton/script.module.routing/)
-
-This add-on requires Kodi 17.4 or higher with InputStream Adaptive installed. Kodi 18 is required for Android based devices.
+All donations are appreciated.
+https://www.paypal.me/mariusz89b
-## DRM protected streams ##
-Viaplay's content is DRM protected and requires the proprietary decryption module Widevine CDM for playback. You will be prompted to install this if you're attempting to play a stream without the binary installed.
-
-Most Android devices have built-in support for Widevine DRM and doesn't require any additional binaries. You can see if your Android device supports Widevine DRM by using the [DRM Info](https://play.google.com/store/apps/details?id=com.androidfung.drminfo) app available in Play Store.
-## Support ##
-Please report any issues or bug reports on the [GitHub Issues](https://github.com/emilsvennesson/kodi-viaplay/issues) page. Remember to include a full, non-cut off Kodi debug log. See the [Kodi wiki page](http://kodi.wiki/view/Log_file/Advanced) for more detailed instructions on how to obtain the log file.
+## Disclaimer
-Additional support/discussion about the add-on can be found in the [Viaplay add-on thread](https://forum.kodi.tv/showthread.php?tid=286387).
+This add-on is unoffical and is not endorsed or supported by the service provided in any way.
+Any trademarks used belong to their owning companies and organisations.
-## License ##
-This add-on is licensed under the **GNU GENERAL PUBLIC LICENSE Version 3**. Please see the [LICENSE.txt](LICENSE.txt) file for details.
+Connection to the service takes place with the help of an API request that is processed by the server and respond back to the client.
+
+## Regulations
+
+Detailed regulations of the television providers can be found on their websites.
+
+* **Viaplay** - https://viaplay.se/se-sv/terms
+
+
+
+Subscription fees are made on the television providers website and are not linked to the add-on.
+
+More information on licensed television providers is found here:
+
+Sweden - Post- och telestyrelsen
+https://www.pts.se/
+
+
+## DRM protected streams
+
+Some content is DRM protected and requires the proprietary decryption module
+Widevine CDM for playback. You will be prompted to install this if you're attempting to
+play a stream without the binary installed.
+
+Most Android devices have built-in support for Widevine DRM and doesn't require
+any additional binaries. You can see if your Android device supports Widevine DRM by
+using the DRM Info app available in Play Store.
+
+
+## License
+
+This add-on is licensed under the **`GNU GENERAL PUBLIC LICENSE Version 3`** and **`MIT LICENSE`**.
+Please see the **`LICENSE.txt`** file for details.
+
+![](https://github.com/Mariusz89B/plugin.video.viaplay/blob/master/resources/fanart.jpg?raw=true?)
+
+Copyright Mariusz89B © 2023
diff --git a/__init__.py b/__init__.py
index b53149b..415c393 100644
--- a/__init__.py
+++ b/__init__.py
@@ -1 +1 @@
-# dummy file to init the directory
+# dummy file to init the directory
\ No newline at end of file
diff --git a/addon.xml b/addon.xml
index 6d3016c..beb391f 100644
--- a/addon.xml
+++ b/addon.xml
@@ -1,34 +1,151 @@
-
-
-
-
-
-
-
-
-
- video
-
-
- Se indhold fra Viaplay.
- Watch content from Viaplay.
- Katso sisältöä Viaplay.
- Se innhold fra Viaplay.
- Titta på innehåll från Viaplay.
- 2019.03.11 v.2.1.2
- + Fix channels listing on some subscriptions
- + Fix 'fallback' image is not always available
-
- all
- sv dk no fi en
- GNU GENERAL PUBLIC LICENSE. Version 3, 29 June 2007
-
- http://forum.kodi.tv/showthread.php?tid=286387
- This add-on requires you to have a subscription to Viaplay.[CR]This add-on is completely unofficial and is not endorsed by Viaplay in any way.
-
- resources/art/icon.png
- resources/art/fanart.jpg
-
-
+
+
+
+
+
+
+
+
+ video
+
+
+
+ Se indhold fra Viaplay.
+ Watch content from Viaplay.
+ Katso sisältöä Viaplay.
+ Se innhold fra Viaplay.
+ Bekijk inhoud van Viaplay.
+ Titta på innehåll från Viaplay.
+ Oglądaj treści z Viaplay.
+ Žiūrėkite turinį iš Viaplay.
+ This add-on is unoffical and is not endorsed or supported by any of the services provided in any way. Any trademarks used belong to their owning companies and organisations, it is also required to have a subscription to Viaplay.
+ da en fi nb nl sv pl lt
+ all
+ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 and MIT LICENSE.
+
+ v2.4.6 (2023-04-17)
+- Fixed live channels.
+
+v2.4.5 (2023-04-17)
+- Bug fixes.
+
+v2.4.4 (2023-03-12)
+- Fixed bug when returning from search.
+- Added option to enable/disable synchronization.
+
+v2.4.3 (2023-03-06)
+- Added profile selector.
+- Added supported countries.
+- Added synchronization with watched list.
+
+v2.4.2 (2023-02-24)
+- Added playcount for sport events.
+
+v2.4.1 (2023-02-21)
+- Add to my list option.
+- Added login option with username and password.
+- Fixed watched markings for sports events.
+- Fixed playback errors.
+
+v2.4.0 (2023-02-12)
+- Fixed watched marking.
+- Fixed playback errors.
+- Other fixes.
+
+v2.3.9 (2023-01-27)
+- Fix live.
+
+v2.3.8 (2023-01-10)
+- Added new Viaplay arts.
+
+v2.3.7 (2023-01-10)
+- Fixes.
+
+v2.3.6 (2023-01-01)
+- Added autologin on Kodi startup.
+
+v2.3.5 (2022-07-28)
+- Fixed live channels.
+
+v2.3.4 (2022-04-23)
+- Fixed py2 support.
+
+v2.3.3 (2022-04-05)
+- Fixed live tv channels and popular sports in .se. @heppen-dev
+
+v2.3.2 (2022-03-27)
+- Fixed sports series event listing. @heppen-dev
+
+v2.3.1 (2022-03-24)
+- Add NL country support.
+- Fixed popular sport tiles.
+
+v2.3.0 (2022-02-17)
+- Added search history function.
+
+v2.2.9 (2022-02-16)
+- Sport category fix.
+
+v2.2.8 (2022-01-28)
+- Kids category updates.
+
+v2.2.7 (2022-01-27)
+- Fixed logout.
+
+v2.2.6 (2022-01-27)
+- Fixed login error.
+
+v2.2.5 (2022-01-25)
+- Fixes.
+
+v2.2.4 (2022-01-23)
+- Fixed external authorization.
+
+v2.2.3 (2021-11-09)
+- Added viaplay.lt.
+
+v2.2.2 (2021-10-24)
+- Fixed plot.
+
+v2.2.1 (2021-10-06)
+- Fixed category sport matches abbreviations.
+
+v2.2.0 (2021-09-29)
+- Fixed subtitles.
+
+v2.1.9 (2021-09-27)
+- Fixed product categories.
+
+v2.1.8 (2021-08-17 )
+- Fixed html import error.
+
+v2.1.7 (2021-08-11)
+- Fixed M3U playlist generator.
+
+v2.1.6 (2021-08-09)
+- Added M3U playlist generator.
+
+v2.1.5 (2021-08-08)
+- Added setting "Hide previously aired Live-Tv programmes".
+
+v2.1.4 (2021-08-07)
+- Added watched and purchased categories for viaplay.pl
+- Fixed category error.
+
+v.2.1.3 (2021-08-05)
+- Added viaplay.pl
+- Added Polish translation. @hevet
+- Added Swedish translation. @Mariusz89B
+
+
+ icon.png
+ resources/fanart.jpg
+ resources/banner.jpg
+ resources/clearlogo.png
+ resources/screenshots/screenshot-01.jpg
+ resources/screenshots/screenshot-02.jpg
+ resources/screenshots/screenshot-03.jpg
+
+
diff --git a/changelog.txt b/changelog.txt
index 8b8cc8b..0e18727 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,87 +1,201 @@
-2019.03.11 v.2.1.2
-+ Fix channels listing on some subscriptions
-+ Fix 'fallback' image is not always available
-
-2018.02.21 v2.1.1
-+ Fix parental control
-
-2018.02.21 v2.1.0
-+ New dependency: script.module.routing
-+ Support for 4K streams and 5.1 audio
-+ Fix audio bug on some sports streams (requires Kodi v18 Leia)
-+ Channels section improvements
-+ Code cleanup
-
-2017.12.27 v2.0.1
-+ Minor changes needed for approval in the official repository
-
-2017.12.27 v2.0.0
-+ New dependency: script.module.inputstreamhelper
-+ Switch to registration code activation
-+ Finally replace HLS with MPEG-DASH (Widevine DRM is now required)
-+ Channels support (previously Viasat TV To Go)
-+ Implement dynamically acquired 'theme pages' in addition to browsing through categories
-+ Starred, Watched and Purchased pages now work
-+ Open add-on settings on first run
-+ Major rewrite and cleanup of the code
-+ Big improvements all-around and a massively enhanced experience
-
-2017.04.10 v1.0.8
-+ Fix audio on newer Viaplay content
-+ Fix icon/fanart
-+ Move addon code to addon.py
-
-2017.01.25 v1.0.7.1
-+ Additional stream URL fix
-
-2017.01.25 v1.0.7
-+ Update for Krypton
-+ Fix stream URL retrieval
-
-2016.12.11 v1.0.6
-+ Improve sports section
-+ Fix broken playback for new content
-+ Remove add-on debugging setting and automatically detect it instead
-+ Code cleanup
-
-2016.09.08 v1.0.5
-+ Bug fix: possible UTC bug when determining game status
-+ Bump required requests version to 2.9.1
-+ Don't set watched status on sports content
-+ Implement parental control
-+ Add option to limit the max allowed bitrate
-+ Code cleanup & refactoring
-
-2016.08.31 v1.0.4
-+ Remove dateutil dependency; add iso8601
-+ Improve the sports section
-+ Get better at catching exceptions
-+ Use 'with' statement to open files
-
-2016.08.28 v1.0.3
-+ New dependency: m3u8
-+ The sports section is getting listed again
-+ Add poster artwork
-+ Add setting to select stream quality (for movies & TV)
-+ Store the device ID in file
-+ List episodes directly if there's only one season available
-+ Turn core code into a Kodi independent module (vialib)
-+ Performance improvements
-+ Significant code refactoring
-
-2016.08.05 v1.0.2
-+ Store support
-+ Bug fix: some categories failed to load due to unavailable artwork
-+ Better info messages when playback fails
-+ Fix episode/sports thumbnails
-+ Code cleanup
-
-2016.07.24 v1.0.1
-+ Bug fix: kids category showed sports category
-+ base_url changed so that more content is available
-+ SSL is now enabled by default. An option to disable it is available.
-+ Code cleanup
-
-2016.07.18 v1.0.0
-+ Initial release
-+ Thanks to eriksoderblom for icon and fanart!
+v2.4.6 (2023-04-17)
+- Fixed live channels.
+
+v2.4.5 (2023-04-17)
+- Bug fixes.
+
+v2.4.4 (2023-03-12)
+- Fixed bug when returning from search.
+- Added option to enable/disable synchronization.
+
+v2.4.3 (2023-03-06)
+- Added profile selector.
+- Added supported countries.
+- Added synchronization with watched list.
+
+v2.4.2 (2023-02-24)
+- Added playcount for sport events.
+
+v2.4.1 (2023-02-21)
+- Add to my list option.
+- Added login option with username and password.
+- Fixed watched markings for sports events.
+- Fixed playback errors.
+
+v2.4.0 (2023-02-12)
+- Fixed watched marking.
+- Fixed playback errors.
+- Other fixes.
+
+v2.3.9 (2023-01-27)
+- Fix live.
+
+v2.3.8 (2023-01-10)
+- Added new Viaplay arts.
+
+v2.3.7 (2023-01-10)
+- Fixes.
+
+v2.3.6 (2023-01-01)
+- Added autologin on Kodi startup.
+
+v2.3.5 (2022-07-28)
+- Fixed live channels.
+
+v2.3.4 (2022-04-23)
+- Fixed py2 support.
+
+v2.3.3 (2022-04-05)
+- Fixed live tv channels and popular sports in .se. @heppen-dev
+
+v2.3.2 (2022-03-27)
+- Fixed sports series event listing. @heppen-dev
+
+v2.3.1 (2022-03-24)
+- Add NL country support.
+- Fixed popular sport tiles.
+
+v2.3.0 (2022-02-17)
+- Added search history function.
+
+v2.2.9 (2022-02-16)
+- Sport category fix.
+
+v2.2.8 (2022-01-28)
+- Kids category updates.
+
+v2.2.7 (2022-01-27)
+- Fixed logout.
+
+v2.2.6 (2022-01-27)
+- Fixed login error.
+
+v2.2.5 (2022-01-25)
+- Fixes.
+
+v2.2.4 (2022-01-23)
+- Fixed external authorization.
+
+v2.2.3 (2021-11-09)
+- Added viaplay.lt.
+
+v2.2.2 (2021-10-24)
+- Fixed plot.
+
+v2.2.1 (2021-10-06)
+- Fixed category sport matches abbreviations.
+
+v2.2.0 (2021-09-29)
+- Fixed subtitles.
+
+v2.1.9 (2021-09-27)
+- Fixed product categories.
+
+v2.1.8 (2021-08-17)
+- Fixed html import error.
+
+v2.1.7 (2021-08-11)
+- Fixed M3U playlist generator.
+
+v2.1.6 (2021-08-09)
+- Added M3U playlist generator.
+
+v2.1.5 (2021-08-08)
+- Added setting "Hide previously aired Live-Tv programmes".
+
+v2.1.4 (2021-08-07)
+- Added watched and purchased categories for viaplay.pl
+- Fixed category error.
+
+v.2.1.3 (2021-08-05)
+- Added viaplay.pl
+- Added Polish translation. @hevet
+- Added Swedish translation. @Mariusz89B
+
+v2.1.2 (2019-03-11)
+- Fix channels listing on some subscriptions.
+- Fix 'fallback' image is not always available.
+
+v2.1.1 (2018-02-21)
+- Fix parental control.
+
+v2.1.0 (2018-02-21)
+- New dependency: script.module.routing.
+- Support for 4K streams and 5.1 audio.
+- Fix audio bug on some sports streams (requires Kodi v18 Leia).
+- Channels section improvements.
+- Code cleanup.
+
+v2.0.1 (2017-12-27)
+- Minor changes needed for approval in the official repository.
+
+v2.0.0 (2017-12-27)
+- New dependency: script.module.inputstreamhelper.
+- Switch to registration code activation.
+- Finally replace HLS with MPEG-DASH (Widevine DRM is now required).
+- Channels support (previously Viasat TV To Go).
+- Implement dynamically acquired 'theme pages' in addition to browsing through categories.
+- Starred, Watched and Purchased pages now work.
+- Open add-on settings on first run.
+- Major rewrite and cleanup of the code.
+- Big improvements all-around and a massively enhanced experience.
+
+v1.0.8 (2017-04-10)
+- Fix audio on newer Viaplay content.
+- Fix icon/fanart.
+- Move addon code to addon.py
+
+v1.0.7.1 (2017-01-25)
+- Additional stream URL fix.
+
+v1.0.7 (2017-01-25)
+- Update for Krypton.
+- Fix stream URL retrieval.
+
+v1.0.6 (2016-12-11)
+- Improve sports section.
+- Fix broken playback for new content.
+- Remove add-on debugging setting and automatically detect it instead.
+- Code cleanup.
+
+v1.0.5 (2016-09-08)
+- Bug fix: possible UTC bug when determining game status.
+- Bump required requests version to 2.9.1.
+- Don't set watched status on sports content.
+- Implement parental control.
+- Add option to limit the max allowed bitrate.
+- Code cleanup & refactoring.
+
+v1.0.4 (2016-08-31)
+- Remove dateutil dependency; add iso8601.
+- Improve the sports section.
+- Get better at catching exceptions.
+- Use 'with' statement to open files.
+
+v1.0.3 (2016-08-28)
+- New dependency: m3u8.
+- The sports section is getting listed again.
+- Add poster artwork.
+- Add setting to select stream quality (for movies & TV).
+- Store the device ID in file.
+- List episodes directly if there's only one season available.
+- Turn core code into a Kodi independent module (vialib).
+- Performance improvements.
+- Significant code refactoring.
+
+v1.0.2 (2016-08-05)
+- Store support.
+- Bug fix: some categories failed to load due to unavailable artwork.
+- Better info messages when playback fails.
+- Fix episode/sports thumbnails.
+- Code cleanup.
+
+v1.0.1 (2016-07-24)
+- Bug fix: kids category showed sports category.
+- base_url changed so that more content is available.
+- SSL is now enabled by default. An option to disable it is available.
+- Code cleanup
+
+v1.0.0 (2016-07-18)
+- Initial release.
+- Thanks to eriksoderblom for icon and fanart!
diff --git a/default.py b/default.py
index 88ccab7..eef9260 100644
--- a/default.py
+++ b/default.py
@@ -2,4 +2,4 @@
from resources.lib import addon
if __name__ == '__main__':
- addon.run()
+ addon.run()
\ No newline at end of file
diff --git a/icon.png b/icon.png
new file mode 100644
index 0000000..1b666dd
Binary files /dev/null and b/icon.png differ
diff --git a/login.py b/login.py
new file mode 100644
index 0000000..708aaf4
--- /dev/null
+++ b/login.py
@@ -0,0 +1,12 @@
+import sys
+from resources.lib.kodihelper import KodiHelper
+
+helper = KodiHelper()
+
+class Login:
+ def __init__(self):
+ if helper.get_setting('autologin'):
+ helper.authorize(autologin=True)
+
+if __name__ == '__main__':
+ r = Login()
\ No newline at end of file
diff --git a/resources/__init__.py b/resources/__init__.py
index b53149b..415c393 100644
--- a/resources/__init__.py
+++ b/resources/__init__.py
@@ -1 +1 @@
-# dummy file to init the directory
+# dummy file to init the directory
\ No newline at end of file
diff --git a/resources/art/fanart.jpg b/resources/art/fanart.jpg
deleted file mode 100644
index 90de970..0000000
Binary files a/resources/art/fanart.jpg and /dev/null differ
diff --git a/resources/art/icon.png b/resources/art/icon.png
deleted file mode 100644
index 095aef1..0000000
Binary files a/resources/art/icon.png and /dev/null differ
diff --git a/resources/banner.jpg b/resources/banner.jpg
new file mode 100644
index 0000000..912c4a8
Binary files /dev/null and b/resources/banner.jpg differ
diff --git a/resources/clearlogo.png b/resources/clearlogo.png
new file mode 100644
index 0000000..1d849d3
Binary files /dev/null and b/resources/clearlogo.png differ
diff --git a/resources/fanart.jpg b/resources/fanart.jpg
new file mode 100644
index 0000000..2e0711b
Binary files /dev/null and b/resources/fanart.jpg differ
diff --git a/resources/icons/fav.png b/resources/icons/fav.png
new file mode 100644
index 0000000..04afb7d
Binary files /dev/null and b/resources/icons/fav.png differ
diff --git a/resources/icons/kids.png b/resources/icons/kids.png
new file mode 100644
index 0000000..55273ac
Binary files /dev/null and b/resources/icons/kids.png differ
diff --git a/resources/icons/logout.png b/resources/icons/logout.png
new file mode 100644
index 0000000..0625613
Binary files /dev/null and b/resources/icons/logout.png differ
diff --git a/resources/icons/movie.png b/resources/icons/movie.png
new file mode 100644
index 0000000..2245108
Binary files /dev/null and b/resources/icons/movie.png differ
diff --git a/resources/icons/root.png b/resources/icons/root.png
new file mode 100644
index 0000000..beeec04
Binary files /dev/null and b/resources/icons/root.png differ
diff --git a/resources/icons/search.png b/resources/icons/search.png
new file mode 100644
index 0000000..fe19e22
Binary files /dev/null and b/resources/icons/search.png differ
diff --git a/resources/icons/settings.png b/resources/icons/settings.png
new file mode 100644
index 0000000..83c589a
Binary files /dev/null and b/resources/icons/settings.png differ
diff --git a/resources/icons/sport.png b/resources/icons/sport.png
new file mode 100644
index 0000000..2de1228
Binary files /dev/null and b/resources/icons/sport.png differ
diff --git a/resources/icons/tv.png b/resources/icons/tv.png
new file mode 100644
index 0000000..d25091c
Binary files /dev/null and b/resources/icons/tv.png differ
diff --git a/resources/icons/vod.png b/resources/icons/vod.png
new file mode 100644
index 0000000..b6f5402
Binary files /dev/null and b/resources/icons/vod.png differ
diff --git a/resources/icons/watched.png b/resources/icons/watched.png
new file mode 100644
index 0000000..11ea918
Binary files /dev/null and b/resources/icons/watched.png differ
diff --git a/resources/language/resource.language.en_gb/strings.po b/resources/language/resource.language.en_gb/strings.po
index 75c1a6b..722ee16 100644
--- a/resources/language/resource.language.en_gb/strings.po
+++ b/resources/language/resource.language.en_gb/strings.po
@@ -170,7 +170,7 @@ msgid "Categories"
msgstr ""
msgctxt "#30042"
-msgid "Log out"
+msgid "Logout"
msgstr ""
msgctxt "#30043"
@@ -216,3 +216,163 @@ msgstr ""
msgctxt "#30053"
msgid "InputStream Adaptive settings"
msgstr ""
+
+msgctxt "#30054"
+msgid "viaplay.pl"
+msgstr ""
+
+msgctxt "#30055"
+msgid "Polish"
+msgstr ""
+
+msgctxt "#30056"
+msgid "Hide previously aired Live-Tv programmes"
+msgstr ""
+
+msgctxt "#30057"
+msgid "Playlist generator"
+msgstr ""
+
+msgctxt "#30058"
+msgid "M3U playlist"
+msgstr ""
+
+msgctxt "#30059"
+msgid "Filename"
+msgstr ""
+
+msgctxt "#30060"
+msgid "Directory"
+msgstr ""
+
+msgctxt "#30061"
+msgid "Create M3U playlist"
+msgstr ""
+
+msgctxt "#30062"
+msgid "Set filename and target directory."
+msgstr ""
+
+msgctxt "#30063"
+msgid "Creating M3U playlist..."
+msgstr ""
+
+msgctxt "#30064"
+msgid "M3U playlist created"
+msgstr ""
+
+msgctxt "#30065"
+msgid "viaplay.lt"
+msgstr ""
+
+msgctxt "#30066"
+msgid "Lithuanian"
+msgstr ""
+
+msgctxt "#30067"
+msgid "viaplay.com/nl"
+msgstr ""
+
+msgctxt "#30068"
+msgid "Autologin on Kodi startup"
+msgstr ""
+
+msgctxt "#30069"
+msgid "InputStream Adaptive"
+msgstr ""
+
+msgctxt "#30070"
+msgid "Add to my list"
+msgstr ""
+
+msgctxt "#30071"
+msgid "Added to my list"
+msgstr ""
+
+msgctxt "#30072"
+msgid "Content could not be added to list"
+msgstr ""
+
+msgctxt "#30073"
+msgid "Username"
+msgstr ""
+
+msgctxt "#30074"
+msgid "Password"
+msgstr ""
+
+msgctxt "#30075"
+msgid "Wrong login credentials"
+msgstr ""
+
+msgctxt "#30076"
+msgid "Error"
+msgstr ""
+
+msgctxt "#30077"
+msgid "My list"
+msgstr ""
+
+msgctxt "#30078"
+msgid "Remove from my list"
+msgstr ""
+
+msgctxt "#30079"
+msgid "New search"
+msgstr ""
+
+msgctxt "#30080"
+msgid "Remove search"
+msgstr ""
+
+msgctxt "#30081"
+msgid "Program search"
+msgstr ""
+
+msgctxt "#30082"
+msgid "viaplay.ee"
+msgstr ""
+
+msgctxt "#30083"
+msgid "Estonian"
+msgstr ""
+
+msgctxt "#30084"
+msgid "viaplay.lv"
+msgstr ""
+
+msgctxt "#30085"
+msgid "Latvian"
+msgstr ""
+
+msgctxt "#30086"
+msgid "viaplay.gb"
+msgstr ""
+
+msgctxt "#30087"
+msgid "English"
+msgstr ""
+
+msgctxt "#30088"
+msgid "Select profile"
+msgstr ""
+
+msgctxt "#30089"
+msgid "Kids account"
+msgstr ""
+
+msgctxt "#30090"
+msgid "Signed in as"
+msgstr ""
+
+msgctxt "#30091"
+msgid "Content removed from list"
+msgstr ""
+
+msgctxt "#30092"
+msgid "Remove watched content"
+msgstr ""
+
+msgctxt "#30093"
+msgid "Synchronize content"
+msgstr ""
\ No newline at end of file
diff --git a/resources/language/resource.language.pl_pl/strings.po b/resources/language/resource.language.pl_pl/strings.po
new file mode 100644
index 0000000..c062cd2
--- /dev/null
+++ b/resources/language/resource.language.pl_pl/strings.po
@@ -0,0 +1,374 @@
+# Kodi Viaplay language file
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi-Viaplay\n"
+"Report-Msgid-Bugs-To: https://github.com/emilsvennesson/kodi-viaplay\n"
+"POT-Creation-Date: 2016-07-05 14:30+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: English\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: en\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgctxt "#30001"
+msgid "Email"
+msgstr "e-mail"
+
+msgctxt "#30002"
+msgid "Password"
+msgstr "Hasło"
+
+msgctxt "#30003"
+msgid "General"
+msgstr "Ogólne"
+
+msgctxt "#30004"
+msgid "Advanced"
+msgstr "Zaawansowane"
+
+msgctxt "#30005"
+msgid "Error"
+msgstr "Błąd"
+
+msgctxt "#30006"
+msgid "Login failed. Please make sure that your account information is correct."
+msgstr "Logowanie nie powiodło się. Upewnij się, że informacje o Twoim koncie są poprawne."
+
+msgctxt "#30007"
+msgid "Site"
+msgstr "Strona"
+
+msgctxt "#30008"
+msgid "viaplay.se"
+msgstr ""
+
+msgctxt "#30009"
+msgid "viaplay.dk"
+msgstr ""
+
+msgctxt "#30010"
+msgid "viaplay.no"
+msgstr ""
+
+msgctxt "#30011"
+msgid "viaplay.fi"
+msgstr ""
+
+msgctxt "#30012"
+msgid "Subtitles"
+msgstr "Napisy"
+
+msgctxt "#30013"
+msgid "List all in alphabetical order"
+msgstr "Lista wszystkich zamówień w porządku alfabetycznym"
+
+msgctxt "#30014"
+msgid "Season {0}"
+msgstr "Sezon {0}"
+
+msgctxt "#30015"
+msgid "Search"
+msgstr "Szukaj"
+
+msgctxt "#30016"
+msgid "This event starts [B]{0}[/B]."
+msgstr "To wydarzenie rozpoczyna się [B]{0}[/B]."
+
+msgctxt "#30017"
+msgid "Information"
+msgstr "Informacje"
+
+msgctxt "#30018"
+msgid "[B]Next page[/B]"
+msgstr "Następna strona"
+
+msgctxt "#30020"
+msgid "This content is not included in your package."
+msgstr "Ta zawartość nie jest zawarta w Twoim pakiecie."
+
+msgctxt "#30021"
+msgid "You need to rent or purchase this movie on the Viaplay website."
+msgstr "Musisz wypożyczyć lub kupić ten film na stronie Viaplay."
+
+msgctxt "#30022"
+msgid "You cannot use Viaplay where you are. For copyright reasons, you can only watch in the following countries: Sweden, Finland, Norway and Denmark."
+msgstr "Nie możesz używać Viaplay tam, gdzie jesteś. Ze względu na prawa autorskie możesz oglądać tylko w następujących krajach: Szwecja, Finlandia, Norwegia, Polska i Dania."
+
+msgctxt "#30023"
+msgid "Preferred stream quality (movies & TV)"
+msgstr "Preferowana jakość strumienia (filmy i telewizja)"
+
+msgctxt "#30024"
+msgid "Always use highest bitrate"
+msgstr "Zawsze używaj najwyższej szybkości transmisji"
+
+msgctxt "#30025"
+msgid "Ask"
+msgstr "Pytaj"
+
+msgctxt "#30026"
+msgid "Select stream quality"
+msgstr "Wybierz jakość strumienia"
+
+msgctxt "#30027"
+msgid "Today"
+msgstr "Dzisiaj"
+
+msgctxt "#30028"
+msgid "Upcoming days"
+msgstr "Nadchodzące dni"
+
+msgctxt "#30029"
+msgid "Previous days"
+msgstr "Poprzednie dni"
+
+msgctxt "#30031"
+msgid "Archived events"
+msgstr "Zarchiwizowane wydarzenia"
+
+msgctxt "#30032"
+msgid "Enter your PIN code"
+msgstr "Wpisz swój kod PIN"
+
+msgctxt "#30033"
+msgid "Parental control"
+msgstr "Kontrola rodzicielska"
+
+msgctxt "#30034"
+msgid "The PIN code you have entered is incorrect."
+msgstr "Wprowadzony kod PIN jest nieprawidłowy."
+
+msgctxt "#30035"
+msgid "Limit bitrate"
+msgstr "Limit bitrate"
+
+msgctxt "#30036"
+msgid "Max bitrate allowed (Kbps)"
+msgstr "Maksymalna dozwolona szybkość transmisji bitów (Kb/s)"
+
+msgctxt "#30037"
+msgid "Live & upcoming events"
+msgstr "Wydarzenia na żywo i nadchodzące"
+
+msgctxt "#30038"
+msgid "No valid stream URL was found."
+msgstr "Nie znaleziono prawidłowego adresu URL transmisji."
+
+msgctxt "#30039"
+msgid "Go to [B]{0}[/B] on your mobile phone, tablet or computer and enter this code: [B]{1}[/B]"
+msgstr "Przejdź do [B]{0}[/B] na telefonie komórkowym, tablecie lub komputerze i wprowadź ten kod: [B]{1}[/B]"
+
+msgctxt "#30040"
+msgid "Log in using a single-use code"
+msgstr "Zaloguj się za pomocą kodu jednorazowego"
+
+msgctxt "#30041"
+msgid "Categories"
+msgstr "Kategorie"
+
+msgctxt "#30042"
+msgid "Logout"
+msgstr "Wyloguj"
+
+msgctxt "#30043"
+msgid "Are you sure you want to log out?"
+msgstr "Czy na pewno chcesz się wylogować?"
+
+msgctxt "#30044"
+msgid "Subtitle language"
+msgstr "Język napisów"
+
+msgctxt "#30045"
+msgid "Swedish"
+msgstr "Szwedzki"
+
+msgctxt "#30046"
+msgid "Danish"
+msgstr "Duński"
+
+msgctxt "#30047"
+msgid "Norwegian"
+msgstr "Norweski"
+
+msgctxt "#30048"
+msgid "Finnish"
+msgstr "Fiński"
+
+msgctxt "#30049"
+msgid "No broadcast"
+msgstr "Brak transmisji"
+
+msgctxt "#30050"
+msgid "You can watch two videos at the same time using a Viaplay account. Your account is currently being used to watch two other videos."
+msgstr "Możesz oglądać dwa filmy jednocześnie, korzystając z konta Viaplay. Twoje konto jest obecnie używane do oglądania dwóch innych filmów"
+
+msgctxt "#30051"
+msgid "You are not logged in"
+msgstr "Nie jesteś zalogowany"
+
+msgctxt "#30052"
+msgid "An activation error occurred."
+msgstr "Wystąpił błąd aktywacji."
+
+msgctxt "#30053"
+msgid "InputStream Adaptive settings"
+msgstr "Ustawienia InputStream Adaptive"
+
+msgctxt "#30054"
+msgid "viaplay.pl"
+msgstr ""
+
+msgctxt "#30055"
+msgid "Polish"
+msgstr "Polski"
+
+msgctxt "#30056"
+msgid "Hide previously aired Live-Tv programmes"
+msgstr "Ukryj wcześniej nadawane programy Live-Tv"
+
+msgctxt "#30057"
+msgid "Playlist generator"
+msgstr "Generator playlisty"
+
+msgctxt "#30058"
+msgid "M3U playlist"
+msgstr "Lista odtwarzania M3U"
+
+msgctxt "#30059"
+msgid "Filename"
+msgstr "Nazwa pliku"
+
+msgctxt "#30060"
+msgid "Directory"
+msgstr "Katalog"
+
+msgctxt "#30061"
+msgid "Create M3U playlist"
+msgstr "Utwórz listę odtwarzania M3U"
+
+msgctxt "#30062"
+msgid "Set filename and target directory."
+msgstr "Ustaw nazwę pliku i katalog docelowy."
+
+msgctxt "#30063"
+msgid "Creating M3U playlist..."
+msgstr "Tworzenie listy odtwarzania M3U..."
+
+msgctxt "#30064"
+msgid "M3U playlist created"
+msgstr "Utworzono listę odtwarzania M3U"
+
+msgctxt "#30065"
+msgid "viaplay.lt"
+msgstr ""
+
+msgctxt "#30066"
+msgid "Lithuanian"
+msgstr "Litewski"
+
+msgctxt "#30067"
+msgid "viaplay.com/nl"
+msgstr ""
+
+msgctxt "#30068"
+msgid "Autologin on Kodi startup"
+msgstr "Automatyczne logowanie przy starcie Kodi"
+
+msgctxt "#30070"
+msgid "Add to my list"
+msgstr "Dodaj do mojej listy"
+
+msgctxt "#30071"
+msgid "Added to my list"
+msgstr "Dodano treść do mojej listy"
+
+msgctxt "#30072"
+msgid "Content could not be added to list"
+msgstr "Nie można dodać treści do listy"
+
+msgctxt "#30073"
+msgid "Username"
+msgstr "Użytkownik"
+
+msgctxt "#30074"
+msgid "Password"
+msgstr "Hasło"
+
+msgctxt "#30075"
+msgid "Wrong login credentials"
+msgstr "Błędne dane logowania"
+
+msgctxt "#30076"
+msgid "Error"
+msgstr "Błąd"
+
+msgctxt "#30077"
+msgid "My list"
+msgstr "Moja lista"
+
+msgctxt "#30078"
+msgid "Remove from my list"
+msgstr "Usuń z mojej listy"
+
+msgctxt "#30079"
+msgid "New search"
+msgstr "Nowe wyszukiwanie"
+
+msgctxt "#30080"
+msgid "Remove search"
+msgstr "Usuń wyszukiwanie"
+
+msgctxt "#30081"
+msgid "Program search"
+msgstr "Wyszukiwanie programów"
+
+msgctxt "#30082"
+msgid "viaplay.ee"
+msgstr "viaplay.ee"
+
+msgctxt "#30083"
+msgid "Estonian"
+msgstr "Estoński"
+
+msgctxt "#30084"
+msgid "viaplay.lv"
+msgstr "viaplay.lv"
+
+msgctxt "#30085"
+msgid "Latvian"
+msgstr "Łotewski"
+
+msgctxt "#30086"
+msgid "viaplay.gb"
+msgstr "viaplay.gb"
+
+msgctxt "#30087"
+msgid "English"
+msgstr "Angelski"
+
+msgctxt "#30088"
+msgid "Select profile"
+msgstr "Wybierz profil"
+
+msgctxt "#30089"
+msgid "Kids account"
+msgstr "Konto dziecięce"
+
+msgctxt "#30090"
+msgid "Signed in as"
+msgstr "Zalogowano jako"
+
+msgctxt "#30091"
+msgid "Content removed from list"
+msgstr "Zawartość usunięto z listy"
+
+msgctxt "#30092"
+msgid "Remove watched content"
+msgstr "Usuń oglądaną zawartość"
+
+msgctxt "#30093"
+msgid "Synchronize content"
+msgstr "Synchronizuj zawartość"
\ No newline at end of file
diff --git a/resources/language/resource.language.sv_se/strings.po b/resources/language/resource.language.sv_se/strings.po
new file mode 100644
index 0000000..943417a
--- /dev/null
+++ b/resources/language/resource.language.sv_se/strings.po
@@ -0,0 +1,374 @@
+# Kodi Viaplay language file
+msgid ""
+msgstr ""
+"Project-Id-Version: Kodi-Viaplay\n"
+"Report-Msgid-Bugs-To: https://github.com/emilsvennesson/kodi-viaplay\n"
+"POT-Creation-Date: 2016-07-05 14:30+0000\n"
+"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
+"Last-Translator: FULL NAME \n"
+"Language-Team: Swedish\n"
+"MIME-Version: 1.0\n"
+"Content-Type: text/plain; charset=UTF-8\n"
+"Content-Transfer-Encoding: 8bit\n"
+"Language: sv\n"
+"Plural-Forms: nplurals=2; plural=(n != 1)\n"
+
+msgctxt "#30001"
+msgid "Email"
+msgstr "e-mail"
+
+msgctxt "#30002"
+msgid "Password"
+msgstr "Lösenord"
+
+msgctxt "#30003"
+msgid "General"
+msgstr "Allmänt"
+
+msgctxt "#30004"
+msgid "Advanced"
+msgstr "Avancerat"
+
+msgctxt "#30005"
+msgid "Error"
+msgstr "Fel"
+
+msgctxt "#30006"
+msgid "Login failed. Please make sure that your account information is correct."
+msgstr "Inloggningen misslyckades. Se till att din kontoinformation är korrekt."
+
+msgctxt "#30007"
+msgid "Site"
+msgstr "Webbplats"
+
+msgctxt "#30008"
+msgid "viaplay.se"
+msgstr ""
+
+msgctxt "#30009"
+msgid "viaplay.dk"
+msgstr ""
+
+msgctxt "#30010"
+msgid "viaplay.no"
+msgstr ""
+
+msgctxt "#30011"
+msgid "viaplay.fi"
+msgstr ""
+
+msgctxt "#30012"
+msgid "Subtitles"
+msgstr "Undertexter"
+
+msgctxt "#30013"
+msgid "List all in alphabetical order"
+msgstr "Lista alla i alfabetisk ordning"
+
+msgctxt "#30014"
+msgid "Season {0}"
+msgstr "Säsong {0}"
+
+msgctxt "#30015"
+msgid "Search"
+msgstr "Sök"
+
+msgctxt "#30016"
+msgid "This event starts [B]{0}[/B]."
+msgstr "Denna händelse startar [B]{0}[/B]."
+
+msgctxt "#30017"
+msgid "Information"
+msgstr "Information"
+
+msgctxt "#30018"
+msgid "[B]Next page[/B]"
+msgstr "Nästa sida"
+
+msgctxt "#30020"
+msgid "This content is not included in your package."
+msgstr "Detta innehåll ingår inte i ditt paket."
+
+msgctxt "#30021"
+msgid "You need to rent or purchase this movie on the Viaplay website."
+msgstr "Du måste hyra eller köpa den här filmen på Viaplays webbplats."
+
+msgctxt "#30022"
+msgid "You cannot use Viaplay where you are. For copyright reasons, you can only watch in the following countries: Sweden, Finland, Norway and Denmark."
+msgstr "Du kan inte använda Viaplay där du är. Av upphovsrättsskäl kan du bara titta i följande länder: Sverige, Finland, Norge och Danmark."
+
+msgctxt "#30023"
+msgid "Preferred stream quality (movies & TV)"
+msgstr "Föredragen strömkvalitet (filmer och TV)"
+
+msgctxt "#30024"
+msgid "Always use highest bitrate"
+msgstr "Använd alltid högsta bithastighet"
+
+msgctxt "#30025"
+msgid "Ask"
+msgstr "Fråga"
+
+msgctxt "#30026"
+msgid "Select stream quality"
+msgstr "Välj strömkvalitet"
+
+msgctxt "#30027"
+msgid "Today"
+msgstr "Idag"
+
+msgctxt "#30028"
+msgid "Upcoming days"
+msgstr "Kommande dagar"
+
+msgctxt "#30029"
+msgid "Previous days"
+msgstr "Tidigare dagar"
+
+msgctxt "#30031"
+msgid "Archived events"
+msgstr "Arkiverade händelser"
+
+msgctxt "#30032"
+msgid "Enter your PIN code"
+msgstr "Ange din PIN kod"
+
+msgctxt "#30033"
+msgid "Parental control"
+msgstr "Föräldrakontroll"
+
+msgctxt "#30034"
+msgid "The PIN code you have entered is incorrect."
+msgstr "PIN koden du har angett är felaktig."
+
+msgctxt "#30035"
+msgid "Limit bitrate"
+msgstr "Begränsa bithastigheten"
+
+msgctxt "#30036"
+msgid "Max bitrate allowed (Kbps)"
+msgstr "Högsta tillåtna bithastighet (Kbps)"
+
+msgctxt "#30037"
+msgid "Live & upcoming events"
+msgstr "Live & kommande evenemang"
+
+msgctxt "#30038"
+msgid "No valid stream URL was found."
+msgstr "Ingen giltig ström -URL hittades."
+
+msgctxt "#30039"
+msgid "Go to [B]{0}[/B] on your mobile phone, tablet or computer and enter this code: [B]{1}[/B]"
+msgstr "Gå till [B]{0}[/B] på din mobiltelefon, surfplatta eller dator och ange den här koden: [B]{1}[/B]"
+
+msgctxt "#30040"
+msgid "Log in using a single-use code"
+msgstr "Logga in med en engångskod"
+
+msgctxt "#30041"
+msgid "Categories"
+msgstr "Kategorier"
+
+msgctxt "#30042"
+msgid "Logout"
+msgstr "Logga ut"
+
+msgctxt "#30043"
+msgid "Are you sure you want to log out?"
+msgstr "Är du säker på att du vill logga ut?"
+
+msgctxt "#30044"
+msgid "Subtitle language"
+msgstr "Textningsspråk"
+
+msgctxt "#30045"
+msgid "Swedish"
+msgstr "Svenska"
+
+msgctxt "#30046"
+msgid "Danish"
+msgstr "Danska"
+
+msgctxt "#30047"
+msgid "Norwegian"
+msgstr "Norska"
+
+msgctxt "#30048"
+msgid "Finnish"
+msgstr "Finska"
+
+msgctxt "#30049"
+msgid "No broadcast"
+msgstr "Ingen sändning"
+
+msgctxt "#30050"
+msgid "You can watch two videos at the same time using a Viaplay account. Your account is currently being used to watch two other videos."
+msgstr "Du kan titta på två videor samtidigt med ett Viaplay konto. Ditt konto används för närvarande för att titta på två andra videor."
+
+msgctxt "#30051"
+msgid "You are not logged in"
+msgstr "Du är inte inloggad"
+
+msgctxt "#30052"
+msgid "An activation error occurred."
+msgstr "Ett aktiveringsfel inträffade."
+
+msgctxt "#30053"
+msgid "InputStream Adaptive settings"
+msgstr "InputStream Adaptive inställningar"
+
+msgctxt "#30054"
+msgid "viaplay.pl"
+msgstr ""
+
+msgctxt "#30055"
+msgid "Polish"
+msgstr "Polska"
+
+msgctxt "#30056"
+msgid "Hide previously aired Live-Tv programmes"
+msgstr "Dölj tidigare sända Live-TV program"
+
+msgctxt "#30057"
+msgid "Playlist generator"
+msgstr "M3U generator"
+
+msgctxt "#30058"
+msgid "M3U playlist"
+msgstr "M3U spellista"
+
+msgctxt "#30059"
+msgid "Filename"
+msgstr "Filnamn"
+
+msgctxt "#30060"
+msgid "Directory"
+msgstr "Katalog"
+
+msgctxt "#30061"
+msgid "Create M3U playlist"
+msgstr "Skapa M3U spellista"
+
+msgctxt "#30062"
+msgid "Set filename and target directory."
+msgstr "Ange filnamn och målkatalog."
+
+msgctxt "#30063"
+msgid "Creating M3U playlist..."
+msgstr "Skapar M3U spellista..."
+
+msgctxt "#30064"
+msgid "M3U playlist created"
+msgstr "M3U spellista skapad"
+
+msgctxt "#30065"
+msgid "viaplay.lt"
+msgstr ""
+
+msgctxt "#30066"
+msgid "Lithuanian"
+msgstr "Litauiska"
+
+msgctxt "#30067"
+msgid "viaplay.com/nl"
+msgstr ""
+
+msgctxt "#30068"
+msgid "Autologin on Kodi startup"
+msgstr "Autoinloggning vid start av Kodi"
+
+msgctxt "#30070"
+msgid "Add to my list"
+msgstr "Lägg till i min lista"
+
+msgctxt "#30071"
+msgid "Added to my list"
+msgstr "Materialet lades till i listan"
+
+msgctxt "#30072"
+msgid "Content could not be added to list"
+msgstr "Innehåll kunde inte läggas till i listan"
+
+msgctxt "#30073"
+msgid "Username"
+msgstr "Användarnamn"
+
+msgctxt "#30074"
+msgid "Password"
+msgstr "Lösenord"
+
+msgctxt "#30075"
+msgid "Wrong login credentials"
+msgstr "Fel inloggningsuppgifter"
+
+msgctxt "#30076"
+msgid "Error"
+msgstr "Fel"
+
+msgctxt "#30077"
+msgid "My list"
+msgstr "Min lista"
+
+msgctxt "#30078"
+msgid "Remove from my list"
+msgstr "Ta bort från min lista"
+
+msgctxt "#30079"
+msgid "New search"
+msgstr "Ny sökning"
+
+msgctxt "#30080"
+msgid "Remove search"
+msgstr "Ta bort sökning"
+
+msgctxt "#30081"
+msgid "Program search"
+msgstr "Programsökning"
+
+msgctxt "#30082"
+msgid "viaplay.ee"
+msgstr "viaplay.ee"
+
+msgctxt "#30083"
+msgid "Estonian"
+msgstr "Estisk"
+
+msgctxt "#30084"
+msgid "viaplay.lv"
+msgstr "viaplay.lv"
+
+msgctxt "#30085"
+msgid "Latvian"
+msgstr "Lettisk"
+
+msgctxt "#30086"
+msgid "viaplay.gb"
+msgstr "viaplay.gb"
+
+msgctxt "#30087"
+msgid "English"
+msgstr "Engelsk"
+
+msgctxt "#30088"
+msgid "Select profile"
+msgstr "Välj profil"
+
+msgctxt "#30089"
+msgid "Kids account"
+msgstr "Barnkonto"
+
+msgctxt "#30090"
+msgid "Signed in as"
+msgstr "Inloggad som"
+
+msgctxt "#30091"
+msgid "Content removed from list"
+msgstr "Innehåll borttaget från listan"
+
+msgctxt "#30092"
+msgid "Remove watched content"
+msgstr "Ta bort sett innehåll"
+
+msgctxt "#30093"
+msgid "Synchronize content"
+msgstr "Synkronisera innehåll"
\ No newline at end of file
diff --git a/resources/lib/addon.py b/resources/lib/addon.py
index b6a7a47..dd4f979 100644
--- a/resources/lib/addon.py
+++ b/resources/lib/addon.py
@@ -1,4 +1,4 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
"""
A Kodi add-on for Viaplay
"""
@@ -7,30 +7,349 @@
from resources.lib.kodihelper import KodiHelper
+try:
+ import urllib.request, urllib.parse, urllib.error
+ from urllib.parse import urlencode, quote_plus, quote, unquote, parse_qsl
+ import http.cookiejar as cookielib
+except ImportError:
+ import urllib
+ import urlparse
+ from urllib import urlencode, quote_plus, quote, unquote
+ from urlparse import parse_qsl
+ import cookielib
+
import xbmc
import xbmcgui
+import xbmcvfs
+import xbmcaddon
import routing
+import re
+import os
+
+import sqlite3
+
+import requests
+import json
+
+if sys.version_info[0] > 2:
+ PY3 = True
+else:
+ PY3 = False
base_url = sys.argv[0]
handle = int(sys.argv[1])
+params = dict(parse_qsl(sys.argv[2][1:]))
helper = KodiHelper(base_url, handle)
plugin = routing.Plugin()
+addon = xbmcaddon.Addon(id='plugin.video.viaplay')
+
+path = addon.getAddonInfo('path')
+resources = os.path.join(path, 'resources')
+icons = os.path.join(resources, 'icons')
+
+movie_icon = os.path.join(icons, 'movie.png')
+tv_icon = os.path.join(icons, 'tv.png')
+vod_icon = os.path.join(icons, 'vod.png')
+sport_icon = os.path.join(icons, 'sport.png')
+kids_icon = os.path.join(icons, 'kids.png')
+fav_icon = os.path.join(icons, 'fav.png')
+search_icon = os.path.join(icons, 'search.png')
+root_icon = os.path.join(icons, 'root.png')
+settings_icon = os.path.join(icons, 'settings.png')
+logout_icon = os.path.join(icons, 'logout.png')
+watched_icon = os.path.join(icons, 'watched.png')
+
+if PY3:
+ profile_path = xbmcvfs.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
+else:
+ profile_path = xbmc.translatePath(xbmcaddon.Addon().getAddonInfo('profile'))
+
+def get_sql_source():
+ kodi_version = xbmc.getInfoLabel('System.BuildVersion')[:2]
+
+ kodi_list = [('18', '116'), ('19', '119'), ('20', '121'), ('21', '121')]
+
+ for k in kodi_list:
+ if k[0] == kodi_version:
+ version = k[1]
+
+ SOURCE_DB = 'MyVideos{v}.db'.format(v=version)
+
+ path = xbmcvfs.translatePath("special://profile/")
+
+ database_path = os.path.join(path, 'Database', SOURCE_DB)
+
+ return database_path
+
+def sql_watched():
+ database_path = get_sql_source()
+
+ conn = sqlite3.connect(database_path, detect_types=sqlite3.PARSE_DECLTYPES, cached_statements=2000)
+ conn.row_factory = sqlite3.Row
+
+ c = conn.cursor()
+
+ watched_list = []
+
+ c.execute('SELECT idFile, strFilename, playcount, lastPlayed FROM files')
+
+ for row in c:
+ viaplay_str = row[str('strFilename')]
+ if 'plugin://plugin.video.viaplay' in viaplay_str:
+ id = row[str('idFile')]
+ playcount = row[str('playcount')]
+ lastplayed = row[str('lastPlayed')]
+
+ kv_pairs = viaplay_str.split("?")[1].split("&")
+ viaplay_dict = {kv.split("=")[0]: kv.split("=")[1] for kv in kv_pairs}
+
+ guid = viaplay_dict['guid'].split('-')[0]
+
+ watched_list.append((guid, playcount, lastplayed, id))
+
+ duration_list = []
+
+ c.execute('SELECT idFile, TimeInSeconds, TotalTimeInSeconds FROM bookmark')
+
+ for row in c:
+ id = row[str('idFile')]
+ time = row[str('TimeInSeconds')]
+ total = row[str('TotalTimeInSeconds')]
+
+ duration_list.append((time, total, id))
+
+ conn.close()
+
+ return watched_list, duration_list
+def sql_remove_watched(guid):
+ database_path = get_sql_source()
+
+ conn = sqlite3.connect(database_path, detect_types=sqlite3.PARSE_DECLTYPES, cached_statements=2000)
+ conn.row_factory = sqlite3.Row
+
+ c = conn.cursor()
+
+ c.execute('SELECT idFile, strFilename FROM files')
+
+ for row in c:
+ viaplay_str = row[str('strFilename')]
+ if 'plugin://plugin.video.viaplay' in viaplay_str:
+ kv_pairs = viaplay_str.split("?")[1].split("&")
+ viaplay_dict = {kv.split("=")[0]: kv.split("=")[1] for kv in kv_pairs}
+
+ guid_ = viaplay_dict['guid']
+
+ if guid.split('-')[0] == guid_.split('-')[0]:
+ id = row[str('idFile')]
+ c.execute('UPDATE files SET playCount=? WHERE idFile=?', [None, id])
+ c.execute('DELETE FROM bookmark WHERE idFile like ?', [id])
+ conn.commit()
+
+ conn.close()
def run():
+ mode = params.get('mode', None)
+ action = params.get('action', '')
+ gen = params.get('guid', '')
+
+ if action == 'BUILD_M3U':
+ generate_m3u()
+
+ elif action == 'remove_watched':
+ guid = sys.argv[3][5:]
+ watched(guid)
+
+ elif action == 'remove_watched_program':
+ guid = sys.argv[3][5:]
+ watched(guid, program=True)
+
+ elif action == 'favourite':
+ guid = sys.argv[3][5:]
+ favourite(guid)
+
+ elif action == 'favourite_program':
+ guid = sys.argv[3][5:]
+ favourite(guid, program=True)
+
+ elif action == 'remove_favourite':
+ guid = sys.argv[3][5:]
+ favourite(guid, remove=True)
+
+ elif action == 'remove_favourite_program':
+ guid = sys.argv[3][5:]
+ favourite(guid, program=True, remove=True)
+
+ elif gen != '':
+ id = params.get('url', '')
+ tve = params.get('tve', '')
+ guid = params.get('guid', '')
+ helper.play(url=id, tve=tve, guid=guid)
+
try:
plugin.run()
except helper.vp.ViaplayError as error:
- if error.value == b'MissingSessionCookieError':
+ missing_cookie = 'MissingSessionCookieError'
+
+ if error.value == missing_cookie:
if helper.authorize():
plugin.run()
else:
show_error(error.value)
+ except:
+ pass
+
+def watched(guid, program=False):
+ params = {
+ 'profileId': helper.vp.get_setting('profileid'),
+ }
+
+ if program:
+ if guid == 'no_guid':
+ message = helper.language(30072)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+ return
+
+ program_guid = guid
+
+ else:
+ guid = guid.split('-')[0]
+
+ program_guid = guid
+
+ if not guid[1:].isnumeric():
+ message = helper.language(30072)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+ return
+
+ url = helper.vp.base_url + '/deleteAllProgress/default/{0}/{1}'.format(program_guid, helper.vp.get_user_id()['id'])
+
+ response = helper.vp.make_request(url=url, method='post', params=params, status=True)
+
+ sql_remove_watched(program_guid)
+
+ xbmc.executebuiltin('Container.Refresh')
+
+ message = helper.language(30091)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+
+def favourite(guid, program=False, remove=False):
+ params = {
+ 'profileId': helper.vp.get_setting('profileid'),
+ }
+
+ if program:
+ if guid == 'no_guid':
+ message = helper.language(30072)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+ return
+
+ program_guid = guid
+
+ else:
+ guid = guid.split('-')[0]
+
+ program_guid = guid
+
+ if not guid[1:].isnumeric():
+ message = helper.language(30072)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+ return
+
+ if remove:
+ json_data = {
+ 'programGuid': program_guid,
+ 'action': 'remove',
+ }
+
+ response = helper.vp.make_request(url=helper.vp.base_url + '/myList', method='put', params=params, payload=json_data, status=True)
+
+ xbmc.executebuiltin('Container.Refresh')
+
+ message = helper.language(30091)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+
+ else:
+ json_data = {
+ 'programGuid': program_guid,
+ 'action': 'add',
+ }
+
+ response = helper.vp.make_request(url=helper.vp.base_url + '/myList', method='put', params=params, payload=json_data, status=True)
+
+ message = helper.language(30071)
+ helper.dialog(dialog_type='notification', heading=helper.language(30017), message=message)
+
+def generate_m3u():
+ sessionid = helper.authorize()
+ if not sessionid:
+ sessionid = helper.authorize()
+
+ file_name = helper.get_setting('fname')
+ path = helper.get_setting('path')
+
+ if file_name == '' or path == '':
+ xbmcgui.Dialog().notification('Viaplay', helper.language(30062),
+ xbmcgui.NOTIFICATION_ERROR)
+ return
+ xbmcgui.Dialog().notification('Viaplay', helper.language(30063), xbmcgui.NOTIFICATION_INFO)
+
+ data = '#EXTM3U\n'
+
+ country_id = helper.get_setting('site')
+ if country_id == '0':
+ chann = 'kanaler'
+ elif country_id == '1':
+ chann = 'kanaler'
+ elif country_id == '2':
+ chann = 'kanaler'
+ elif country_id == '3':
+ chann = 'channels'
+ elif country_id == '4':
+ chann = 'channels'
+
+ url = helper.vp.base_url + '/{0}'.format(chann)
+
+ response = helper.vp.make_request(url=url, method='get')
+ channels_block = response['_embedded']['viaplay:blocks'][0]['_embedded']['viaplay:blocks']
+ channels = [x['viaplay:channel']['content']['title'] for x in channels_block]
+ images = [x['viaplay:channel']['_embedded']['viaplay:products'][0]['station']['images']['fallbackImage']['template'] for x in channels_block]
+ guids = [x['viaplay:channel']['_embedded']['viaplay:products'][1]['epg']['channelGuids'][0] for x in channels_block]
+
+ for i in range(len(channels)):
+ image = images[i].split('{')[0]
+
+ img = re.compile(r'replace-(.*?)_.*\.png')
+
+ try:
+ title = img.search(image).group(1)
+ title = re.sub(r"(\w)([A-Z])", r"\1 \2", title)
+ title = title + ' ' + helper.get_country_code().upper()
+
+ except:
+ title = channels[i] + ' ' + helper.get_country_code().upper()
+
+ guid = guids[i]
+ data += '#EXTINF:-1 tvg-id="%s" tvg-name="%s" tvg-logo="%s" group-title="Viasat",%s\nplugin://plugin.video.viaplay/play?guid=%s&url=None&tve=true\n' % (guid, title, image, title, guid)
+
+ f = xbmcvfs.File(path + file_name, 'wb')
+ if sys.version_info[0] > 2:
+ f.write(data)
+ else:
+ f.write(bytearray(data, 'utf-8'))
+ f.close()
+ xbmcgui.Dialog().notification('Viaplay', helper.language(30064), xbmcgui.NOTIFICATION_INFO)
+
+@plugin.route('/profiles')
+def profiles():
+ helper.profiles_dialog()
+ xbmc.executebuiltin('Container.Refresh()')
@plugin.route('/')
def root():
pages = helper.vp.get_root_page()
+
supported_pages = {
'viaplay:root': start,
'viaplay:search': search,
@@ -47,15 +366,56 @@ def root():
'channels': channels
}
- for page in pages:
+ profiles_dict = helper.vp.get_profiles()
+ if profiles_dict:
+ for profile in profiles_dict:
+ if helper.vp.get_setting('profileid'):
+ id = helper.vp.get_setting('profileid')
+ else:
+ id = helper.vp.get_user_id()['id']
+
+ if id == profile['data'].get('id'):
+ name = '{0} {1}'.format(helper.language(30090), profile['data'].get('name'))
+ avatar = {'thumb': profile['embedded']['avatar']['data'].get('url')}
+ helper.add_item(name, plugin.url_for(profiles), art=avatar)
+
+ sorted_json = sorted(pages, key=lambda x: x['name'] == 'viaplay:logout')
+
+ for page in sorted_json:
+ page['title'] = capitalize(page['title'])
+
if page['name'] in supported_pages:
- helper.add_item(page['title'], plugin.url_for(supported_pages[page['name']], url=page['href']))
+ art = None
+ if page['name'] == 'viaplay:root':
+ art = {'icon': root_icon}
+ elif page['name'] == 'viaplay:search':
+ art = {'icon': search_icon}
+ elif page['name'] == 'viaplay:logout':
+ page['title'] = helper.language(30042)
+ art = {'icon': logout_icon}
+ elif page['name'] == 'viaplay:starred':
+ page['title'] = helper.language(30077)
+ art = {'icon': fav_icon}
+ elif page['name'] == 'viaplay:watched':
+ art = {'icon': watched_icon}
+ elif page['name'] == 'series':
+ art = {'icon': vod_icon}
+ elif page['name'] == 'movie':
+ art = {'icon': movie_icon}
+ elif page['name'] == 'kids':
+ art = {'icon': kids_icon}
+ elif page['name'] == 'sport':
+ art = {'icon': sport_icon}
+ elif page['name'] == 'channels':
+ art = {'icon': tv_icon}
+
+ helper.add_item(page['title'], plugin.url_for(supported_pages[page['name']], url=page['href']), art=art)
elif 'type' in page and page['type'] in supported_pages: # weird channels listing fix on some subscriptions
helper.add_item(page['title'], plugin.url_for(supported_pages[page['type']], url=page['href']))
else:
helper.log('Unsupported page found: %s' % page['name'])
- helper.eod()
+ helper.eod()
@plugin.route('/start')
def start():
@@ -69,9 +429,74 @@ def start():
@plugin.route('/search')
def search():
- query = helper.get_user_input(helper.language(30015))
- if query:
- list_products(plugin.args['url'][0], search_query=query)
+ pages = helper.vp.get_root_page()
+ title = [x['title'] for x in pages if x['name'] == 'viaplay:search'][0]
+
+ file_search = os.path.join(profile_path, 'last_search.list')
+
+ if xbmc.getInfoLabel('ListItem.Label') == title:
+ file_name = os.path.join(profile_path, 'title_search.list')
+ f = xbmcvfs.File(file_name, 'rb')
+ searches = sorted(f.read().splitlines())
+ f.close()
+
+ actions = [helper.language(30079), helper.language(30080)] + searches
+
+ action = helper.dialog(dialog_type='select', heading=helper.language(30081), options=actions)
+ title = None
+
+ if action == -1:
+ return
+ elif action == 0:
+ pass
+ elif action == 1:
+ which = helper.dialog(dialog_type='multiselect', heading=helper.language(30080), options=searches)
+ if which is None:
+ return
+ else:
+ for item in reversed(which):
+ del searches[item]
+
+ f = xbmcvfs.File(file_name, 'wb')
+ if sys.version_info[0] < 3:
+ searches = [x.decode('utf-8') for x in searches]
+ f.write(bytearray('\n'.join(searches), 'utf-8'))
+ f.close()
+ return
+ else:
+ if searches:
+ title = searches[action - 2]
+
+ if action == 0:
+ search = helper.get_user_input(helper.language(30015))
+
+ else:
+ if sys.version_info[0] > 2:
+ search = title
+ else:
+ search = title.encode('utf-8')
+
+ if not search:
+ return
+ searches = (set([search] + searches))
+ f = xbmcvfs.File(file_name, 'wb')
+ if sys.version_info[0] < 3:
+ searches = [x.decode('utf-8') for x in searches]
+ f.write(bytearray('\n'.join(searches), 'utf-8'))
+ f.close()
+
+ if search != '':
+ f = xbmcvfs.File(file_search, 'w')
+ f.write(search)
+ f.close()
+
+ list_products(plugin.args['url'][0], search_query=search)
+ else:
+ f = xbmcvfs.File(file_search, 'r')
+ search = f.read().splitlines()
+ f.close()
+
+ list_products(plugin.args['url'][0], search_query=search)
@plugin.route('/vod')
@@ -79,10 +504,57 @@ def vod():
"""List categories and collections from the VOD pages (movies, series, kids, store)."""
helper.add_item(helper.language(30041), plugin.url_for(categories, url=plugin.args['url'][0]))
collections = helper.vp.get_collections(plugin.args['url'][0])
+
for i in collections:
- helper.add_item(i['title'], plugin.url_for(list_products, url=i['_links']['self']['href']))
- helper.eod()
+ if i['type'] == 'list-featurebox': # skip feature box for now
+ continue
+ if i['title'] == '':
+ i = None
+
+ try:
+ helper.add_item(i['title'], plugin.url_for(list_products, url=i['_links']['self']['href']))
+ except:
+ pass
+
+ """
+ add_lst = []
+
+ for i in collections:
+ if 'a6-01' in i['id'] or 'a6-00' in i['id']:
+ add_lst.append(i['_links']['self']['href'])
+ add = False
+
+ if add_lst:
+ ordered_lst = ""
+
+ for url in add_lst:
+ ordered_lst += url
+
+ helper.add_item('Seriale', plugin.url_for(list_products, url=ordered_lst))
+
+
+ for i in collections:
+ add = True
+
+ if i['type'] == 'list-featurebox': # skip feature box for now
+ continue
+
+ if i['title'] == '':
+ for x in i['_embedded']['viaplay:products']:
+ if x['type'] != 'series':
+ title = x['content']['title']
+ url = x['_links']['self']['href']
+ helper.add_item(title, plugin.url_for(list_products, url=url))
+ add = False
+ else:
+ add = False
+
+ if add:
+ helper.add_item(i['title'], plugin.url_for(list_products, url=i['_links']['self']['href']))
+ """
+
+ helper.eod()
@plugin.route('/sport')
def sport():
@@ -95,8 +567,9 @@ def sport():
helper.add_item(i['_links']['viaplay:seeTableau']['title'], plugin_url)
schedule_added = True
- if i['totalProductCount'] < 1:
- continue # hide empty collections
+ if i.get('totalProductCount'):
+ if i.get('totalProductCount', 0) < 1:
+ continue # hide empty collections
helper.add_item(i['title'], plugin.url_for(list_products, url=i['_links']['self']['href']))
helper.eod()
@@ -116,15 +589,24 @@ def channels():
'fanart': channel_image
}
- for program in channel['_embedded']['viaplay:products']: # get current live program
- if helper.vp.get_event_status(program) == 'live':
- if 'content' in program:
- current_program_title = coloring(program['content']['title'], 'live')
- else: # no broadcast
- current_program_title = coloring(helper.language(30049), 'no_broadcast')
- break
+ current_program_title = coloring(helper.language(30049), 'no_broadcast')
+
+ for index, program in enumerate(channel['_embedded']['viaplay:products']): # get current live program
+ if index > 0:
+ if helper.vp.get_event_status(program) in ['live', 'archive', 'upcoming']:
+ if program.get('content'):
+ current_program_title = coloring(program['content']['title'], 'live')
+ else: # no broadcast
+ current_program_title = coloring(helper.language(30049), 'no_broadcast')
+ break
- list_title = '[B]{0}[/B]: {1}'.format(channel['content']['title'], current_program_title)
+ regex = fr'\(SimplyTV\)\s*|\b{helper.get_country_code().upper()}\b\s*'
+ title = re.sub(regex, '', channel['content']['title'])
+
+ if sys.version_info[0] > 2:
+ list_title = '[B]{0}[/B]: {1}'.format(title, current_program_title)
+ else:
+ list_title = '[B]{0}[/B]: {1}'.format(title, current_program_title.encode('utf-8'))
helper.add_item(list_title, plugin_url, art=art)
@@ -135,25 +617,31 @@ def channels():
@plugin.route('/log_out')
def log_out():
- helper.log_out()
+ confirm = helper.dialog('yesno', helper.language(30042), helper.language(30043))
+ if confirm:
+ helper.vp.log_out()
@plugin.route('/list_products')
def list_products(url=None, search_query=None):
- if not url:
+ if not url or url is None:
url = plugin.args['url'][0]
products_dict = helper.vp.get_products(url, search_query=search_query)
for product in products_dict['products']:
if product['type'] == 'series':
- add_series(product)
+ add_series(product, url)
elif product['type'] == 'episode':
- add_episode(product)
+ add_episode(product, url)
elif product['type'] == 'movie':
- add_movie(product)
+ add_movie(product, url)
elif product['type'] == 'sport':
- add_sports_event(product)
+ add_sports_event(product, url)
+ elif product['type'] == 'sportSeries':
+ add_sports_series(product, url)
elif product['type'] == 'tvEvent':
- add_tv_event(product)
+ add_tv_event(product, url)
+ elif product['type'] == 'clip':
+ add_event(product, url)
else:
helper.log('product type: {0} is not (yet) supported.'.format(product['type']))
return False
@@ -171,6 +659,16 @@ def sports_schedule():
helper.eod()
+@plugin.route('/sport_series')
+def sport_series():
+ categories = helper.vp.get_sport_series(plugin.args['url'][0])
+ for category in categories:
+ if category['content'].get('title'):
+ if category['_links'].get('self'):
+ helper.add_item(category['content']['title'], plugin.url_for(list_products, url=category['_links']['self']['href']))
+ helper.eod()
+
+
@plugin.route('/seasons_page')
def seasons_page():
"""List all series seasons."""
@@ -202,8 +700,11 @@ def sortings():
@plugin.route('/play')
def play():
- helper.play(guid=plugin.args['guid'][0], url=plugin.args['url'][0], tve=plugin.args['tve'][0])
+ sessionid = helper.authorize()
+ if not sessionid:
+ sessionid = helper.authorize()
+ helper.play(guid=plugin.args['guid'][0], url=plugin.args['url'][0], tve=plugin.args['tve'][0])
@plugin.route('/dialog')
def dialog():
@@ -216,22 +717,31 @@ def dialog():
def ia_settings():
helper.ia_settings()
+def capitalize(string):
+ return string[0].upper()+string[1:]
-def add_movie(movie):
+def add_movie(movie, site):
+ #print('Category: add_movie')
if movie['system'].get('guid'):
- guid = movie['system']['guid']
url = None
+ guid = movie['system']['guid']
else:
guid = None
url = movie['_links']['self']['href']
plugin_url = plugin.url_for(play, guid=guid, url=url, tve='false')
+
details = movie['content']
+ try:
+ plotx = details.get('synopsis')
+ except:
+ plotx = ''
+
movie_info = {
'mediatype': 'movie',
'title': details['title'],
- 'plot': details.get('synopsis'),
+ 'plot': plotx,
'genre': ', '.join([x['title'] for x in movie['_links']['viaplay:genres']]),
'year': details['production'].get('year'),
'duration': int(details['duration'].get('milliseconds')) // 1000 if 'duration' in details else None,
@@ -243,14 +753,32 @@ def add_movie(movie):
'code': details['imdb'].get('id') if 'imdb' in details else None
}
- helper.add_item(movie_info['title'], plugin_url, info=movie_info, art=add_art(details['images'], 'movie'),
- content='movies', playable=True)
+ watched_list, duration_list = sql_watched()
+ properties = []
-def add_series(show):
+ for w in watched_list:
+ if w[0] == guid:
+ movie_info.update({'playcount': w[1], 'lastplayed': w[2]})
+
+ for d in duration_list:
+ if d[2] == w[3]:
+ properties.append((d[0], d[1]))
+
+ helper.add_item(movie_info['title'], plugin_url, info=movie_info, art=add_art(details['images'], 'movie'),
+ site=site, content='movies', playable=True, properties=properties, context=True)
+
+def add_series(show, site):
+ #print('Category: add_series')
plugin_url = plugin.url_for(seasons_page, url=show['_links']['viaplay:page']['href'])
+
details = show['content']
+ if show['system'].get('guid'):
+ guid = show['system']['guid']
+ else:
+ guid = None
+
series_info = {
'mediatype': 'tvshow',
'title': details['series']['title'],
@@ -268,17 +796,24 @@ def add_series(show):
}
helper.add_item(series_info['title'], plugin_url, folder=True, info=series_info,
- art=add_art(details['images'], 'series'), content='tvshows')
+ art=add_art(details['images'], 'series'), site=site, content='tvshows', context=True)
-def add_episode(episode):
+def add_episode(episode, site):
+ #print('Category: add_episode')
plugin_url = plugin.url_for(play, guid=episode['system']['guid'], url=None, tve='false')
+
details = episode['content']
+ if episode['system'].get('guid'):
+ guid = episode['system']['guid']
+ else:
+ guid = None
+
episode_info = {
'mediatype': 'episode',
- 'title': details.get('title'),
- 'list_title': details['series']['episodeTitle'] if details['series'].get('episodeTitle') else details.get(
+ 'originaltitle': details.get('title'),
+ 'title': details['series']['episodeTitle'] if details['series'].get('episodeTitle') else details.get(
'title'),
'tvshowtitle': details['series'].get('title'),
'plot': details['synopsis'] if details.get('synopsis') else details['series'].get('synopsis'),
@@ -293,13 +828,26 @@ def add_episode(episode):
'code': details['imdb'].get('id') if 'imdb' in details else None,
'season': int(details['series']['season'].get('seasonNumber')),
'episode': int(details['series'].get('episodeNumber'))
- }
+ }
+
+ watched_list, duration_list = sql_watched()
- helper.add_item(episode_info['list_title'], plugin_url, info=episode_info,
- art=add_art(details['images'], 'episode'), content='episodes', playable=True)
+ properties = []
+ for w in watched_list:
+ if w[0] == guid:
+ episode_info.update({'playcount': w[1], 'lastplayed': w[2]})
-def add_sports_event(event):
+ for d in duration_list:
+ if d[2] == w[3]:
+ properties.append((d[0], d[1]))
+
+ helper.add_item(episode_info['title'], plugin_url, info=episode_info,
+ art=add_art(details['images'], 'episode'), site=site, content='episodes', playable=True, episode=True, properties=properties, context=True)
+
+
+def add_sports_event(event, site):
+ #print('Category: add_sports_event')
now = datetime.now()
date_today = now.date()
event_date = helper.vp.parse_datetime(event['epg']['start'], localize=True)
@@ -316,61 +864,207 @@ def add_sports_event(event):
else:
plugin_url = plugin.url_for(dialog, dialog_type='ok',
heading=helper.language(30017),
- message=helper.language(30016).format(start_time))
+ message=helper.language(30016).format(start_time).encode('utf-8'))
playable = False
details = event['content']
+
+ if event['system'].get('guid'):
+ guid = event['system']['guid']
+ else:
+ guid = None
+
+ if sys.version_info[0] > 2:
+ title = details.get('title')
+ else:
+ title = details.get('title').encode('utf-8')
+ try:
+ plotx = details.get('synopsis')
+ except:
+ plotx = ''
+
event_info = {
'mediatype': 'video',
- 'title': details.get('title'),
- 'plot': details['synopsis'],
+ 'originaltitle': details.get('title'),
+ 'plot': plotx,
'year': int(details['production'].get('year')),
'genre': details['format'].get('title'),
- 'list_title': '[B]{0}:[/B] {1}'.format(coloring(start_time, event_status), details.get('title'))
+ 'title': '[B]{0}:[/B] {1}'.format(coloring(start_time, event_status), title)
}
- helper.add_item(event_info['list_title'], plugin_url, playable=playable, info=event_info,
- art=add_art(details['images'], 'sport'), content='episodes')
+ watched_list, duration_list = sql_watched()
+
+ properties = []
+
+ for w in watched_list:
+ if w[0] == guid:
+ event_info.update({'playcount': w[1], 'lastplayed': w[2]})
+
+ for d in duration_list:
+ if d[2] == w[3]:
+ properties.append((d[0], d[1]))
+
+ helper.add_item(event_info['title'], plugin_url, playable=playable, info=event_info,
+ art=add_art(details['images'], 'sport'), sys_guid=event['system']['guid'], site=site, content='episodes', properties=properties, context=True)
-def add_tv_event(event):
+def add_sports_series(event, site):
+ #print('Category: add_sports_series')
now = datetime.now()
date_today = now.date()
- start_time_obj = helper.vp.parse_datetime(event['epg']['startTime'], localize=True)
+ if event.get('epg'):
+ event_date = helper.vp.parse_datetime(event['epg']['start'], localize=True)
+ else:
+ event_date = helper.vp.parse_datetime(event['system']['availability']['start'], localize=True)
event_status = helper.vp.get_event_status(event)
- # hide non-available catchup items
- if now > helper.vp.parse_datetime(event['system']['catchupAvailability']['end'], localize=True):
- return
- if date_today == start_time_obj.date():
- start_time = '{0} {1}'.format(helper.language(30027), start_time_obj.strftime('%H:%M'))
+ if date_today == event_date.date():
+ start_time = '{0} {1}'.format(helper.language(30027), event_date.strftime('%H:%M'))
else:
- start_time = start_time_obj.strftime('%Y-%m-%d %H:%M')
+ start_time = event_date.strftime('%Y-%m-%d %H:%M')
+
+ event_url = event['_links']['viaplay:page']['href']
if event_status != 'upcoming':
- plugin_url = plugin.url_for(play, guid=event['system']['guid'] + '-%s' % helper.get_country_code().upper(), url=None, tve='true')
- playable = True
+ plugin_url = plugin.url_for(sport_series, url=event_url)
+ playable = False
else:
plugin_url = plugin.url_for(dialog, dialog_type='ok',
heading=helper.language(30017),
- message=helper.language(30016).format(start_time))
+ message=helper.language(30016).format(start_time).encode('utf-8'))
playable = False
details = event['content']
+
+ if sys.version_info[0] > 2:
+ if details.get('title'):
+ title = details.get('title')
+ else:
+ title = details.get('series', {}).get('title')
+ else:
+ if details.get('title'):
+ title = details.get('title').encode('utf-8')
+ else:
+ title = details.get('series', {}).get('title').encode('utf-8')
+ try:
+ plotx = details.get('synopsis')
+ except:
+ plotx = ''
+
+ if details.get('format'):
+ genre = details.get('format').get('title')
+ else:
+ genre = ''
+
event_info = {
'mediatype': 'video',
- 'title': details.get('title'),
+ 'originaltitle': title,
+ 'plot': plotx,
+ 'year': details['production'].get('year'),
+ 'genre': genre,
+ 'title': '[B]{0}:[/B] {1}'.format(coloring(start_time, event_status), title)
+ }
+
+ helper.add_item(event_info['title'], plugin_url, playable=playable, info=event_info,
+ art=add_art(details['images'], 'sport'), sys_guid=event['system']['guid'], site=site, content='episodes', context=True)
+
+
+def add_tv_event(event, site):
+ #print('Category: add_tv_event')
+ now = datetime.now()
+ date_today = now.date()
+
+ start_time_obj = helper.vp.parse_datetime(event['epg']['startTime'], localize=True)
+ end_time_obj = helper.vp.parse_datetime(event['epg']['endTime'], localize=True)
+
+ event_status = helper.vp.get_event_status(event)
+
+ status = False
+
+ if end_time_obj >= now and helper.get_setting('previous_channels'):
+ status = True
+ elif not helper.get_setting('previous_channels'):
+ status = True
+
+ if status:
+ # hide non-available catchup items
+ start_time = str(datetime.now())[:-16]
+ if now > helper.vp.parse_datetime(event['system']['catchupAvailability']['end'], localize=True):
+ return
+
+ if date_today == start_time_obj.date():
+ start_time = '{0} {1}'.format(helper.language(30027), start_time_obj.strftime('%H:%M'))
+ else:
+ start_time = start_time_obj.strftime('%Y-%m-%d %H:%M')
+
+ if event_status != 'upcoming':
+ plugin_url = plugin.url_for(play, guid=event['system']['guid'] + '-%s' % helper.get_country_code().upper(), url=None, tve='true')
+ playable = True
+ else:
+ plugin_url = plugin.url_for(dialog, dialog_type='ok',
+ heading=helper.language(30017),
+ message=helper.language(30016).format(start_time).encode('utf-8'))
+ playable = False
+
+ details = event['content']
+
+ if sys.version_info[0] > 2:
+ title = details.get('title')
+ else:
+ title = details.get('title').encode('utf-8')
+
+ event_info = {
+ 'mediatype': 'video',
+ 'originaltitle': details.get('title'),
+ 'plot': details.get('synopsis'),
+ 'year': details['production'].get('year'),
+ 'title': '[B]{0}:[/B] {1}'.format(coloring(start_time, event_status), title)
+ }
+
+ art = {
+ 'thumb': event['content']['images']['landscape']['template'].split('{')[0] if 'landscape' in details['images'] else None,
+ 'fanart': event['content']['images']['landscape']['template'].split('{')[0] if 'landscape' in details['images'] else None
+ }
+
+ helper.add_item(event_info['title'], plugin_url, playable=playable, info=event_info, art=art, sys_guid=event['system']['guid'], site=site, content='episodes', context=True)
+
+def add_event(event, site):
+ #print('Category: add_event')
+ plugin_url = plugin.url_for(play, guid=event['system']['guid'], url=None, tve='false')
+
+ details = event['content']
+
+ if sys.version_info[0] > 2:
+ title = details.get('title')
+ else:
+ title = details.get('title').encode('utf-8')
+
+ event_info = {
+ 'mediatype': 'episode',
+ 'originaltitle': details.get('title'),
'plot': details.get('synopsis'),
'year': details['production'].get('year'),
- 'list_title': '[B]{0}:[/B] {1}'.format(coloring(start_time, event_status), details.get('title'))
+ 'title': '{0}'.format(title),
}
+
art = {
'thumb': event['content']['images']['landscape']['template'].split('{')[0] if 'landscape' in details['images'] else None,
'fanart': event['content']['images']['landscape']['template'].split('{')[0] if 'landscape' in details['images'] else None
}
- helper.add_item(event_info['list_title'], plugin_url, playable=playable, info=event_info, art=art, content='episodes')
+ watched_list, duration_list = sql_watched()
+
+ properties = []
+
+ for w in watched_list:
+ if w[0] == event['system']['guid']:
+ event_info.update({'playcount': w[1], 'lastplayed': w[2]})
+ for d in duration_list:
+ if d[2] == w[3]:
+ properties.append((d[0], d[1]))
+
+ helper.add_item(event_info['title'], plugin_url, playable=True, info=event_info, art=art, site=site, content='episodes', properties=properties, context=True)
def add_art(images, content_type):
artwork = {}
@@ -411,15 +1105,18 @@ def coloring(text, meaning):
def show_error(error):
- if error == b'UserNotAuthorizedForContentError':
+ if error == 'UserNotAuthorizedForContentError':
message = helper.language(30020)
- elif error == b'PurchaseConfirmationRequiredError':
+ elif error == 'PurchaseConfirmationRequiredError':
message = helper.language(30021)
- elif error == b'UserNotAuthorizedRegionBlockedError':
+ elif error == 'UserNotAuthorizedRegionBlockedError':
message = helper.language(30022)
- elif error == b'ConcurrentStreamsLimitReachedError':
+ elif error == 'ConcurrentStreamsLimitReachedError':
message = helper.language(30050)
+ elif error == 'PersistentLoginError':
+ message = error
else:
message = error
- helper.dialog(dialog_type='ok', heading=helper.language(30017), message=message)
+
+ helper.dialog(dialog_type='ok', heading=helper.language(30017), message=message)
\ No newline at end of file
diff --git a/resources/lib/kodihelper.py b/resources/lib/kodihelper.py
index b983b06..da0fc06 100644
--- a/resources/lib/kodihelper.py
+++ b/resources/lib/kodihelper.py
@@ -1,4 +1,10 @@
-from .viaplay import Viaplay
+import urllib
+import sys
+
+if sys.version_info[0] > 2:
+ from .viaplay import Viaplay
+else:
+ from viaplay import Viaplay
import xbmc
import xbmcvfs
@@ -7,14 +13,23 @@
import inputstreamhelper
from xbmcaddon import Addon
+try:
+ from urllib.parse import unquote
+except ImportError:
+ import urllib
+ from urllib import unquote
class KodiHelper(object):
def __init__(self, base_url=None, handle=None):
addon = self.get_addon()
self.base_url = base_url
self.handle = handle
- self.addon_path = xbmcvfs.translatePath(addon.getAddonInfo('path'))
- self.addon_profile = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
+ if sys.version_info[0] > 2:
+ self.addon_path = xbmcvfs.translatePath(addon.getAddonInfo('path'))
+ self.addon_profile = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
+ else:
+ self.addon_path = xbmc.translatePath(addon.getAddonInfo('path'))
+ self.addon_profile = xbmc.translatePath(addon.getAddonInfo('profile'))
self.addon_name = addon.getAddonInfo('id')
self.addon_version = addon.getAddonInfo('version')
self.language = addon.getLocalizedString
@@ -55,77 +70,158 @@ def get_country_code(self):
country_code = 'dk'
elif country_id == '2':
country_code = 'no'
- else:
+ elif country_id == '3':
country_code = 'fi'
+ elif country_id == '4':
+ country_code = 'pl'
+ elif country_id == '5':
+ country_code = 'lt'
+ elif country_id == '6':
+ country_code = 'nl'
+ elif country_id == '7':
+ country_code = 'ee'
+ elif country_id == '8':
+ country_code = 'lv'
+ elif country_id == '9':
+ country_code = 'gb'
+
+ return country_code
+ def get_tld(self):
+ country_code = self.get_country_code()
+ if country_code == 'nl':
+ return 'com'
+ elif country_code == 'gb':
+ return 'com'
return country_code
- def dialog(self, dialog_type, heading, message=None, options=None, nolabel=None, yeslabel=None):
+ def dialog(self, dialog_type, heading, message=None, options=None, nolabel=None, yeslabel=None, useDetails=False):
dialog = xbmcgui.Dialog()
if dialog_type == 'ok':
dialog.ok(heading, message)
elif dialog_type == 'yesno':
return dialog.yesno(heading, message, nolabel=nolabel, yeslabel=yeslabel)
elif dialog_type == 'select':
- ret = dialog.select(heading, options)
+ ret = dialog.select(heading, options, useDetails=useDetails)
if ret > -1:
return ret
else:
return None
+ elif dialog_type == 'multiselect':
+ ret = dialog.multiselect(heading, options)
+ if ret:
+ return ret
+ else:
+ return None
+ elif dialog_type == 'notification':
+ dialog.notification(heading, message)
+
+ def ensure_profile(self):
+ if not self.vp.get_user_id():
+ self.vp.validate_session()
+ if not self.vp.get_setting('profile_id'):
+ self.profiles_dialog()
+
+ def profiles_dialog(self):
+ profiles = self.vp.get_profiles()
+
+ options = []
+ ids = []
+
+ for profile in profiles:
+ profile_type = profile['data'].get('type')
+ if profile_type != 'adult':
+ profile_type = self.language(30089)
+ else:
+ profile_type = ''
+
+ listitem = xbmcgui.ListItem(
+ label=profile['data'].get('name'),
+ label2=profile_type
+ )
+
+ listitem.setArt({
+ 'thumb': profile['embedded']['avatar']['data'].get('url'),
+ })
+
+ options.append(listitem)
+ ids.append(profile['data'].get('id'))
+
+ idx = self.dialog('select', self.language(30088), options=options, useDetails=True)
+
+ if idx >= 0:
+ self.set_setting('profileid', ids[idx])
def log_out(self):
confirm = self.dialog('yesno', self.language(30042), self.language(30043))
if confirm:
self.vp.log_out()
- # send Kodi back to home screen
- xbmc.executebuiltin("Action(Back,%s)" % xbmcgui.getCurrentWindowId())
- def authorize(self):
- try:
- self.vp.validate_session()
- return True
- except self.vp.ViaplayError as error:
- if not error.value == b'PersistentLoginError' or error.value == b'MissingSessionCookieError':
- raise
- else:
- return self.device_registration()
+ def authorize(self, autologin=False):
+ if xbmc.getCondVisibility('!Window.IsVisible(Home)') or autologin:
+ try:
+ self.vp.validate_session()
+ return True
+ except self.vp.ViaplayError as error:
+ cookie_error = 'MissingSessionCookieError'
+ login_error = 'PersistentLoginError'
+
+ if not error.value == login_error or error.value == cookie_error:
+ raise
+ else:
+ return self.device_registration()
def device_registration(self):
"""Presents a dialog with information on how to activate the device.
Attempts to authorize the device using the interval returned by the activation data."""
activation_data = self.vp.get_activation_data()
- message = self.language(30039).format(activation_data['verificationUrl'], activation_data['userCode'])
- dialog = xbmcgui.DialogProgress()
- xbmc.sleep(200) # small delay to prevent DialogProgress from hanging
- dialog.create(self.language(30040), message)
- secs = 0
- expires = activation_data['expires']
-
- while not xbmc.Monitor().abortRequested() and secs < expires:
- try:
- self.vp.authorize_device(activation_data)
- dialog.close()
+
+ if self.vp.get_setting('viaplay_username') != '' and self.vp.get_setting('viaplay_password') != '':
+ login = self.vp.login()
+ if login:
return True
- except self.vp.ViaplayError as error:
- # raise all non-pending authorization errors
- if error.value == b'DeviceAuthorizationPendingError':
- secs += activation_data['interval']
- percent = int(100 * float(secs) / float(expires))
- dialog.update(percent, message)
- xbmc.Monitor().waitForAbort(activation_data['interval'])
- if dialog.iscanceled():
+ else:
+ message = self.language(30075)
+ self.dialog(dialog_type='notification', heading=self.language(30076), message=message)
+ return False
+
+ else:
+ message = self.language(30039).format(activation_data['verificationUrl'], activation_data['userCode'])
+ dialog = xbmcgui.DialogProgress()
+ xbmc.sleep(200) # small delay to prevent DialogProgress from hanging
+ dialog.create(self.language(30040), message)
+
+ secs = 0
+ expires = activation_data['expires']
+
+ while not xbmc.Monitor().abortRequested() and secs < expires:
+ try:
+ self.vp.authorize_device(activation_data)
+ dialog.close()
+ return True
+ except self.vp.ViaplayError as error:
+ # raise all non-pending authorization errors
+ auth_error = 'DeviceAuthorizationPendingError'
+ dev_error = 'DeviceAuthorizationNotFound'
+
+ if error.value == auth_error:
+ secs += activation_data['interval']
+ percent = int(100 * float(secs) / float(expires))
+ dialog.update(percent, message)
+ xbmc.Monitor().waitForAbort(activation_data['interval'])
+ if dialog.iscanceled():
+ dialog.close()
+ return False
+ elif error.value == dev_error: # time expired
dialog.close()
+ self.dialog('ok', self.language(30051), self.language(30052))
return False
- elif error.value == b'DeviceAuthorizationNotFound': # time expired
- dialog.close()
- self.dialog('ok', self.language(30051), self.language(30052))
- return False
- else:
- dialog.close()
- raise
+ else:
+ dialog.close()
+ raise
- dialog.close()
- return False
+ dialog.close()
+ return False
def get_user_input(self, heading, hidden=False):
keyboard = xbmc.Keyboard('', heading, hidden)
@@ -150,14 +246,67 @@ def get_numeric_input(self, heading):
else:
return None
- def add_item(self, title, url, folder=True, playable=False, info=None, art=None, content=False):
+ def add_item(self, title, url, sys_guid=None, folder=True, playable=False, info=None, art=None, site=None, content=False, episode=False, properties=None, context=False):
addon = self.get_addon()
+
+ if info:
+ title = info.get('title')
+ else:
+ info = {'title': title}
+
listitem = xbmcgui.ListItem(label=title)
+ if properties:
+ listitem.setProperty('ResumeTime', str(int(properties[0][0])))
+ listitem.setProperty('TotalTime', str(int(properties[0][1])))
+
+ if context:
+ kv_pairs = url.split("?")[1].split("&")
+ viaplay_dict = {kv.split("=")[0]: kv.split("=")[1] for kv in kv_pairs}
+
+ guid = viaplay_dict.get('guid')
+ program_guid = None
+ if not guid:
+ if viaplay_dict.get('url'):
+ program_guid = unquote(viaplay_dict.get('url')).split('/byguid/')[1]
+ elif viaplay_dict.get('message'):
+ guid = sys_guid
+ else:
+ program_guid = 'no_guid'
+
+ if site.split('?')[0] == 'https://content.viaplay.{0}/xdk-{0}/watched'.format(self.vp.country):
+ txt = self.language(30092)
+
+ if program_guid:
+ context_menu = [('{0}'.format(txt), 'RunScript(plugin.video.viaplay,-1,?action=remove_watched_program,guid={0})'.format(program_guid))]
+ else:
+ context_menu = [('{0}'.format(txt), 'RunScript(plugin.video.viaplay,-1,?action=remove_watched,guid={0})'.format(guid))]
+
+ elif site.split('?')[0] == 'https://content.viaplay.{0}/xdk-{0}/starred'.format(self.vp.country):
+ txt = self.language(30078)
+
+ if program_guid:
+ context_menu = [('{0}'.format(txt), 'RunScript(plugin.video.viaplay,-1,?action=remove_favourite_program,guid={0})'.format(program_guid))]
+ else:
+ context_menu = [('{0}'.format(txt), 'RunScript(plugin.video.viaplay,-1,?action=remove_favourite,guid={0})'.format(guid))]
+
+ else:
+ txt = self.language(30070)
+ if program_guid:
+ context_menu = [('{0}'.format(txt), 'RunScript(plugin.video.viaplay,-1,?action=favourite_program,guid={0})'.format(program_guid))]
+ else:
+ context_menu = [('{0}'.format(txt), 'RunScript(plugin.video.viaplay,-1,?action=favourite,guid={0})'.format(guid))]
+
+ listitem.addContextMenuItems(context_menu, replaceItems=True)
+
if playable:
listitem.setProperty('IsPlayable', 'true')
folder = False
+ else:
+ listitem.setProperty('IsPlayable', 'false')
+
if art:
+ art.update({'fanart': addon.getAddonInfo('fanart')})
listitem.setArt(art)
else:
art = {
@@ -165,27 +314,45 @@ def add_item(self, title, url, folder=True, playable=False, info=None, art=None,
'fanart': addon.getAddonInfo('fanart')
}
listitem.setArt(art)
+
if info:
- listitem.setInfo('video', info)
+ listitem.setInfo('Video', info)
+
if content:
xbmcplugin.setContent(self.handle, content)
xbmcplugin.addDirectoryItem(self.handle, url, listitem, folder)
+ if episode:
+ xbmcplugin.addSortMethod(handle=self.handle, sortMethod=xbmcplugin.SORT_METHOD_EPISODE)
+
def eod(self):
"""Tell Kodi that the end of the directory listing is reached."""
- xbmcplugin.endOfDirectory(self.handle)
+ xbmcplugin.endOfDirectory(self.handle, cacheToDisc=False)
def play(self, guid=None, url=None, pincode=None, tve='false'):
if url and url != 'None':
guid = self.vp.get_products(url)['products'][0]['system']['guid']
+
try:
stream = self.vp.get_stream(guid, pincode=pincode, tve=tve)
+
except self.vp.ViaplayError as error:
- if error.value == b'MissingSessionCookieError':
+ if error.value == 'MissingVideoError':
+ message = 'Content is missing'
+ self.dialog(dialog_type='notification', heading=self.language(30017), message=message)
+ return
+
+ elif error.value == 'AnonymousProxyError':
+ message = 'This content is not available via an anonymous proxy'
+ self.dialog(dialog_type='notification', heading=self.language(30017), message=message)
+ return
+
+ elif error.value == 'ParentalGuidancePinChallengeNeededError':
self.authorize()
return
- if error.value == b'ParentalGuidancePinChallengeNeededError':
+
+ if error.value == 'ParentalGuidancePinChallengeNeededError':
if pincode:
self.dialog(dialog_type='ok', heading=self.language(30033), message=self.language(30034))
else:
@@ -201,13 +368,15 @@ def play(self, guid=None, url=None, pincode=None, tve='false'):
playitem = xbmcgui.ListItem(path=stream['mpd_url'])
playitem.setContentLookup(False)
playitem.setMimeType('application/xml+dash') # prevents HEAD request that causes 404 error
- playitem.setProperty('inputstream', 'inputstream.adaptive')
+ if sys.version_info[0] > 2:
+ playitem.setProperty('inputstream', 'inputstream.adaptive')
+ else:
+ playitem.setProperty('inputstreamaddon', 'inputstream.adaptive')
playitem.setProperty('inputstream.adaptive.manifest_type', 'mpd')
playitem.setProperty('inputstream.adaptive.manifest_update_parameter', 'full')
playitem.setProperty('inputstream.adaptive.license_type', 'com.widevine.alpha')
- playitem.setProperty('inputstream.adaptive.license_key',
- stream['license_url'].replace('{widevineChallenge}', 'B{SSM}') + '|||JBlicense')
- if 'subtitles' in stream:
+ playitem.setProperty('inputstream.adaptive.license_key',stream['license_url'].replace('{widevineChallenge}', 'B{SSM}') + '|||JBlicense')
+ if self.get_setting('subtitles') and 'subtitles' in stream:
playitem.setSubtitles(self.vp.download_subtitles(stream['subtitles']))
xbmcplugin.setResolvedUrl(self.handle, True, listitem=playitem)
diff --git a/resources/lib/viaplay.py b/resources/lib/viaplay.py
index 9da0eea..5780d5b 100644
--- a/resources/lib/viaplay.py
+++ b/resources/lib/viaplay.py
@@ -1,65 +1,180 @@
-# -*- coding: utf-8 -*-
+# -*- coding: utf-8 -*-
"""
A Kodi-agnostic library for Viaplay
"""
+import sys
import os
-import http.cookiejar as cookielib
+
+if sys.version_info[0] > 2:
+ import http.cookiejar as cookielib
+ import html
+else:
+ import cookielib
+ import HTMLParser
+
import calendar
import re
import json
import uuid
-import html.parser as HTMLParser
from collections import OrderedDict
from datetime import datetime, timedelta
import iso8601
import requests
-
+import xbmc
+import xbmcvfs
+import xbmcgui
+import xbmcplugin
+from xbmcaddon import Addon
class Viaplay(object):
+
+ class ViaplayError(Exception):
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
def __init__(self, settings_folder, country, debug=False):
+ addon = self.get_addon()
self.debug = debug
self.country = country
+ self.tld = self.get_tld_for(country)
self.settings_folder = settings_folder
+ if sys.version_info[0] > 2:
+ self.addon_path = xbmcvfs.translatePath(addon.getAddonInfo('path'))
+ self.addon_profile = xbmcvfs.translatePath(addon.getAddonInfo('profile'))
+ else:
+ self.addon_path = xbmc.translatePath(addon.getAddonInfo('path'))
+ self.addon_profile = xbmc.translatePath(addon.getAddonInfo('profile'))
self.cookie_jar = cookielib.LWPCookieJar(os.path.join(self.settings_folder, 'cookie_file'))
+ #self.replace_cookies = self.replace_cookies() ### workaround to switch country sites
self.tempdir = os.path.join(settings_folder, 'tmp')
if not os.path.exists(self.tempdir):
os.makedirs(self.tempdir)
self.deviceid_file = os.path.join(settings_folder, 'deviceId')
self.http_session = requests.Session()
- self.device_key = 'xdk-%s' % self.country
- self.base_url = 'https://content.viaplay.{0}/{1}'.format(self.country, self.device_key)
- self.login_api = 'https://login.viaplay.%s/api' % self.country
+ self.device_key = 'xdk-{0}'.format(self.country)
+ self.profile_url = 'https://viaplay.mtg-api.com'
+ self.base_url = 'https://content.viaplay.{0}/{1}'.format(self.tld, self.device_key)
+ self.cronos_url = 'https://cronos-events.viaplay.{0}'.format(self.tld)
+ self.socket_url = 'https://socket.viaplay.{0}'.format(self.tld)
+ self.play_api = 'https://play.viaplay.{0}/api'.format(self.tld)
+ self.play_live_api = 'https://play-live.viaplay.{0}/api'.format(self.tld)
+ self.login_api = 'https://login.viaplay.{0}/api'.format(self.tld)
+
try:
self.cookie_jar.load(ignore_discard=True, ignore_expires=True)
except IOError:
pass
self.http_session.cookies = self.cookie_jar
- class ViaplayError(Exception):
- def __init__(self, value):
- self.value = value
+ def get_addon(self):
+ """Returns a fresh addon instance."""
+ return Addon()
- def __str__(self):
- return repr(self.value)
+ def get_setting(self, setting_id):
+ addon = self.get_addon()
+ setting = addon.getSetting(setting_id)
+ if setting == 'true':
+ return True
+ elif setting == 'false':
+ return False
+ else:
+ return setting
+
+ def get_country_code(self):
+ country_id = self.get_setting('site')
+ if country_id == '0':
+ country_code = 'se'
+ elif country_id == '1':
+ country_code = 'dk'
+ elif country_id == '2':
+ country_code = 'no'
+ elif country_id == '3':
+ country_code = 'fi'
+ elif country_id == '4':
+ country_code = 'pl'
+ elif country_id == '5':
+ country_code = 'lt'
+ elif country_id == '6':
+ country_code = 'nl'
+ elif country_id == '7':
+ country_code = 'ee'
+ elif country_id == '8':
+ country_code = 'lv'
+ elif country_id == '9':
+ country_code = 'gb'
+
+ return country_code
+
+ def get_tld(self):
+ return self.get_tld_for(self.get_country_code())
+
+ def get_tld_for(self, country_code):
+ if country_code == 'nl':
+ return 'com'
+ elif country_code == 'gb':
+ return 'com'
+ return country_code
+
+ def replace_cookies(self):
+ cookie_file = os.path.join(self.addon_profile, 'cookie_file')
+ f = open(cookie_file, 'r')
+ cookies = f.read()
+
+ tld = self.get_tld()
+
+ pattern = re.compile(r'viaplay.(\w{2})', re.IGNORECASE)
+ n_tld = pattern.search(cookies).group(1)
+
+ if n_tld != tld:
+ cookies = re.sub('viaplay.{cc}'.format(cc=n_tld), 'viaplay.{cc}'.format(cc=tld), cookies)
+ w = open(cookie_file, 'w')
+ w.write(cookies)
+ w.close()
def log(self, string):
if self.debug:
- print('[Viaplay]: %s' % string)
+ try:
+ print('[Viaplay]: %s' % string)
+ except UnicodeEncodeError:
+ # we can't anticipate everything in unicode they might throw at
+ # us, but we can handle a simple BOM
+ bom = unicode(codecs.BOM_UTF8, 'utf8')
+ print('[Viaplay]: %s' % string.replace(bom, ''))
+ except:
+ pass
def parse_url(self, url):
"""Sometimes, Viaplay adds some weird templated stuff to the URL
we need to get rid of. Example: https://content.viaplay.se/androiddash-se/serier{?dtg}"""
template = r'\{.+?\}'
- result = re.search(template, url)
+ result = re.search(template, str(url))
if result:
self.log('Unparsed URL: {0}'.format(url))
url = re.sub(template, '', url)
return url
- def make_request(self, url, method, params=None, payload=None, headers=None):
+ def make_request(self, url, method, params=None, payload=None, headers=None, status=False):
"""Make an HTTP request. Return the response."""
+ if not params:
+ params = {}
+
+ id = self.get_setting('profileid')
+ if id:
+ params['profileId'] = id
+
+ try:
+ return self._make_request(url, method, params=params, payload=payload, headers=headers)
+ except self.ViaplayError:
+ self.validate_session()
+ return self._make_request(url, method, params=params, payload=payload, headers=headers)
+
+ def _make_request(self, url, method, params=None, payload=None, headers=None, status=False):
+ """Helper. Make an HTTP request. Return the response."""
url = self.parse_url(url)
self.log('Request URL: %s' % url)
self.log('Method: %s' % method)
@@ -80,19 +195,75 @@ def make_request(self, url, method, params=None, payload=None, headers=None):
self.log('Response: %s' % req.content)
self.cookie_jar.save(ignore_discard=True, ignore_expires=False)
- return self.parse_response(req.content)
+ if status:
+ return self.parse_response(req.status_code)
+ else:
+ return self.parse_response(req.content)
+
+ def get_user_id(self):
+ url = self.login_api + '/persistentLogin/v1'
+ params = {
+ 'deviceKey': self.device_key
+ }
+ data = self.make_request(url=url, method='get', params=params)
+
+ return {'id': data['userData']['userId'], 'token': data['userData']['accessToken']}
+
+ def get_profiles(self):
+ url = self.profile_url + '/profiles/users/{0}/profiles/'.format(self.get_user_id()['id'])
+
+ headers = {
+ 'authorization': 'MTG-AT {0}'.format(self.get_user_id()['token']),
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36 Edg/112.0.1722.48'
+ }
+
+ params = {
+ 'language': self.get_country_code()
+ }
+
+ data = self.make_request(url=url, method='get', params=params, headers=headers)
+
+ profiles = None
+
+ if data:
+ em = data.get('embedded')
+ if em:
+ profiles = em.get('profiles')
+
+ return profiles
def parse_response(self, response):
"""Try to load JSON data into dict and raise potential errors."""
try:
- response = json.loads(response, object_pairs_hook=OrderedDict) # keep the key order
+ response = json.loads(response)#, object_pairs_hook=OrderedDict) # keep the key order
if 'success' in response and not response['success']: # raise ViaplayError when 'success' is False
- raise self.ViaplayError(response['name'].encode('utf-8'))
+ raise self.ViaplayError(response['name'])
+
except ValueError: # if response is not json
pass
return response
+ def login(self):
+ try:
+ url = self.login_api + '/login/v1'
+
+ params = {
+ 'deviceKey': self.device_key,
+ 'username': self.get_setting('viaplay_username'),
+ 'persistent': 'true',
+ }
+
+ data = {
+ 'password': self.get_setting('viaplay_password'),
+ }
+
+ response = self.make_request(url=url, method='post', params=params, payload=data)
+ self.validate_session() # we need this to validate the new cookies
+ return response
+ except:
+ return False
+
def get_activation_data(self):
"""Get activation data (reg code etc) needed to authorize the device."""
url = self.login_api + '/device/code'
@@ -112,7 +283,7 @@ def authorize_device(self, activation_data):
'userCode': activation_data['userCode']
}
- self.make_request(url=url, method='get', params=params)
+ self._make_request(url=url, method='get', params=params)
self.validate_session() # we need this to validate the new cookies
return True
@@ -122,7 +293,7 @@ def validate_session(self):
params = {
'deviceKey': self.device_key
}
- self.make_request(url=url, method='get', params=params)
+ self._make_request(url=url, method='get', params=params)
return True
def log_out(self):
@@ -131,27 +302,107 @@ def log_out(self):
params = {
'deviceKey': self.device_key
}
- self.make_request(url=url, method='get', params=params)
- return True
- def get_stream(self, guid, pincode=None, tve='false'):
+ res = self.make_request(url=url, method='get', params=params)
+ if res:
+ cookie_file = os.path.join(self.settings_folder, 'cookie_file')
+ if os.path.exists(cookie_file):
+ os.remove(cookie_file)
+
+ xbmc.executebuiltin('Container.Update')
+
+ xbmc.executebuiltin("Dialog.Close(all, true)")
+ xbmc.executebuiltin("ActivateWindow(Home)")
+
+
+ def get_stream(self, guid, pincode=None, tve='false', url=''):
"""Return a dict with the stream URL:s and available subtitle URL:s."""
stream = {}
- url = 'https://play.viaplay.%s/api/stream/bymediaguid' % self.country
+
+ country_code = self.get_country_code()
+ tld = self.get_tld()
+
+ if 'ch-' in guid:
+ url = 'https://epg.viaplay.{c1}/xdk-{c2}/channel/{guid}/'.format(c1=tld, c2=country_code,guid=guid)
+ response = self.make_request(url=url, method='get')['_embedded']['viaplay:products']
+
+ for i in response:
+ start_time_obj = self.parse_datetime(i['epg']['startTime'], localize=True)
+ end_time_obj = self.parse_datetime(i['epg']['endTime'], localize=True)
+
+ now = datetime.now()
+
+ if start_time_obj <= now <= end_time_obj:
+ guid = i['system']['guid'] + '-' + country_code.upper()
+ url = self.play_live_api + '/stream/bymediaguid'
+
+ elif self.tld.upper() in guid:
+ guid = guid
+ url = self.play_live_api + '/stream/bymediaguid'
+
+ elif 'GB' in guid:
+ guid = guid
+ url = self.play_live_api + '/stream/bymediaguid'
+
+ elif 'NL' in guid:
+ guid = guid
+ url = self.play_live_api + '/stream/bymediaguid'
+
+ else:
+ guid = guid
+ url = self.play_api + '/stream/bymediaguid'
+
params = {
'deviceId': self.get_deviceid(),
'deviceName': 'web',
'deviceType': 'pc',
'userAgent': 'Kodi',
- 'deviceKey': 'chromecast-%s' % self.country,
+ 'deviceKey': 'chromecast-{0}'.format(country_code),
+ #'guid': guid
'mediaGuid': guid
}
+
if pincode:
params['pgPin'] = pincode
if tve == 'true':
params['isTve'] = tve
data = self.make_request(url=url, method='get', params=params)
+
+ correlation_id = data['cseReporting']['data']['correlationId']
+
+ url = self.cronos_url + '/cronos-events/session/viaplay/xdk/5.54.1'
+
+ response = self.make_request(url=url, method='get', status=True)
+
+ url = data['cseReporting']['link']['reportingUrl']['href']
+
+ params = {
+ 'profileId': self.get_setting('profileid'),
+ }
+
+ for a in data['cseReporting']['data']['reportAction']:
+ if a['key'] == 'unload':
+ action = a['value']
+
+ json_data = {
+ 'bitrate': 10000,
+ 'clientVersion': '1.1.1-657ada7f80f',
+ 'deltaTime': data['duration'],
+ 'correlationId': correlation_id,
+ 'sequenceNumber': 1,
+ 'actionType': action,
+ 'consentData': {
+ 'approved': ['functional', 'targeted', 'performance'],
+ 'rejected': ['personalization']
+ },
+ 'duration': data['duration'],
+ 'position': 0
+ }
+
+ if self.get_setting('synchronize'):
+ response = self.make_request(url=url, method='post', payload=json.dumps(json_data), params=params, status=True)
+
if 'viaplay:media' in data['_links']:
mpd_url = data['_links']['viaplay:media']['href']
elif 'viaplay:fallbackMedia' in data['_links']:
@@ -164,11 +415,16 @@ def get_stream(self, guid, pincode=None, tve='false'):
self.log('Failed to retrieve stream URL.')
return False
+ subs_list = []
+
stream['mpd_url'] = mpd_url
stream['license_url'] = data['_links']['viaplay:license']['href']
stream['release_pid'] = data['_links']['viaplay:license']['releasePid']
if 'viaplay:sami' in data['_links']:
- stream['subtitles'] = data['_links']['viaplay:sami']
+ #stream['subtitles'] = [x['href'] for x in data['_links']['viaplay:sami']]
+ for subs in data['_links']['viaplay:sami']:
+ subs_list.append(subs['href'])
+ stream['subtitles'] = subs_list
return stream
@@ -178,20 +434,22 @@ def get_root_page(self):
pages = []
blacklist = ['byGuid']
data = self.make_request(url=self.base_url, method='get')
+
if 'user' not in data:
- raise self.ViaplayError(b'MissingSessionCookieError') # raise error if user is not logged in
+ raise self.ViaplayError('MissingSessionCookieError') # raise error if user is not logged in
for link in data['_links']:
if isinstance(data['_links'][link], dict):
# sort out _links that doesn't contain a title
if 'title' in data['_links'][link]:
title = data['_links'][link]['title']
+
data['_links'][link]['name'] = link # add name key to dict
- if not title.islower() and title not in blacklist:
+ if title not in blacklist:
pages.append(data['_links'][link])
else: # list (viaplay:sections for example)
for i in data['_links'][link]:
- if 'title' in i and not i['title'].islower():
+ if 'title' in i:
pages.append(i)
return pages
@@ -200,15 +458,23 @@ def get_collections(self, url):
"""Return all available collections."""
data = self.make_request(url=url, method='get')
# return all blocks (collections) with 'list' in type
+
return [x for x in data['_embedded']['viaplay:blocks'] if 'list' in x['type'].lower()]
def get_products(self, url, filter_event=False, search_query=None):
"""Return a dict containing the products and next page if available."""
if search_query:
+ headers = {
+ 'accept': '*/*',
+ 'accept-language': 'sv,en;q=0.9,en-GB;q=0.8,en-US;q=0.7',
+ 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/110.0.0.0 Safari/537.36 Edg/110.0.1587.41',
+ }
params = {'query': search_query}
else:
+ headers = None
params = None
- data = self.make_request(url, method='get', params=params)
+
+ data = self.make_request(url, method='get', params=params, headers=headers)
if 'list' in data['type'].lower():
products = data['_embedded']['viaplay:products']
@@ -222,6 +488,7 @@ def get_products(self, url, filter_event=False, search_query=None):
# try to collect all products found in viaplay:blocks
products = [p for x in data['_embedded']['viaplay:blocks'] if 'viaplay:products' in x['_embedded'] for p in x['_embedded']['viaplay:products']]
+
if filter_event:
# filter out and only return products with event_status in filter_event
products = [x for x in products if x['event_status'] in filter_event]
@@ -249,15 +516,53 @@ def get_seasons(self, url):
data = self.make_request(url=url, method='get')
return [x for x in data['_embedded']['viaplay:blocks'] if x['type'] == 'season-list']
+ def get_sport_series(self, url):
+ """Return all available sport series."""
+ data = self.make_request(url=url, method='get')
+ return [p for x in data['_embedded']['viaplay:blocks'] if 'viaplay:products' in x['_embedded'] for p in x['_embedded']['viaplay:products']]
+
def download_subtitles(self, suburls):
"""Download the SAMI subtitles, decode the HTML entities and save to temp directory.
Return a list of the path to the downloaded subtitles."""
paths = []
- for sub_data in suburls:
- sami = self.make_request(url=sub_data['href'], method='get').decode('utf-8', 'ignore').strip()
- htmlparser = HTMLParser.HTMLParser()
- subtitle = htmlparser.unescape(sami).encode('utf-8')
- path = os.path.join(self.tempdir, '{0}.sami'.format(sub_data['languageCode']))
+ lookup_table_replace = {}
+
+ for url in suburls:
+ lang_pattern = re.search(r'[_]([a-z]+)', str(url))
+ if lang_pattern:
+ sub_lang = lang_pattern.group(1)
+ else:
+ sub_lang = 'unknown'
+ self.log('Failed to identify subtitle language.')
+
+ sami = self.make_request(url=url, method='get').decode('utf-8', 'ignore').strip()
+
+ try:
+ if sys.version_info[0] < 3:
+ if sub_lang == 'pl':
+ lookup_table_replace = {
+ 'ą': 'ą', 'Ą': 'Ą',
+ 'ć': 'ć', 'Ć': 'Ć',
+ 'ę': 'ę', 'Ę': 'Ę',
+ 'ł': 'ł', 'Ł': 'Ł',
+ 'ń': 'ń', 'Ń': 'Ń',
+ 'ś': 'ś', 'Ś': 'Ś',
+ 'ź': 'ź', 'Ź': 'Ź',
+ 'ż': 'ż', 'Ż': 'Ż'
+ }
+
+ for k, v in lookup_table_replace.items():
+ sami = sami.replace(k, v.decode('utf-8'))
+ except:
+ pass
+
+ if sys.version_info[0] < 3:
+ html = HTMLParser.HTMLParser()
+ else:
+ import html
+
+ subtitle = html.unescape(sami).encode('utf-8')
+ path = os.path.join(self.tempdir, '{0}.sami'.format(sub_lang))
with open(path, 'wb') as subfile:
subfile.write(subtitle)
paths.append(path)
@@ -278,12 +583,21 @@ def get_deviceid(self):
def get_event_status(self, data):
"""Return whether the event/program is live/upcoming/archive."""
now = datetime.utcnow()
- if 'startTime' in data['epg']:
- start_time = data['epg']['startTime']
- end_time = data['epg']['endTime']
- else:
- start_time = data['epg']['start']
- end_time = data['epg']['end']
+ try:
+ if data.get('epg'):
+ if data['epg'].get('startTime'):
+ start_time = data['epg']['startTime']
+ end_time = data['epg']['endTime']
+ else:
+ start_time = data['epg']['start']
+ end_time = data['epg']['end']
+ else:
+ start_time = data['system']['availability']['start']
+ end_time = data['system']['availability']['end']
+ except:
+ start_time = str(datetime.now())
+ end_time = str(datetime.now())
+
start_time_obj = self.parse_datetime(start_time).replace(tzinfo=None)
end_time_obj = self.parse_datetime(end_time).replace(tzinfo=None)
@@ -307,6 +621,7 @@ def get_next_page(self, data):
break
elif data['type'] == 'product':
data = data['_embedded']['viaplay:product']
+
if 'next' in data['_links']:
next_page_url = data['_links']['next']['href']
return next_page_url
diff --git a/resources/settings.xml b/resources/settings.xml
index 41ff44b..329c007 100644
--- a/resources/settings.xml
+++ b/resources/settings.xml
@@ -1,8 +1,117 @@
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+ 0
+ 0
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Select
+
+
+
+ 0
+
+
+ true
+
+
+ 30073
+
+
+
+ 0
+
+
+ true
+
+
+ 30074
+ true
+
+
+
+ 0
+ false
+
+
+
+ 0
+ true
+
+
+
+ 0
+ true
+
+
+
+ 0
+ false
+
+
+
+ 0
+ false
+ false
+
+
+
+
+
+
+
+ 0
+ viaplay_iptv.m3u
+
+ 30059
+
+
+
+
+
+ true
+ true
+
+
+ 30060
+
+
+
+ 0
+ RunPlugin(plugin://plugin.video.viaplay?action=BUILD_M3U)
+
+ true
+
+
+
+
+
+
+
+ 0
+ RunScript(special://home/addons/plugin.video.teliaplay/ia_settings.py,settings)
+
+ true
+
+
+
+
+
+
+
\ No newline at end of file