From 4095916a2e19e2acac163780caaa3f843f75c6b7 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Sun, 24 Jul 2016 22:51:14 -0400 Subject: [PATCH 01/17] Added duration to ServiceCode Duration was previously missing from both MetadataObjectForURL and MediaObjectsForURL. I am now obtaining a JSON object with more metadata, fixing this problem. xPath is still used as a backup. --- Contents/Services/URL/pornhub/ServiceCode.pys | 104 ++++++++++++------ 1 file changed, 70 insertions(+), 34 deletions(-) diff --git a/Contents/Services/URL/pornhub/ServiceCode.pys b/Contents/Services/URL/pornhub/ServiceCode.pys index ded182d..2218690 100644 --- a/Contents/Services/URL/pornhub/ServiceCode.pys +++ b/Contents/Services/URL/pornhub/ServiceCode.pys @@ -1,57 +1,93 @@ -from collections import OrderedDict +import json -PH_POTENTIAL_RESOLUTIONS = ["1080", "720", "480", "240", "180"] +PH_POTENTIAL_RESOLUTIONS = ["1080", "720", "480", "240", "180"] -PH_VIDEO_URL_REGEX = "var player_quality_%sp = '([^']+)'" +PH_VIDEO_URL_REGEX = "var player_quality_%sp = '([^']+)'" +PH_VIDEO_METADATA_JSON_REGEX = "var flashvars_\d+ = ({[\S\s]+?});" def NormalizeURL(url): return url def MetadataObjectForURL(url): - + + # Get the HTML string from the given URL html = HTML.ElementFromURL(url) + htmlString = HTML.StringFromElement(html) + + # Search for the video metadata JSON string + videoMetaDataString = Regex(PH_VIDEO_METADATA_JSON_REGEX).search(htmlString) + + if (videoMetaDataString): + # If found, convert the JSON string to an object + videoMetaData = json.loads(videoMetaDataString.group(1)) + + return VideoClipObject( + title = videoMetaData["video_title"], + summary = videoMetaData["video_title"], + thumb = Resource.ContentsOfURLWithFallback(videoMetaData["image_url"], fallback='icon-default.jpg'), + content_rating = 'X', + duration = int(videoMetaData["video_duration"]) * 1000 + ) + else: + # Fall back to old xPath method + title = html.xpath('//title/text()')[0].strip() + thumbnail = html.xpath('//meta[@property="og:image"]/@content')[0].strip() + #tags = html.xpath('//div[@id="media-tags-container"]/h4/a/text()') - title = html.xpath('//title/text()')[0].strip() - thumbnail = html.xpath('//meta[@property="og:image"]/@content')[0].strip() - #tags = html.xpath('//div[@id="media-tags-container"]/h4/a/text()') - - return VideoClipObject( - title = title, - summary = title, - thumb = Resource.ContentsOfURLWithFallback([thumbnail], fallback='icon-default.jpg'), - content_rating = 'X' - #tags = tags - ) + return VideoClipObject( + title = title, + summary = title, + thumb = Resource.ContentsOfURLWithFallback(thumbnail, fallback='icon-default.jpg'), + content_rating = 'X' + #tags = tags + ) @deferred def MediaObjectsForURL(url): - availableResolutions = OrderedDict([]) + # The list of MediaObjects to be returned mediaObjects = [] - data = HTTP.Request(url).content + # Get the HTML string from the given URL + html = HTML.ElementFromURL(url) + htmlString = HTML.StringFromElement(html) + + # Search for the video metadata JSON string + videoMetaDataString = Regex(PH_VIDEO_METADATA_JSON_REGEX).search(htmlString) + if videoMetaDataString: + # If found, convert the JSON string to an object + videoMetaData = json.loads(videoMetaDataString.group(1)) + + # Loop through all potential resolutions for resolution in PH_POTENTIAL_RESOLUTIONS: - video = Regex(PH_VIDEO_URL_REGEX % resolution).search(data) + # Search for the video URL string + video = Regex(PH_VIDEO_URL_REGEX % resolution).search(htmlString) + # If video with the given resolution is found, add it to the list if video: - availableResolutions[resolution] = video.group(1) - - for resolution, videoURL in availableResolutions.items(): - mediaObjects.append(MediaObject( - container = Container.MP4, - video_codec = VideoCodec.H264, - video_resolution = resolution, - audio_codec = AudioCodec.AAC, - audio_channels = 2, - optimized_for_streaming = True if Client.Product not in ['Plex Web'] else False, - parts = [ - PartObject( - key = videoURL - ) - ] - )) + + mediaObject = MediaObject( + container = Container.MP4, + video_codec = VideoCodec.H264, + video_resolution = resolution, + audio_codec = AudioCodec.AAC, + audio_channels = 2, + optimized_for_streaming = True if Client.Product not in ['Plex Web'] else False, + parts = [ + PartObject( + key = video.group(1) + ) + ] + ) + + # Check to see if extra metadata is available + if videoMetaDataString: + mediaObject.duration = int(videoMetaData["video_duration"]) * 1000 + + mediaObjects.append(mediaObject) + return mediaObjects From 31edc42f6c78ab71fef29ef88593fb4eb12358fe Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 01:02:13 -0400 Subject: [PATCH 02/17] Added thumbnails Added photo album of thumbnails to video sub menu. --- Contents/Code/PHCommon.py | 55 ++++++++++++++++++++++++++++++++++++++- Contents/Code/__init__.py | 1 + 2 files changed, 55 insertions(+), 1 deletion(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index a3da73c..51fac94 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -1,3 +1,4 @@ +import json import urllib import urlparse from collections import OrderedDict @@ -18,6 +19,8 @@ MAX_VIDEOS_PER_PORNSTAR_PAGE = 26 MAX_VIDEOS_PER_USER_PAGE = 48 +PH_VIDEO_METADATA_JSON_REGEX = "var flashvars_\d+ = ({[\S\s]+?});" + SORT_ORDERS = OrderedDict([ ('Most Recent', {'o':'mr'}), ('Most Viewed - All Time', {'o':'mv', 't':'a'}), @@ -177,7 +180,22 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): oc.add(vco) # Get the HTML of the site - html = HTML.ElementFromURL(url) + html = HTML.ElementFromURL(url) + htmlString = HTML.StringFromElement(html) + + # Search for the video metadata JSON string + videoMetaDataString = Regex(PH_VIDEO_METADATA_JSON_REGEX).search(htmlString) + + if (videoMetaDataString): + # If found, convert the JSON string to an object + videoMetaData = json.loads(videoMetaDataString.group(1)) + + if (videoMetaData["thumbs"] and videoMetaData["thumbs"]["urlPattern"]): + oc.add(PhotoAlbumObject( + key = Callback(VideoThumbnails, url=url), + rating_key = url + " - Thumbnails", + title = "Thumbnails" + )) # Use xPath to extract the uploader of the video uploader = html.xpath("//div[contains(@class, 'video-info-row')]/div[contains(@class, 'usernameWrap')]") @@ -298,6 +316,41 @@ def GenerateVideoPornStarDirectoryObject(pornStarElement): thumb = pornStarThumbnail ) +@route(ROUTE_PREFIX + '/video/thumbnails') +def VideoThumbnails(url, title="Thumbnails"): + # Create the object to contain the thumbnails + oc = ObjectContainer(title2=title) + + # Get the HTML of the site + html = HTML.ElementFromURL(url) + htmlString = HTML.StringFromElement(html) + + # Search for the video metadata JSON string + videoMetaDataString = Regex(PH_VIDEO_METADATA_JSON_REGEX).search(htmlString) + + if (videoMetaDataString): + # If found, convert the JSON string to an object + videoMetaData = json.loads(videoMetaDataString.group(1)) + + if (videoMetaData["thumbs"] and videoMetaData["thumbs"]["urlPattern"] != False): + + videoThumbnailsCount = Regex("/S{(\d+)}.jpg").search(videoMetaData["thumbs"]["urlPattern"]) + + if (videoThumbnailsCount): + videoThumbnailsCountString = videoThumbnailsCount.group(1) + + for i in range(int(videoThumbnailsCountString) + 1): + thumbnailURL = videoMetaData["thumbs"]["urlPattern"].replace("/S{" + videoThumbnailsCountString + "}.jpg", "/S" + str(i) + ".jpg") + + oc.add(PhotoObject( + key = thumbnailURL, + rating_key = thumbnailURL, + title = str(i), + thumb = thumbnailURL + )) + + return oc + @route(ROUTE_PREFIX + '/video/related') def RelatedVideos(url, title="Related Videos"): # Create the object to contain the related videos diff --git a/Contents/Code/__init__.py b/Contents/Code/__init__.py index 2462441..d3374d4 100644 --- a/Contents/Code/__init__.py +++ b/Contents/Code/__init__.py @@ -18,6 +18,7 @@ def Start(): # Set the defaults of Directory Objects DirectoryObject.thumb = R(ICON) + PhotoAlbumObject.thumb = R(ICON) # Set the default language Locale.DefaultLocale = "en" From 912bdb5e3f085c1278c4463ab108c3ca4dcb37f6 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 02:55:13 -0400 Subject: [PATCH 03/17] Added actions Added a menu that shows time stamps for actions (e.g. different positions). I made it using recursive DirectoryObjects because I didn't really know how else to display information like that. So you can technically click through that menu infinitely and never get anywhere, but hopefully after a few clicks you will realize what is happening and start backing out. If Plex was able to start a video from a certain time that would be sweet, because it would be trivial to modify this to add a VideoClipObject with a time offset if that existed. If it does, I don't know how to do it. --- Contents/Code/PHCommon.py | 65 ++++++++++++++++++++++++++++++++++----- 1 file changed, 58 insertions(+), 7 deletions(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index 51fac94..39a4f0b 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -1,4 +1,5 @@ import json +import time import urllib import urlparse from collections import OrderedDict @@ -167,6 +168,9 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): # Create the object to contain all of the videos options oc = ObjectContainer(title2 = title) + # Create an empty object for video metadata + videoMetaData = {} + # Create the Video Clip Object vco = URLService.MetadataObjectForURL(url) @@ -189,13 +193,13 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): if (videoMetaDataString): # If found, convert the JSON string to an object videoMetaData = json.loads(videoMetaDataString.group(1)) - - if (videoMetaData["thumbs"] and videoMetaData["thumbs"]["urlPattern"]): - oc.add(PhotoAlbumObject( - key = Callback(VideoThumbnails, url=url), - rating_key = url + " - Thumbnails", - title = "Thumbnails" - )) + + if (videoMetaData["thumbs"] and videoMetaData["thumbs"]["urlPattern"]): + oc.add(PhotoAlbumObject( + key = Callback(VideoThumbnails, url=url), + rating_key = url + " - Thumbnails", + title = "Thumbnails" + )) # Use xPath to extract the uploader of the video uploader = html.xpath("//div[contains(@class, 'video-info-row')]/div[contains(@class, 'usernameWrap')]") @@ -269,6 +273,12 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): thumb = playlistsThumb )) + if (videoMetaData["actionTags"]): + oc.add(DirectoryObject( + key = Callback(VideoActions, url=url), + title = "Action" + )) + return oc @route(ROUTE_PREFIX + '/search') @@ -401,6 +411,47 @@ def PlaylistsContainingVideo(url, title="Playlists Containing Video"): return oc +@route(ROUTE_PREFIX + '/video/actions') +def VideoActions(url, title="Actions", header=None, message=None, replace_parent=None): + # Create the object to contain the actions + oc = ObjectContainer(title2=title) + + if (header): + oc.header = header + if (message): + oc.message = message + if (replace_parent): + oc.replace_parent = replace_parent + + # Get the HTML of the site + html = HTML.ElementFromURL(url) + htmlString = HTML.StringFromElement(html) + + # Search for the video metadata JSON string + videoMetaDataString = Regex(PH_VIDEO_METADATA_JSON_REGEX).search(htmlString) + + if (videoMetaDataString): + # If found, convert the JSON string to an object + videoMetaData = json.loads(videoMetaDataString.group(1)) + + if (videoMetaData["actionTags"]): + actions = videoMetaData["actionTags"].split(",") + + for action in actions: + actionSegments = action.split(":") + + actionTimestamp = time.strftime('%H:%M:%S', time.gmtime(int(actionSegments[1]))) + actionTitle = actionSegments[0] + + Log("Action: " + actionTimestamp + " - " + actionTitle) + + oc.add(DirectoryObject( + key = Callback(VideoActions, url=url, title=title, header=actionTitle, message=actionTitle + " starts at " + actionTimestamp, replace_parent=True), + title = actionTimestamp + ": " + actionTitle + )) + + return oc + def GenerateMenu(title, menuItems, no_cache=False): # Create the object to contain the menu items oc = ObjectContainer(title2=title, no_cache=no_cache) From 9a112f2c0d59089956c383addda7a600b0ecf58f Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 03:32:09 -0400 Subject: [PATCH 04/17] Fixed some pagination issues Porn Stars menus were missing the next page button. Browse All Videos and Category pages were missing the next page button from page 2 onward. Pagination seems to be working for everything now --- Contents/Code/PHCommon.py | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index 39a4f0b..29dd0ad 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -14,10 +14,11 @@ PH_CHANNEL_HOVER_URL = BASE_URL + '/channel/hover?id=%s' PH_USER_HOVER_URL = BASE_URL + '/user/hover?id=%s' -MAX_VIDEOS_PER_PAGE = 32 +MAX_VIDEOS_PER_PAGE = 44 +MAX_VIDEOS_PER_PAGE_PAGE_ONE = 32 MAX_VIDEOS_PER_SEARCH_PAGE = 20 MAX_VIDEOS_PER_CHANNEL_PAGE = 36 -MAX_VIDEOS_PER_PORNSTAR_PAGE = 26 +MAX_VIDEOS_PER_PORNSTAR_PAGE = 36 MAX_VIDEOS_PER_USER_PAGE = 48 PH_VIDEO_METADATA_JSON_REGEX = "var flashvars_\d+ = ({[\S\s]+?});" @@ -75,7 +76,7 @@ def BrowseVideos(title=L("DefaultBrowseVideosTitle"), url = PH_VIDEO_URL, sortOr return GenerateMenu(title, browseVideosMenuItems) @route(ROUTE_PREFIX + '/videos/list') -def ListVideos(title=L("DefaultListVideosTitle"), url=PH_VIDEO_URL, page=1, pageLimit = MAX_VIDEOS_PER_PAGE): +def ListVideos(title=L("DefaultListVideosTitle"), url=PH_VIDEO_URL, page=1, pageLimit = MAX_VIDEOS_PER_PAGE_PAGE_ONE): # Create the object to contain all of the videos oc = ObjectContainer(title2 = title) @@ -93,6 +94,9 @@ def ListVideos(title=L("DefaultListVideosTitle"), url=PH_VIDEO_URL, page=1, page pageLimit = MAX_VIDEOS_PER_PORNSTAR_PAGE elif ("/users/" in url): pageLimit = MAX_VIDEOS_PER_USER_PAGE + elif ("/video" in url and page > 1): + # In the Browse All Videos and Categories menus, they display MAX_VIDEOS_PER_PAGE_PAGE_ONE on page one, and MAX_VIDEOS_PER_PAGE from page two onward + pageLimit = MAX_VIDEOS_PER_PAGE # Get the HTML of the site html = HTML.ElementFromURL(url) From 3605c612381e26a634962ff78ce181c2f1eb6f46 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 04:14:52 -0400 Subject: [PATCH 05/17] Added duration in one more spot Added duration to the directory objects that point to the video sub menu. --- Contents/Code/PHCommon.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index 29dd0ad..b51bdf6 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -155,7 +155,8 @@ def ListVideos(title=L("DefaultListVideosTitle"), url=PH_VIDEO_URL, page=1, page oc.add(DirectoryObject( key = Callback(VideoMenu, url=videoURL, title=videoTitle, duration=duration), title = videoTitle, - thumb = thumbnail + thumb = thumbnail, + duration = duration )) # There is a slight change that this will break... If the number of videos returned in total is divisible by MAX_VIDEOS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. From 5be7f47e1558b2ea1fff7a84001fe4f17b475ff4 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 05:00:39 -0400 Subject: [PATCH 06/17] Cleaned up video menu Shortened the titles in the video menu, and lengthened the summary --- Contents/Code/PHCommon.py | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index b51bdf6..dd3df6b 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -182,6 +182,9 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): # As I am calling MetadataObjectForURL from the URL Service, it only returns the metadata, it doesn't contain the URL vco.url = url + # Overide the title + vco.title = "Play Video" + if (int(duration) > 0): vco.duration = int(duration) @@ -203,7 +206,8 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): oc.add(PhotoAlbumObject( key = Callback(VideoThumbnails, url=url), rating_key = url + " - Thumbnails", - title = "Thumbnails" + title = "Thumbnails", + summary = "Tiled thumbnails from this video" )) # Use xPath to extract the uploader of the video @@ -233,6 +237,7 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): oc.add(DirectoryObject( key = Callback(BrowseVideos, url=uploaderURL + '/videos', title=uploaderName), title = uploaderName, + summary = "Channel this video appears in", thumb = channelThumbnail )) elif (uploaderType == "user"): @@ -250,7 +255,8 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): elif (len(pornStars) > 1): oc.add(DirectoryObject( key = Callback(GenerateVideoPornStarMenu, url=url), - title = "Porn Stars" + title = "Porn Stars", + summary = "Porn Stars that appear in this video" )) # Use xPath to extract the related videos @@ -263,6 +269,7 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): oc.add(DirectoryObject( key = Callback(RelatedVideos, url=url), title = "Related Videos", + summary = "Videos related to this video", thumb = relatedVideosThumb )) @@ -274,14 +281,16 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): oc.add(DirectoryObject( key = Callback(PlaylistsContainingVideo, url=url), - title = "Playlists Containing Video", + title = "Playlists", + summary = "Playlists that contain this video", thumb = playlistsThumb )) if (videoMetaData["actionTags"]): oc.add(DirectoryObject( key = Callback(VideoActions, url=url), - title = "Action" + title = "Action", + summary = "Timestamps of when actions (e.g. different positions) happen in this video" )) return oc @@ -328,6 +337,7 @@ def GenerateVideoPornStarDirectoryObject(pornStarElement): return DirectoryObject( key = Callback(BrowseVideos, url=pornStarURL, title=pornStarName), title = pornStarName, + summary = "Porn Star appearing in this video", thumb = pornStarThumbnail ) @@ -386,6 +396,7 @@ def RelatedVideos(url, title="Related Videos"): oc.add(DirectoryObject( key = Callback(VideoMenu, url=relatedVideoURL, title=relatedVideoTitle), title = relatedVideoTitle, + summary = relatedVideoTitle, thumb = relatedVideoThumb )) @@ -447,12 +458,12 @@ def VideoActions(url, title="Actions", header=None, message=None, replace_parent actionTimestamp = time.strftime('%H:%M:%S', time.gmtime(int(actionSegments[1]))) actionTitle = actionSegments[0] - - Log("Action: " + actionTimestamp + " - " + actionTitle) + actionSummary = actionTitle + " starts at " + actionTimestamp oc.add(DirectoryObject( - key = Callback(VideoActions, url=url, title=title, header=actionTitle, message=actionTitle + " starts at " + actionTimestamp, replace_parent=True), - title = actionTimestamp + ": " + actionTitle + key = Callback(VideoActions, url=url, title=title, header=actionTitle, message=actionSummary, replace_parent=True), + title = actionTimestamp + ": " + actionTitle, + summary = actionSummary )) return oc From 518feafbf8ecd17855ab4e232152d398aed5f583 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 05:35:15 -0400 Subject: [PATCH 07/17] Added video sub menu preferences I've thrown a lot of extra options into the video menu lately. In case anyone wants to tone that down, you can now filter them out using Preferences. --- Contents/Code/PHCommon.py | 157 ++++++++++++++++++++----------------- Contents/DefaultPrefs.json | 36 +++++++++ 2 files changed, 121 insertions(+), 72 deletions(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index dd3df6b..bb5222f 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -171,7 +171,7 @@ def ListVideos(title=L("DefaultListVideosTitle"), url=PH_VIDEO_URL, page=1, page @route(ROUTE_PREFIX + '/videos/menu') def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): # Create the object to contain all of the videos options - oc = ObjectContainer(title2 = title) + oc = ObjectContainer(title2 = title, no_cache=True) # Create an empty object for video metadata videoMetaData = {} @@ -201,8 +201,9 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): if (videoMetaDataString): # If found, convert the JSON string to an object videoMetaData = json.loads(videoMetaDataString.group(1)) - - if (videoMetaData["thumbs"] and videoMetaData["thumbs"]["urlPattern"]): + + # Check to see if Thumbnails are enabled in the video sub menu in the Preferences, and also if the Thumbnail metadata exists + if (Prefs["videoMenuShowThumbnails"] and videoMetaData["thumbs"] and videoMetaData["thumbs"]["urlPattern"]): oc.add(PhotoAlbumObject( key = Callback(VideoThumbnails, url=url), rating_key = url + " - Thumbnails", @@ -210,88 +211,100 @@ def VideoMenu(url, title=L("DefaultVideoMenuTitle"), duration=0): summary = "Tiled thumbnails from this video" )) - # Use xPath to extract the uploader of the video - uploader = html.xpath("//div[contains(@class, 'video-info-row')]/div[contains(@class, 'usernameWrap')]") - - # Make sure one is returned - if (len(uploader) > 0): - # Get the link within - uploaderLink = uploader[0].xpath("./a") + # Check to see if Uploaders are enabled in the video sub menu in the Preferences + if (Prefs["videoMenuShowUploader"]): + # Use xPath to extract the uploader of the video + uploader = html.xpath("//div[contains(@class, 'video-info-row')]/div[contains(@class, 'usernameWrap')]") - # Make sure it exists - if (len(uploaderLink) > 0): - uploaderURL = BASE_URL + uploaderLink[0].xpath("./@href")[0] - uploaderName = uploaderLink[0].xpath("./text()")[0] - - uploaderType = uploader[0].xpath("./@data-type")[0] + # Make sure one is returned + if (len(uploader) > 0): + # Get the link within + uploaderLink = uploader[0].xpath("./a") - # Check to see if the video is listed under a channel or a user - if (uploaderType == "channel"): - channelID = uploader[0].xpath("./@data-channelid")[0] + # Make sure it exists + if (len(uploaderLink) > 0): + uploaderURL = BASE_URL + uploaderLink[0].xpath("./@href")[0] + uploaderName = uploaderLink[0].xpath("./text()")[0] - # Fetch the thumbnail - channelHoverHTML = HTML.ElementFromURL(PH_CHANNEL_HOVER_URL % channelID) + uploaderType = uploader[0].xpath("./@data-type")[0] - channelThumbnail = channelHoverHTML.xpath("//div[contains(@class, 'avatarIcon')]/a/img/@src")[0] - - oc.add(DirectoryObject( - key = Callback(BrowseVideos, url=uploaderURL + '/videos', title=uploaderName), - title = uploaderName, - summary = "Channel this video appears in", - thumb = channelThumbnail - )) - elif (uploaderType == "user"): - pass - - # Use xPath to extract a list of porn stars in the video - pornStars = html.xpath("//div[contains(@class, 'pornstarsWrapper')]/a[contains(@class, 'pstar-list-btn')]") + # Check to see if the video is listed under a channel or a user + if (uploaderType == "channel"): + channelID = uploader[0].xpath("./@data-channelid")[0] + + # Fetch the thumbnail + channelHoverHTML = HTML.ElementFromURL(PH_CHANNEL_HOVER_URL % channelID) + + channelThumbnail = channelHoverHTML.xpath("//div[contains(@class, 'avatarIcon')]/a/img/@src")[0] + + oc.add(DirectoryObject( + key = Callback(BrowseVideos, url=uploaderURL + '/videos', title=uploaderName), + title = uploaderName, + summary = "Channel this video appears in", + thumb = channelThumbnail + )) + elif (uploaderType == "user"): + pass - # Check how any porn stars are returned. - # If just one, then display a Directory Object pointing to the porn star - if (len(pornStars) == 1): - oc.add(GenerateVideoPornStarDirectoryObject(pornStars[0])) + # Check to see if Porn Stars are enabled in the video sub menu in the Preferences + if (Prefs["videoMenuShowPornStars"]): + # Use xPath to extract a list of porn stars in the video + pornStars = html.xpath("//div[contains(@class, 'pornstarsWrapper')]/a[contains(@class, 'pstar-list-btn')]") - # If more than one, create a Directory Object to another menu where all porn stars will be listed - elif (len(pornStars) > 1): - oc.add(DirectoryObject( - key = Callback(GenerateVideoPornStarMenu, url=url), - title = "Porn Stars", - summary = "Porn Stars that appear in this video" - )) + # Check how any porn stars are returned. + # If just one, then display a Directory Object pointing to the porn star + if (len(pornStars) == 1): + oc.add(GenerateVideoPornStarDirectoryObject(pornStars[0])) + + # If more than one, create a Directory Object to another menu where all porn stars will be listed + elif (len(pornStars) > 1): + oc.add(DirectoryObject( + key = Callback(GenerateVideoPornStarMenu, url=url), + title = "Porn Stars", + summary = "Porn Stars that appear in this video" + )) - # Use xPath to extract the related videos - relatedVideos = html.xpath("//div[contains(@class, 'wrap')]/div[contains(@class, 'phimage')]") - if (len(relatedVideos) > 0): - relatedVideosThumb = relatedVideos[0].xpath("./a/div[contains(@class, 'img')]/img/@data-mediumthumb")[0] + # Check to see if Related Videos are enabled in the video sub menu in the Preferences + if (Prefs["videoMenuShowRelatedVideos"]): + # Use xPath to extract the related videos + relatedVideos = html.xpath("//div[contains(@class, 'wrap')]/div[contains(@class, 'phimage')]") - # Add the Related Videos Directory Object - oc.add(DirectoryObject( - key = Callback(RelatedVideos, url=url), - title = "Related Videos", - summary = "Videos related to this video", - thumb = relatedVideosThumb - )) + if (len(relatedVideos) > 0): + relatedVideosThumb = relatedVideos[0].xpath("./a/div[contains(@class, 'img')]/img/@data-mediumthumb")[0] + + # Add the Related Videos Directory Object + oc.add(DirectoryObject( + key = Callback(RelatedVideos, url=url), + title = "Related Videos", + summary = "Videos related to this video", + thumb = relatedVideosThumb + )) - # Fetch playlists containing the video (if any) - playlists = html.xpath("//ul[contains(@class, 'playlist-listingSmall')]/li/div[contains(@class, 'wrap')]") - if (len(playlists) > 0): - playlistsThumb = playlists[0].xpath("./div[contains(@class, 'linkWrapper')]/img/@data-mediumthumb")[0] + # Check to see if Playlists are enabled in the video sub menu in the Preferences + if (Prefs["videoMenuShowPlaylists"]): + # Fetch playlists containing the video (if any) + playlists = html.xpath("//ul[contains(@class, 'playlist-listingSmall')]/li/div[contains(@class, 'wrap')]") - oc.add(DirectoryObject( - key = Callback(PlaylistsContainingVideo, url=url), - title = "Playlists", - summary = "Playlists that contain this video", - thumb = playlistsThumb - )) + if (len(playlists) > 0): + playlistsThumb = playlists[0].xpath("./div[contains(@class, 'linkWrapper')]/img/@data-mediumthumb")[0] + + oc.add(DirectoryObject( + key = Callback(PlaylistsContainingVideo, url=url), + title = "Playlists", + summary = "Playlists that contain this video", + thumb = playlistsThumb + )) - if (videoMetaData["actionTags"]): - oc.add(DirectoryObject( - key = Callback(VideoActions, url=url), - title = "Action", - summary = "Timestamps of when actions (e.g. different positions) happen in this video" - )) + # Check to see if Action is enabled in the video sub menu in the Preferences + if (Prefs["videoMenuShowAction"]): + if (videoMetaData["actionTags"]): + oc.add(DirectoryObject( + key = Callback(VideoActions, url=url), + title = "Action", + summary = "Timestamps of when actions (e.g. different positions) happen in this video" + )) return oc diff --git a/Contents/DefaultPrefs.json b/Contents/DefaultPrefs.json index e0ac4e1..1d4c000 100644 --- a/Contents/DefaultPrefs.json +++ b/Contents/DefaultPrefs.json @@ -12,5 +12,41 @@ "type": "enum", "values": ["default.png", "stealth.png"], "default": "default.png" + }, + { + "id": "videoMenuShowThumbnails", + "label": "Video Menu - Show Thumbnails", + "type": "bool", + "default": "true", + }, + { + "id": "videoMenuShowUploader", + "label": "Video Menu - Show Uploader", + "type": "bool", + "default": "true", + }, + { + "id": "videoMenuShowPornStars", + "label": "Video Menu - Show Porn Stars", + "type": "bool", + "default": "true", + }, + { + "id": "videoMenuShowRelatedVideos", + "label": "Video Menu - Show Related Videos", + "type": "bool", + "default": "true", + }, + { + "id": "videoMenuShowPlaylists", + "label": "Video Menu - Show Playlists", + "type": "bool", + "default": "true", + }, + { + "id": "videoMenuShowAction", + "label": "Video Menu - Show Action", + "type": "bool", + "default": "true", } ] \ No newline at end of file From 72d71fc3d6f4a314ff54af424cd4259ef1088ab0 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 14:54:08 -0400 Subject: [PATCH 08/17] Added Member Channel options Added the ability to view Channels that Members have created, and also Channels that Members are subscribed to. --- Contents/Code/PHMembers.py | 71 ++++++++++++++++++++++++++++++++++---- 1 file changed, 64 insertions(+), 7 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index 97d07b8..76f0a12 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -3,7 +3,8 @@ PH_DISCOVER_MEMBERS_URL = BASE_URL + '/user/discover' PH_SEARCH_MEMBERS_URL = BASE_URL + '/user/search?username=%s' -PH_MAX_MEMBERS_PER_PAGE = 42 +PH_MAX_MEMBERS_PER_PAGE = 42 +PH_MAX_MEMBER_CHANNELS_PER_PAGE = 8 @route(ROUTE_PREFIX + '/members') def BrowseMembers(title=L("DefaultBrowseMembersTitle"), url=PH_DISCOVER_MEMBERS_URL): @@ -80,11 +81,67 @@ def MemberMenu(title, url, username): # Create a dictionary of menu items memberMenuItems = OrderedDict([ - ('Public Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Public Videos", 'url':url + '/videos/public'}}), - ('Favorite Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Favorite Videos", 'url':url + '/videos/favorites'}}), - ('Watched Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Watched Videos", 'url':url + '/videos/recent'}}), - ('Public Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Public Playlists", 'url':url + '/playlists/public'}}), - ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}), + ('Channels', {'function':MemberChannels, 'functionArgs':{'title':username + "'s Channels", 'url':url + '/channels'}}), + ('Subscribed Channels', {'function':MemberSubscribedChannels, 'functionArgs':{'title':username + "'s Subscribed Channels", 'url':url + '/channel_subscriptions'}}), + ('Public Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Public Videos", 'url':url + '/videos/public'}}), + ('Favorite Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Favorite Videos", 'url':url + '/videos/favorites'}}), + ('Watched Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Watched Videos", 'url':url + '/videos/recent'}}), + ('Public Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Public Playlists", 'url':url + '/playlists/public'}}), + ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}) ]) - return GenerateMenu(title, memberMenuItems) \ No newline at end of file + return GenerateMenu(title, memberMenuItems) + +@route(ROUTE_PREFIX + '/members/channels') +def MemberChannels(url, title="Member Channels", page=1): + + # Create a dictionary of menu items + memberChannelMenuItems = OrderedDict() + + # Add the page number into the query string + if (int(page) != 1): + url = addURLParameters(url, {'page':str(page)}) + + # Get the HTML of the page + html = HTML.ElementFromURL(url) + + # Use xPath to extract a list of channels + channels = html.xpath("//div[contains(@class, 'sectionWrapper')]/div[contains(@class, 'topheader')]") + + for channel in channels: + # Use xPath to extract channel details + channelTitle = channel.xpath("./div[contains(@class, 'floatLeft')]/div[contains(@class, 'title')]/a/text()")[0] + channelURL = BASE_URL + channel.xpath("./div[contains(@class, 'floatLeft')]/div[contains(@class, 'title')]/a/@href")[0] + "/videos" + channelThumb = channel.xpath("./div[contains(@class, 'avatarWrapper')]/a/img/@src")[0] + + # Add a menu item for the member + memberChannelMenuItems[channelTitle] = {'function':BrowseVideos, 'functionArgs':{'url':channelURL, 'title':channelTitle}, 'directoryObjectArgs':{'thumb':channelThumb}} + + # There is a slight change that this will break... If the number of members returned in total is divisible by PH_MAX_MEMBER_CHANNELS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. + if (len(channels) == PH_MAX_MEMBER_CHANNELS_PER_PAGE): + memberChannelMenuItems['Next Page'] = {'function':MemberChannels, 'functionArgs':{'title':title, 'url':url, 'page':int(page)+1}, 'nextPage':True} + + return GenerateMenu(title, memberChannelMenuItems) + +@route(ROUTE_PREFIX + '/members/channels/subscribed') +def MemberSubscribedChannels(url, title="Member's Subscribed Channels"): + + # Create a dictionary of menu items + memberSubscribedChannelMenuItems = OrderedDict() + + # Get the HTML of the page + html = HTML.ElementFromURL(url) + + # Use xPath to extract a list of subscribed channels + channels = html.xpath("//div[contains(@class, 'channelSubWidgetContainer')]/ul/li[contains(@class, 'channelSubChannelWig')]") + + for channel in channels: + # Use xPath to extract channel details + channelTitle = channel.xpath("./div/div[contains(@class, 'wtitle')]/a/text()")[0] + channelURL = BASE_URL + channel.xpath("./div/div[contains(@class, 'wtitle')]/a/@href")[0] + "/videos" + channelThumb = channel.xpath("./div/div/a/img/@src")[0] + + # Add a menu item for the member + memberSubscribedChannelMenuItems[channelTitle] = {'function':BrowseVideos, 'functionArgs':{'url':channelURL, 'title':channelTitle}, 'directoryObjectArgs':{'thumb':channelThumb}} + + return GenerateMenu(title, memberSubscribedChannelMenuItems) \ No newline at end of file From cd756d4a28df860afae868ee17bdc7730c894712 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 17:34:27 -0400 Subject: [PATCH 09/17] Added "brains" to Member menu Now the items in the Member menu only show up if they contain items --- Contents/Code/PHMembers.py | 47 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index 76f0a12..f4453ed 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -3,6 +3,7 @@ PH_DISCOVER_MEMBERS_URL = BASE_URL + '/user/discover' PH_SEARCH_MEMBERS_URL = BASE_URL + '/user/search?username=%s' +# Only the Members search results page has 42 results. The other Member pages have 48 results, but don't feature pagination PH_MAX_MEMBERS_PER_PAGE = 42 PH_MAX_MEMBER_CHANNELS_PER_PAGE = 8 @@ -79,6 +80,11 @@ def SearchMembers(query): @route(ROUTE_PREFIX + '/members/menu') def MemberMenu(title, url, username): + # Get the HTML of the Member's spash page, as well as their Video and Playlist pages + memberHTML = HTML.ElementFromURL(url) + memberVideosHTML = HTML.ElementFromURL(url + '/videos') + memberPlaylistsHTML = HTML.ElementFromURL(url + '/playlists') + # Create a dictionary of menu items memberMenuItems = OrderedDict([ ('Channels', {'function':MemberChannels, 'functionArgs':{'title':username + "'s Channels", 'url':url + '/channels'}}), @@ -90,6 +96,47 @@ def MemberMenu(title, url, username): ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}) ]) + # This dictionary will hold the conditons on which we want to display Member menu options + memberMenuChecks = { + "Channels": { + "xpath": "//div[contains(@class,'channelSubWidgetContainer')]/ul/li[contains(@class,'channelSubChannelWig')]", + "htmlElement": memberHTML + }, + "Subscribed Channels": { + "xpath": "//div[contains(@class,'userWidgetContainer')]/ul/li[contains(@class,'userChannelWig')]", + "htmlElement": memberHTML + }, + "Public Videos": { + "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']", + "htmlElement": memberVideosHTML + }, + "Favorite Videos": { + "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']", + "htmlElement": memberVideosHTML + }, + "Watched Videos": { + "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Watched']", + "htmlElement": memberVideosHTML + }, + "Public Playlists": { + "xpath": "//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']", + "htmlElement": memberPlaylistsHTML + }, + "Favorite Playlists": { + "xpath": "//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']", + "htmlElement": memberPlaylistsHTML + } + } + + # Loop through Member menu option conditons + for memberMenuCheck in memberMenuChecks: + # Attempt to get the element from the page + elements = memberMenuChecks[memberMenuCheck]["htmlElement"].xpath(memberMenuChecks[memberMenuCheck]["xpath"]) + + if (len(elements) == 0): + # If no elements are found, do not display the Member menu option + del memberMenuItems[memberMenuCheck] + return GenerateMenu(title, memberMenuItems) @route(ROUTE_PREFIX + '/members/channels') From b968b736e241f3fc5c58e879f1042c0eebb7c4c3 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 17:39:49 -0400 Subject: [PATCH 10/17] Hide empty Playlists Hiding Playlists with 0 videos --- Contents/Code/PHPlaylists.py | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/Contents/Code/PHPlaylists.py b/Contents/Code/PHPlaylists.py index d4c7894..b24a729 100644 --- a/Contents/Code/PHPlaylists.py +++ b/Contents/Code/PHPlaylists.py @@ -43,13 +43,16 @@ def ListPlaylists(title, url = PH_PLAYLISTS_URL, page=1): # Loop through all playlists for playlist in playlists: - # Use xPath to extract playlist details - playlistTitle = playlist.xpath("./div/div[contains(@class, 'thumbnail-info-wrapper')]/span[contains(@class, 'title')]/a[contains(@class, 'title')]/text()")[0] - playlistURL = BASE_URL + playlist.xpath("./div/div[contains(@class, 'thumbnail-info-wrapper')]/span[contains(@class, 'title')]/a[contains(@class, 'title')]/@href")[0] - playlistThumbnail = playlist.xpath("./div/div[contains(@class, 'linkWrapper')]/img[contains(@class, 'largeThumb')]/@data-mediumthumb")[0] - - # Add a menu item for the playlist - listPlaylistsMenuItems[playlistTitle] = {'function':BrowseVideos, 'functionArgs':{'url':playlistURL}, 'directoryObjectArgs':{'thumb':playlistThumbnail}} + # Make sure Playlist isn't empty + if (len(playlist.xpath(".//span[contains(@class,'playlist-videos')]/span[contains(@class,'number')]/span[text()='0']")) < 1): + + # Use xPath to extract playlist details + playlistTitle = playlist.xpath("./div/div[contains(@class, 'thumbnail-info-wrapper')]/span[contains(@class, 'title')]/a[contains(@class, 'title')]/text()")[0] + playlistURL = BASE_URL + playlist.xpath("./div/div[contains(@class, 'thumbnail-info-wrapper')]/span[contains(@class, 'title')]/a[contains(@class, 'title')]/@href")[0] + playlistThumbnail = playlist.xpath("./div/div[contains(@class, 'linkWrapper')]/img[contains(@class, 'largeThumb')]/@data-mediumthumb")[0] + + # Add a menu item for the playlist + listPlaylistsMenuItems[playlistTitle] = {'function':BrowseVideos, 'functionArgs':{'url':playlistURL}, 'directoryObjectArgs':{'thumb':playlistThumbnail}} # There is a slight change that this will break... If the number of playlists returned in total is divisible by MAX_PLAYLISTS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. if (len(playlists) == MAX_PLAYLISTS_PER_PAGE): From ce256e64102a8aedbe4a85230bb113a139b9f72f Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 22:28:51 -0400 Subject: [PATCH 11/17] Added Member Porn Star subscriptions option You can now see what Porn Stars a Member is subscribed to. Also fixed up a comments that didn't make sense (due to lazy copypasta) --- Contents/Code/PHMembers.py | 58 ++++++++++++++++++++++++++++---------- 1 file changed, 43 insertions(+), 15 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index f4453ed..bb75454 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -87,13 +87,14 @@ def MemberMenu(title, url, username): # Create a dictionary of menu items memberMenuItems = OrderedDict([ - ('Channels', {'function':MemberChannels, 'functionArgs':{'title':username + "'s Channels", 'url':url + '/channels'}}), - ('Subscribed Channels', {'function':MemberSubscribedChannels, 'functionArgs':{'title':username + "'s Subscribed Channels", 'url':url + '/channel_subscriptions'}}), - ('Public Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Public Videos", 'url':url + '/videos/public'}}), - ('Favorite Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Favorite Videos", 'url':url + '/videos/favorites'}}), - ('Watched Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Watched Videos", 'url':url + '/videos/recent'}}), - ('Public Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Public Playlists", 'url':url + '/playlists/public'}}), - ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}) + ('Channels', {'function':MemberChannels, 'functionArgs':{'title':username + "'s Channels", 'url':url + '/channels'}}), + ('Subscribed Channels', {'function':MemberSubscribedChannels, 'functionArgs':{'title':username + "'s Subscribed Channels", 'url':url + '/channel_subscriptions'}}), + ('Subscribed Porn Stars', {'function':MemberSubscribedPornStars, 'functionArgs':{'title':username + "'s Subscribed Porn Stars", 'url':url + '/pornstar_subscriptions'}}), + ('Public Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Public Videos", 'url':url + '/videos/public'}}), + ('Favorite Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Favorite Videos", 'url':url + '/videos/favorites'}}), + ('Watched Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Watched Videos", 'url':url + '/videos/recent'}}), + ('Public Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Public Playlists", 'url':url + '/playlists/public'}}), + ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}) ]) # This dictionary will hold the conditons on which we want to display Member menu options @@ -106,6 +107,10 @@ def MemberMenu(title, url, username): "xpath": "//div[contains(@class,'userWidgetContainer')]/ul/li[contains(@class,'userChannelWig')]", "htmlElement": memberHTML }, + "Subscribed Porn Stars": { + "xpath": "//section[@id='sidebarPornstars']//ul[contains(@class,'pornStarSideBar')]/li[contains(@class,'pornstarsElements')]", + "htmlElement": memberHTML + }, "Public Videos": { "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']", "htmlElement": memberVideosHTML @@ -152,19 +157,19 @@ def MemberChannels(url, title="Member Channels", page=1): # Get the HTML of the page html = HTML.ElementFromURL(url) - # Use xPath to extract a list of channels + # Use xPath to extract a list of Channels channels = html.xpath("//div[contains(@class, 'sectionWrapper')]/div[contains(@class, 'topheader')]") for channel in channels: - # Use xPath to extract channel details + # Use xPath to extract Channel details channelTitle = channel.xpath("./div[contains(@class, 'floatLeft')]/div[contains(@class, 'title')]/a/text()")[0] channelURL = BASE_URL + channel.xpath("./div[contains(@class, 'floatLeft')]/div[contains(@class, 'title')]/a/@href")[0] + "/videos" channelThumb = channel.xpath("./div[contains(@class, 'avatarWrapper')]/a/img/@src")[0] - # Add a menu item for the member + # Add a menu item for the Channel memberChannelMenuItems[channelTitle] = {'function':BrowseVideos, 'functionArgs':{'url':channelURL, 'title':channelTitle}, 'directoryObjectArgs':{'thumb':channelThumb}} - # There is a slight change that this will break... If the number of members returned in total is divisible by PH_MAX_MEMBER_CHANNELS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. + # There is a slight change that this will break... If the number of Channels returned in total is divisible by PH_MAX_MEMBER_CHANNELS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. if (len(channels) == PH_MAX_MEMBER_CHANNELS_PER_PAGE): memberChannelMenuItems['Next Page'] = {'function':MemberChannels, 'functionArgs':{'title':title, 'url':url, 'page':int(page)+1}, 'nextPage':True} @@ -179,16 +184,39 @@ def MemberSubscribedChannels(url, title="Member's Subscribed Channels"): # Get the HTML of the page html = HTML.ElementFromURL(url) - # Use xPath to extract a list of subscribed channels + # Use xPath to extract a list of subscribed Channels channels = html.xpath("//div[contains(@class, 'channelSubWidgetContainer')]/ul/li[contains(@class, 'channelSubChannelWig')]") for channel in channels: - # Use xPath to extract channel details + # Use xPath to extract Channel details channelTitle = channel.xpath("./div/div[contains(@class, 'wtitle')]/a/text()")[0] channelURL = BASE_URL + channel.xpath("./div/div[contains(@class, 'wtitle')]/a/@href")[0] + "/videos" channelThumb = channel.xpath("./div/div/a/img/@src")[0] - # Add a menu item for the member + # Add a menu item for the Channel memberSubscribedChannelMenuItems[channelTitle] = {'function':BrowseVideos, 'functionArgs':{'url':channelURL, 'title':channelTitle}, 'directoryObjectArgs':{'thumb':channelThumb}} - return GenerateMenu(title, memberSubscribedChannelMenuItems) \ No newline at end of file + return GenerateMenu(title, memberSubscribedChannelMenuItems) + +@route(ROUTE_PREFIX + '/members/pornstars') +def MemberSubscribedPornStars(url, title="Member's Subscribed Porn Stars"): + + # Create a dictionary of menu items + memberSubscribedPornStarsMenuItems = OrderedDict() + + # Get the HTML of the page + html = HTML.ElementFromURL(url) + + # Use xPath to extract a list of subscribed Porn Stars + pornStars = html.xpath("//ul[contains(@class,'pornStarGrid')]/li/div[contains(@class,'user-flag')]/div[contains(@class,'avatarWrap')]/a") + + for pornStar in pornStars: + # Use xPath to extract Porn Star details + pornStarTitle = pornStar.xpath("./img/@alt")[0] + pornStarURL = BASE_URL + pornStar.xpath("./@href")[0] + pornStarThumb = pornStar.xpath("./img/@src")[0] + + # Add a menu item for the Porn Star + memberSubscribedPornStarsMenuItems[pornStarTitle] = {'function':BrowseVideos, 'functionArgs':{'url':pornStarURL, 'title':pornStarTitle}, 'directoryObjectArgs':{'thumb':pornStarThumb}} + + return GenerateMenu(title, memberSubscribedPornStarsMenuItems) \ No newline at end of file From 68c372230a8abcf65f7e640466430b80edf06aad Mon Sep 17 00:00:00 2001 From: NotVinny Date: Mon, 25 Jul 2016 23:15:40 -0400 Subject: [PATCH 12/17] Minor pagination fix Forgot to pass along the page limit in the NextPageObject --- Contents/Code/PHCommon.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Contents/Code/PHCommon.py b/Contents/Code/PHCommon.py index bb5222f..e07f591 100644 --- a/Contents/Code/PHCommon.py +++ b/Contents/Code/PHCommon.py @@ -162,7 +162,7 @@ def ListVideos(title=L("DefaultListVideosTitle"), url=PH_VIDEO_URL, page=1, page # There is a slight change that this will break... If the number of videos returned in total is divisible by MAX_VIDEOS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. if (len(videos) == int(pageLimit)): oc.add(NextPageObject( - key = Callback(ListVideos, title=title, url=url, page = int(page)+1), + key = Callback(ListVideos, title=title, url=url, page = int(page)+1, pageLimit=int(pageLimit)), title = 'Next Page' )) From b9b6e1c1d610ebb232a6d959d3a70783b4ca24c7 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Tue, 26 Jul 2016 01:57:46 -0400 Subject: [PATCH 13/17] Added additional Member menu items You can now view a Member's subscribers, Member subscriptions, and friends. Also re-organized the menu a little bit to bring the content closer to the top. One thing to note is that while the xPath to check for a Member's subscribers/Member subscriptions/friends is fairly accurate, there are times when it is inaccurate. I will be addressing this in a forthcoming commit where I will add the option to get accurate results for those menu options (at the expense of 1 extra HTTP request each). It will be configurable in the Preferences if you don't want to create any extra HTTP requests --- Contents/Code/PHMembers.py | 70 ++++++++++++++++++++++++-------------- 1 file changed, 44 insertions(+), 26 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index bb75454..20f926e 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -4,8 +4,11 @@ PH_SEARCH_MEMBERS_URL = BASE_URL + '/user/search?username=%s' # Only the Members search results page has 42 results. The other Member pages have 48 results, but don't feature pagination -PH_MAX_MEMBERS_PER_PAGE = 42 -PH_MAX_MEMBER_CHANNELS_PER_PAGE = 8 +PH_MAX_MEMBERS_PER_PAGE = 42 +PH_MAX_MEMBERS_PER_MEMBER_SUBSCRIBERS_PAGE = 100 +PH_MAX_MEMBERS_PER_MEMBER_SUBSCRIPTIONS_PAGE = 100 +PH_MAX_MEMBERS_PER_MEMBER_FRIENDS_PAGE = 100 +PH_MAX_MEMBER_CHANNELS_PER_PAGE = 8 @route(ROUTE_PREFIX + '/members') def BrowseMembers(title=L("DefaultBrowseMembersTitle"), url=PH_DISCOVER_MEMBERS_URL): @@ -34,7 +37,7 @@ def BrowseMembers(title=L("DefaultBrowseMembersTitle"), url=PH_DISCOVER_MEMBERS_ return GenerateMenu(title, browseMembersMenuItems) @route(ROUTE_PREFIX + '/members/list') -def ListMembers(title, url=PH_DISCOVER_MEMBERS_URL, page=1): +def ListMembers(title, url=PH_DISCOVER_MEMBERS_URL, page=1, pageLimit=PH_MAX_MEMBERS_PER_PAGE): # Create a dictionary of menu items listMembersMenuItems = OrderedDict() @@ -60,9 +63,9 @@ def ListMembers(title, url=PH_DISCOVER_MEMBERS_URL, page=1): # Add a menu item for the member listMembersMenuItems[memberTitle] = {'function':MemberMenu, 'functionArgs':{'url':memberURL, 'username':memberTitle}, 'directoryObjectArgs':{'thumb':memberThumbnail}} - # There is a slight change that this will break... If the number of members returned in total is divisible by PH_MAX_MEMBERS_PER_PAGE with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. - if (len(members) == PH_MAX_MEMBERS_PER_PAGE): - listMembersMenuItems['Next Page'] = {'function':ListMembers, 'functionArgs':{'title':title, 'url':url, 'page':int(page)+1}, 'nextPage':True} + # There is a slight change that this will break... If the number of members returned in total is divisible by pageLimit with no remainder, there could possibly be no additional page after. This is unlikely though and I'm too lazy to handle it. + if (len(members) == int(pageLimit)): + listMembersMenuItems['Next Page'] = {'function':ListMembers, 'functionArgs':{'title':title, 'url':url, 'page':int(page)+1, 'pageLimit':int(pageLimit)}, 'nextPage':True} return GenerateMenu(title, listMembersMenuItems) @@ -87,30 +90,21 @@ def MemberMenu(title, url, username): # Create a dictionary of menu items memberMenuItems = OrderedDict([ - ('Channels', {'function':MemberChannels, 'functionArgs':{'title':username + "'s Channels", 'url':url + '/channels'}}), - ('Subscribed Channels', {'function':MemberSubscribedChannels, 'functionArgs':{'title':username + "'s Subscribed Channels", 'url':url + '/channel_subscriptions'}}), - ('Subscribed Porn Stars', {'function':MemberSubscribedPornStars, 'functionArgs':{'title':username + "'s Subscribed Porn Stars", 'url':url + '/pornstar_subscriptions'}}), - ('Public Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Public Videos", 'url':url + '/videos/public'}}), - ('Favorite Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Favorite Videos", 'url':url + '/videos/favorites'}}), - ('Watched Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Watched Videos", 'url':url + '/videos/recent'}}), - ('Public Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Public Playlists", 'url':url + '/playlists/public'}}), - ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}) + ('Public Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Public Videos", 'url':url + '/videos/public'}}), + ('Favorite Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Favorite Videos", 'url':url + '/videos/favorites'}}), + ('Watched Videos', {'function':ListVideos, 'functionArgs':{'title':username + "'s Watched Videos", 'url':url + '/videos/recent'}}), + ('Public Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Public Playlists", 'url':url + '/playlists/public'}}), + ('Favorite Playlists', {'function':ListPlaylists, 'functionArgs':{'title':username + "'s Favorite Playlists", 'url':url + '/playlists/favorites'}}), + ('Channels', {'function':MemberChannels, 'functionArgs':{'title':username + "'s Channels", 'url':url + '/channels'}}), + ('Channel Subscriptions', {'function':MemberSubscribedChannels, 'functionArgs':{'title':username + "'s Channel Subscriptions", 'url':url + '/channel_subscriptions'}}), + ('Porn Star Subscriptions', {'function':MemberSubscribedPornStars, 'functionArgs':{'title':username + "'s Porn Star Subscriptions", 'url':url + '/pornstar_subscriptions'}}), + ('Subscribers', {'function':ListMembers, 'functionArgs':{'title':username + "'s Subscribers", 'url':url + '/subscribers', 'pageLimit':PH_MAX_MEMBERS_PER_MEMBER_SUBSCRIBERS_PAGE}}), + ('Member Subscriptions', {'function':ListMembers, 'functionArgs':{'title':username + "'s Member Subscriptions", 'url':url + '/subscriptions', 'pageLimit':PH_MAX_MEMBERS_PER_MEMBER_SUBSCRIPTIONS_PAGE}}), + ('Friends', {'function':ListMembers, 'functionArgs':{'title':username + "'s Friends", 'url':url + '/friends', 'pageLimit':PH_MAX_MEMBERS_PER_MEMBER_FRIENDS_PAGE}}) ]) # This dictionary will hold the conditons on which we want to display Member menu options memberMenuChecks = { - "Channels": { - "xpath": "//div[contains(@class,'channelSubWidgetContainer')]/ul/li[contains(@class,'channelSubChannelWig')]", - "htmlElement": memberHTML - }, - "Subscribed Channels": { - "xpath": "//div[contains(@class,'userWidgetContainer')]/ul/li[contains(@class,'userChannelWig')]", - "htmlElement": memberHTML - }, - "Subscribed Porn Stars": { - "xpath": "//section[@id='sidebarPornstars']//ul[contains(@class,'pornStarSideBar')]/li[contains(@class,'pornstarsElements')]", - "htmlElement": memberHTML - }, "Public Videos": { "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']", "htmlElement": memberVideosHTML @@ -130,6 +124,30 @@ def MemberMenu(title, url, username): "Favorite Playlists": { "xpath": "//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']", "htmlElement": memberPlaylistsHTML + }, + "Channels": { + "xpath": "//div[contains(@class,'channelSubWidgetContainer')]/ul/li[contains(@class,'channelSubChannelWig')]", + "htmlElement": memberHTML + }, + "Channel Subscriptions": { + "xpath": "//div[contains(@class,'userWidgetContainer')]/ul/li[contains(@class,'userChannelWig')]", + "htmlElement": memberHTML + }, + "Porn Star Subscriptions": { + "xpath": "//section[@id='sidebarPornstars']//ul[contains(@class,'pornStarSideBar')]/li[contains(@class,'pornstarsElements')]", + "htmlElement": memberHTML + }, + "Subscribers": { + "xpath": "//ul[contains(@class,'subViewsInfoContainer')]/li[a[span[contains(@class,'connections')][contains(text(),'subscriber')]]]/a/span[contains(@class,'number')][not(text()='0')]", + "htmlElement": memberHTML + }, + "Member Subscriptions": { + "xpath": "//section[@id='profileSubscriptions']//ul/li[contains(@class,'subscriptionsElement')]", + "htmlElement": memberHTML + }, + "Friends": { + "xpath": "//ul[contains(@class,'subViewsInfoContainer')]/li[a[span[contains(@class,'connections')][contains(text(),'friend')]]]/a/span[contains(@class,'number')][not(text()='0')]", + "htmlElement": memberHTML } } From b2cbf81f1f2b4b31ee79f7f70724153c24a62196 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Tue, 26 Jul 2016 02:38:12 -0400 Subject: [PATCH 14/17] Added more accurate Member menu options There are now overrides to get more accurate Member menu options at the expense of extra HTTP requests. It's possible to override subscriptions, however I think the default xPath is good enough so it is set to false by default for now. --- Contents/Code/PHMembers.py | 29 +++++++++++++ Contents/DefaultPrefs.json | 86 +++++++++++++++++++++++--------------- 2 files changed, 81 insertions(+), 34 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index 20f926e..6dcaf35 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -151,6 +151,35 @@ def MemberMenu(title, url, username): } } + # There overrides perform a more accurate check, however they all require an extra HTTP request + memberMenuPreferenceOverrides = { + "memberMenuAccurateSubscribers": { + 'title': 'Subscribers', + 'urlSuffix': '/subscribers' + }, + "memberMenuAccurateMemberSubscriptions": { + 'title': 'Member Subscriptions', + 'urlSuffix': '/subscriptions' + }, + "memberMenuAccurateFriends": { + 'title': 'Friends', + 'urlSuffix': '/friends' + } + } + + # Loop through Preference overrides + for key in memberMenuPreferenceOverrides: + # Check to see if the Preference is set + if (Prefs[key]): + # Get the HTML of the page + memberMenuPreferenceOverrideHTML = HTML.ElementFromURL(url + memberMenuPreferenceOverrides[key]["urlSuffix"]) + + # Override the menu check + memberMenuChecks[memberMenuPreferenceOverrides[key]["title"]] = { + "xpath": "//ul[contains(@class, 'userWidgetWrapperGrid')]/li", + "htmlElement": memberMenuPreferenceOverrideHTML + } + # Loop through Member menu option conditons for memberMenuCheck in memberMenuChecks: # Attempt to get the element from the page diff --git a/Contents/DefaultPrefs.json b/Contents/DefaultPrefs.json index 1d4c000..bd9f490 100644 --- a/Contents/DefaultPrefs.json +++ b/Contents/DefaultPrefs.json @@ -1,52 +1,70 @@ [ { - "id": "channelBackgroundArt", - "label": "Background Art", - "type": "enum", - "values": ["default.jpg", "original.png", "alternate-1.jpg", "alternate-2.jpg", "stealth.png"], - "default": "default.jpg" + "id": "channelBackgroundArt", + "label": "Background Art", + "type": "enum", + "values": ["default.jpg", "original.png", "alternate-1.jpg", "alternate-2.jpg", "stealth.png"], + "default": "default.jpg" }, { - "id": "channelIconArt", - "label": "Icon Art", - "type": "enum", - "values": ["default.png", "stealth.png"], - "default": "default.png" + "id": "channelIconArt", + "label": "Icon Art", + "type": "enum", + "values": ["default.png", "stealth.png"], + "default": "default.png" }, { - "id": "videoMenuShowThumbnails", - "label": "Video Menu - Show Thumbnails", - "type": "bool", - "default": "true", + "id": "videoMenuShowThumbnails", + "label": "Video Menu - Show Thumbnails", + "type": "bool", + "default": "true", }, { - "id": "videoMenuShowUploader", - "label": "Video Menu - Show Uploader", - "type": "bool", - "default": "true", + "id": "videoMenuShowUploader", + "label": "Video Menu - Show Uploader", + "type": "bool", + "default": "true", }, { - "id": "videoMenuShowPornStars", - "label": "Video Menu - Show Porn Stars", - "type": "bool", - "default": "true", + "id": "videoMenuShowPornStars", + "label": "Video Menu - Show Porn Stars", + "type": "bool", + "default": "true", }, { - "id": "videoMenuShowRelatedVideos", - "label": "Video Menu - Show Related Videos", - "type": "bool", - "default": "true", + "id": "videoMenuShowRelatedVideos", + "label": "Video Menu - Show Related Videos", + "type": "bool", + "default": "true", }, { - "id": "videoMenuShowPlaylists", - "label": "Video Menu - Show Playlists", - "type": "bool", - "default": "true", + "id": "videoMenuShowPlaylists", + "label": "Video Menu - Show Playlists", + "type": "bool", + "default": "true", }, { - "id": "videoMenuShowAction", - "label": "Video Menu - Show Action", - "type": "bool", - "default": "true", + "id": "videoMenuShowAction", + "label": "Video Menu - Show Action", + "type": "bool", + "default": "true", + }, + { + "id": "memberMenuAccurateSubscribers", + "label": "Member Menu - Accurate Subscribers", + "type": "bool", + "default": "true", + }, + { + "id": "memberMenuAccurateMemberSubscriptions", + "label": "Member Menu - Accurate Member Subscriptions", + "type": "bool", + "default": "false", + }, + { + "id": "memberMenuAccurateFriends", + "label": "Member Menu - Accurate Friends", + "type": "bool", + "default": "true", } ] \ No newline at end of file From 4bfbf205bf74659424fe702a0ff3be21bce66c50 Mon Sep 17 00:00:00 2001 From: NotVinny Date: Tue, 26 Jul 2016 02:48:01 -0400 Subject: [PATCH 15/17] Couple more Member menu cleanup items Turned off caching on Member menu, so that Preferences are reflected immediately. Also set the default for subscriber override to false, as I think the default xPath is good enough for that (though ugly). --- Contents/Code/PHMembers.py | 2 +- Contents/DefaultPrefs.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index 6dcaf35..9e053fe 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -189,7 +189,7 @@ def MemberMenu(title, url, username): # If no elements are found, do not display the Member menu option del memberMenuItems[memberMenuCheck] - return GenerateMenu(title, memberMenuItems) + return GenerateMenu(title, memberMenuItems, no_cache=True) @route(ROUTE_PREFIX + '/members/channels') def MemberChannels(url, title="Member Channels", page=1): diff --git a/Contents/DefaultPrefs.json b/Contents/DefaultPrefs.json index bd9f490..a609f69 100644 --- a/Contents/DefaultPrefs.json +++ b/Contents/DefaultPrefs.json @@ -53,7 +53,7 @@ "id": "memberMenuAccurateSubscribers", "label": "Member Menu - Accurate Subscribers", "type": "bool", - "default": "true", + "default": "false", }, { "id": "memberMenuAccurateMemberSubscriptions", From 615f15afef3099d62992d1c8aa60947bf8009a7a Mon Sep 17 00:00:00 2001 From: NotVinny Date: Tue, 26 Jul 2016 23:55:20 -0400 Subject: [PATCH 16/17] Changed how Member menu Preference overrides work You can now override multiple checks per each Preference override. This is because in cases like Playlists, you can check if a user has Public or Favorite Playlists from the same URL. --- Contents/Code/PHMembers.py | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index 9e053fe..cc8698a 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -151,19 +151,25 @@ def MemberMenu(title, url, username): } } - # There overrides perform a more accurate check, however they all require an extra HTTP request + # These overrides perform a more accurate check, however they all require an extra HTTP request memberMenuPreferenceOverrides = { "memberMenuAccurateSubscribers": { - 'title': 'Subscribers', - 'urlSuffix': '/subscribers' + 'urlSuffix': '/subscribers', + 'checks': [ + {'key':'Subscribers', 'xpath':"//ul[contains(@class, 'userWidgetWrapperGrid')]/li"} + ] }, "memberMenuAccurateMemberSubscriptions": { - 'title': 'Member Subscriptions', - 'urlSuffix': '/subscriptions' + 'urlSuffix': '/subscriptions', + 'checks': [ + {'key':'Member Subscriptions', 'xpath':"//ul[contains(@class, 'userWidgetWrapperGrid')]/li"} + ] }, "memberMenuAccurateFriends": { - 'title': 'Friends', - 'urlSuffix': '/friends' + 'urlSuffix': '/friends', + 'checks': [ + {'key':'Friends', 'xpath':"//ul[contains(@class, 'userWidgetWrapperGrid')]/li"} + ] } } @@ -174,11 +180,13 @@ def MemberMenu(title, url, username): # Get the HTML of the page memberMenuPreferenceOverrideHTML = HTML.ElementFromURL(url + memberMenuPreferenceOverrides[key]["urlSuffix"]) - # Override the menu check - memberMenuChecks[memberMenuPreferenceOverrides[key]["title"]] = { - "xpath": "//ul[contains(@class, 'userWidgetWrapperGrid')]/li", - "htmlElement": memberMenuPreferenceOverrideHTML - } + # Loop through the checks + for check in memberMenuPreferenceOverrides[key]["checks"]: + # Override the menu check + memberMenuChecks[check['key']] = { + "xpath": check['xpath'], + "htmlElement": memberMenuPreferenceOverrideHTML + } # Loop through Member menu option conditons for memberMenuCheck in memberMenuChecks: From 23825709a1b926b38d8cc9094da5ddb00b4b583b Mon Sep 17 00:00:00 2001 From: NotVinny Date: Wed, 27 Jul 2016 01:08:28 -0400 Subject: [PATCH 17/17] Added the ability to choose accuracy of Videos and Playlists in Member menu It is now possible to only make one HTTP request in the Member menu, which increases speed greatly but reduces accuracy and removes certain menu options altogether. Every Preference you turn on for Member menu adds one HTTP request, but makes your results more accurate and shows all possible menu items. --- Contents/Code/PHMembers.py | 56 ++++++++++++++++++++++---------------- Contents/DefaultPrefs.json | 12 ++++++++ 2 files changed, 45 insertions(+), 23 deletions(-) diff --git a/Contents/Code/PHMembers.py b/Contents/Code/PHMembers.py index cc8698a..1d13992 100644 --- a/Contents/Code/PHMembers.py +++ b/Contents/Code/PHMembers.py @@ -106,25 +106,16 @@ def MemberMenu(title, url, username): # This dictionary will hold the conditons on which we want to display Member menu options memberMenuChecks = { "Public Videos": { - "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']", - "htmlElement": memberVideosHTML - }, - "Favorite Videos": { - "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']", - "htmlElement": memberVideosHTML - }, - "Watched Videos": { - "xpath": "//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Watched']", - "htmlElement": memberVideosHTML + "xpath": "//section[@id='profileVideos']//ul[contains(@class,'videos')]/li[contains(@class,'videoblock')]", + "htmlElement": memberHTML }, + "Favorite Videos": None, + "Watched Videos": None, "Public Playlists": { - "xpath": "//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']", - "htmlElement": memberPlaylistsHTML - }, - "Favorite Playlists": { - "xpath": "//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']", - "htmlElement": memberPlaylistsHTML + "xpath": "//section[@id='playlistsSidebar']//ul[contains(@class,'user-playlist')]/li[contains(@id,'playlist_')]", + "htmlElement": memberHTML }, + "Favorite Playlists": None, "Channels": { "xpath": "//div[contains(@class,'channelSubWidgetContainer')]/ul/li[contains(@class,'channelSubChannelWig')]", "htmlElement": memberHTML @@ -153,6 +144,21 @@ def MemberMenu(title, url, username): # These overrides perform a more accurate check, however they all require an extra HTTP request memberMenuPreferenceOverrides = { + "memberMenuAccurateVideos": { + 'urlSuffix': '/videos', + 'checks': [ + {'key':'Public Videos', 'xpath':"//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']"}, + {'key':'Favorite Videos', 'xpath':"//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']"}, + {'key':'Watched Videos', 'xpath':"//section[@id='videosTab']//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Watched']"} + ] + }, + "memberMenuAccuratePlaylists": { + 'urlSuffix': '/playlists', + 'checks': [ + {'key':'Public Playlists', 'xpath':"//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Public']"}, + {'key':'Favorite Playlists', 'xpath':"//nav[contains(@class,'sectionMenu')]/ul/li/a[text()='Favorites']"} + ] + }, "memberMenuAccurateSubscribers": { 'urlSuffix': '/subscribers', 'checks': [ @@ -189,13 +195,17 @@ def MemberMenu(title, url, username): } # Loop through Member menu option conditons - for memberMenuCheck in memberMenuChecks: - # Attempt to get the element from the page - elements = memberMenuChecks[memberMenuCheck]["htmlElement"].xpath(memberMenuChecks[memberMenuCheck]["xpath"]) - - if (len(elements) == 0): - # If no elements are found, do not display the Member menu option - del memberMenuItems[memberMenuCheck] + for key in memberMenuChecks: + # Make sure the check exists + if (memberMenuChecks[key] is not None): + # Attempt to get the element from the page + elements = memberMenuChecks[key]["htmlElement"].xpath(memberMenuChecks[key]["xpath"]) + + if (len(elements) == 0): + # If no elements are found, do not display the Member menu option + del memberMenuItems[key] + else: + del memberMenuItems[key] return GenerateMenu(title, memberMenuItems, no_cache=True) diff --git a/Contents/DefaultPrefs.json b/Contents/DefaultPrefs.json index a609f69..ba9e55a 100644 --- a/Contents/DefaultPrefs.json +++ b/Contents/DefaultPrefs.json @@ -49,6 +49,18 @@ "type": "bool", "default": "true", }, + { + "id": "memberMenuAccurateVideos", + "label": "Member Menu - Accurate Videos", + "type": "bool", + "default": "true", + }, + { + "id": "memberMenuAccuratePlaylists", + "label": "Member Menu - Accurate Playlists", + "type": "bool", + "default": "true", + }, { "id": "memberMenuAccurateSubscribers", "label": "Member Menu - Accurate Subscribers",