diff options
Diffstat (limited to 'addon.py')
| -rw-r--r-- | addon.py | 299 |
1 files changed, 299 insertions, 0 deletions
diff --git a/addon.py b/addon.py new file mode 100644 index 0000000..ac2009f --- /dev/null +++ b/addon.py @@ -0,0 +1,299 @@ +# Copyright © 2024 Jakob L. Kreuze <zerodaysfordays@sdf.org> +# +# 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 <https://www.gnu.org/licenses/>. + +""" +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:]) |