From 56f7c184955e5f3f1efaf955fd007086803bc96a Mon Sep 17 00:00:00 2001 From: krateng Date: Sat, 23 Apr 2022 05:59:47 +0200 Subject: [PATCH 1/6] Create API.md --- API.md | 84 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 84 insertions(+) create mode 100644 API.md diff --git a/API.md b/API.md new file mode 100644 index 0000000..d2717c3 --- /dev/null +++ b/API.md @@ -0,0 +1,84 @@ +The native Maloja API is reachable at `/apis/mlj_1`. Endpoints are listed on `/api_explorer`. + +All endpoints return JSON data. POST request can be made with query string or form data arguments, but this is discouraged - JSON should be used whenever possible. + +No application should ever rely on the non-existence of fields in the JSON data - i.e., additional fields can be added at any time without this being considered a breaking change. Existing fields should usually not be removed or changed, but it is always a good idea to add basic handling for missing fields. + +## Entity Structure + +Whenever a list of entities is returned, they have the following fields: + +### Scrobble + +| Key | Type | Description | +| --- | --- | --- | +| `time` | Integer | Timestamp of the Scrobble in UTC | +| `track` | Mapping | The [track](#Track) being scrobbled | +| `duration` | Integer | How long the track was played for in seconds | +| `origin` | String | Client that submitted the scrobble, or import source | + +**Example** + +```json +{ + "time": 1650684324, + "track": { + "artists": ["Jennie Kim","HyunA","LE","SunMi"], + "title": "Wow Thing", + "length":200 + }, + "duration": 196, + "origin": "client:navidrome_desktop" +} +``` + +### Track + +| Key | Type | Description | +| --- | --- | --- | +| `artists` | List | The [artists](#Artist) credited with the track | +| `title` | String | The title of the track | +| `length` | Integer | The full length of the track in seconds | + +**Example** + +```json +{ + "artists": ["Blackpink","Chou Tzuyu"], + "title": "MORE", + "length": 171 +} +``` + +### Artist + +Artists are just represented as raw Strings. + +**Example** + +```json +"Red Velvet" +``` + +## General Structure + +Most endpoints follow this structure: + +| Key | Type | Description | +| --- | --- | --- | +| `status` | String | Status of the request. Can be `success`, `ok`, `error`, `failure`, `no_operation` | +| `error` | Mapping | Details about the error if one occured. | +| `warnings` | List | Any warnings that did not result in failure, but should be noted. Field is omitted if there are no warnings! | +| `desc` | String | Human-readable feedback. This can be shown directly to the user if desired. | + +Both errors and warnings have the following structure: + + +| Key | Type | Description | +| --- | --- | --- | +| `type` | String | Name of the error or warning type | +| `value` | varies | Specific data for this error or warning instance | +| `desc` | String | Human-readable error or warning description. This can be shown directly to the user if desired. | + + + From 528d3565b76ab5d389d551130197829aac807e29 Mon Sep 17 00:00:00 2001 From: krateng Date: Sat, 23 Apr 2022 14:38:18 +0200 Subject: [PATCH 2/6] Moved API information --- API.md | 70 +++++++++++++++++++++++++++++++++++++------------------ README.md | 51 ++-------------------------------------- 2 files changed, 49 insertions(+), 72 deletions(-) diff --git a/API.md b/API.md index d2717c3..5ef412a 100644 --- a/API.md +++ b/API.md @@ -1,9 +1,56 @@ +# Scrobbling + +In order to scrobble from a wide selection of clients, you can use Maloja's standard-compliant APIs with the following settings: + +GNU FM |   +------ | --------- +Gnukebox URL | Your Maloja URL followed by `/apis/audioscrobbler` +Username | Doesn't matter +Password | Any of your API keys + +ListenBrainz |   +------ | --------- +API URL | Your Maloja URL followed by `/apis/listenbrainz` +Username | Doesn't matter +Auth Token | Any of your API keys + +Audioscrobbler v1.2 |   +------ | --------- +Server URL | Your Maloja URL followed by `/apis/audioscrobbler_legacy` +Username | Doesn't matter +Password | Any of your API keys + +Note that this is the base URL - some scrobblers ask you for the full endpoint instead. + +# API Documentation + The native Maloja API is reachable at `/apis/mlj_1`. Endpoints are listed on `/api_explorer`. All endpoints return JSON data. POST request can be made with query string or form data arguments, but this is discouraged - JSON should be used whenever possible. No application should ever rely on the non-existence of fields in the JSON data - i.e., additional fields can be added at any time without this being considered a breaking change. Existing fields should usually not be removed or changed, but it is always a good idea to add basic handling for missing fields. +## General Structure + +Most endpoints follow this structure: + +| Key | Type | Description | +| --- | --- | --- | +| `status` | String | Status of the request. Can be `success`, `ok`, `error`, `failure`, `no_operation` | +| `error` | Mapping | Details about the error if one occured. | +| `warnings` | List | Any warnings that did not result in failure, but should be noted. Field is omitted if there are no warnings! | +| `desc` | String | Human-readable feedback. This can be shown directly to the user if desired. | + +Both errors and warnings have the following structure: + + +| Key | Type | Description | +| --- | --- | --- | +| `type` | String | Name of the error or warning type | +| `value` | varies | Specific data for this error or warning instance | +| `desc` | String | Human-readable error or warning description. This can be shown directly to the user if desired. | + + ## Entity Structure Whenever a list of entities is returned, they have the following fields: @@ -59,26 +106,3 @@ Artists are just represented as raw Strings. ```json "Red Velvet" ``` - -## General Structure - -Most endpoints follow this structure: - -| Key | Type | Description | -| --- | --- | --- | -| `status` | String | Status of the request. Can be `success`, `ok`, `error`, `failure`, `no_operation` | -| `error` | Mapping | Details about the error if one occured. | -| `warnings` | List | Any warnings that did not result in failure, but should be noted. Field is omitted if there are no warnings! | -| `desc` | String | Human-readable feedback. This can be shown directly to the user if desired. | - -Both errors and warnings have the following structure: - - -| Key | Type | Description | -| --- | --- | --- | -| `type` | String | Name of the error or warning type | -| `value` | varies | Specific data for this error or warning instance | -| `desc` | String | Human-readable error or warning description. This can be shown directly to the user if desired. | - - - diff --git a/README.md b/README.md index 1ba0d76..5be8caf 100644 --- a/README.md +++ b/README.md @@ -156,56 +156,9 @@ To backup your data, run `maloja backup`, optional with `--include_images`. You can set up any amount of API keys in the file `apikeys.yml` in your configuration folder (or via the web interface). It is recommended to define a different API key for every scrobbler you use. -### Native support +Some scrobbler clients support Maloja's native API. You can also use any scrobbler that allows you to set a custom Listenbrainz or GNUFM server. See [API.md](API.md) for details. -These solutions allow you to directly setup scrobbling to your Maloja server: -* [Tauon](https://tauonmusicbox.rocks) Desktop Player -* [Web Scrobbler](https://github.com/web-scrobbler/web-scrobbler) Browser Extension -* [Multi Scrobbler](https://github.com/FoxxMD/multi-scrobbler) Desktop Application -* [Cmus-maloja-scrobbler](https://git.sr.ht/~xyank/cmus-maloja-scrobbler) Script -* [OngakuKiroku](https://github.com/Atelier-Shiori/OngakuKiroku) Desktop Application (Mac) -* [Maloja Scrobbler](https://chrome.google.com/webstore/detail/maloja-scrobbler/cfnbifdmgbnaalphodcbandoopgbfeeh) Chromium Extension (also included in the repository) for Plex Web, Spotify, Bandcamp, Soundcloud or Youtube Music - -### Native API - -If you want to implement your own method of scrobbling, it's very simple: You only need one POST request to `/apis/mlj_1/newscrobble` with the keys `artist`, `title` and `key` (and optionally `album`,`duration` (in seconds) and `time`(for cached scrobbles)) - either as form-data or json. - -If you're the maintainer of a music player or server and would like to implement native Maloja scrobbling, feel free to reach out - I'll try my best to help. For Python applications, you can simply use the [`malojalib` package](https://pypi.org/project/maloja-lib/) for a consistent interface even with future updates. - -### Standard-compliant API - -You can use any third-party scrobbler that supports the audioscrobbler (GNUFM) or the ListenBrainz protocol. This is still somewhat experimental, but give it a try with these settings: - -GNU FM |   ------- | --------- -Gnukebox URL | Your Maloja URL followed by `/apis/audioscrobbler` -Username | Doesn't matter -Password | Any of your API keys - -ListenBrainz |   ------- | --------- -API URL | Your Maloja URL followed by `/apis/listenbrainz` -Username | Doesn't matter -Auth Token | Any of your API keys - -Audioscrobbler v1.2 |   ------- | --------- -Server URL | Your Maloja URL followed by `/apis/audioscrobbler_legacy` -Username | Doesn't matter -Password | Any of your API keys - -Known working scrobblers: -* [Pano Scrobbler](https://github.com/kawaiiDango/pScrobbler) for Android -* [Simple Scrobbler](https://simple-last-fm-scrobbler.github.io) for Android -* [Airsonic Advanced](https://github.com/airsonic-advanced/airsonic-advanced) (requires you to supply the full endpoint (`yoururl.tld/apis/listenbrainz/1/submit-listens`)) -* [Funkwhale](https://dev.funkwhale.audio/funkwhale/funkwhale) (use the legacy API `yoururl.tld/apis/audioscrobbler_legacy`) -* [mpdscribble](https://github.com/MusicPlayerDaemon/mpdscribble) (use the legacy API `yoururl.tld/apis/audioscrobbler_legacy`) - -I'm thankful for any feedback whether other scrobblers work! - - - -### Manual +If you're the maintainer of a music player or server and would like to implement native Maloja scrobbling, feel free to reach out! If you can't automatically scrobble your music, you can always do it manually on the `/admin_manual` page of your Maloja server. From 0424fa7795a7dcff262e3a8c59fb420101e20f41 Mon Sep 17 00:00:00 2001 From: krateng Date: Sat, 23 Apr 2022 15:25:42 +0200 Subject: [PATCH 3/6] Update API.md --- API.md | 55 +++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 51 insertions(+), 4 deletions(-) diff --git a/API.md b/API.md index 5ef412a..deb7e88 100644 --- a/API.md +++ b/API.md @@ -20,7 +20,32 @@ Server URL | Your Maloja URL followed by `/apis/audioscrobbler_legacy` Username | Doesn't matter Password | Any of your API keys -Note that this is the base URL - some scrobblers ask you for the full endpoint instead. +| :warning: | Note that these are the base URLs - some scrobblers ask you for the full endpoint instead. | +|---------------|:------------------------| + +## Scrobbling Guideline + +Maloja makes no assumptions about scrobbling behaviour. The clients should decide when and whether a play is scrobbled - the server will accept it as long as it contains all necessary data. However, a general guideline is: + +* As soon as a track has been played for 50% of its length or 4 minutes, it should be counted as a scrobble +* That scrobble should be submitted when the play has ended in order to know its duration +* If the total play duration is enough to count as a scrobble, but not longer than the total track length + enough for a second scrobble, it should be submitted as a scrobble with the according duration +* If the duration exceeds this value, the first scrobble should be submitted as a scrobble with the duration of the full track length, while the second scrobble is queued up following the above suggestions in regards to remaining time + + + + + +
:memo: Example
+ +The user starts playing '(Fine Layers of) Slaysenflite', which is exactly 3:00 minutes long. +* If the user ends the play after 1:22, no scrobble is submitted +* If the user ends the play after 2:06, a scrobble with `"duration":126` is submitted +* If the user jumps back several times and ends the play after 3:57, a scrobble with `"duration":237` is submitted +* If the user jumps back several times and ends the play after 4:49, two scrobbles with `"duration":180` and `"duration":109` should be submitted + +
+ # API Documentation @@ -64,8 +89,11 @@ Whenever a list of entities is returned, they have the following fields: | `duration` | Integer | How long the track was played for in seconds | | `origin` | String | Client that submitted the scrobble, or import source | -**Example** +
+ + +
:memo: Example
+ ```json { "time": 1650684324, @@ -78,6 +106,11 @@ Whenever a list of entities is returned, they have the following fields: "origin": "client:navidrome_desktop" } ``` + +
+ + ### Track @@ -87,8 +120,10 @@ Whenever a list of entities is returned, they have the following fields: | `title` | String | The title of the track | | `length` | Integer | The full length of the track in seconds | -**Example** - + + + +
:memo: Example
+ ```json { "artists": ["Blackpink","Chou Tzuyu"], @@ -96,6 +131,11 @@ Whenever a list of entities is returned, they have the following fields: "length": 171 } ``` + +
+ + ### Artist @@ -103,6 +143,13 @@ Artists are just represented as raw Strings. **Example** + + + +
:memo: Example
+ ```json "Red Velvet" ``` + +
From e330678a055586b04ceb21d17877735a3abb7912 Mon Sep 17 00:00:00 2001 From: krateng Date: Sat, 23 Apr 2022 15:45:32 +0200 Subject: [PATCH 4/6] Update API.md --- API.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/API.md b/API.md index deb7e88..5692116 100644 --- a/API.md +++ b/API.md @@ -57,6 +57,7 @@ No application should ever rely on the non-existence of fields in the JSON data ## General Structure + Most endpoints follow this structure: | Key | Type | Description | @@ -65,10 +66,11 @@ Most endpoints follow this structure: | `error` | Mapping | Details about the error if one occured. | | `warnings` | List | Any warnings that did not result in failure, but should be noted. Field is omitted if there are no warnings! | | `desc` | String | Human-readable feedback. This can be shown directly to the user if desired. | +| `list` | List | List of returned [entities](#Entity-Structure) | + Both errors and warnings have the following structure: - | Key | Type | Description | | --- | --- | --- | | `type` | String | Name of the error or warning type | From 519c26b8d8da2bd1dbed024c58b9b419d7272909 Mon Sep 17 00:00:00 2001 From: northys Date: Sat, 23 Apr 2022 15:34:49 +0200 Subject: [PATCH 5/6] README.md: docker import suggestion [closes #125] [closes #126] [closes #127] --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 5be8caf..80c329d 100644 --- a/README.md +++ b/README.md @@ -141,6 +141,16 @@ If you would like to import your previous scrobbles, use the command `maloja imp * an official [Spotify data export file](https://www.spotify.com/us/account/privacy/) * the export of another Maloja instance +⚠️ Never import your data while maloja is running. When you need to do import inside docker container start it in shell mode instead and perform import before starting the container as mentioned above. + +```console + docker run -it --entrypoint sh -v $PWD/malojadata:/mljdata -e MALOJA_DATA_DIRECTORY=/mljdata krateng/maloja + cd /mljdata + maloja import my_last_fm_export.csv +``` + +--- + To backup your data, run `maloja backup`, optional with `--include_images`. ### Customization From db8bf60aef75e163221b1fc111c83337db4f8c1c Mon Sep 17 00:00:00 2001 From: krateng Date: Sat, 23 Apr 2022 16:36:35 +0200 Subject: [PATCH 6/6] Moved search functionality to database --- maloja/database/__init__.py | 4 ++-- maloja/database/sqldb.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/maloja/database/__init__.py b/maloja/database/__init__.py index d83c8f3..6595e88 100644 --- a/maloja/database/__init__.py +++ b/maloja/database/__init__.py @@ -423,7 +423,7 @@ def start_db(): def db_search(query,type=None): results = [] if type=="ARTIST": - results = [a for a in sqldb.get_artists() if sqldb.normalize_name(query) in sqldb.normalize_name(a)] + results = sqldb.search_artist(query) if type=="TRACK": - results = [t for t in sqldb.get_tracks() if sqldb.normalize_name(query) in sqldb.normalize_name(t['title'])] + results = sqldb.search_track(query) return results diff --git a/maloja/database/sqldb.py b/maloja/database/sqldb.py index e0954d9..4ee49ca 100644 --- a/maloja/database/sqldb.py +++ b/maloja/database/sqldb.py @@ -691,7 +691,25 @@ def get_artist(id,dbconn=None): return artist_db_to_dict(artistinfo,dbconn=dbconn) +@cached_wrapper +@connection_provider +def search_artist(searchterm,dbconn=None): + op = DB['artists'].select().where( + DB['artists'].c.name_normalized.ilike(normalize_name(f"%{searchterm}%")) + ) + result = dbconn.execute(op).all() + return [get_artist(row.id,dbconn=dbconn) for row in result] + +@cached_wrapper +@connection_provider +def search_track(searchterm,dbconn=None): + op = DB['tracks'].select().where( + DB['tracks'].c.title_normalized.ilike(normalize_name(f"%{searchterm}%")) + ) + result = dbconn.execute(op).all() + + return [get_track(row.id,dbconn=dbconn) for row in result] ##### MAINTENANCE