diff options
Diffstat (limited to 'addon.py')
| -rw-r--r-- | addon.py | 192 |
1 files changed, 187 insertions, 5 deletions
@@ -20,6 +20,7 @@ Video plugin for animations posted to Newgrounds. import concurrent.futures from datetime import datetime import dateutil +import json import os import sys from urllib.parse import urlencode, parse_qsl @@ -51,6 +52,119 @@ def get_url(**kwargs): return "{}?{}".format(URL, urlencode(kwargs)) +def get_series(official=True, sort_by="date", offset=1): + """ + Return a list of series on Newgrounds + + :param official: whether to exclude non-official series + :param sort_by: one of "date", "views" + :param offset: pagination offset + :return: list of video list entries + :rtype: list + """ + html = requests.get( + "https://www.newgrounds.com/series", + params={ + "filter": "all" if not official else None, + "order": sort_by, + "page": offset, + }, + ).text + soup = BeautifulSoup(html, "html.parser") + visual_links = [ + json.loads(li["data-visual-link"]) + for li in soup.find_all("li", "visual-link-container") + ] + return fetch_visual_links(visual_links) + + +def get_collections(official=True, sort_by="date", offset=1): + """ + Return a list of playlists on Newgrounds + + :param official: whether to exclude non-official collections + :param sort_by: one of "date", "views" + :param offset: pagination offset + :return: list of video list entries + :rtype: list + """ + html = requests.get( + "https://www.newgrounds.com/collections", + params={ + "filter": "all" if not official else None, + "order": sort_by, + "page": offset, + }, + ).text + soup = BeautifulSoup(html, "html.parser") + visual_links = [ + json.loads(li["data-visual-link"]) + for li in soup.find_all("li", "visual-link-container") + ] + return fetch_visual_links(visual_links) + + +def get_series_videos(url): + """ + Return videos in a series on Newgrounds + + :param url: URL of series page to scrape + :return: list of video list entries + :rtype: list + """ + html = requests.get(url).text + soup = BeautifulSoup(html, "html.parser") + visual_links = [ + json.loads(li["data-visual-link"]) + for li in soup.find_all("li", "visual-link-container") + ] + return fetch_visual_links(visual_links) + + +def fetch_visual_links(visual_links): + """ + Fetch a list of video playlist entries. + + :param kwargs: "argument=value" pairs + :return: parsed out video metadata + :rtype: list + """ + + r = requests.post( + "https://www.newgrounds.com/visual-links-fetch", + params={ + "X-Requested-With": "XMLHttpRequest", + }, + data={ + "ids": json.dumps(visual_links).replace(" ", ""), + "component_params[include_author]": "1", + "include_all_suitabilities": "0", + "isAjaxRequest": "1", + }, + ) + + result = r.json() + entries = [] + if isinstance(result["partials"], dict): + for partial in result["partials"].values(): + if isinstance(partial, dict): + for partial in partial.values(): + soup = BeautifulSoup(partial, "html.parser") + title = soup.find("h4").string + thumbnail = soup.find("img")["src"] + author = soup.find("strong").string + url = soup.find("a")["href"] + entries.append( + { + "title": title, + "thumbnail": thumbnail, + "author": author, + "url": url, + } + ) + return entries + + def list_top_level(): """ Create the list of entrypoints in the Kodi interface. @@ -69,6 +183,24 @@ def list_top_level(): is_folder = False xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) + # Add a dialog for listing the series running on Newgrounds + list_item = xbmcgui.ListItem(label="Series") + info_tag = list_item.getVideoInfoTag() + info_tag.setMediaType("video") + info_tag.setTitle("Series") + url = get_url(action="series") + is_folder = True + xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) + + # Add a dialog for listing playlists + list_item = xbmcgui.ListItem(label="Collections") + info_tag = list_item.getVideoInfoTag() + info_tag.setMediaType("video") + info_tag.setTitle("Collections") + url = get_url(action="collections") + is_folder = True + xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) + for name in genres: list_item = xbmcgui.ListItem(label=name) @@ -81,7 +213,7 @@ def list_top_level(): is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) - xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) + xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.endOfDirectory(HANDLE) @@ -114,7 +246,7 @@ def list_categories(genre="Featured"): url = get_url(action="listing", genre=genre, category=categories[name]) is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) - xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) + xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.endOfDirectory(HANDLE) @@ -185,7 +317,7 @@ def get_addon_video_info(url): faves = sidestats_popularity[1].a if faves is not None: metadata["faves"] = int(faves.string.replace(",", "")) - metadata["votes"] = int(sidestats_popularity[2].string) + metadata["votes"] = int(sidestats_popularity[2].string.replace(",", "")) score = sidestats_popularity[3].span if score is not None: metadata["score"] = float(score.string) @@ -371,8 +503,44 @@ def list_videos(video_urls, title=None, next_page=None): is_folder = True xbmcplugin.addDirectoryItem(HANDLE, next_page, list_item, is_folder) - xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_LABEL_IGNORE_THE) - xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_VIDEO_YEAR) + xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) + xbmcplugin.endOfDirectory(HANDLE) + + +def list_cards(cards, title="Cards", next_page=None): + """ + Create the list of folders in the Kodi interface. + + :param cards: card summaries from `fetch_visual_links` + """ + videos = [] + xbmcplugin.setPluginCategory(HANDLE, title) + xbmcplugin.setContent(HANDLE, "movies") + + for card in cards: + list_item = xbmcgui.ListItem(label=card["title"]) + if card["thumbnail"] is not None: + list_item.setArt({"thumb": card["thumbnail"]}) + + info_tag = list_item.getVideoInfoTag() + info_tag.setMediaType("movie") + info_tag.setTitle(card["title"]) + info_tag.setDirectors([card["author"]]) + + url = get_url(action="listing_series", url=card["url"]) + is_folder = True + xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) + + # Add a "continue" folder. + if next_page is not None and len(cards) > 0: + list_item = xbmcgui.ListItem(label="Next Page") + info_tag = list_item.getVideoInfoTag() + info_tag.setMediaType("video") + info_tag.setTitle("Next Page") + is_folder = True + xbmcplugin.addDirectoryItem(HANDLE, next_page, list_item, is_folder) + + xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.endOfDirectory(HANDLE) @@ -415,6 +583,20 @@ def router(paramstring): list_videos(video_urls, params["genre"], next_page) elif params["action"] == "listing": list_categories(params["genre"]) + elif params["action"] == "series": + offset = int(params.get("offset", 1)) + cards = get_series(offset=offset) + next_page = get_url(action="series", offset=offset + 1) + list_cards(cards, "Series", next_page) + elif params["action"] == "collections": + offset = int(params.get("offset", 1)) + cards = get_collections(offset=offset) + next_page = get_url(action="collections", offset=offset + 1) + list_cards(cards, "Collections", next_page) + elif params["action"] == "listing_series": + cards = get_series_videos(params["url"]) + video_urls = [card["url"] for card in cards] + list_videos(video_urls) elif params["action"] == "search": term = params["term"] offset = int(params.get("offset", 1)) |