From 0f49dfb3ac8a90f977759019ba1c59df32dd760f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Iv=C3=A1n=20=C3=81valos?= Date: Mon, 18 Mar 2024 22:58:03 -0600 Subject: [PATCH] Add ListenBrainz documentation and tests --- README.md | 4 +++- config.sample | 5 +++++ docs/index.rst | 2 ++ docs/setup/configuration.rst | 29 +++++++++++++++++++++++++++++ tests/frontend/test_user.py | 16 ++++++++++++++++ tests/net/test_listenbrainz.py | 26 ++++++++++++++++++++++++++ 6 files changed, 81 insertions(+), 1 deletion(-) create mode 100644 tests/net/test_listenbrainz.py diff --git a/README.md b/README.md index 387d832..8ebf6fa 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ Current supported features are: * cover art * starred tracks/albums and ratings * [Last.fm][lastfm] scrobbling +* [ListenBrainz][listenbrainz] scrobbling * Jukebox mode Supysonic currently targets the version 1.12.0 of the Subsonic API. For more @@ -21,6 +22,7 @@ details, go check the [API implementation status][docs-api]. [subsonic]: http://www.subsonic.org/ [lastfm]: https://www.last.fm/ +[listenbrainz]: https://listenbrainz.org/ [docs-api]: https://supysonic.readthedocs.io/en/latest/api.html ## Documentation @@ -72,6 +74,6 @@ And there's also the tests (which require `lxml` to run): $ python -m unittest tests.net.suite The last command runs a few tests that make HTTP requests to remote third-party -services (namely Last.fm and ChartLyrics). +services (namely Last.fm, ListenBrainz and ChartLyrics). [flask]: https://flask.palletsprojects.com/ diff --git a/config.sample b/config.sample index 3daeddf..e0c2b63 100644 --- a/config.sample +++ b/config.sample @@ -74,6 +74,11 @@ log_rotate = yes ;api_key = ;secret = +[listenbrainz] +; root URL of the ListenBrainz API. +; Defaults: https://api.listenbrainz.org/ +;api_url = + [transcoding] ; Programs used to convert from one format/bitrate to another. Defaults: none transcoder_mp3_mp3 = lame --quiet --mp3input -b %outrate %srcpath - diff --git a/docs/index.rst b/docs/index.rst index 0411c00..f2b0a64 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -12,10 +12,12 @@ Current supported features are: * cover art * starred tracks/albums and ratings * `Last.FM`__ scrobbling +* `ListenBrainz`__ scrobbling * Jukebox mode __ http://www.subsonic.org/ __ https://www.last.fm/ +__ https://listenbrainz.org/ .. rubric:: User's guide diff --git a/docs/setup/configuration.rst b/docs/setup/configuration.rst index 01463bf..2dd7572 100644 --- a/docs/setup/configuration.rst +++ b/docs/setup/configuration.rst @@ -293,6 +293,35 @@ Sample configuration:: ;api_key = ;secret = +.. _conf-listenbrainz: + +``[listenbrainz]`` section +-------------------------- + +This section allows a custom ListenBrainz instance to be configured +for scrobbling. ListenBrainz is a music scrobbling service with social +features, similar to LastFM, but it is open source and +self-hostable. Supysonic can configured with any ListenBrainz +instance, but it connects to the official instance by default. + +In order to connect to ListenBrainz, each user requires an user token +that can be obtained from their ListenBrainz profile (more information +in the API docs). This token has to be configured per profile using +the web UI. + +The ListenBrainz API documentation can be found here: +https://listenbrainz.readthedocs.io/en/latest/users/api/index.html + +``api_url`` + root URL of the ListenBrainz API for the instance + +Sample configuration:: + + [listenbrainz] + ; root URL of the ListenBrainz API. + ; Defaults: https://api.listenbrainz.org/ + ;api_url = + .. _conf-transcoding: ``[transcoding]`` section diff --git a/tests/frontend/test_user.py b/tests/frontend/test_user.py index 297eaaf..dff2f5f 100644 --- a/tests/frontend/test_user.py +++ b/tests/frontend/test_user.py @@ -255,6 +255,22 @@ class UserTestCase(FrontendTestBase): rv = self.client.get("/user/me/lastfm/unlink", follow_redirects=True) self.assertIn("Unlinked", rv.data) + def test_listenbrainz_link(self): + self._login("alice", "Alic3") + rv = self.client.get("/user/me/listenbrainz/link", follow_redirects=True) + self.assertIn("Missing ListenBrainz auth token", rv.data) + # # Testing this requires an HTTP request! + # rv = self.client.get( + # "/user/me/listenbrainz/link", + # query_string={"token": "abcdef"}, + # follow_redirects=True, + # ) + # self.assertIn("Error: ", rv.data) + + def test_listenbrainz_unlink(self): + self._login("alice", "Alic3") + rv = self.client.get("/user/me/listenbrainz/unlink", follow_redirects=True) + self.assertIn("Unlinked", rv.data) if __name__ == "__main__": unittest.main() diff --git a/tests/net/test_listenbrainz.py b/tests/net/test_listenbrainz.py new file mode 100644 index 0000000..abd2031 --- /dev/null +++ b/tests/net/test_listenbrainz.py @@ -0,0 +1,26 @@ +# This file is part of Supysonic. +# Supysonic is a Python implementation of the Subsonic server API. +# +# Copyright (C) 2017-2018 Alban 'spl0k' Féron +# Copyright (C) 2024 Iván Ávalos +# +# Distributed under terms of the GNU AGPLv3 license. + +import logging +import unittest + +from supysonic.listenbrainz import ListenBrainz + +class ListenBrainzTestCase(unittest.TestCase): + """Basic test of unauthenticated ListenBrainz API method""" + + def test_request(self): + logging.getLogger("supysonic.listenbrainz").addHandler(logging.NullHandler()) + listenbrainz = ListenBrainz({"api_url": "https://api.listenbrainz.org/"}, None) + + user = "aavalos" + rv = listenbrainz._ListenBrainz__api_request(False, "/1/search/users/?search_term={0}".format(user), token="123") + self.assertIsInstance(rv, dict) + +if __name__ == "__main__": + unittest.main()