# Copyright © 2024 Jakob L. Kreuze # # This program is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 3 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program. If not, see . """ Video plugin for animations posted to Newgrounds. """ import os import sys from urllib.parse import urlencode, parse_qsl from newgrounds import Grouping, Newgrounds, MOVIE_CATEGORIES import xbmc import xbmcgui import xbmcplugin from xbmcaddon import Addon from xbmcvfs import translatePath URL = sys.argv[0] HANDLE = int(sys.argv[1]) ADDON_PATH = translatePath(Addon().getAddonInfo("path")) ICONS_DIR = os.path.join(ADDON_PATH, "resources", "images", "icons") FANART_DIR = os.path.join(ADDON_PATH, "resources", "images", "fanart") def get_url(**kwargs): """ Create a URL for calling the plugin recursively from the given set of keyword arguments. :param kwargs: "argument=value" pairs :return: plugin call URL :rtype: str """ return f"{URL}?{urlencode(kwargs)}" def authentication_specified(): addon = Addon() username = addon.getSetting("newgrounds.username") password = addon.getSetting("newgrounds.password") return username != "" and password != "" def get_session(): addon = Addon() session = addon.getSetting("newgrounds.session") if session is not None: # and test_logged_in(session): return {SESSION_COOKIE_NAME: session} username = addon.getSetting("newgrounds.username") password = addon.getSetting("newgrounds.password") if username == "" or password == "": return {} session = open_new_session(username, password) addon.setSetting("newgrounds.session", session) return {SESSION_COOKIE_NAME: session} class NewgroundsPlugin(object): def __init__(self): self.ng = Newgrounds() def invoke(paramstring): ng = NewgroundsPlugin() params = dict(parse_qsl(paramstring)) if "action" in params: method = getattr(ng, params["action"]) if method is None: raise ValueError(f"Invalid paramstring: {paramstring}!") else: method = ng.default method(**params) def default(self, **kwargs): self.list_front_page() def prompt_search(self, **kwargs): dialog = xbmcgui.Dialog() term = dialog.input(heading="Search") if term is not None: url = get_url(action="search", term=term) xbmc.executebuiltin("Container.Update(%s)" % url) def search(self, **kwargs): assert "term" in kwargs term = kwargs["term"] offset = int(kwargs.get("offset", 1)) video_urls = self.ng.search(term, offset=offset) next_page = get_url(action="search", term=term, offset=offset + 1) self._list_videos(video_urls, "Search Results: {}".format(term), next_page) def list_front_page(self, **kwargs): """ Create the list of entrypoints in the Kodi interface. """ xbmcplugin.setPluginCategory(HANDLE, "Newgrounds") xbmcplugin.setContent(HANDLE, "movies") groupings = { "Featured": Grouping.FEATURED, "Latest": Grouping.LATEST, "Popular": Grouping.POPULAR, } # Add a search dialog list_item = xbmcgui.ListItem(label="Search") info_tag = list_item.getVideoInfoTag() info_tag.setMediaType("video") info_tag.setTitle("Search") url = get_url(action="prompt_search") is_folder = False xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) # Add a dialog for displaying the user's feed if authentication_specified(): list_item = xbmcgui.ListItem(label="Your Feed") info_tag = list_item.getVideoInfoTag() info_tag.setMediaType("video") info_tag.setTitle("Your Feed") url = get_url(action="list_feed") is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) for name, enum in groupings.items(): list_item = xbmcgui.ListItem(label=name) info_tag = list_item.getVideoInfoTag() info_tag.setMediaType("video") info_tag.setTitle(name) info_tag.setGenres([name]) url = get_url(action="list_categories", grouping=enum.value) is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.endOfDirectory(HANDLE) def list_categories(self, **kwargs): """ Create the list of movie categories in the Kodi interface. """ grouping = kwargs.get("grouping", Grouping.FEATURED.value) xbmcplugin.setPluginCategory(HANDLE, "Categories") xbmcplugin.setContent(HANDLE, "movies") for name in MOVIE_CATEGORIES.keys(): list_item = xbmcgui.ListItem(label=name) info_tag = list_item.getVideoInfoTag() info_tag.setMediaType("video") info_tag.setTitle(name) info_tag.setGenres([name]) url = get_url(action="list_videos", grouping=grouping, category=name) is_folder = True xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) xbmcplugin.addSortMethod(HANDLE, xbmcplugin.SORT_METHOD_NONE) xbmcplugin.endOfDirectory(HANDLE) def list_videos(self, **kwargs): """ Create the list of playable videos in the Kodi interface. """ grouping = int(kwargs.get("grouping", 0)) offset = int(kwargs.get("offset", 0)) category = kwargs.get("category", "All") videos = self.ng.front_page( grouping=int(grouping), interval="all", category=category, offset=offset, ) next_page = get_url( action="list_videos", grouping=grouping, category=category, offset=offset + 20, ) self._list_videos(videos, f"{category} - {grouping}", next_page) # TODO: Support offsets. def list_feed(self, **kwargs): # offset = int(kwargs.get("offset", 1)) # next_page = get_url(action="list_series", offset=offset + 1) addon = Addon() username = addon.getSetting("newgrounds.username") password = addon.getSetting("newgrounds.password") self.ng.login(username, password) videos = self.ng.feed() self._list_videos(videos, f"Feed - {username}") def list_series(self, **kwargs): offset = int(kwargs.get("offset", 1)) cards = self.ng.series(offset=offset) next_page = get_url(action="list_series", offset=offset + 1) self._list_cards( title="Series", cards=cards, next_page=next_page, ) def list_collections(self, **kwargs): offset = int(kwargs.get("offset", 1)) cards = self.ng.collections(offset=offset) next_page = get_url(action="list_collections", offset=offset + 1) self._list_cards( title="Collections", cards=cards, next_page=next_page, ) # TODO: Support offsets. def list_playlist(self, **kwargs): assert "url" in kwargs cards = self.ng.playlist_entries(kwargs["url"]) # next_page = get_url(action="list_playlist", offset=offset + 1) self._list_cards( title="Series", cards=cards, # next_page=next_page, ) def _list_cards(self, cards, next_page=None, title=""): """ Create the list of folders in the Kodi interface. """ 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="list_playlist", 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) def _list_videos(self, videos, title="Videos", next_page=None): xbmcplugin.setPluginCategory(HANDLE, title) xbmcplugin.setContent(HANDLE, "movies") for video in videos: list_item = xbmcgui.ListItem(label=video.title) if video.thumbnail is not None: list_item.setArt({"thumb": video.thumbnail}) info_tag = list_item.getVideoInfoTag() info_tag.setMediaType("movie") info_tag.setTitle(video.title) info_tag.setYear(video.upload_date.year) info_tag.setRating(video.score, video.votes) info_tag.setPlaycount(video.views) info_tag.setPlot(video.description) info_tag.setGenres([video.genre]) info_tag.setDirectors(video.authors) info_tag.setPremiered(video.upload_date.strftime("%Y-%m-%d")) info_tag.setTags(video.tags) if video.suitability == "a": info_tag.setMpaa("NC-17") elif video.suitability == "m": info_tag.setMpaa("R") elif video.suitability == "t": info_tag.setMpaa("PG-13") else: info_tag.setMpaa("PG") list_item.setProperty("IsPlayable", "true") url = get_url(action="play_video", video=video.content_url) is_folder = False xbmcplugin.addDirectoryItem(HANDLE, url, list_item, is_folder) # Add a "continue" folder. if next_page is not None and len(videos) > 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) def play_video(self, **kwargs): assert "video" in kwargs path = kwargs["video"] play_item = xbmcgui.ListItem(path=path) play_item.setProperty("IsPlayable", "true") xbmcplugin.setResolvedUrl(HANDLE, True, play_item) if __name__ == "__main__": NewgroundsPlugin.invoke(sys.argv[2][1:])