summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJakob L. Kreuze <zerodaysfordays@sdf.org>2024-06-19 12:11:39 -0400
committerJakob L. Kreuze <zerodaysfordays@sdf.org>2024-06-19 12:19:05 -0400
commit8578fc1dee49c59c8f1a9b2fc41bef46c1daaffb (patch)
treef85949c044b3082274480ae8ead3a3ff0ee98c2b
parent9fea13c74e560ca015fa3ff731c78706b9e638c0 (diff)
Add support for series and collections
-rw-r--r--addon.py192
1 files changed, 187 insertions, 5 deletions
diff --git a/addon.py b/addon.py
index 015a7ae..6dd59a8 100644
--- a/addon.py
+++ b/addon.py
@@ -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))