diff --git a/.github/workflows/make-release.yml b/.github/workflows/make-release.yml index 621405513..78d0518b0 100644 --- a/.github/workflows/make-release.yml +++ b/.github/workflows/make-release.yml @@ -40,11 +40,12 @@ jobs: - name: Get Changelog id: changelog run: | - readarray -t changes < <(xmlstarlet sel -t -v '//news' -n addon.xml) + readarray -t changes < <(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt) echo "body<<${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT for change in "${changes[@]}"; do echo "${change}" >> $GITHUB_OUTPUT done + news=$(printf '%s\n' "${changes[@]}" | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') echo "${{ secrets.GITHUB_TOKEN }}" >> $GITHUB_OUTPUT working-directory: ${{ github.event.repository.name }} @@ -54,6 +55,8 @@ jobs: mv .git .. rm -rf .??* rm *.md + news=$(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') + xmlstarlet ed -L -P -s '/addon/extension[@point="xbmc.addon.metadata"]' -t elem -n news -v "${news:0:1500}" addon.xml version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml) filename=${{ github.event.repository.name }}-${version}.zip cd .. @@ -75,8 +78,10 @@ jobs: mv .git .. rm -rf .??* rm *.md + news=$(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') + xmlstarlet ed -L -P -s '/addon/extension[@point="xbmc.addon.metadata"]' -t elem -n news -v "${news:0:1500}" addon.xml version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml) - xmlstarlet ed -L -u '/addon/@version' -v "${version}+unofficial.1" addon.xml + xmlstarlet ed -L -P -u '/addon/@version' -v "${version}+unofficial.1" addon.xml filename=${{ github.event.repository.name }}-${version}.unofficial.1.zip cd .. zip -r $filename ${{ github.event.repository.name }} @@ -93,11 +98,13 @@ jobs: mv .git .. rm -rf .??* rm *.md + news=$(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') + xmlstarlet ed -L -P -s '/addon/extension[@point="xbmc.addon.metadata"]' -t elem -n news -v "${news:0:1500}" addon.xml version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml) - xmlstarlet ed -L -u '/addon/@version' -v "${version}+matrix.1" addon.xml - xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml - xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml - xmlstarlet ed -L -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml + xmlstarlet ed -L -P -u '/addon/@version' -v "${version}+matrix.1" addon.xml + xmlstarlet ed -L -P -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml + xmlstarlet ed -L -P -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml + xmlstarlet ed -L -P -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml filename=${{ github.event.repository.name }}-${version}.matrix.1.zip cd .. zip -r $filename ${{ github.event.repository.name }} @@ -118,11 +125,13 @@ jobs: mv .git .. rm -rf .??* rm *.md + news=$(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') + xmlstarlet ed -L -P -s '/addon/extension[@point="xbmc.addon.metadata"]' -t elem -n news -v "${news:0:1500}" addon.xml version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml) - xmlstarlet ed -L -u '/addon/@version' -v "${version}+matrix.unofficial.1" addon.xml - xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml - xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml - xmlstarlet ed -L -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml + xmlstarlet ed -L -P -u '/addon/@version' -v "${version}+matrix.unofficial.1" addon.xml + xmlstarlet ed -L -P -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml + xmlstarlet ed -L -P -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml + xmlstarlet ed -L -P -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml filename=${{ github.event.repository.name }}-${version}.matrix.unofficial.1.zip cd .. zip -r $filename ${{ github.event.repository.name }} diff --git a/.github/workflows/submit-release.yml b/.github/workflows/submit-release.yml index 5b3574ec3..14185b4b8 100644 --- a/.github/workflows/submit-release.yml +++ b/.github/workflows/submit-release.yml @@ -52,6 +52,10 @@ jobs: rm *.md git add . git commit -m "Remove Unwanted Files" + news=$(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') + xmlstarlet ed -L -P -s '/addon/extension[@point="xbmc.addon.metadata"]' -t elem -n news -v "${news:0:1500}" addon.xml + git add . + git commit -m "Update news" working-directory: ${{ github.event.repository.name }} - name: Submit to Official Repository (Nexus) @@ -74,11 +78,17 @@ jobs: rm -rf .??* mv ../.git . rm *.md + git add . + git commit -m "Remove Unwanted Files" + news=$(awk '/^## /{rel_num++} {if(rel_num==2){exit} if(rel_num==1){print}}' changelog.txt | sed -E 's/ ?#[[:digit:]]+[., ]?//;s/\r//') + xmlstarlet ed -L -P -s '/addon/extension[@point="xbmc.addon.metadata"]' -t elem -n news -v "${news:0:1500}" addon.xml + git add . + git commit -m "Update news" version=$(xmlstarlet sel -t -v 'string(/addon/@version)' addon.xml) - xmlstarlet ed -L -u '/addon/@version' -v "${version}+matrix.1" addon.xml - xmlstarlet ed -L -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml - xmlstarlet ed -L -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml - xmlstarlet ed -L -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml + xmlstarlet ed -L -P -u '/addon/@version' -v "${version}+matrix.1" addon.xml + xmlstarlet ed -L -P -u '/addon/requires/import[@addon="xbmc.python"]/@version' -v '3.0.0' addon.xml + xmlstarlet ed -L -P -u '/addon/requires/import[@addon="inputstream.adaptive"]/@version' -v '19.0.0' addon.xml + xmlstarlet ed -L -P -d '/addon/requires/import[@addon="script.module.infotagger"]' addon.xml git add . git commit -m "Kodi 19 Patch" working-directory: ${{ github.event.repository.name }} diff --git a/addon.xml b/addon.xml index 29f59368b..29f3ec62d 100644 --- a/addon.xml +++ b/addon.xml @@ -1,5 +1,5 @@ - + @@ -12,44 +12,10 @@ - + executable - -### New -- Add display of extra video information (premieres, views, comments, likes) #18, #464, #503 -- Add support for Clips #450 -- Add ability to combine playlists #480 -- Add support for timestamps in links #502 -- Add initial support for higher bitrate streams #505 -- Add ability to limit video FPS at max resolution #539 -- Add local Watch Later and History for use when not logged in or custom playlist not set -- Update main menu items: - - New Recommended videos (similar to YouTube home page, will use login if available) - - Old Recommended videos renamed to Related videos (requires local/remote history enabled) - - Popular right now renamed to Trending - -### Changed -- Local history made optional and enabled by default -- Existing user data will be lost due to changes in data format: - - Search, local history, and local watch later is stored per user - - Function and data cache will be wiped (will also become per user in future) -- Disable OPUS audio by default #537 - -### Fixed -- Fix sharing links #115, #250, #538 -- Fix date and sorting issues #411, #425, #434 -- Fix issue with switching between H264/AV1 streams #532 -- Fix prompt for subtitles #534 -- Fix issues with corrupt user data #536 -- Fix issues with live streams #530, #540 -- Fix issues with loading large playlists #545 -- Fix Recommendations, Related Videos, and Auto-play next #508 -- Fix queuing from current playlist #549 -- Fix issues with randomising playlists #485 -- Workaround for crashes #113, #540 - resources/media/icon.png resources/media/fanart.jpg diff --git a/changelog.txt b/changelog.txt index 377d25903..19626a5da 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,11 +1,62 @@ -7.0.2.2 +## v7.0.3+beta.2 +### Changed +- Function and data cache are now created per user +- Video channel no longer used as studio infolabel +- Since v7.0.3+beta.1 setting values are logged to the debug log + - Now sensitive details (location, API key, etc.) are masked +- Deprecated plugin actions removed +- Changed caching of playlists + - First page of playlists are only cached for 5 minutes + - Following pages are cached for 1 hour + +### Fixed +- Fix error when opening My Subscriptions after login #555 +- Fix directory items being treated as short videos #556 +- Fix error with Setup Wizard not loading #557 +- Refresh context menu item will now actually refresh listings #558 + +## v7.0.3+beta.1 +### New +- Add display of extra video information (premieres, views, comments, likes) #18, #464, #503 +- Add support for Clips #450 +- Add ability to combine playlists #480 +- Add support for timestamps in links #502 +- Add initial support for higher bitrate streams #505 +- Add ability to limit video FPS at max resolution #539 +- Add local Watch Later and History for use when not logged in or custom playlist not set +- Update main menu items: + - New Recommended videos (similar to YouTube home page, will use login if available) + - Old Recommended videos renamed to Related videos (requires local/remote history enabled) + - Popular right now renamed to Trending + +### Changed +- Local history made optional and enabled by default +- Existing user data will be lost due to changes in data format: + - Search, local history, and local watch later is stored per user + - Function and data cache will be wiped (will also become per user in future) +- Disable OPUS audio by default #537 + +### Fixed +- Fix sharing links #115, #250, #538 +- Fix date and sorting issues #411, #425, #434 +- Fix issues with randomising playlists #485 +- Fix Recommendations, Related Videos, and Auto-play next #508 +- Fix issue with switching between H264/AV1 streams #532 +- Fix issues with live streams #530, #540 +- Fix prompt for subtitles #534 +- Fix issues with corrupt user data #536 +- Fix issues with loading large playlists #545 +- Fix queuing from current playlist #549 +- Workaround for crashes #113, #540 + +## v7.0.2.2 [fix] Fix Matrix compatibility [fix] Update unofficial patch -7.0.2.1 +## v7.0.2.1 [fix] fix Matrix InputStream.Adaptive dependency version -7.0.2 +## v7.0.2 [new] quality and stream feature selection [new] enable HLS live streams [new] stream and language labelling @@ -16,32 +67,32 @@ [fix] fix handling of connection failures [upd] Translations updated from Kodi Weblate -7.0.1 +## v7.0.1 [fix] video duration not showing in lists [upd] Translations updated from Kodi Weblate -7.0.0 +## v7.0.0 [rem] removed support for versions of Kodi pre-Nexus v20 [rem] removed support for python 2 [rem] removed dependency on six [upd] Translations updated from Kodi Weblate -6.8.25 +## v6.8.25 [fix] Nexus compatibility with InfoTagVideo |contrib: jurialmunkey| [chg] make httpd /api url case insensitive due to skin choice of uppercase settings text [chg] use listitem property for Inputstream Adaptive headers instead of url piped on Nexus+ [upd] Translations updated from Kodi Weblate -6.8.24 +## v6.8.24 [fixup] resolve playback history not being updated [add] add opus audio support [upd] use more descript reasons for failed playback [upd] Translations updated from Kodi Weblate -6.8.23 +## v6.8.23 [revert] subscription groups/tags - causing breaking issues -6.8.21 +## v6.8.21 [chg] Icons and Fanart updated |contrib: papercore-dev| [add] Add setting General - Hide short videos (1 minute or less) [add] Add support for /shorts/video_id format links |contrib: jmbreuer| @@ -51,75 +102,75 @@ [fix] Misc missing fanart [lang] translation updates from Weblate -6.8.20 +## v6.8.20 [fix] Fix to_str |contrib: RNavega| [fix] Fix playback buffering |contrib: RNavega| [fix] Subtitle language names [lang] translation updates from Weblate -6.8.19 +## v6.8.19 [fix] /uri2addon/ endpoint for playback of youtube.com urls [fix] Don't assume addon is installed in home directory |contrib: jfly| [lang] translation updates from Weblate -6.8.18 +## v6.8.18 [fix] Fix to_str |contrib: RNavega| [fix] Fix playback buffering |contrib: RNavega| [lang] translation updates from Weblate -6.8.17 +## v6.8.17 [fix] Fix comments on Kodi 19+ |contrib: ramateur| [fix] Fix ratebypass [upd] Update icon and fanart |contrib: papercore-dev| [lang] translation updates from Weblate -6.8.16 +## v6.8.16 [fix] Fix playback of some content (ie. Music videos) [fix] Calcuate n parameter to enable ratebypass -6.8.15 +## v6.8.15 [fixup] No streams found, consent cookies |contrib: jaylinski| [fixup] Age gate detection, show age gate error instead of 'No streams found' [fixup] encoding of some titles/authors - ie. russian [lang] translation updates from Weblate -6.8.14 +## v6.8.14 [fixup] playback of some streams due to encoding issues [fixup] default player response type -6.8.13 +## v6.8.13 [fix] player config and client discovery [fix] No Streams Found |contrib: 14mRh4X0r| [fix] capability map to contain unversioned capabilities |contrib: jmbreuer| [lang] zh_tw strings |contrib: JuenTingShie| -6.8.11 +## v6.8.11 [fix] mktime overflow when loading My Subscriptions [fix] encoding issues in search history |contrib: dreznichuk| -6.8.10 +## v6.8.10 [fix] fix javascript player retrieval and playback of some videos for affected users |contrib: thomazz-nl| - Invalid URL 'http://': No host supplied -6.8.9 +## v6.8.9 [fix] my subscriptions section using YouTube RSS |contrib: PureHemp| [fix] items per page settings on Kodi 19 [chg] recommendations only available for users with custom history playlist id configured [chg] no longer show watch later unless custom watch later playlist is configured [rem] remove usage of discontinued api and dual login (no longer required) -6.8.8 +## v6.8.8 [fixup] encoding of subtitle track names -6.8.7 +## v6.8.7 [fixup] encoding of titles and authors during playback -6.8.6 +## v6.8.6 [fix] some videos failing to play with an Invalid URL exception [fix] searching in some instances with encoding issues |contrib: tsaklidis| [fix] support python 3.9 html.unescape |contrib: asavah| -6.8.5 +## v6.8.5 [fix] playback of purchased movies |contrib: MisterD81| [fix] notifications for some languages on Kodi 19 [fix] searching for some languages @@ -127,14 +178,14 @@ [fix] some videos failing to play with an Invalid URL exception [upd] use xbmcvfs.translatePath if available, clean up for Kodi 19 -6.8.4 +## v6.8.4 [fix] next page now ignores sorting, remains at the end of the directory [fix] searching, no longer require remote safe search [fix] notifications for some languages [chg] only log http server ping failures [lang] zh_cn strings |contrib: yureng, xiaojun| -6.8.3 +## v6.8.3 [fix] playback of some videos [fix] notifications for some languages |contrib: Mutronics| [fix] fix rating videos causing and error even though it succeeded @@ -143,10 +194,10 @@ [lang] el_gr strings |contrib: twilight0| [lang] hu_hu strings |contrib: thelacesz| -6.8.2 +## v6.8.2 [fixup] recommendations with > 50 channels -6.8.1 +## v6.8.1 [fix] playback failing [fix] viewing comments when logged in [fix] intermittent issue with Subscriptions @@ -157,7 +208,7 @@ [lang] pl_pl strings |contrib: drrak| [lang] nl_nl strings |contrib: Markman-B| -6.8.0 +## v6.8.0 [add] support for video comments, Comments in the More... dialog |contrib: doko-desuka| [add] simulate deprecated recommended home API |contrib: hedleyroos| [fix] error caused by timestamp formatting @@ -168,7 +219,7 @@ [lang] es_es strings |contrib: roliverosc| [lang] pt_pt strings |contrib: daniel3x| -6.7.0 +## v6.7.0 [add] send PlaybackInit, PlaybackStarted, PlaybackStopped notifications containing video id, channel id, and the status of the playing video [fix] allow playback regardless of API status - fixes playback for strm, playlists, other add-ons, and sharing videos from devices @@ -183,7 +234,7 @@ [lang] hu_hu strings |contrib: thelacesz| [lang] pl_pl strings |contrib: drrak| -6.6.0 +## v6.6.0 [add] 'Play (Ask for quality)' context menu [add] search by channel or playlist id [add] hide_folders, hide_playlists, hide_search, and hide_live to /channel// end-point @@ -199,7 +250,7 @@ [lang] it_it strings |contrib: SebastianoPistore| [lang] de_de strings |contrib: tweimer| -6.5.2 +## v6.5.2 [fix] playback (No streams found) [fix] missing metadata [fix] ended live streams not using mpeg-dash when enabled @@ -209,7 +260,7 @@ [lang] pl_pl strings |contrib: drrak| [lang] nl_nl strings |contrib: Markman-B| -6.5.1 +## v6.5.1 [add] get_live to youtube_requests module to retrieve live stream info for a channel [add] Adaptive (MP4) and Adaptive (WEBM) to MPEG-DASH - Video quality [fix] live stream playback @@ -218,7 +269,7 @@ [fix] sqlite requests breaking when another add-on/module uses sqlite3.register_converter(..) [fix] signal endofdirectory in case of a provider navigation exception |contrib: enen92| -6.5.0 +## v6.5.0 [add] Video quality selection to Settings - MPEG-DASH [add] Limit to 30fps to Settings - MPEG-DASH [add] Uploads playlist to channel playlists @@ -234,7 +285,7 @@ [lang] es_es strings |contrib: roliverosc| [lang] ko_kr strings |contrib: parkmino| -6.4.1 +## v6.4.1 [upd] Use the activation url provided by the authentication request [add] add Settings - MPEG-DASH - Default to WEBM adaptation set - required for 4k stream automatic selection @@ -251,7 +302,7 @@ [lang] nl_nl strings |contrib: MB1968| [lang] el_gr strings |contrib: twilight0| -6.4.0 +## v6.4.0 [add] 4K videos with InputStream Adaptive >= v2.3.14 - Thanks to @peak3d [fix] some videos not playing when logged in @@ -270,12 +321,12 @@ - result: next video is always the most relevant video to the last video played [upd] player javascript retrieval -6.3.1 +## v6.3.1 [fix] ensure playback monitoring is monitoring the correct item [fix] check for abortRequested while processing purchased collections [upd] Use setArt instead of iconImage, thumbnailImage and setProperty('fanart_image') -6.3.0 +## v6.3.0 [add] support for keymapping of some endpoints, wiki: https://kodi.wiki/view/Add-on:YouTube#Keymaps [add] 'Rate videos in playlists' setting to 'Settings - General - Rate after watching' [add] Support for purchased collections |contrib: MisterD81| @@ -296,7 +347,7 @@ [lang] ko_kr strings |contrib: parkmino| [lang] nl_nl strings |contrib: Markman-B| -6.2.3 +## v6.2.3 [add] Next Page to 'More...' -> 'Add to...' context menu [add] icon and description to playlists in 'More...' -> 'Add to...' context menu on Kodi 17+ [add] 'Play audio only' context menu item @@ -309,15 +360,15 @@ [lang] el_gr strings |contrib: twilight0| [lang] es_es strings |contrib: roliverosc| -6.2.2 +## v6.2.2 [fix] signature function not found on some videos -6.2.1 +## v6.2.1 [fix] some streams reporting as Unavailable [fix] utf-8 file paths [fix] settings, and switch user causing perpetual busy dialog on Kodi 18 -6.2.0 +## v6.2.0 [add] Context menu -> 'Play with subtitles' |contrib: solarus| [add] Option to download subtitles before playback in Settings -> General -> Configure subtitles [add] Refresh context menu to all videos/live streams @@ -357,13 +408,13 @@ [lang] nl_nl strings |contrib: Markman-B| [lang] ru_ru strings |contrib: vlmaksime| -6.1.4 +## v6.1.4 [fix] Signature function not found -6.1.3 +## v6.1.3 [fix] timedelta.total_seconds python 2.6 compat. -6.1.2 +## v6.1.2 [fix] dash settings check and defaults [fix] addon-check raised warning for services complex entry point [fix] codacy raised issue in http server (https://app.codacy.com/app/Kodi/repo-plugins/pullRequest?prid=2080401) @@ -373,10 +424,10 @@ [lang] ru_ru strings |contrib: Burgaduk| [lang] zh_tw strings |contrib: Zankio| -6.1.1 +## v6.1.1 [fix] http proxy using incorrect setting id causing 403 errors -6.1.0 +## v6.1.0 [add] Multi-user support - Switch User folder (default: on) Settings -> Folders - User management (Add, Remove, Rename, Switch) Settings - Users @@ -412,13 +463,13 @@ [lang] el_gr strings |contrib: twilight0| [lang] nl_nl strings |contrib: Markman-B| -6.0.2 +## v6.0.2 [fix] incorrectly raised 'Proxy is not running' error -6.0.1 +## v6.0.1 [fix] notification encoding -6.0.0 +## v6.0.0 [add] Python 3 compat. [add] Settings -> Maintenance -> Delete temporary files [add] Settings -> General -> Rate video after watching @@ -458,7 +509,7 @@ [upd] Greek strings [twilight0] [upd] Spanish strings [roliverosc] -5.5.0 +## v5.5.0 [add] Saved Playlists [add] allow developers to use their own api key - xbmcgui.Window(10000).setProperty('plugin.video.youtube-configs', json.dumps({"origin": ADDON_ID, "main": {"system": SYSTEM_NAME, "key": API_KEY[, "id": CLIENT_ID, "secret": CLIENT_SECRET]}})) @@ -495,13 +546,13 @@ [upd] Italian strings [DjDiabolik] [upd] Hungarian strings [lacesz-sh] -5.4.5 +## v5.4.5 [fix] tighten regex for routing, resolving incorrect routing -5.4.4 +## v5.4.4 [fix] overlapping regex causing no playlist videos -5.4.3 +## v5.4.3 [add] mpeg-dash support for live content (requires inputstream.adaptive >= 2.0.12) [add] add route to update api keys plugin://plugin.video.youtube/api/update/?enable=true&client_id=&client_secret=&api_key= @@ -511,16 +562,16 @@ [upd] Greek strings [Twilight0] [upd] Polish strings [Etharr] -5.4.2 +## v5.4.2 [fix/upd] My Subscriptions -5.4.1 +## v5.4.1 [fix/upd] dash working for vevo/etc [upd] rework of auto subtitles [fix] leia nightly login issues [fix] 403 error on 'My Channel'/uploaded videos -5.4.0 +## v5.4.0 [upd] Greek strings [Twilight0] [upd] Polish strings [Etharr] [upd] Finnish strings [teemue] @@ -550,17 +601,17 @@ videos = youtube_requests.get_videos([, , ]) """ -5.3.11 +## v5.3.11 [fix/upd] replace custom simple_requests with script.module.requests to resolve ssl and cross-platform issues [add] 'Reset access manager' added to Maintenance section, sign out process(available when logged out) to clean up tokens in settings.xml [upd] set default for SSL certificate verification to 'true' -5.3.10 +## v5.3.10 [fix/upd] resolve 'No video streams found' for age-gated content and other minor. [fix/upd] unescaping HTML entities in subtitles [mdmdmdmdmd] [upd] Polish strings [Etharr] -5.3.6-9 +## v5.3.6-9 [fix/upd] resolve 'No video streams found' [fix/upd] SPMC dash support guidosarducci [upd] Italian strings [iz8mbw] @@ -568,7 +619,7 @@ [upd] Dutch strings [Sjord] [fix/upd] Frodo backwards compat. setArt [lulol] -5.3.5 +## v5.3.5 [add] MPEG-DASH support for Kodi 17 w/ inputstream.adaptive Add-on [add] optional History and Watch Later custom playlist to Settings -> Folders, or (My)Playlists context menu to set/remove [add] Maintenance section to Settings @@ -584,89 +635,89 @@ [upd] Dutch strings [flemlion] [fix/upd] API tweaking and various other updates -5.2.2 (2016-05-28) +## v5.2.2 (2016-05-28) [upd] Insert old API-Keys from Bromix because of Quota-Exceed-Error [add] Dutch strings (thanks to Guilouz) [upd] French strings (thanks to royreinders) [upd] Greek strings (thanks to Twilight0) -5.2.1 (2016-05-12) +## v5.2.1 (2016-05-12) [add] Additional "Recommend for You" section (thanks to anxdpanic) -5.2.0 (2016-05-11) +## v5.2.0 (2016-05-11) [upd] APi-Changer - First login delete old Credentials - New Login Required! Sorry :) -5.1.20.7 (2016-05-03) -Workaround for the Quota Exceed-Error. +## v5.1.20.7 (2016-05-03) +Workaround for the Quota Exceed-Error. [add] API-Changer for a Pool of 6 API-Keys [add] Auto-Reauthenticate with Pool-Key on "Quota Exceed" error [add] "Use own API"-Button [hint] The Message "Invalid Credentials" after Update could be fixed with deleting the Userdata-Settings.xml [add] add Recommendations to main listing when logged in [anxdpanic] -5.1.20.6 (2016-04-03) +## v5.1.20.6 (2016-04-03) [add] Greek Translation from twilight0 [upd] Fixed italian translation from DjDiabolik -5.1.20.5 (2016-03-20) +## v5.1.20.5 (2016-03-20) [upd] Ignore if '.apps.googleusercontent.com' entered in API Id string in add-on settings -5.1.20.4 (2016-03-05) +## v5.1.20.4 (2016-03-05) [add] API Key in add-on settings [upd] All string.po for new API Key functionality. New entries in language string.po files not translated from English. -5.1.20.3 (2016-03-04) +## v5.1.20.3 (2016-03-04) [fix] All Key changed with YouTube TV -5.1.20.2 (2016-03-04) +## v5.1.20.2 (2016-03-04) [fix] Live streams itag 91 patch [add] Kodi 17 Krypton support. Patch by Uukrul -5.1.20 (2016-02-25) +## v5.1.20 (2016-02-25) [fix] Final fix for APIs -5.1.16 (2015-12-24) +## v5.1.16 (2015-12-24) [fix] reverted search order by date -5.1.15 (2015-12-22) +## v5.1.15 (2015-12-22) [fix] signature calculation -5.1.14 (2015-12-22) +## v5.1.14 (2015-12-22) [upd] default search order by date (newest first) [fix] VEVO signature calculation -5.1.13 (2015-12-20) +## v5.1.13 (2015-12-20) [fix] frodo...DIE FRODO DIE !!! [fix] some cleanup for Jarvis -5.1.12 (2015-11-29) +## v5.1.12 (2015-11-29) [fix] sort issues with Jarvis (thx 2 guidosarducci) -5.1.11 (2015-11-24) +## v5.1.11 (2015-11-24) [fix] signature calculation (e.g. VEVO) -5.1.10 (2015-11-13) +## v5.1.10 (2015-11-13) [fix] signature calculation (e.g. VEVO) -5.1.9 (2015-09-21) +## v5.1.9 (2015-09-21) [add] Hebrew (thx 2 smoky-jr) [upd] don't set default season and episodes (1x1) [upd] another try to optimize the quota problem. Move all searches to the individual keys instead of one global [fix] playback issues (skip discontinued video/format by YouTube) -5.1.8 (2015-08-23) +## v5.1.8 (2015-08-23) [add] Bulgarian (thx 2 NEOhidra) [upd] new quota shaping [fix] personal playlists (Watch Later, Liked Video, ...) should work again [fix] playback of VEVO videos - some videos still using discontinued ITAG13 [fix] show login, when an endpoint is called, which requires permissions. -5.1.7 (2015-07-15) +## v5.1.7 (2015-07-15) ADD: quota optimization via separate key for none-token depended API calls UPD: new login semantic to sign out if someone upgrades to a newer version of KODI FIX: 'Next Page (X)' item was added to the current playlist for playback -5.1.6 (2015-06-14) +## v5.1.6 (2015-06-14) ADD: Chinese (Traditional) (thx 2 beddfaf916) ADD: Czech (thx 2 tomaswcz) ADD: Romanian (thx 2 Danny3) @@ -675,28 +726,28 @@ UPD: Polish (thx 2 Etharr) FIX: show notification again for unsupported rtmpe streams FIX: unicode/utf-8 trouble in kodion -5.1.5 (2015-05-29) +## v5.1.5 (2015-05-29) UPD: remove deprecation warning for Isengard FIX: Issue 7163 (Missing pagination for subscriptions - YouTube APIv3 bug) -5.1.4 (2015-05-26) +## v5.1.4 (2015-05-26) ADD: Frodo support (only if you install via zip or bromix repo) UPD: new API-KEY - thanks to the guy for hijacking the key for trailer searches UPD: Russian (thx 2 BytEvil) FIX: cache wasn't working correctly without login -5.1.3 (2015-05-16) +## v5.1.3 (2015-05-16) UPD: code cleaning FIX: minor fixes -5.1.2 (2015-05-15) +## v5.1.2 (2015-05-15) ADD: ask for video quality (optional) UPD: code cleaning UPD: Russian (thx 2 BytEvil) UPD: kodion FIX: add 'Play all' for playlist (again) -5.1.1 (2015-05-10) +## v5.1.1 (2015-05-10) ADD: 'More...' in context menu to provide more items ADD: show deprecation warning of old plugin calls starting with Isengard ADD: EndPoint: 'plugin://plugin.video.youtube/search/?q=[URL_ENC_TEXT]' @@ -711,7 +762,7 @@ FIX: refresh after rating a video (own playlist only) FIX: add 'refresh' again for 'My subscriptions' DEL: removed old v2 API -5.1.0 (2015-04-22) +## v5.1.0 (2015-04-22) ADD: Double login to support 'My subscriptions' via YouTube TV API ADD: 'More Links from the description' - extracts further links from the description ADD: Korean (thx 2 piodio) @@ -730,17 +781,17 @@ FIX: script error while resolving url fails FIX: script error while adding video to a playlist FIX: show seconds (runtime) in Gotham -5.0.9 (2015-03-14) +## v5.0.9 (2015-03-14) ADD: 'duration' optimized for KODI 15.X (Isengard) ADD: EndPoint for listing the videos of a playlist: "plugin://plugin.video.youtube/playlist//" UPD: optimized client-/server transfer (use gzip) -5.0.8 (2015-03-05) +## v5.0.8 (2015-03-05) UPD: Polish (thx 2 Etharr) UPD: Russian (thx 2 BytEvil) FIX: improved playback -5.0.7 (2015-02-27) +## v5.0.7 (2015-02-27) ADD: 'Refresh' in 'My Subscriptions' and all my playlists ADD: rename search history item UPD: French (thx 2 Trilip) @@ -750,7 +801,7 @@ FIX: possible crash on not well formed language ids FIX: optimized logging FIX: unicode trouble -5.0.6 (2015-02-17) +## v5.0.6 (2015-02-17) ADD: setting for hiding the channel name in the description ADD: Ukrainian (thx 2 posledov) UPD: show 'Watch History' (default) @@ -762,7 +813,7 @@ FIX: don't crash while trying to play rtmpe FIX: corrected ITAG 18 in 360p instead of 260p -5.0.5 (2015-02-01) +## v5.0.5 (2015-02-01) ADD: clear search history items UPD: replace context menu not everywhere UPD: dropped dependency of 'requests'-module because of performance issues @@ -771,7 +822,7 @@ FIX: maybe a fix for a problem (again!) on unix systems while using python 2.7.9 FIX: removed pagination for "Related Videos" - the fix before produced some nasty side-effects FIX: Vevo signature -5.0.4 (2015-01-15) +## v5.0.4 (2015-01-15) ADD: 'Live' in search ADD: 'Play with...' for supporting external/alternative player ADD: live events (needed some tests and feedback) @@ -783,7 +834,7 @@ FIX: Pagination in APIv2 FIX: settings for hiding 'Live' menu DEL: support for Frodo -5.0.3 (2015-01-08) +## v5.0.3 (2015-01-08) ADD: 'Disliked Videos' ADD: 240p, 360p and 480p UPD: adjustment in KODION classes (maybe causing problems on MAC OS X 10.10.X + Python 2.6) @@ -793,14 +844,14 @@ UPD: Polish (thx 2 D.K.) FIX: German translation FIX: some Frodo updates -5.0.2 (2015-01-01) +## v5.0.2 (2015-01-01) ADD: Support for shared playlists via Yatse ADD: 'Play from here' for playlists ADD: 'Queue Video' FIX: German translation FIX: Portuguese (Brazil) (thx 2 ASH_Macedo) -5.0.1 (2014-12-31) +## v5.0.1 (2014-12-31) ADD: 'Play all' (default, reverse, shuffle) ADD: Italian (thx 2 peppe_sr) ADD: Portuguese (Brazil) (thx 2 ASH_Macedo) @@ -816,7 +867,7 @@ FIX: update meta data for video played via ext. url FIX: remove video from 'Watch Later' playlist REV: reverted to requests -5.0.0 (2014-12-27) +## v5.0.0 (2014-12-27) DEL: removed beta ADD: new rating system - like/dislike/remove ADD: show confirmation before remove a video or deleting a playlist @@ -832,28 +883,28 @@ FIX: rename playlist FIX: show newest videos first (uploads of channel) FIX: show fanarts in 'Watch Later' and 'Liked Videos' -1.0.0~alpha29 (2014-12-14) +## v1.0.0~alpha29 (2014-12-14) UPD: new login mechanism (removed the old one) ADD: translation fr-FR (thanks to roondar) ADD: manage playlists - create, remove and add videos to a selected playlists FIX: support cyrillic letters -1.0.0~alpha28 (2014-12-07) +## v1.0.0~alpha28 (2014-12-07) ADD: subscribe to channel (from videos and playlist) UPD: rework of handling context menu FIX: remove next page for related videos (because APIv3 is broken) -1.0.0~alpha27 (2014-12-01) +## v1.0.0~alpha27 (2014-12-01) UPD: performance improved for auto-removing videos from 'Watch Later' list FIX: error while navigating to the next page -1.0.0~alpha26 (2014-11-30) +## v1.0.0~alpha26 (2014-11-30) ADD: hide all entries (except search) via settings in the root menu ADD: missing pagination for 'My Subscriptions' FIX: next page is working again (Watch Later) UPD: disabled language (for now) Kodi isn't providing reliable language IDs -1.0.0~alpha25 (2014-11-29) +## v1.0.0~alpha25 (2014-11-29) UPD: requests with language/region UPD: new icons ADD: Auto-Remove from 'Watch Later' list @@ -861,37 +912,37 @@ ADD: 'Related Videos' UPD: show channel name in the description FIX: disable verification warnings -1.0.0~alpha24 (2014-11-27) +## v1.0.0~alpha24 (2014-11-27) ADD: 'Browse Channels' FIX: support for skins calling '.*/extrafanart/' -1.0.0~alpha23 (2014-11-26) +## v1.0.0~alpha23 (2014-11-26) ADD: 'What to watch' FIX: 'My subscriptions' switched to version APIv2.1 (Google) DAMN YOU!!! -1.0.0~alpha22 (2014-11-25) +## v1.0.0~alpha22 (2014-11-25) ADD: sorting FIX: crash when calling 'My subscriptions' FIX: signature calculation -1.0.0~alpha21 (2014-11-24) +## v1.0.0~alpha21 (2014-11-24) CHG: method for playing a video changed to a more query like uri FIX: crash under android systems. Regular Expressions work a little different :) -1.0.0~alpha20 (2014-11-23) +## v1.0.0~alpha20 (2014-11-23) FIX: Git merge with master -1.0.0~alpha19 (2014-11-23) +## v1.0.0~alpha19 (2014-11-23) UPD: complete rewrite UPD: switch to kodion -1.0.0~alpha18 (2014-08-31) +## v1.0.0~alpha18 (2014-08-31) - ADD: Support for ETag - could improve performance - ADD: remove item of search history - FIX: #22 calculation of duration - FIX: #28 display 'general' instead of 'like' in settings -1.0.0~alpha17 (2014-08-10) +## v1.0.0~alpha17 (2014-08-10) - ADD: 'Go to CHANNEL' of a selected video - ADD: Support for 'Subscribe' and 'Unsubscribe' of channels - ADD: Full support for 'Like' @@ -901,15 +952,15 @@ UPD: switch to kodion - FIX: Displaying false channel in video description - FIX: Don't update search history before showing the search result -1.0.0~alpha16 (2014-08-08) +## v1.0.0~alpha16 (2014-08-08) - FIX: provide 'Next Page' under 'My Subscriptions' if there are more results -- ADD: Hide and show menu items via settings +- ADD: Hide and show menu items via settings - ADD: Search history (set the size via settings from 0-50 items) -1.0.0~alpha15 (2014-08-04) +## v1.0.0~alpha15 (2014-08-04) - FIX: moved (only) for the subscribtions to V2 to see new uploaded videos -1.0.0~alpha14 (2014-08-03) +## v1.0.0~alpha14 (2014-08-03) - ADD: 'Remove' via context menu for playlists - ADD: 'Watch Later' via context menu to add a video to the 'Watch Later' playlist - ADD: Automatic remove from the 'Watch Later' playlists (optional via settings - default = true) @@ -917,50 +968,50 @@ UPD: switch to kodion - ADD: Fanart for channels. Only if you select a channel directly. - optimized some of the addon code -1.0.0~alpha13 (2014-08-02) +## v1.0.0~alpha13 (2014-08-02) - FIX: improved token and login validation -1.0.0~alpha12 (2014-08-01) +## v1.0.0~alpha12 (2014-08-01) - ADD: Show 'Published on' and 'Channel' in the description of a video (can be disabled via setting) - ADD: support for 3D videos (can be disabled via setting) -- Accelerated resolving of video URLs (almost 2x as fast) +- Accelerated resolving of video URLs (almost 2x as fast) -1.0.0~alpha11 (2014-07-30) +## v1.0.0~alpha11 (2014-07-30) - FIX: added a fallback for videos that require a login (works very fast?!?!) -- FIX: if username or password is missing reset the whole token logic +- FIX: if username or password is missing reset the whole token logic -1.0.0~alpha10 (2014-07-29) -- FIX: improved performance on decoding signatures for some videos +## v1.0.0~alpha10 (2014-07-29) +- FIX: improved performance on decoding signatures for some videos - ADD: support for custom-created playlists - FIX: sort own subscriptions - ADD: enable/disable video, channel and/or playlist search -1.0.0~alpha9 (2014-07-28) +## v1.0.0~alpha9 (2014-07-28) - FIX: removed debug -1.0.0~alpha8 (2014-07-28) +## v1.0.0~alpha8 (2014-07-28) - ADD: login tests -1.0.0~alpha7 (2014-07-27) +## v1.0.0~alpha7 (2014-07-27) - FIX: added support from freemake.com to decode signatures of some videos -1.0.0~alpha6 (2014-07-27) +## v1.0.0~alpha6 (2014-07-27) - added support of playlists of a channel (first page only) -- optimized some major routines for easier support of all youtube's content +- optimized some major routines for easier support of all youtube's content -1.0.0~alpha5 (2014-07-27) +## v1.0.0~alpha5 (2014-07-27) - version bump for tests -1.0.0~alpha4 (2014-07-26) +## v1.0.0~alpha4 (2014-07-26) - added runtime for each video (crazy hack) - disabled debug -1.0.0~alpha3 (2014-07-26) +## v1.0.0~alpha3 (2014-07-26) - little fixes regarding youtube channels -1.0.0~alpha2 (2014-07-26) +## v1.0.0~alpha2 (2014-07-26) - added youtube channels and search - added playback -1.0.0~alpha (2014-07-26) +## v1.0.0~alpha (2014-07-26) - initial version for tests diff --git a/resources/lib/script.py b/resources/lib/script.py new file mode 100644 index 000000000..98b3dd30b --- /dev/null +++ b/resources/lib/script.py @@ -0,0 +1,17 @@ +# -*- coding: utf-8 -*- +""" + + Copyright (C) 2024-present plugin.video.youtube + + SPDX-License-Identifier: GPL-2.0-only + See LICENSES/GPL-2.0-only for more information. +""" + +from __future__ import absolute_import, division, unicode_literals + +import sys + +from youtube_plugin import script_actions + + +script_actions.run(sys.argv) diff --git a/resources/lib/youtube_plugin/__init__.py b/resources/lib/youtube_plugin/__init__.py index 44b1ba27b..502077bea 100644 --- a/resources/lib/youtube_plugin/__init__.py +++ b/resources/lib/youtube_plugin/__init__.py @@ -26,4 +26,4 @@ } } -__all__ = ['kodion', 'youtube', 'key_sets', 'script'] +__all__ = ['kodion', 'youtube', 'key_sets', 'script_actions'] diff --git a/resources/lib/youtube_plugin/kodion/abstract_provider.py b/resources/lib/youtube_plugin/kodion/abstract_provider.py index 5f85ea08b..c7f85f70b 100644 --- a/resources/lib/youtube_plugin/kodion/abstract_provider.py +++ b/resources/lib/youtube_plugin/kodion/abstract_provider.py @@ -37,29 +37,29 @@ def __init__(self): # register some default paths self.register_path(r'^/$', '_internal_root') - self.register_path(r''.join([ + self.register_path(r''.join(( '^/', paths.WATCH_LATER, '/(?Padd|clear|list|remove)/?$' - ]), '_internal_watch_later') + )), '_internal_watch_later') - self.register_path(r''.join([ + self.register_path(r''.join(( '^/', paths.FAVORITES, '/(?Padd|clear|list|remove)/?$' - ]), '_internal_favorite') + )), '_internal_favorite') - self.register_path(r''.join([ + self.register_path(r''.join(( '^/', paths.SEARCH, '/(?Pinput|query|list|remove|clear|rename)/?$' - ]), '_internal_search') + )), '_internal_search') - self.register_path(r''.join([ + self.register_path(r''.join(( '^/', paths.HISTORY, '/$' - ]), 'on_playback_history') + )), 'on_playback_history') self.register_path(r'(?P.*\/)extrafanart\/([\?#].+)?$', '_internal_on_extra_fanart') @@ -92,7 +92,6 @@ def run_wizard(self, context): settings.set_bool(settings.SETUP_WIZARD, False) wizard_steps = self.get_wizard_steps(context) - wizard_steps.extend(ui.get_view_manager().get_wizard_steps()) if (wizard_steps and ui.on_yes_no_input( context.get_name(), context.localize('setup_wizard.execute') diff --git a/resources/lib/youtube_plugin/kodion/context/abstract_context.py b/resources/lib/youtube_plugin/kodion/context/abstract_context.py index 19c8d5d78..540a09839 100644 --- a/resources/lib/youtube_plugin/kodion/context/abstract_context.py +++ b/resources/lib/youtube_plugin/kodion/context/abstract_context.py @@ -134,11 +134,6 @@ def get_language_name(self, lang_id=None): def get_region(self): raise NotImplementedError() - def get_cache_path(self): - if not self._cache_path: - self._cache_path = os.path.join(self.get_data_path(), 'kodion') - return self._cache_path - def get_playback_history(self): if not self._playback_history: uuid = self.get_access_manager().get_current_user_id() @@ -155,8 +150,9 @@ def get_data_cache(self): cache_size = 10 else: cache_size /= 2.0 + uuid = self.get_access_manager().get_current_user_id() filename = 'data_cache.sqlite' - filepath = os.path.join(self.get_cache_path(), filename) + filepath = os.path.join(self.get_data_path(), uuid, filename) self._data_cache = DataCache(filepath, max_file_size_mb=cache_size) return self._data_cache @@ -168,8 +164,9 @@ def get_function_cache(self): cache_size = 10 else: cache_size /= 2.0 + uuid = self.get_access_manager().get_current_user_id() filename = 'cache.sqlite' - filepath = os.path.join(self.get_cache_path(), filename) + filepath = os.path.join(self.get_data_path(), uuid, filename) self._function_cache = FunctionCache(filepath, max_file_size_mb=cache_size) return self._function_cache diff --git a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py index 9e77ae4e1..271709431 100644 --- a/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py +++ b/resources/lib/youtube_plugin/kodion/context/xbmc/xbmc_context.py @@ -270,7 +270,6 @@ def __init__(self, is_plugin_invocation = uri.startswith('plugin://') if is_plugin_invocation: # first the path of the uri - self._uri = uri parsed_url = urlsplit(uri) self._path = unquote(parsed_url.path) @@ -278,12 +277,13 @@ def __init__(self, if num_args > 2: params = sys.argv[2][1:] if params: - self._uri = '?'.join((self._uri, params)) self.parse_params(dict(parse_qsl(params))) # then Kodi resume status if num_args > 3 and sys.argv[3].lower() == 'resume:true': self._params['resume'] = True + + self._uri = self.create_uri(self._path, self._params) elif num_args: uri = sys.argv[0] is_plugin_invocation = uri.startswith('plugin://') diff --git a/resources/lib/youtube_plugin/kodion/items/base_item.py b/resources/lib/youtube_plugin/kodion/items/base_item.py index ea0cdfa5d..3de3291e2 100644 --- a/resources/lib/youtube_plugin/kodion/items/base_item.py +++ b/resources/lib/youtube_plugin/kodion/items/base_item.py @@ -27,10 +27,8 @@ class BaseItem(object): def __init__(self, name, uri, image='', fanart=''): self._version = BaseItem.VERSION - try: - self._name = unescape(name) - except: - self._name = name + self._name = None + self.set_name(name) self._uri = uri @@ -98,6 +96,13 @@ def get_id(self): md5_hash.update(self._uri.encode('utf-8')) return md5_hash.hexdigest() + def set_name(self, name): + try: + self._name = unescape(name) + except: + self._name = name + return self._name + def get_name(self): """ Returns the name of the item. diff --git a/resources/lib/youtube_plugin/kodion/items/directory_item.py b/resources/lib/youtube_plugin/kodion/items/directory_item.py index 8f74a8793..74f9236eb 100644 --- a/resources/lib/youtube_plugin/kodion/items/directory_item.py +++ b/resources/lib/youtube_plugin/kodion/items/directory_item.py @@ -22,21 +22,42 @@ def __init__(self, fanart='', action=False, category_label=None): - if category_label is None: - category_label = name - if category_label: - uri = ('&' if '?' in uri else '?').join(( - uri, - urlencode({'category_label': category_label}), - )) super(DirectoryItem, self).__init__(name, uri, image, fanart) - self._plot = self.get_name() + name = self.get_name() + self._category_label = None + self.set_category_label(category_label or name) + self._plot = name self._is_action = action self._channel_subscription_id = None self._channel_id = None - def set_name(self, name): - self._name = name + def set_name(self, name, category_label=None): + name = super(DirectoryItem, self).set_name(name) + if hasattr(self, '_category_label'): + self.set_category_label(category_label or name) + return name + + def set_category_label(self, label): + if label == '__inherit__': + self._category_label = None + return + + if self._category_label and self._category_label != label: + uri = self.get_uri() + self.set_uri(uri.replace( + urlencode({'category_label': self._category_label}), + urlencode({'category_label': label}) if label else '', + )) + elif label: + uri = self.get_uri() + self.set_uri(('&' if '?' in uri else '?').join(( + uri, + urlencode({'category_label': label}), + ))) + self._category_label = label + + def get_category_label(self): + return self._category_label def set_plot(self, plot): self._plot = plot diff --git a/resources/lib/youtube_plugin/kodion/items/favorites_item.py b/resources/lib/youtube_plugin/kodion/items/favorites_item.py index 580c343ca..242d9eef6 100644 --- a/resources/lib/youtube_plugin/kodion/items/favorites_item.py +++ b/resources/lib/youtube_plugin/kodion/items/favorites_item.py @@ -24,7 +24,7 @@ def __init__(self, context, name=None, image=None, fanart=None): super(FavoritesItem, self).__init__(name, context.create_uri( - [paths.FAVORITES, 'list'] + (paths.FAVORITES, 'list',), ), image=image) diff --git a/resources/lib/youtube_plugin/kodion/items/menu_items.py b/resources/lib/youtube_plugin/kodion/items/menu_items.py index 24a252919..fd707a9b3 100644 --- a/resources/lib/youtube_plugin/kodion/items/menu_items.py +++ b/resources/lib/youtube_plugin/kodion/items/menu_items.py @@ -22,7 +22,7 @@ def more_for_video(context, video_id, logged_in=False, refresh_container=False): 'video_id': video_id, 'logged_in': logged_in, 'refresh_container': refresh_container, - } + }, )) ) @@ -34,7 +34,7 @@ def related_videos(context, video_id): ('special', 'related_videos',), { 'video_id': video_id, - } + }, )) ) @@ -46,7 +46,7 @@ def video_comments(context, video_id): ('special', 'parent_comments',), { 'video_id': video_id, - } + }, )) ) @@ -58,7 +58,7 @@ def content_from_description(context, video_id): ('special', 'description_links',), { 'video_id': video_id, - } + }, )) ) @@ -73,7 +73,10 @@ def play_with(context): def refresh(context): return ( context.localize('refresh'), - 'Container.Refresh' + 'ReplaceWindow(Videos, {0})'.format(context.create_uri( + context.get_path(), + dict(context.get_params(), refresh=True), + )) ) @@ -94,7 +97,7 @@ def play_all_from_playlist(context, playlist_id, video_id=''): 'playlist_id': playlist_id, 'video_id': video_id, 'play': True, - } + }, )) ) return ( @@ -104,7 +107,7 @@ def play_all_from_playlist(context, playlist_id, video_id=''): { 'playlist_id': playlist_id, 'play': True, - } + }, )) ) @@ -116,7 +119,7 @@ def add_video_to_playlist(context, video_id): ('playlist', 'select', 'playlist',), { 'video_id': video_id, - } + }, )) ) @@ -130,7 +133,7 @@ def remove_video_from_playlist(context, playlist_id, video_id, video_name): 'playlist_id': playlist_id, 'video_id': video_id, 'video_name': video_name, - } + }, )) ) @@ -143,7 +146,7 @@ def rename_playlist(context, playlist_id, playlist_name): { 'playlist_id': playlist_id, 'playlist_name': playlist_name - } + }, )) ) @@ -156,7 +159,7 @@ def delete_playlist(context, playlist_id, playlist_name): { 'playlist_id': playlist_id, 'playlist_name': playlist_name - } + }, )) ) @@ -169,7 +172,7 @@ def remove_as_watch_later(context, playlist_id, playlist_name): { 'playlist_id': playlist_id, 'playlist_name': playlist_name - } + }, )) ) @@ -182,7 +185,7 @@ def set_as_watch_later(context, playlist_id, playlist_name): { 'playlist_id': playlist_id, 'playlist_name': playlist_name - } + }, )) ) @@ -195,7 +198,7 @@ def remove_as_history(context, playlist_id, playlist_name): { 'playlist_id': playlist_id, 'playlist_name': playlist_name - } + }, )) ) @@ -208,7 +211,7 @@ def set_as_history(context, playlist_id, playlist_name): { 'playlist_id': playlist_id, 'playlist_name': playlist_name - } + }, )) ) @@ -221,7 +224,7 @@ def remove_my_subscriptions_filter(context, channel_name): { 'channel_name': channel_name, 'action': 'remove' - } + }, )) ) @@ -234,7 +237,7 @@ def add_my_subscriptions_filter(context, channel_name): { 'channel_name': channel_name, 'action': 'add', - } + }, )) ) @@ -247,7 +250,7 @@ def rate_video(context, video_id, refresh_container=False): { 'video_id': video_id, 'refresh_container': refresh_container, - } + }, )) ) @@ -260,7 +263,7 @@ def watch_later_add(context, playlist_id, video_id): { 'playlist_id': playlist_id, 'video_id': video_id, - } + }, )) ) @@ -273,7 +276,7 @@ def watch_later_local_add(context, item): { 'video_id': item.video_id, 'item': item.dumps(), - } + }, )) ) @@ -285,7 +288,7 @@ def watch_later_local_remove(context, video_id): (paths.WATCH_LATER, 'remove',), { 'video_id': video_id, - } + }, )) ) @@ -294,7 +297,7 @@ def watch_later_local_clear(context): return ( context.localize('watch_later.clear'), 'RunPlugin({0})'.format(context.create_uri( - (paths.WATCH_LATER, 'clear',) + (paths.WATCH_LATER, 'clear',), )) ) @@ -303,7 +306,7 @@ def go_to_channel(context, channel_id, channel_name): return ( context.localize('go_to_channel') % context.get_ui().bold(channel_name), 'ActivateWindow(Videos, {0}, return)'.format(context.create_uri( - ('channel', channel_id,) + ('channel', channel_id,), )) ) @@ -316,7 +319,7 @@ def subscribe_to_channel(context, channel_id, channel_name=''): ('subscriptions', 'add',), { 'subscription_id': channel_id, - } + }, )) ) return ( @@ -325,7 +328,7 @@ def subscribe_to_channel(context, channel_id, channel_name=''): ('subscriptions', 'add',), { 'subscription_id': channel_id, - } + }, )) ) @@ -337,7 +340,7 @@ def unsubscribe_from_channel(context, channel_id): ('subscriptions', 'remove',), { 'subscription_id': channel_id, - } + }, )) ) @@ -350,7 +353,7 @@ def play_with_subtitles(context, video_id): { 'video_id': video_id, 'prompt_for_subtitles': True, - } + }, )) ) @@ -363,7 +366,7 @@ def play_audio_only(context, video_id): { 'video_id': video_id, 'audio_only': True, - } + }, )) ) @@ -376,7 +379,7 @@ def play_ask_for_quality(context, video_id): { 'video_id': video_id, 'ask_for_quality': True, - } + }, )) ) @@ -385,11 +388,11 @@ def history_remove(context, video_id): return ( context.localize('history.remove'), 'RunPlugin({0})'.format(context.create_uri( - [paths.HISTORY], + (paths.HISTORY,), { 'action': 'remove', 'video_id': video_id - } + }, )) ) @@ -398,10 +401,10 @@ def history_clear(context): return ( context.localize('history.clear'), 'RunPlugin({0})'.format(context.create_uri( - [paths.HISTORY], + (paths.HISTORY,), { 'action': 'clear' - } + }, )) ) @@ -410,11 +413,11 @@ def history_mark_watched(context, video_id): return ( context.localize('history.mark.watched'), 'RunPlugin({0})'.format(context.create_uri( - [paths.HISTORY], + (paths.HISTORY,), { 'video_id': video_id, 'action': 'mark_watched', - } + }, )) ) @@ -423,11 +426,11 @@ def history_mark_unwatched(context, video_id): return ( context.localize('history.mark.unwatched'), 'RunPlugin({0})'.format(context.create_uri( - [paths.HISTORY], + (paths.HISTORY,), { 'video_id': video_id, 'action': 'mark_unwatched', - } + }, )) ) @@ -436,11 +439,11 @@ def history_reset_resume(context, video_id): return ( context.localize('history.reset.resume_point'), 'RunPlugin({0})'.format(context.create_uri( - [paths.HISTORY], + (paths.HISTORY,), { 'video_id': video_id, 'action': 'reset_resume', - } + }, )) ) @@ -453,7 +456,7 @@ def favorites_add(context, item): { 'video_id': item.video_id, 'item': item.dumps(), - } + }, )) ) @@ -465,7 +468,7 @@ def favorites_remove(context, video_id): (paths.FAVORITES, 'remove',), { 'vide_id': video_id, - } + }, )) ) @@ -477,7 +480,7 @@ def search_remove(context, query): (paths.SEARCH, 'remove',), { 'q': query, - } + }, )) ) @@ -489,7 +492,7 @@ def search_rename(context, query): (paths.SEARCH, 'rename',), { 'q': query, - } + }, )) ) @@ -498,6 +501,6 @@ def search_clear(context): return ( context.localize('search.clear'), 'RunPlugin({0})'.format(context.create_uri( - (paths.SEARCH, 'clear',) + (paths.SEARCH, 'clear',), )) ) diff --git a/resources/lib/youtube_plugin/kodion/items/new_search_item.py b/resources/lib/youtube_plugin/kodion/items/new_search_item.py index 73a052e98..397e72ec0 100644 --- a/resources/lib/youtube_plugin/kodion/items/new_search_item.py +++ b/resources/lib/youtube_plugin/kodion/items/new_search_item.py @@ -42,8 +42,8 @@ def __init__(self, super(NewSearchItem, self).__init__(name, context.create_uri( - [paths.SEARCH, 'input'], - params=params + (paths.SEARCH, 'input',), + params=params, ), image=image) if fanart: diff --git a/resources/lib/youtube_plugin/kodion/items/next_page_item.py b/resources/lib/youtube_plugin/kodion/items/next_page_item.py index 556dd8aa7..a85a2b963 100644 --- a/resources/lib/youtube_plugin/kodion/items/next_page_item.py +++ b/resources/lib/youtube_plugin/kodion/items/next_page_item.py @@ -25,7 +25,7 @@ def __init__(self, context, current_page=1, image=None, fanart=None): new_params ), image=image, - category_label=False) + category_label='__inherit__') if fanart: self.set_fanart(fanart) diff --git a/resources/lib/youtube_plugin/kodion/items/search_history_item.py b/resources/lib/youtube_plugin/kodion/items/search_history_item.py index afd349683..93f2e3ba9 100644 --- a/resources/lib/youtube_plugin/kodion/items/search_history_item.py +++ b/resources/lib/youtube_plugin/kodion/items/search_history_item.py @@ -26,8 +26,8 @@ def __init__(self, context, query, image=None, fanart=None, location=False): super(SearchHistoryItem, self).__init__(query, context.create_uri( - [paths.SEARCH, 'query'], - params=params + (paths.SEARCH, 'query',), + params=params, ), image=image) diff --git a/resources/lib/youtube_plugin/kodion/items/search_item.py b/resources/lib/youtube_plugin/kodion/items/search_item.py index e505b67be..4b909c54c 100644 --- a/resources/lib/youtube_plugin/kodion/items/search_item.py +++ b/resources/lib/youtube_plugin/kodion/items/search_item.py @@ -33,8 +33,8 @@ def __init__(self, super(SearchItem, self).__init__(name, context.create_uri( - [paths.SEARCH, 'list'], - params=params + (paths.SEARCH, 'list',), + params=params, ), image=image) diff --git a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py index 6ae79116e..de8b12419 100644 --- a/resources/lib/youtube_plugin/kodion/items/watch_later_item.py +++ b/resources/lib/youtube_plugin/kodion/items/watch_later_item.py @@ -24,7 +24,7 @@ def __init__(self, context, name=None, image=None, fanart=None): super(WatchLaterItem, self).__init__(name, context.create_uri( - [paths.WATCH_LATER, 'list'] + (paths.WATCH_LATER, 'list',), ), image=image) diff --git a/resources/lib/youtube_plugin/kodion/network/requests.py b/resources/lib/youtube_plugin/kodion/network/requests.py index 70abae383..694a838ae 100644 --- a/resources/lib/youtube_plugin/kodion/network/requests.py +++ b/resources/lib/youtube_plugin/kodion/network/requests.py @@ -170,9 +170,10 @@ def request(self, url, method='GET', ] if part])) if raise_exc: - if not callable(raise_exc): - raise_exc = self._default_exc[-1] - raise_exc = raise_exc(error_title) + if not isinstance(raise_exc, BaseException): + if not callable(raise_exc): + raise_exc = self._default_exc[-1] + raise_exc = raise_exc(error_title) if isinstance(raise_exc, BaseException): raise_exc.__cause__ = exc diff --git a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py index b94d781a7..c06ae42d4 100644 --- a/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py +++ b/resources/lib/youtube_plugin/kodion/plugin/xbmc/xbmc_runner.py @@ -47,9 +47,9 @@ def run(self, provider, context): items = ui.get_property('playlist') if items: ui.clear_property('playlist') - context.log_error('Multiple busy dialogs active - playlist' - ' reloading to prevent Kodi crashing') playlist.add_items(items, loads=True) + context.log_error('Multiple busy dialogs active - ' + 'playlist reloaded to prevent Kodi crash') return False if settings.is_setup_wizard_enabled(): diff --git a/resources/lib/youtube_plugin/kodion/service.py b/resources/lib/youtube_plugin/kodion/service.py index d549a75e1..4761b4ea2 100644 --- a/resources/lib/youtube_plugin/kodion/service.py +++ b/resources/lib/youtube_plugin/kodion/service.py @@ -22,13 +22,6 @@ def run(): context = Context() context.log_debug('YouTube service initialization...') context.get_ui().clear_property('abort_requested') - # wipe function cache on updates/restarts to fix cipher related issues on - # update, valid for one day otherwise - try: - context.get_function_cache().clear() - except Exception: - # prevent service failing due to cache related issues - pass monitor = ServiceMonitor() player = PlayerMonitor(provider=Provider(), context=context) diff --git a/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py b/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py index 710e6cef2..5e0776fa8 100644 --- a/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py +++ b/resources/lib/youtube_plugin/kodion/settings/xbmc/xbmc_plugin_settings.py @@ -172,9 +172,17 @@ def get_string(self, setting, default='', echo=None): value = default if self._echo and echo is not False: - log_debug('Get |{setting}|: "{value}" (str, {status})'.format( + if setting == 'youtube.location': + echo = '|xx.xxxx,xx.xxxx|' + elif setting == 'youtube.api.id': + echo = '...'.join((value[:3], value[-5:])) + elif setting in ('youtube.api.key', 'youtube.api.secret'): + echo = '...'.join((value[:3], value[-3:])) + else: + echo = value + log_debug('Get |{setting}|: "{echo}" (str, {status})'.format( setting=setting, - value=value, + echo=echo, status=error if error else 'success' )) self._cache[setting] = value @@ -191,9 +199,17 @@ def set_string(self, setting, value, echo=None): error = exc if self._echo and echo is not False: - log_debug('Set |{setting}|: "{value}" (str, {status})'.format( + if setting == 'youtube.location': + echo = '|xx.xxxx,xx.xxxx|' + elif setting == 'youtube.api.id': + echo = '...'.join((value[:3], value[-5:])) + elif setting in ('youtube.api.key', 'youtube.api.secret'): + echo = '...'.join((value[:3], value[-3:])) + else: + echo = value + log_debug('Set |{setting}|: "{echo}" (str, {status})'.format( setting=setting, - value=value, + echo=echo, status=error if error else 'success' )) return not error diff --git a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py index 1ac1f4fc2..015ed30a2 100644 --- a/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py +++ b/resources/lib/youtube_plugin/kodion/sql_store/function_cache.py @@ -57,39 +57,37 @@ def _create_id_from_func(partial_func): md5_hash.update(str(partial_func.keywords).encode('utf-8')) return md5_hash.hexdigest() - def _get_cached_data(self, partial_func, seconds=None): - cache_id = self._create_id_from_func(partial_func) - return self._get(cache_id, seconds=seconds), cache_id - - def get_cached_only(self, func, *args, **keywords): - partial_func = partial(func, *args, **keywords) + def get_result(self, func, *args, **kwargs): + partial_func = partial(func, *args, **kwargs) # if caching is disabled call the function if not self._enabled: return partial_func() # only return before cached data - data, _ = self._get_cached_data(partial_func) - return data + cache_id = self._create_id_from_func(partial_func) + return self._get(cache_id) - def get(self, func, seconds, *args, **keywords): + def run(self, func, seconds, *args, _refresh=False, **kwargs): """ Returns the cached data of the given function. :param func, function to cache - :param seconds: time to live in seconds + :param seconds: time to live in + :param _refresh: bool, updates cache with new func result :return: """ - - partial_func = partial(func, *args, **keywords) + partial_func = partial(func, *args, **kwargs) # if caching is disabled call the function if not self._enabled: return partial_func() - data, cache_id = self._get_cached_data(partial_func, seconds=seconds) + cache_id = self._create_id_from_func(partial_func) + data = None if _refresh else self._get(cache_id, seconds=seconds) if data is None: data = partial_func() self._set(cache_id, data) + return data def _optimize_item_count(self, limit=-1, defer=False): diff --git a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py index 3cf8fdb7a..50de065b0 100644 --- a/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py +++ b/resources/lib/youtube_plugin/kodion/ui/xbmc/xbmc_items.py @@ -111,9 +111,9 @@ def video_playback_item(context, video_item, show_fanart=None): if show_fanart is None: show_fanart = settings.show_fanart() - image = video_item.get_image() or 'DefaultVideo.png' + image = video_item.get_image() list_item.setArt({ - 'icon': image, + 'icon': image or 'DefaultVideo.png', 'fanart': show_fanart and video_item.get_fanart() or '', 'thumb': image, }) diff --git a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py index df1487afe..7676698e0 100644 --- a/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py +++ b/resources/lib/youtube_plugin/kodion/utils/datetime_parser.py @@ -230,12 +230,15 @@ def datetime_to_since(context, dt, local=True): def strptime(datetime_str, fmt=None): if fmt is None: - fmt = '%Y-%m-%dT%H%M%S' + fmt = '%Y-%m-%d%H%M%S' if ' ' in datetime_str: date_part, time_part = datetime_str.split(' ') elif 'T' in datetime_str: date_part, time_part = datetime_str.split('T') + else: + date_part = None + time_part = datetime_str if ':' in time_part: time_part = time_part.replace(':', '') @@ -259,7 +262,7 @@ def strptime(datetime_str, fmt=None): if timezone and timezone_part and offset: time_part = offset.join((time_part, timezone_part)) - datetime_str = 'T'.join((date_part, time_part)) + datetime_str = ''.join((date_part, time_part)) if date_part else time_part try: return datetime.strptime(datetime_str, fmt) diff --git a/resources/lib/youtube_plugin/script.py b/resources/lib/youtube_plugin/script_actions.py similarity index 92% rename from resources/lib/youtube_plugin/script.py rename to resources/lib/youtube_plugin/script_actions.py index 3ff79f8df..2bd4560b5 100644 --- a/resources/lib/youtube_plugin/script.py +++ b/resources/lib/youtube_plugin/script_actions.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- """ - Copyright (C) 2018-2018 plugin.video.youtube + Copyright (C) 2024-present plugin.video.youtube SPDX-License-Identifier: GPL-2.0-only See LICENSES/GPL-2.0-only for more information. @@ -11,13 +11,12 @@ import os import socket -import sys -from kodion.compatibility import parse_qsl, xbmc, xbmcaddon, xbmcvfs -from kodion.constants import DATA_PATH, TEMP_PATH -from kodion.context import Context -from kodion.network import get_client_ip_address, is_httpd_live -from kodion.utils import rm_dir +from .kodion.compatibility import parse_qsl, xbmc, xbmcaddon, xbmcvfs +from .kodion.constants import DATA_PATH, TEMP_PATH +from .kodion.context import Context +from .kodion.network import get_client_ip_address, is_httpd_live +from .kodion.utils import rm_dir def _config_actions(action, *_args): @@ -200,8 +199,6 @@ def switch_to_user(user): localize('user.changed') % access_manager.get_username(user), localize('user.switch') ) - context.get_data_cache().clear() - context.get_function_cache().clear() if context.get_param('refresh') is not False: ui.refresh_container() @@ -271,22 +268,36 @@ def switch_to_user(user): return True -if __name__ == '__main__': - args = sys.argv[1:] +def run(argv): + args = argv[1:] if args: args = args[0].split('/') - num_args = len(args) - category = args[0] if num_args else None - action = args[1] if num_args > 1 else None - params = args[2] if num_args > 2 else None - if not category: + num_args = len(args) + if num_args > 2: + category, action, params = args[:3] + elif num_args == 2: + category, action = args + params = None + elif num_args: + category = args[0] + action = params = None + else: xbmcaddon.Addon().openSettings() - elif action == 'refresh': + return + + if action == 'refresh': xbmc.executebuiltin('Container.Refresh') - elif category == 'config': + return + + if category == 'config': _config_actions(action, params) - elif category == 'maintenance': + return + + if category == 'maintenance': _maintenance_actions(action, params) - elif category == 'users': + return + + if category == 'users': _user_actions(action, params) + return diff --git a/resources/lib/youtube_plugin/youtube/client/login_client.py b/resources/lib/youtube_plugin/youtube/client/login_client.py index f23853dda..4937adefb 100644 --- a/resources/lib/youtube_plugin/youtube/client/login_client.py +++ b/resources/lib/youtube_plugin/youtube/client/login_client.py @@ -64,24 +64,16 @@ class LoginClient(YouTubeRequestClient): def __init__(self, config=None, - language='en_US', - region='', access_token='', - access_token_tv=''): + access_token_tv='', + **kwargs): self._config = self.CONFIGS['main'] if config is None else config self._config_tv = self.CONFIGS['youtube-tv'] - # the default language is always en_US (like YouTube on the WEB) - if not language: - language = 'en_US' - else: - language = language.replace('-', '_') - self._language = language - self._region = region self._access_token = access_token self._access_token_tv = access_token_tv - super(LoginClient, self).__init__(exc_type=LoginException) + super(LoginClient, self).__init__(exc_type=LoginException, **kwargs) @staticmethod def _response_hook(**kwargs): @@ -106,7 +98,7 @@ def _error_hook(**kwargs): if json_data['error'] == 'authorization_pending': return None, None, None, json_data, False, False if (json_data['error'] == 'invalid_grant' - and json_data.get('code') == '400'): + and json_data.get('code') == 400): return None, None, None, json_data, False, InvalidGrant(json_data) return None, None, None, json_data, False, LoginException(json_data) @@ -162,12 +154,12 @@ def refresh_token(self, refresh_token, client_id='', client_secret=''): 'grant_type': 'refresh_token'} config_type = self._get_config_type(client_id, client_secret) - client = ''.join([ + client = ''.join(( '(config_type: |', config_type, - '| client_id: |', client_id[:5], '...', client_id[-5:], - '| client_secret: |', client_secret[:5], '...', client_secret[-5:], + '| client_id: |', client_id[:3], '...', client_id[-5:], + '| client_secret: |', client_secret[:3], '...', client_secret[-3:], '|)' - ]) + )) log_debug('Refresh token for {0}'.format(client)) json_data = self.request(self.TOKEN_URL, @@ -211,12 +203,12 @@ def request_access_token(self, code, client_id='', client_secret=''): 'grant_type': 'http://oauth.net/grant_type/device/1.0'} config_type = self._get_config_type(client_id, client_secret) - client = ''.join([ + client = ''.join(( '(config_type: |', config_type, - '| client_id: |', client_id[:5], '...', client_id[-5:], - '| client_secret: |', client_secret[:5], '...', client_secret[-5:], + '| client_id: |', client_id[:3], '...', client_id[-5:], + '| client_secret: |', client_secret[:3], '...', client_secret[-3:], '|)' - ]) + )) log_debug('Requesting access token for {0}'.format(client)) json_data = self.request(self.TOKEN_URL, @@ -249,10 +241,10 @@ def request_device_and_user_code(self, client_id=''): 'scope': 'https://www.googleapis.com/auth/youtube'} config_type = self._get_config_type(client_id) - client = ''.join([ + client = ''.join(( '(config_type: |', config_type, '|', ' client_id: |', client_id[:5], '...|)', - ]) + )) log_debug('Requesting device and user code for {0}'.format(client)) json_data = self.request(self.DEVICE_CODE_URL, diff --git a/resources/lib/youtube_plugin/youtube/client/request_client.py b/resources/lib/youtube_plugin/youtube/client/request_client.py index 442178cef..13c2d5f53 100644 --- a/resources/lib/youtube_plugin/youtube/client/request_client.py +++ b/resources/lib/youtube_plugin/youtube/client/request_client.py @@ -258,13 +258,20 @@ class YouTubeRequestClient(BaseRequestsClass): }, } - def __init__(self, exc_type=None): + def __init__(self, language=None, region=None, exc_type=None, **_kwargs): + common_client = self.CLIENTS['_common']['json']['context']['client'] + # the default language is always en_US (like YouTube on the WEB) + language = language.replace('-', '_') if language else 'en_US' + self._language = common_client['hl'] = language + self._region = common_client['gl'] = region if region else 'US' + if isinstance(exc_type, tuple): exc_type = (YouTubeException,) + exc_type elif exc_type: exc_type = (YouTubeException, exc_type) else: exc_type = (YouTubeException,) + super(YouTubeRequestClient, self).__init__(exc_type=exc_type) @classmethod diff --git a/resources/lib/youtube_plugin/youtube/client/youtube.py b/resources/lib/youtube_plugin/youtube/client/youtube.py index ccb49226f..21958d354 100644 --- a/resources/lib/youtube_plugin/youtube/client/youtube.py +++ b/resources/lib/youtube_plugin/youtube/client/youtube.py @@ -52,6 +52,14 @@ class YouTube(LoginClient): }, '_common': { '_access_token': None, + 'json': { + 'context': { + 'client': { + 'gl': None, + 'hl': None, + }, + }, + }, 'headers': { 'Accept-Encoding': 'gzip, deflate', 'Accept-Charset': 'ISO-8859-1,utf-8;q=0.7,*;q=0.7', @@ -72,8 +80,6 @@ class YouTube(LoginClient): def __init__(self, context, **kwargs): self._context = context - if not kwargs.get('config'): - kwargs['config'] = {} if 'items_per_page' in kwargs: self._max_results = kwargs.pop('items_per_page') @@ -1578,10 +1584,12 @@ def _response_hook(self, **kwargs): try: json_data = response.json() if 'error' in json_data: + kwargs.setdefault('pass_data', True) raise YouTubeException('"error" in response JSON data', json_data=json_data, **kwargs) except ValueError as exc: + kwargs.setdefault('raise_exc', True) raise InvalidJSON(exc, **kwargs) response.raise_for_status() return json_data @@ -1589,8 +1597,14 @@ def _response_hook(self, **kwargs): def _error_hook(self, **kwargs): exc = kwargs['exc'] json_data = getattr(exc, 'json_data', None) - data = getattr(exc, 'pass_data', None) and json_data - exception = getattr(exc, 'raise_exc', None) and YouTubeException + if getattr(exc, 'pass_data', False): + data = json_data + else: + data = None + if getattr(exc, 'raise_exc', False): + exception = YouTubeException + else: + exception = None if not json_data or 'error' not in json_data: return None, None, None, data, None, exception @@ -1599,8 +1613,7 @@ def _error_hook(self, **kwargs): reason = details.get('errors', [{}])[0].get('reason', 'Unknown') message = strip_html_from_text(details.get('message', 'Unknown error')) - notify = getattr(exc, 'notify', True) - if notify: + if getattr(exc, 'notify', True): ok_dialog = False timeout = 5000 if reason == 'accessNotConfigured': @@ -1644,7 +1657,7 @@ def api_request(self, } if headers: client_data['headers'] = headers - if post_data: + if post_data and method == 'POST': client_data['json'] = post_data if params: client_data['params'] = params @@ -1659,15 +1672,17 @@ def api_request(self, if 'key' in client['params'] and not client['params']['key']: client['params']['key'] = self._config_tv['key'] + if method != 'POST' and 'json' in client: + del client['json'] + params = client.get('params') if params: log_params = deepcopy(params) if 'location' in log_params: - log_params['location'] = 'xx.xxxx,xx.xxxx' + log_params['location'] = '|xx.xxxx,xx.xxxx|' if 'key' in log_params: - key = list(log_params['key']) - key[5:-5] = '...' - log_params['key'] = ''.join(key) + key = log_params['key'] + log_params['key'] = '...'.join((key[:3], key[-3:])) else: log_params = None @@ -1675,7 +1690,7 @@ def api_request(self, if headers: log_headers = deepcopy(headers) if 'Authorization' in log_headers: - log_headers['Authorization'] = 'logged in' + log_headers['Authorization'] = '|logged in|' else: log_headers = None diff --git a/resources/lib/youtube_plugin/youtube/helper/__init__.py b/resources/lib/youtube_plugin/youtube/helper/__init__.py index 12b93382c..3fff8d215 100644 --- a/resources/lib/youtube_plugin/youtube/helper/__init__.py +++ b/resources/lib/youtube_plugin/youtube/helper/__init__.py @@ -13,4 +13,3 @@ from .resource_manager import ResourceManager from .url_resolver import UrlResolver from .url_to_item_converter import UrlToItemConverter -from .utils import extract_urls diff --git a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py index 01f52ee4b..05ac7f873 100644 --- a/resources/lib/youtube_plugin/youtube/helper/resource_manager.py +++ b/resources/lib/youtube_plugin/youtube/helper/resource_manager.py @@ -16,7 +16,7 @@ def __init__(self, context, client): self._context = context self._client = client self._data_cache = context.get_data_cache() - self._func_cache = context.get_function_cache() + self._function_cache = context.get_function_cache() self._show_fanart = context.get_settings().get_bool( 'youtube.channel.fanart.show', True ) @@ -29,11 +29,8 @@ def _list_batch(input_list, n=50): for i in range(0, len(input_list), n): yield input_list[i:i + n] - def clear(self): - self._func_cache.clear() - self._data_cache.clear() - def get_channels(self, ids, defer_cache=False): + refresh = self._context.get_param('refresh') updated = [] for channel_id in ids: if not channel_id: @@ -43,9 +40,12 @@ def get_channels(self, ids, defer_cache=False): updated.append(channel_id) continue - data = self._func_cache.get(self._client.get_channel_by_username, - self._func_cache.ONE_DAY, - channel_id) + data = self._function_cache.run( + self._client.get_channel_by_username, + self._function_cache.ONE_DAY, + _refresh=refresh, + username=channel_id + ) items = data.get('items', [{'id': 'mine'}]) try: @@ -56,7 +56,10 @@ def get_channels(self, ids, defer_cache=False): .format(data=data)) ids = updated - result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH) + if refresh: + result = {} + else: + result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH) to_update = [id_ for id_ in ids if id_ not in result or result[id_].get('partial')] @@ -119,7 +122,11 @@ def get_fanarts(self, channel_ids, defer_cache=False): def get_playlists(self, ids, defer_cache=False): ids = tuple(ids) - result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH) + refresh = self._context.get_param('refresh') + if refresh: + result = {} + else: + result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH) to_update = [id_ for id_ in ids if id_ not in result or result[id_].get('partial')] @@ -162,6 +169,8 @@ def get_playlist_items(self, ids=None, batch_id=None, defer_cache=False): if not ids and not batch_id: return None + refresh = self._context.get_param('refresh') + if batch_id: ids = [batch_id[0]] page_token = batch_id[1] @@ -178,8 +187,14 @@ def get_playlist_items(self, ids=None, batch_id=None, defer_cache=False): while 1: batch_id = (playlist_id, page_token) batch_ids.append(batch_id) - batch = self._data_cache.get_item(batch_id, - self._data_cache.ONE_HOUR) + if refresh: + batch = None + else: + batch = self._data_cache.get_item( + batch_id, + self._data_cache.ONE_HOUR if page_token + else self._data_cache.ONE_MINUTE * 5 + ) if not batch: to_update.append(batch_id) break @@ -228,7 +243,7 @@ def get_playlist_items(self, ids=None, batch_id=None, defer_cache=False): return result def get_related_playlists(self, channel_id, defer_cache=False): - result = self.get_channels([channel_id], defer_cache=defer_cache) + result = self.get_channels((channel_id,), defer_cache=defer_cache) # transform item = None @@ -250,7 +265,11 @@ def get_videos(self, suppress_errors=False, defer_cache=False): ids = tuple(ids) - result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH) + refresh = self._context.get_param('refresh') + if refresh: + result = {} + else: + result = self._data_cache.get_items(ids, self._data_cache.ONE_MONTH) to_update = [id_ for id_ in ids if id_ not in result or result[id_].get('partial')] diff --git a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py index a8549afcd..f6f59b0a7 100644 --- a/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py +++ b/resources/lib/youtube_plugin/youtube/helper/signature/cipher.py @@ -25,12 +25,9 @@ def __init__(self, context, javascript): def get_signature(self, signature): function_cache = self._context.get_function_cache() - json_script = function_cache.get_cached_only(self._load_javascript, - self._javascript) - if not json_script: - json_script = function_cache.get(self._load_javascript, - function_cache.ONE_DAY, - self._javascript) + json_script = function_cache.run(self._load_javascript, + function_cache.ONE_DAY, + javascript=self._javascript) if json_script: json_script_engine = JsonScriptEngine(json_script) diff --git a/resources/lib/youtube_plugin/youtube/helper/tv.py b/resources/lib/youtube_plugin/youtube/helper/tv.py index 6c532038c..d87151f89 100644 --- a/resources/lib/youtube_plugin/youtube/helper/tv.py +++ b/resources/lib/youtube_plugin/youtube/helper/tv.py @@ -41,7 +41,7 @@ def my_subscriptions_to_items(provider, context, json_data, do_filter=False): or (not black_list and channel in filter_list)): video_id = item['id'] item_params['video_id'] = video_id - item_uri = context.create_uri(['play'], item_params) + item_uri = context.create_uri(('play',), item_params) video_item = VideoItem(item['title'], uri=item_uri) if incognito: video_item.set_play_count(0) @@ -89,7 +89,7 @@ def tv_videos_to_items(provider, context, json_data): for item in items: video_id = item['id'] item_params['video_id'] = video_id - item_uri = context.create_uri(['play'], item_params) + item_uri = context.create_uri(('play',), item_params) video_item = VideoItem(item['title'], uri=item_uri) if incognito: video_item.set_play_count(0) @@ -144,12 +144,12 @@ def saved_playlists_to_items(provider, context, json_data): if channel_id: item_uri = context.create_uri( - ['channel', channel_id, 'playlist', playlist_id], + ('channel', channel_id, 'playlist', playlist_id,), item_params, ) else: item_uri = context.create_uri( - ['playlist', playlist_id], + ('playlist', playlist_id), item_params, ) diff --git a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py index 73b4eeb4a..8d744560d 100644 --- a/resources/lib/youtube_plugin/youtube/helper/url_resolver.py +++ b/resources/lib/youtube_plugin/youtube/helper/url_resolver.py @@ -205,7 +205,7 @@ def resolve(self, url, url_components, method='HEAD'): class UrlResolver(object): def __init__(self, context): self._context = context - self._cache = context.get_function_cache() + self._function_cache = context.get_function_cache() self._resolver_map = { 'common_resolver': CommonResolver(context), 'youtube_resolver': YouTubeResolver(context), @@ -215,9 +215,6 @@ def __init__(self, context): 'youtube_resolver', ] - def clear(self): - self._cache.clear() - def _resolve(self, url): # try one of the resolvers resolved_url = url @@ -239,7 +236,12 @@ def _resolve(self, url): return resolved_url def resolve(self, url): - resolved_url = self._cache.get(self._resolve, self._cache.ONE_DAY, url) + resolved_url = self._function_cache.run( + self._resolve, + self._function_cache.ONE_DAY, + _refresh=self._context.get_param('refresh'), + url=url + ) if not resolved_url or resolved_url == '/': return url diff --git a/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py b/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py index 0bde8abe2..c0580b9e5 100644 --- a/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py +++ b/resources/lib/youtube_plugin/youtube/helper/url_to_item_converter.py @@ -85,7 +85,7 @@ def add_url(self, url, context): video_id = new_params['video_id'] video_item = VideoItem( - '', context.create_uri(['play'], new_params) + '', context.create_uri(('play',), new_params) ) self._video_id_dict[video_id] = video_item @@ -97,7 +97,7 @@ def add_url(self, url, context): return playlist_item = DirectoryItem( - '', context.create_uri(['playlist', playlist_id], new_params), + '', context.create_uri(('playlist', playlist_id,), new_params), ) self._playlist_id_dict[playlist_id] = playlist_item @@ -110,9 +110,9 @@ def add_url(self, url, context): return channel_item = VideoItem( - '', context.create_uri(['play'], new_params) + '', context.create_uri(('play',), new_params) ) if live else DirectoryItem( - '', context.create_uri(['channel', channel_id], new_params) + '', context.create_uri(('channel', channel_id,), new_params) ) self._channel_id_dict[channel_id] = channel_item @@ -130,12 +130,17 @@ def get_items(self, provider, context, skip_title=False): # remove duplicates self._channel_ids = list(set(self._channel_ids)) + item_label = context.localize('channels') channels_item = DirectoryItem( - context.get_ui().bold(context.localize('channels')), - context.create_uri(['special', 'description_links'], { - 'channel_ids': ','.join(self._channel_ids), - }), - image='{media}/playlist.png' + context.get_ui().bold(item_label), + context.create_uri( + ('special', 'description_links',), + { + 'channel_ids': ','.join(self._channel_ids), + }, + ), + image='{media}/playlist.png', + category_label=item_label, ) result.append(channels_item) @@ -143,19 +148,30 @@ def get_items(self, provider, context, skip_title=False): # remove duplicates self._playlist_ids = list(set(self._playlist_ids)) - playlists_item = UriItem( - context.create_uri(['play'], { - 'playlist_ids': ','.join(self._playlist_ids), - 'play': True, - }), - playable=True - ) if context.get_param('uri') else DirectoryItem( - context.get_ui().bold(context.localize('playlists')), - context.create_uri(['special', 'description_links'], { - 'playlist_ids': ','.join(self._playlist_ids), - }), - image='{media}/playlist.png' - ) + if context.get_param('uri'): + playlists_item = UriItem( + context.create_uri( + ('play',), + { + 'playlist_ids': ','.join(self._playlist_ids), + 'play': True, + }, + ), + playable=True, + ) + else: + item_label = context.localize('playlists') + playlists_item = DirectoryItem( + context.get_ui().bold(item_label), + context.create_uri( + ('special', 'description_links',), + { + 'playlist_ids': ','.join(self._playlist_ids), + }, + ), + image='{media}/playlist.png', + category_label=item_label, + ) result.append(playlists_item) if self._channel_id_dict: diff --git a/resources/lib/youtube_plugin/youtube/helper/utils.py b/resources/lib/youtube_plugin/youtube/helper/utils.py index 29c4ddba0..c956915c6 100644 --- a/resources/lib/youtube_plugin/youtube/helper/utils.py +++ b/resources/lib/youtube_plugin/youtube/helper/utils.py @@ -524,7 +524,7 @@ def update_video_infos(provider, context, video_id_dict, else ui.new_line(start_at, cr_after=1)) if start_at else '', description, )) - video_item.set_studio(channel_name) + # video_item.set_studio(channel_name) # video_item.add_cast(channel_name) video_item.add_artist(channel_name) video_item.set_plot(description) @@ -607,8 +607,8 @@ def update_video_infos(provider, context, video_id_dict, ) ) - # provide 'remove' for videos in my playlists - # we support all playlist except 'Watch History' + # provide 'remove' for videos in my playlists + # we support all playlist except 'Watch History' if (logged_in and video_id in playlist_item_id_dict and playlist_id and playlist_channel_id == 'mine' and playlist_id.strip().lower() not in ('hl', 'wl')): @@ -621,7 +621,6 @@ def update_video_infos(provider, context, video_id_dict, ) ) - # got to [CHANNEL] only if we are not directly in the channel if (channel_id and channel_name and create_path('channel', channel_id) != path): @@ -834,5 +833,5 @@ def filter_short_videos(items): return [ item for item in items - if item.playable and not 0 <= item.get_duration() <= 60 + if not item.playable or not 0 <= item.get_duration() <= 60 ] diff --git a/resources/lib/youtube_plugin/youtube/helper/v3.py b/resources/lib/youtube_plugin/youtube/helper/v3.py index 439ce29c9..80b849c37 100644 --- a/resources/lib/youtube_plugin/youtube/helper/v3.py +++ b/resources/lib/youtube_plugin/youtube/helper/v3.py @@ -28,7 +28,7 @@ def _process_list_response(provider, context, json_data): yt_items = json_data.get('items', []) if not yt_items: - context.log_warning('List of search result is empty') + context.log_warning('v3 response: Items list is empty') return [] video_id_dict = {} diff --git a/resources/lib/youtube_plugin/youtube/helper/video_info.py b/resources/lib/youtube_plugin/youtube/helper/video_info.py index 64088ff27..d031dfc0d 100644 --- a/resources/lib/youtube_plugin/youtube/helper/video_info.py +++ b/resources/lib/youtube_plugin/youtube/helper/video_info.py @@ -588,22 +588,18 @@ class VideoInfo(YouTubeRequestClient): 'video': {'height': 0, 'encoding': ''}} } - def __init__(self, context, access_token='', language='en-US'): - settings = context.get_settings() - + def __init__(self, context, access_token='', **kwargs): self.video_id = None self._context = context self._data_cache = self._context.get_data_cache() - self._language = (settings.get_string('youtube.language', language) - .replace('-', '_')) - self._language_base = self._language[0:2] + self._language_base = kwargs.get('language', 'en_US')[0:2] self._access_token = access_token self._player_js = None self._calculate_n = True self._cipher = None self._selected_client = None - client_selection = settings.client_selection() + client_selection = context.get_settings().client_selection() # Default client selection uses the Android or iOS client as the first # option to ensure that the age gate setting is enforced, regardless of @@ -642,12 +638,7 @@ def __init__(self, context, access_token='', language='en-US'): 'android_embedded', ) - self.CLIENTS['_common']['json']['context']['client'] = { - 'hl': self._language, - 'gl': settings.get_string('youtube.region', 'US'), - } - - super(VideoInfo, self).__init__() + super(VideoInfo, self).__init__(**kwargs) @staticmethod def _generate_cpn(): diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_login.py b/resources/lib/youtube_plugin/youtube/helper/yt_login.py index ac7b2bbf8..b953d57e4 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_login.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_login.py @@ -20,9 +20,6 @@ def process(mode, provider, context, sign_out_refresh=True): addon_id = context.get_param('addon_id', None) def _do_logout(): - # we clear the cache, so none cached data of an old account will be displayed. - provider.get_resource_manager(context).clear() - signout_access_manager = context.get_access_manager() if addon_id: if signout_access_manager.developer_has_refresh_token(addon_id): @@ -154,9 +151,6 @@ def _do_login(_for_tv=False): refresh_token = '%s|%s' % (refresh_token_tv, refresh_token_kodi) expires_in = min(expires_in_tv, expires_in_kodi) - # we clear the cache, so none cached data of an old account will be displayed. - provider.get_resource_manager(context).clear() - provider.reset_client() if addon_id: diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py b/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py deleted file mode 100644 index c7501eb77..000000000 --- a/resources/lib/youtube_plugin/youtube/helper/yt_old_actions.py +++ /dev/null @@ -1,67 +0,0 @@ -# -*- coding: utf-8 -*- -""" - - Copyright (C) 2014-2016 bromix (plugin.video.youtube) - Copyright (C) 2016-2018 plugin.video.youtube - - SPDX-License-Identifier: GPL-2.0-only - See LICENSES/GPL-2.0-only for more information. -""" - -from __future__ import absolute_import, division, unicode_literals - -from ... import kodion - - -def _process_play_video(provider, context, re_match): - """ - plugin://plugin.video.youtube/?action=play_video&videoid=[ID] - """ - video_id = context.get_param('videoid', '') - if not video_id: - raise kodion.KodionException('old_actions/play_video: missing video_id') - - context.log_warning('DEPRECATED "%s"' % context.get_uri()) - context.log_warning('USE INSTEAD "plugin://%s/play/?video_id=%s"' % (context.get_id(), video_id)) - new_params = {'video_id': video_id} - new_path = '/play/' - new_context = context.clone(new_path=new_path, new_params=new_params) - return provider.on_play(new_context, re_match) - - -def _process_play_all(provider, context, re_match): - """ - plugin://plugin.video.youtube/?path=/root/video&action=play_all&playlist=PL8_6CHho8Tq4Iie-oNxb-g0ECxIhq3CxW - plugin://plugin.video.youtube/?action=play_all&playlist=PLZRRxQcaEjA5fgfW3a3Q0rzm6NgbmICtg&videoid=qmlYe2KS0-Y - """ - playlist_id = context.get_param('playlist', '') - if not playlist_id: - raise kodion.KodionException('old_actions/play_all: missing playlist_id') - - # optional starting video id of the playlist - video_id = context.get_param('videoid', '') - if video_id: - context.log_warning( - 'USE INSTEAD "plugin://%s/play/?playlist_id=%s&video_id=%s"' % (context.get_id(), playlist_id, video_id)) - else: - context.log_warning('USE INSTEAD "plugin://%s/play/?playlist_id=%s"' % (context.get_id(), playlist_id)) - new_params = {'playlist_id': playlist_id} - new_path = '/play/' - new_context = context.clone(new_path=new_path, new_params=new_params) - return provider.on_play(new_context, re_match) - - -def process_old_action(provider, context, re_match): - """ - if context.get_system_version().get_version() >= (15, 0): - message = u"You're using old YouTube-Plugin calls - please review the log for updated end points starting with Isengard" - context.get_ui().show_notification(message, time_ms=15000) - """ - - action = context.get_param('action', '') - if action == 'play_video': - return _process_play_video(provider, context, re_match) - elif action == 'play_all': - return _process_play_all(provider, context, re_match) - else: - raise kodion.KodionException('old_actions: unknown action "%s"' % action) diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py index c3cf87cf0..cbd880c40 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_playlist.py @@ -141,12 +141,13 @@ def _process_select_playlist(provider, context): # Get listitem path asap, relies on listitems focus path = context.get_infolabel('Container.ListItem(0).FileNameAndPath') + params = context.get_params() ui = context.get_ui() keymap_action = False page_token = '' current_page = 0 - video_id = context.get_param('video_id', '') + video_id = params.get('video_id', '') if not video_id: if context.is_plugin_path(path, 'play/'): video_id = find_video_id(path) @@ -160,18 +161,14 @@ def _process_select_playlist(provider, context): client = provider.get_client(context) while True: current_page += 1 - if not page_token: - json_data = function_cache.get(client.get_playlists_of_channel, - function_cache.ONE_MINUTE // 3, - channel_id='mine') - else: - json_data = function_cache.get(client.get_playlists_of_channel, - function_cache.ONE_MINUTE // 3, - channel_id='mine', - page_token=page_token) + json_data = function_cache.run(client.get_playlists_of_channel, + function_cache.ONE_MINUTE // 3, + _refresh=params.get('refresh'), + channel_id='mine', + page_token=page_token) playlists = json_data.get('items', []) - page_token = json_data.get('nextPageToken', False) + page_token = json_data.get('nextPageToken', '') items = [] if current_page == 1: diff --git a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py index 1f5cc53b3..f388a5d84 100644 --- a/resources/lib/youtube_plugin/youtube/helper/yt_specials.py +++ b/resources/lib/youtube_plugin/youtube/helper/yt_specials.py @@ -10,14 +10,7 @@ from __future__ import absolute_import, division, unicode_literals -from . import utils -from ..helper import ( - UrlResolver, - UrlToItemConverter, - extract_urls, - tv, - v3, -) +from . import UrlResolver, UrlToItemConverter, tv, utils, v3 from ...kodion import KodionException from ...kodion.constants import content from ...kodion.items import DirectoryItem, UriItem @@ -28,19 +21,22 @@ def _process_related_videos(provider, context): context.set_content(content.VIDEO_CONTENT) function_cache = context.get_function_cache() - video_id = context.get_param('video_id', '') + params = context.get_params() + video_id = params.get('video_id', '') if video_id: - json_data = function_cache.get( + json_data = function_cache.run( provider.get_client(context).get_related_videos, function_cache.ONE_HOUR, + _refresh=params.get('refresh'), video_id=video_id, - page_token=context.get_param('page_token', ''), + page_token=params.get('page_token', ''), ) else: - json_data = function_cache.get( + json_data = function_cache.run( provider.get_client(context).get_related_for_home, function_cache.ONE_HOUR, - page_token=context.get_param('page_token', ''), + _refresh=params.get('refresh'), + page_token=params.get('page_token', ''), ) if not json_data: @@ -85,9 +81,10 @@ def _process_recommendations(provider, context): params = context.get_params() function_cache = context.get_function_cache() - json_data = function_cache.get( + json_data = function_cache.run( provider.get_client(context).get_recommended_for_home, function_cache.ONE_HOUR, + _refresh=params.get('refresh'), visitor=params.get('visitor', ''), page_token=params.get('page_token', ''), click_tracking=params.get('click_tracking', ''), @@ -119,7 +116,7 @@ def _process_browse_channels(provider, context): json_data = client.get_guide_category(guide_id) else: function_cache = context.get_function_cache() - json_data = function_cache.get(client.get_guide_categories, + json_data = function_cache.run(client.get_guide_categories, function_cache.ONE_MONTH) if not json_data: @@ -183,9 +180,10 @@ def _extract_urls(video_id): description = strip_html_from_text(snippet['description']) function_cache = context.get_function_cache() - urls = function_cache.get(extract_urls, - function_cache.ONE_WEEK, - description) + urls = function_cache.run(utils.extract_urls, + function_cache.ONE_DAY, + _refresh=params.get('refresh'), + text=description) progress_dialog.set_total(len(urls)) @@ -221,7 +219,7 @@ def _display_channels(channel_ids): channel_id_dict = {} for channel_id in channel_ids: channel_item = DirectoryItem( - '', context.create_uri(['channel', channel_id], item_params) + '', context.create_uri(('channel', channel_id,), item_params) ) channel_id_dict[channel_id] = channel_item @@ -246,7 +244,7 @@ def _display_playlists(playlist_ids): playlist_id_dict = {} for playlist_id in playlist_ids: playlist_item = DirectoryItem( - '', context.create_uri(['playlist', playlist_id], item_params) + '', context.create_uri(('playlist', playlist_id,), item_params) ) playlist_id_dict[playlist_id] = playlist_item diff --git a/resources/lib/youtube_plugin/youtube/provider.py b/resources/lib/youtube_plugin/youtube/provider.py index 26517f7c9..52e3f1ce9 100644 --- a/resources/lib/youtube_plugin/youtube/provider.py +++ b/resources/lib/youtube_plugin/youtube/provider.py @@ -21,7 +21,6 @@ UrlToItemConverter, v3, yt_login, - yt_old_actions, yt_play, yt_playlist, yt_setup_wizard, @@ -136,9 +135,9 @@ def get_client(self, context): origin = ADDON_ID if api_last_origin != origin: - context.log_debug('API key origin changed, clearing cache. |%s|' % origin) + context.log_debug('API key origin changed: |{old}| to |{new}|' + .format(old=api_last_origin, new=origin)) access_manager.set_last_origin(origin) - self.get_resource_manager(context).clear() if dev_id: access_tokens = access_manager.get_dev_access_token(dev_id).split('|') @@ -173,10 +172,15 @@ def get_client(self, context): access_tokens = access_manager.get_dev_access_token(dev_id) if access_tokens: access_tokens = access_tokens.split('|') + else: + access_tokens = [] refresh_tokens = access_manager.get_dev_refresh_token(dev_id) if refresh_tokens: refresh_tokens = refresh_tokens.split('|') + else: + refresh_tokens = [] + context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens))) else: context.log_debug('Selecting YouTube config "%s"' % youtube_config['system']) @@ -190,10 +194,15 @@ def get_client(self, context): access_tokens = access_manager.get_access_token() if access_tokens: access_tokens = access_tokens.split('|') + else: + access_tokens = [] refresh_tokens = access_manager.get_refresh_token() if refresh_tokens: refresh_tokens = refresh_tokens.split('|') + else: + refresh_tokens = [] + context.log_debug('Access token count: |%d| Refresh token count: |%d|' % (len(access_tokens), len(refresh_tokens))) client = YouTube(context=context, @@ -220,7 +229,6 @@ def get_client(self, context): access_manager.update_access_token(access_token, expires_in) except (InvalidGrant, LoginException) as exc: self.handle_exception(context, exc) - access_tokens = ['', ''] # reset access_token if isinstance(exc, InvalidGrant): if dev_id: @@ -231,18 +239,17 @@ def get_client(self, context): access_manager.update_dev_access_token(dev_id, '') else: access_manager.update_access_token('') - # we clear the cache, so none cached data of an old account will be displayed. - self.get_resource_manager(context).clear() # in debug log the login status self._logged_in = len(access_tokens) == 2 - context.log_debug('User is logged in' if self._logged_in else - 'User is not logged in') - - if not access_tokens: - access_tokens = ['', ''] - client.set_access_token(access_token=access_tokens[1]) - client.set_access_token_tv(access_token_tv=access_tokens[0]) + if self._logged_in: + context.log_debug('User is logged in') + client.set_access_token_tv(access_token_tv=access_tokens[0]) + client.set_access_token(access_token=access_tokens[1]) + else: + context.log_debug('User is not logged in') + client.set_access_token_tv(access_token_tv='') + client.set_access_token(access_token='') self._client = client return self._client @@ -322,13 +329,15 @@ def _on_channel_playlists(self, context, re_match): playlists = resource_manager.get_related_playlists(channel_id) uploads_playlist = playlists.get('uploads', '') if uploads_playlist: + item_label = context.localize('uploads') uploads_item = DirectoryItem( - context.get_ui().bold(context.localize('uploads')), + context.get_ui().bold(item_label), context.create_uri( - ['channel', channel_id, 'playlist', uploads_playlist], - new_params + ('channel', channel_id, 'playlist', uploads_playlist), + new_params, ), image='{media}/playlist.png', + category_label=item_label, ) result.append(uploads_item) @@ -384,6 +393,7 @@ def _on_channel(self, context, re_match): localize = context.localize create_uri = context.create_uri function_cache = context.get_function_cache() + params = context.get_params() ui = context.get_ui() method = re_match.group('method') @@ -414,9 +424,10 @@ def _on_channel(self, context, re_match): if method == 'user' or channel_id == 'mine': context.log_debug('Trying to get channel id for user "%s"' % channel_id) - json_data = function_cache.get(client.get_channel_by_username, + json_data = function_cache.run(client.get_channel_by_username, function_cache.ONE_DAY, - channel_id) + _refresh=params.get('refresh'), + username=channel_id) if not json_data: return False @@ -434,7 +445,6 @@ def _on_channel(self, context, re_match): channel_fanarts = resource_manager.get_fanarts((channel_id, )) - params = context.get_params() page = params.get('page', 1) page_token = params.get('page_token', '') incognito = params.get('incognito') @@ -454,11 +464,16 @@ def _on_channel(self, context, re_match): hide_live = params.get('hide_live') if not hide_playlists: + item_label = localize('playlists') playlists_item = DirectoryItem( - ui.bold(localize('playlists')), - create_uri(['channel', channel_id, 'playlists'], new_params), + ui.bold(item_label), + create_uri( + ('channel', channel_id, 'playlists'), + new_params, + ), image='{media}/playlist.png', fanart=channel_fanarts.get(channel_id), + category_label=item_label, ) result.append(playlists_item) @@ -474,19 +489,22 @@ def _on_channel(self, context, re_match): result.append(search_item) if not hide_live: + item_label = localize('live') live_item = DirectoryItem( - ui.bold(localize('live')), - create_uri(['channel', search_live_id, 'live'], new_params), + ui.bold(item_label), + create_uri(('channel', search_live_id, 'live'), new_params), image='{media}/live.png', + category_label=item_label, ) result.append(live_item) playlists = resource_manager.get_related_playlists(channel_id) upload_playlist = playlists.get('uploads', '') if upload_playlist: - json_data = function_cache.get(client.get_playlist_items, + json_data = function_cache.run(client.get_playlist_items, function_cache.ONE_MINUTE * 5, - upload_playlist, + _refresh=params.get('refresh'), + playlist_id=upload_playlist, page_token=page_token) if not json_data: return result @@ -518,8 +536,8 @@ def _on_my_location(self, context, re_match): live_events_item = DirectoryItem( localize('live.completed'), create_uri( - ['special', 'completed_live'], - params={'location': True} + ('special', 'completed_live'), + params={'location': True}, ), image='{media}/live.png', ) @@ -530,8 +548,8 @@ def _on_my_location(self, context, re_match): live_events_item = DirectoryItem( localize('live.upcoming'), create_uri( - ['special', 'upcoming_live'], - params={'location': True} + ('special', 'upcoming_live'), + params={'location': True}, ), image='{media}/live.png', ) @@ -541,8 +559,8 @@ def _on_my_location(self, context, re_match): live_events_item = DirectoryItem( localize('live'), create_uri( - ['special', 'live'], - params={'location': True} + ('special', 'live'), + params={'location': True}, ), image='{media}/live.png', ) @@ -627,7 +645,7 @@ def on_play(self, context, re_match): if builtin: context.execute(builtin.format( - context.create_uri(['play'], params) + context.create_uri(('play',), params) )) return False return yt_play.play_video(self, context) @@ -748,19 +766,23 @@ def on_search(self, search_text, context, re_match): if page == 1 and search_type == 'video' and not event_type and not hide_folders: if not channel_id and not location: channel_params = dict(params, search_type='channel') + item_label = context.localize('channels') channel_item = DirectoryItem( - context.get_ui().bold(context.localize('channels')), - context.create_uri([context.get_path()], channel_params), + context.get_ui().bold(item_label), + context.create_uri((context.get_path(),), channel_params), image='{media}/channels.png', + category_label=item_label, ) result.append(channel_item) if not location: playlist_params = dict(params, search_type='playlist') + item_label = context.localize('playlists') playlist_item = DirectoryItem( - context.get_ui().bold(context.localize('playlists')), - context.create_uri([context.get_path()], playlist_params), + context.get_ui().bold(item_label), + context.create_uri((context.get_path(),), playlist_params), image='{media}/playlist.png', + category_label=item_label, ) result.append(playlist_item) @@ -769,19 +791,22 @@ def on_search(self, search_text, context, re_match): live_params = dict(params, search_type='video', event_type='live') + item_label = context.localize('live') live_item = DirectoryItem( - context.get_ui().bold(context.localize('live')), + context.get_ui().bold(item_label), context.create_uri( - [context.get_path().replace('input', 'query')], - live_params + (context.get_path().replace('input', 'query'),), + live_params, ), image='{media}/live.png', + category_label=item_label, ) result.append(live_item) function_cache = context.get_function_cache() - json_data = function_cache.get(self.get_client(context).search, + json_data = function_cache.run(self.get_client(context).search, function_cache.ONE_MINUTE * 10, + _refresh=params.get('refresh'), q=search_text, search_type=search_type, event_type=event_type, @@ -860,7 +885,6 @@ def maintenance_actions(self, context, re_match): context.get_name(), localize('reset.access_manager.confirm') )): try: - context.get_function_cache().clear() access_manager = context.get_access_manager() client = self.get_client(context) if access_manager.has_refresh_token(): @@ -1017,13 +1041,6 @@ def on_playback_history(self, context, re_match): return True def on_root(self, context, re_match): - """ - Support old YouTube url calls, but also log a deprecation warnings. - """ - old_action = context.get_param('action') - if old_action: - return yt_old_actions.process_old_action(self, context, re_match) - create_uri = context.create_uri localize = context.localize settings = context.get_settings() @@ -1038,25 +1055,24 @@ def on_root(self, context, re_match): # sign in if not logged_in and settings.get_bool('youtube.folder.sign.in.show', True): + item_label = localize('sign.in') sign_in_item = DirectoryItem( - ui.bold(localize('sign.in')), + ui.bold(item_label), create_uri(('sign', 'in')), image='{media}/sign_in.png', - action=True + action=True, + category_label=item_label, ) result.append(sign_in_item) if logged_in and settings.get_bool('youtube.folder.my_subscriptions.show', True): # my subscription - - # clear cache - cache = context.get_data_cache() - cache.set_item('my-subscriptions-items', '[]') - + item_label = localize('my_subscriptions') my_subscriptions_item = DirectoryItem( - ui.bold(localize('my_subscriptions')), + ui.bold(item_label), create_uri(('special', 'new_uploaded_videos_tv')), image='{media}/new_uploads.png', + category_label=item_label, ) result.append(my_subscriptions_item) @@ -1212,7 +1228,7 @@ def on_root(self, context, re_match): elif local_history: watch_history_item = DirectoryItem( localize('history'), - create_uri([paths.HISTORY], params={'action': 'list'}), + create_uri((paths.HISTORY,), params={'action': 'list'}), image='{media}/history.png', ) result.append(watch_history_item)