# 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 . """ Audio plugin for songs posted to Newgrounds. """ import os import sys from urllib.parse import urlencode, parse_qsl from newgrounds import Conduct, Grouping, Newgrounds, AUDIO_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, conduct=Conduct.AUDIO, offset=offset) next_page = get_url(action="search", term=term, offset=offset + 1) self._list_songs(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, "songs") 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("song") 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("song") 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("song") 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 audio categories in the Kodi interface. """ grouping = kwargs.get("grouping", Grouping.FEATURED.value) xbmcplugin.setPluginCategory(HANDLE, "Categories") xbmcplugin.setContent(HANDLE, "songs") for name in AUDIO_CATEGORIES.keys(): list_item = xbmcgui.ListItem(label=name) info_tag = list_item.getMusicInfoTag() info_tag.setMediaType("song") info_tag.setTitle(name) info_tag.setGenres([name]) url = get_url(action="list_audio", 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_audio(self, **kwargs): """ Create the list of playable songs in the Kodi interface. """ grouping = int(kwargs.get("grouping", 0)) offset = int(kwargs.get("offset", 0)) category = kwargs.get("category", "All") songs = self.ng.front_page( conduct=Conduct.AUDIO, grouping=int(grouping), interval="all", category=category, offset=offset, ) next_page = get_url( action="list_audio", grouping=grouping, category=category, offset=offset + 20, ) self._list_songs(songs, f"{category} - {grouping}", next_page) 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) songs = self.ng.feed(conduct=Conduct.AUDIO) self._list_songs(songs, f"Feed - {username}") # 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, "sonmgs") 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.getMusicInfoTag() info_tag.setMediaType("song") 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.getMusicInfoTag() info_tag.setMediaType("song") 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_songs(self, songs, title="Songs", next_page=None): xbmcplugin.setPluginCategory(HANDLE, title) xbmcplugin.setContent(HANDLE, "songs") for song in songs: list_item = xbmcgui.ListItem(label=song.title) if song.thumbnail is not None: list_item.setArt({"thumb": song.thumbnail}) info_tag = list_item.getMusicInfoTag() info_tag.setMediaType("song") info_tag.setTitle(song.title) info_tag.setReleaseDate(song.upload_date.strftime("%Y-%m-%d")) info_tag.setRating(song.score) # info_tag.setPlaycount(song.views) # info_tag.setPlot(song.description) info_tag.setGenres([song.genre]) info_tag.setArtist(song.author) # info_tag.setTags(song.tags) list_item.setProperty("IsPlayable", "true") url = get_url(action="play_audio", audio=song.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(songs) > 0: list_item = xbmcgui.ListItem(label="Next Page") info_tag = list_item.getMusicInfoTag() info_tag.setMediaType("audio") 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_audio(self, **kwargs): assert "video" in kwargs path = kwargs["audio"] 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:])