├── .nojekyll ├── .gitattributes ├── docs ├── .vuepress │ ├── override.styl │ └── config.js ├── ws │ ├── README.md │ ├── usage.md │ └── spec.md ├── api │ ├── requests.md │ ├── README.md │ ├── account.md │ ├── artists.md │ ├── songs.md │ ├── favorites.md │ ├── posts.md │ ├── feeds.md │ └── users.md └── README.md ├── .gitignore └── package.json /.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /docs/.vuepress/override.styl: -------------------------------------------------------------------------------- 1 | $accentColor = #ff015b 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Packages 2 | node_modules/ 3 | 4 | # Log files 5 | logs/ 6 | *.log 7 | 8 | # Authentication 9 | 10 | # Test files 11 | 12 | # Miscellaneous 13 | .tmp/ 14 | .vscode/ 15 | 16 | docs/.vuepress/dist 17 | -------------------------------------------------------------------------------- /docs/ws/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | If you are building an app or service that interacts with [LISTEN.moe](https://listen.moe) chances are you also want to provide with currently playing song information, and the WebSocket is the way to get it. 4 | 5 | The WebSocket allows you to establish a connection and wait to be notified of new data in order to avoid spamming the API for updates. This means that whenever a song changes or a music event starts, we will notify you over the WebSocket for you to do with that data as you please. 6 | 7 | First head over to the [spec](spec.html) to see how the WebSocket works and behaves and then take a code sample from [usage](usage.html) or write your own! 8 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "docs", 3 | "version": "4.0.0", 4 | "description": "The docs for LISTEN.moe API", 5 | "private": true, 6 | "author": { 7 | "name": "iCrawl", 8 | "email": "icrawltogo@gmail.com" 9 | }, 10 | "license": "UNLICENSED", 11 | "scripts": { 12 | "lint": "eslint src", 13 | "dev": "vuepress dev docs", 14 | "build": "vuepress build docs" 15 | }, 16 | "repository": { 17 | "type": "git", 18 | "url": "git+https://github.com/LISTEN-moe/docs.git" 19 | }, 20 | "bugs": { 21 | "url": "https://github.com/LISTEN-moe/docs/issues" 22 | }, 23 | "keywords": [ 24 | "docs" 25 | ], 26 | "homepage": "https://github.com/LISTEN-moe/docs.git#readme", 27 | "dependencies": {}, 28 | "devDependencies": { 29 | "eslint": "^4.19.1", 30 | "eslint-config-aqua": "^4.3.0", 31 | "vuepress": "^0.10.1" 32 | }, 33 | "eslintConfig": { 34 | "extends": "aqua/node" 35 | }, 36 | "engines": { 37 | "node": ">=8.0.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /docs/api/requests.md: -------------------------------------------------------------------------------- 1 | # Requests 2 | 3 | ## Request a song 4 | 5 | ``` 6 | Type: POST 7 | Route: /requests/{id} 8 | ``` 9 | 10 | URI Parameters 11 | ``` 12 | Name: id 13 | Type: String 14 | Required: True 15 | ``` 16 | 17 | Response `204` 18 | 19 | Possible errors 20 | 21 | ```json 22 | # Code 400 23 | { "message": "No params provided." } 24 | 25 | # Code 403 26 | { "message": "Not authorized." } 27 | { "message": "All requests used for today." } 28 | { "message": "Song already queued." } 29 | 30 | # Code 404 31 | { "message": "No song found." } 32 | ``` 33 | 34 | ## Remove a request from the queue 35 | 36 | ``` 37 | Type: DELETE 38 | Route: /requests/{id} 39 | ``` 40 | 41 | URI Parameters 42 | ``` 43 | Name: id 44 | Type: String 45 | Required: True 46 | ``` 47 | 48 | Response `204` 49 | 50 | Possible errors 51 | 52 | ```json 53 | # Code 400 54 | { "message": "No params provided." } 55 | 56 | # Code 403 57 | { "message": "Not authorized." } 58 | 59 | # Code 404 60 | { "message": "No request found." } 61 | ``` 62 | -------------------------------------------------------------------------------- /docs/api/README.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | The LISTEN.moe API allows users and developers alike to access a wide range of features offered through the website such as getting the list of songs to even enabling 2FA on your account! Every single request to the API must have the following headers present, otherwise the request will be dropped. 4 | 5 | ``` 6 | Content-Type: application/json 7 | Accept: application/vnd.listen.v4+json 8 | ``` 9 | ---- 10 | 11 | After you successfully log in by hitting the `login` endpoint, we keep track of your session by issuing a JSON Web Token that you need to save locally and attach on every subsequent request, otherwise you won't be able to interact with the API. To do so, attach the issued token to your request header like so: 12 | 13 | ``` 14 | Content-Type: application/json 15 | Accept: application/vnd.listen.v4+json 16 | Authorization: Bearer JWT 17 | ``` 18 | 19 | ---- 20 | 21 | Be aware that every endpoint base url is `https://listen.moe/api`, meaning that if you need to make a http request to the login endpoint you should be pointing it to `https://listen.moe/api/login`. 22 | 23 | ::: warning 24 | Every request to the API needs a valid Authorization header unless stated otherwise, just like the login or register routes. 25 | ::: 26 | -------------------------------------------------------------------------------- /docs/README.md: -------------------------------------------------------------------------------- 1 | # Welcome to the docs 2 | 3 | Developers can use this resource to build third party apps, bots or services that interact with the LISTEN.moe ecosystem. The documentation is divided in 2 parts which are [API](api/) and [WebSocket](ws/) docs. While most of the user related features requires intereaction through the API, everything about playback, queue and past songs is done through our WebSocket. 4 | 5 | If you need access to the raw streams you can find them at: 6 | 7 | | Type | Jpop | Kpop | Quality | 8 | | ---------- |:-----------------------------------:| ----------------------------------------:| -------:| 9 | | Vorbis | [link](https://listen.moe/stream) | [link](https://listen.moe/kpop/stream) | 128kbps | 10 | | Opus | [link](https://listen.moe/opus) | [link](https://listen.moe/kpop/opus) | 128kbps | 11 | | MP3 | [link](https://listen.moe/fallback) | [link](https://listen.moe/kpop/fallback) | 128kbps | 12 | 13 | ::: tip 14 | Keep in mind that the Vorbis stream carries metadata of the current song and has the best quality bandwidth-wise, so it's always encouraged to use this one. 15 | ::: 16 | 17 | In case you want to listen to the radio in your own player you can either use one of the url provided above or download the respective **.m3u** files for [jpop](https://listen.moe/m3u8/jpop.m3u) and [kpop](https://listen.moe/m3u8/kpop.m3u) 18 | 19 | Without further ado, let's dive into the docs. 20 | -------------------------------------------------------------------------------- /docs/api/account.md: -------------------------------------------------------------------------------- 1 | # Account 2 | 3 | ## Register 4 | 5 | ``` 6 | Type: POST 7 | Route: /register 8 | ``` 9 | 10 | Body 11 | 12 | ```json 13 | { 14 | "email": "testUser@gmail.com", 15 | "username": "TestUser", 16 | "password": "superSecurePassword" 17 | } 18 | ``` 19 | 20 | Response `200` 21 | 22 | ```json 23 | { "message": "You have successfully created a new account." } 24 | ``` 25 | 26 | Possible errors 27 | 28 | ```json 29 | # Code 400 30 | { "message": "Invalid e-mail address provided." } 31 | { "message": "An account with that email address and/or username already exists." } 32 | ``` 33 | 34 | ## Login 35 | 36 | ``` 37 | Type: POST 38 | Route: /login 39 | ``` 40 | 41 | Body 42 | 43 | ```json 44 | { 45 | "username": "TestUser", 46 | "password": "superSecurePassword" 47 | } 48 | # or 49 | { 50 | "username": "testUser@gmail.com", 51 | "password": "superSecurePassword" 52 | } 53 | ``` 54 | 55 | Response `200` 56 | 57 | ```json 58 | { 59 | "message": "Successfully logged in.", 60 | "token": "JWT", 61 | "apiKey": "your-api-key" #Only if requested via developer access 62 | } 63 | # or 64 | { 65 | "message": "Please provide the 2FA code to log in.", 66 | "mfa": true, 67 | "token": "JWT" #This token is only valid for ~2m from this point on 68 | } 69 | ``` 70 | Possible errors 71 | ```json 72 | # Code 400 73 | { "message": "No body provided." } 74 | { "message": "Invalid body provided." } 75 | 76 | # Code 401 77 | { "message": "Invalid authorization." } 78 | 79 | # Code 403 80 | { "message": "This account has been deactivated." } 81 | ``` 82 | 83 | ## Login 2FA 84 | 85 | ``` 86 | Type: POST 87 | Route: /login/mfa 88 | ``` 89 | 90 | Additional headers 91 | 92 | ``` 93 | Authorization: Bearer Temp2FAJWT 94 | ``` 95 | 96 | Body 97 | 98 | ```json 99 | { 100 | "token": "OPTCode" 101 | } 102 | ``` 103 | 104 | Response `200` 105 | 106 | ```json 107 | { 108 | "message": "Successfully logged in.", 109 | "token": "mfa.JWT", 110 | "apiKey": "your-api-key" # Only if requested via developer access 111 | } 112 | ``` 113 | Possible errors 114 | ```json 115 | # Code 400 116 | { "message": "No body provided." } 117 | { "message": "Invalid body provided." } 118 | 119 | # Code 401 120 | { "message": "Invalid authorization." } 121 | 122 | # Code 403 123 | { "message": "This account has been disabled." } 124 | -------------------------------------------------------------------------------- /docs/ws/usage.md: -------------------------------------------------------------------------------- 1 | # Usage 2 | 3 | ## JavaScript 4 | 5 | A simple JavaScript implementation could be as follows: 6 | 7 | ```js 8 | let heartbeatInterval; 9 | let ws; 10 | 11 | function heartbeat(interval) { 12 | heartbeatInterval = setInterval(() => { 13 | ws.send(JSON.stringify({ op: 9 })); 14 | }, interval); 15 | } 16 | 17 | function connect() { 18 | ws = new WebSocket('wss://listen.moe/gateway_v2'); 19 | 20 | ws.onopen = () => { 21 | clearInterval(heartbeatInterval); 22 | heartbeatInterval = null; 23 | }; 24 | 25 | ws.onmessage = message => { 26 | if (!message.data.length) return; 27 | let response; 28 | try { 29 | response = JSON.parse(message.data); 30 | } catch (error) { 31 | return; 32 | } 33 | switch (response.op) { 34 | case 0: 35 | ws.send(JSON.stringify({ op: 9 })); 36 | heartbeat(response.d.heartbeat); 37 | break; 38 | case 1: 39 | if (response.t !== 'TRACK_UPDATE' && response.t !== 'TRACK_UPDATE_REQUEST' && response.t !== 'QUEUE_UPDATE' && response.t !== 'NOTIFICATION') break; 40 | console.log(response.d); // Do something with the data 41 | break; 42 | default: 43 | break; 44 | } 45 | }; 46 | 47 | ws.onclose = error => { 48 | clearInterval(heartbeatInterval); 49 | heartbeatInterval = null; 50 | if (ws) { 51 | ws.close(); 52 | ws = null; 53 | } 54 | setTimeout(() => connect(), 5000); 55 | }; 56 | } 57 | 58 | connect(); 59 | ``` 60 | 61 | ## Python 62 | 63 | Python3 implementation that uses the websockets library. 64 | 65 | ```python 66 | # python >= 3.5, websockets~=4.0.1 67 | 68 | from pprint import pprint 69 | import json 70 | import asyncio 71 | 72 | import websockets 73 | 74 | async def send_ws(ws, data): 75 | json_data = json.dumps(data) 76 | await ws.send(json_data) 77 | 78 | async def _send_pings(ws, interval=45): 79 | while True: 80 | await asyncio.sleep(interval) 81 | msg = { 'op': 9 } 82 | await send_ws(ws, msg) 83 | 84 | async def main(loop): 85 | url = 'wss://listen.moe/gateway_v2' 86 | ws = await websockets.connect(url) 87 | 88 | while True: 89 | data = json.loads(await ws.recv()) 90 | 91 | if data['op'] == 0: 92 | heartbeat = data['d']['heartbeat'] / 1000 93 | loop.create_task(_send_pings(ws, heartbeat)) 94 | elif data['op'] == 1: 95 | pprint(data) 96 | # Now we do with data as we wish. 97 | 98 | if __name__ == '__main__': 99 | loop = asyncio.get_event_loop() 100 | loop.run_until_complete(main(loop)) 101 | ``` 102 | -------------------------------------------------------------------------------- /docs/api/artists.md: -------------------------------------------------------------------------------- 1 | # Artists 2 | 3 | ## Get all artists 4 | 5 | ``` 6 | Type: GET 7 | Route: /artists 8 | ``` 9 | 10 | Response `200` 11 | 12 | ```json 13 | { 14 | "message": "Successfully retrieved all artists.", 15 | "artists": [ 16 | { 17 | "id": 1, 18 | "image": null, 19 | "name": "Aiobahn", 20 | "nameRomaji": null, 21 | "releaseCount": 1, 22 | "slug": "Aiobahn", 23 | "songCount": 9 24 | }, 25 | ... 26 | ] 27 | } 28 | ``` 29 | 30 | Possible errors 31 | 32 | ```json 33 | # Code 403 34 | { "message": "Not authorized." } 35 | ``` 36 | 37 | ## Get a specific artist 38 | 39 | ``` 40 | Type: GET 41 | Route: /artists/{id} 42 | ``` 43 | 44 | URI Parameters 45 | ``` 46 | Name: id 47 | Type: String 48 | Required: True 49 | ``` 50 | 51 | Response `200` 52 | 53 | ```json 54 | { 55 | "message": "Successfully retrieved artist.", 56 | "artist": { 57 | "albums": [ 58 | { 59 | "artistAlbum": { 60 | "artistId": 1, 61 | "albumId": 1 62 | }, 63 | "id": 1, 64 | "image": "Märchen-EP_cover.jpg", 65 | "name": "Märchen EP", 66 | "nameRomaji": null, 67 | "releaseDate": "1970-01-01T00:00:00.000Z", 68 | "type": 1 69 | }, 70 | ... 71 | ], 72 | "id": 1, 73 | "image": null, 74 | "lastPlayed": "1970-01-01T00:00:00.000Z", 75 | "name": "Aiobahn", 76 | "nameRomaji": null, 77 | "played": 15, 78 | "slug": "Aiobahn", 79 | "songs": [ 80 | { 81 | "albums": [ 82 | { 83 | "albumId": 1, 84 | "songId": 1, 85 | "trackNumber": 1 86 | }, 87 | ... 88 | ], 89 | "artistSong": { 90 | "artistId": 1, 91 | "songId": 1 92 | }, 93 | "artists": [ 94 | { 95 | "artistSong": { 96 | "artistId": 1, 97 | "songId": 1 98 | }, 99 | "id": 1, 100 | "name": "Aiobahn", 101 | "nameRomaji": null 102 | }, 103 | ... 104 | ], 105 | "duration": 176, 106 | "id": 1, 107 | "lastPlayed": "1970-01-01T00:00:00.000Z", 108 | "played": 1, 109 | "snippet": "1-1. ヘンゼルとグレーテルの森_snippet.mp3", 110 | "title": "ヘンゼルとグレーテルの森", 111 | "titleRomaji": null, 112 | "uploader": { 113 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 114 | "username": "testuser", 115 | "displayName": "TestUser" 116 | } 117 | }, 118 | ... 119 | ] 120 | } 121 | } 122 | ``` 123 | 124 | Possible errors 125 | 126 | ```json 127 | # Code 400 128 | { "message": "No params provided." } 129 | { "message": "Invalid params provided." } 130 | 131 | # Code 403 132 | { "message": "Not authorized." } 133 | 134 | # Code 404 135 | { "message": "No artist found." } 136 | ``` 137 | -------------------------------------------------------------------------------- /docs/api/songs.md: -------------------------------------------------------------------------------- 1 | # Songs 2 | 3 | ## Song list 4 | 5 | ``` 6 | Type: GET 7 | Route: /songs 8 | ``` 9 | 10 | Response `200` 11 | 12 | ```json 13 | { 14 | "message": "Successfully retrieved songs.", 15 | "songs": [ 16 | { 17 | "albums": [ "Märchen EP" ], 18 | "albumsId": [ 1 ], 19 | "albumsCover": [ "Märchen-EP_cover.jpg" ], 20 | "albumsReleaseDate": [ "1970-01-01T00:00:00.000Z" ], 21 | "albumsRomaji": [ null ], 22 | "albumsSearchRomaji": [ null ], 23 | "albumsTrackNumber": [ 1 ], 24 | "albumsType": [ 1 ], 25 | "artists": [ "Aiobahn" ], 26 | "artistsId": [ 1 ], 27 | "artistsRomaji": [ null ], 28 | "artistsSearchRomaji": [ null ], 29 | "duration": 176, 30 | "enabled": true, 31 | "favorite": false, 32 | "groups": [], 33 | "groupsId": [], 34 | "groupsRomaji": [], 35 | "groupsSearchRomaji": [], 36 | "id": 1, 37 | "lastPlayed": "1970-01-01T00:00:00.000Z", 38 | "snippet": "1-1. ヘンゼルとグレーテルの森_snippet.mp3", 39 | "sources": [ null ], 40 | "sourcesRomaji": [ null ], 41 | "tags": [], 42 | "title": "ヘンゼルとグレーテルの森", 43 | "titleRomaji": null 44 | "titleSearchRomaji": null, 45 | "uploaderUuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 46 | "uploaderUsername": "testuser", 47 | "uploaderDisplayName": "TestUser" 48 | }, 49 | ... 50 | ] 51 | } 52 | ``` 53 | 54 | ## User uploads 55 | 56 | ``` 57 | Type: GET 58 | Route: /songs/{username}/uploads 59 | ``` 60 | 61 | URI Parameters: 62 | ``` 63 | Name: username 64 | Type: String 65 | Required: True 66 | ``` 67 | 68 | Response `200` 69 | 70 | ```json 71 | { 72 | "message": "Successfully retrieved uploads.", 73 | "uploads": [ 74 | { 75 | "albums": [ 76 | { 77 | "id": 1, 78 | "name": "Märchen EP", 79 | "nameRomaji": null, 80 | "image": "Märchen-EP_cover.jpg", 81 | "releaseDate": "1970-01-01T00:00:00.000Z" 82 | }, 83 | ... 84 | ], 85 | "artists": [ 86 | { 87 | "id": 1, 88 | "name": "Aiobahn", 89 | "nameRomaji": null, 90 | "image": null 91 | }, 92 | ... 93 | ], 94 | "duration": 176, 95 | "groups": [], 96 | "id": 1, 97 | "lastPlayed": "1970-01-01T00:00:00.000Z", 98 | "source": [], 99 | "title": "ヘンゼルとグレーテルの森", 100 | "titleRomaji": null, 101 | "titleSearchRomaji": null 102 | "uploader": { 103 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 104 | "username": "testuser", 105 | "displayName": "TestUser" 106 | } 107 | }, 108 | ... 109 | ] 110 | } 111 | # or 112 | { 113 | "message": "This user does not have any uploads." 114 | "uploads": [] 115 | } 116 | ``` 117 | 118 | Possible errors 119 | ```json 120 | # Code 400 121 | { "message": "No params provided." } 122 | { "message": "Invalid params provided." } 123 | { "message": "Not a valid username." } 124 | 125 | # Code 404 126 | { "message": "No user found." } 127 | ``` 128 | -------------------------------------------------------------------------------- /docs/ws/spec.md: -------------------------------------------------------------------------------- 1 | # Specification 2 | 3 | ## Connecting 4 | 5 | To connect to our websocket start by establishing a connection to `wss://listen.moe/gateway_v2` or `wss://listen.moe/kpop/gateway_v2` depending which platform you want to retrieve data from. 6 | 7 | ## Received OP Codes 8 | 9 | * `OP 0` - Received welcome and heartbeat interval 10 | * `OP 1` - Received playback information 11 | 12 | ## Sending OP Codes 13 | 14 | * `OP 9` - Send heartbeat to keep connection alive 15 | 16 | ## Heartbeating 17 | 18 | After connecting, the websocket is going to send you a `{ op: 0 }`, which means the websocket is asking you to prepare the heartbeat and it's interval to keep the connection alive. The data should look like this: 19 | 20 | ```json 21 | { 22 | "op": 0, 23 | "d": { 24 | "message": "Welcome to LISTEN.moe! Enjoy your stay!", 25 | "heartbeat": 45000 26 | } 27 | } 28 | ``` 29 | 30 | > A heartbeat is sent via `{ op: 9 }` and acknowledged by getting `{ op: 10 }` returned. 31 | 32 | ## Receiving data 33 | 34 | Only the `d` property of `OP 0` and `OP 1` contain actual data received from the WebSocket. 35 | The `t` property of `OP 1` is to identify the data received, it can be one of the following: 36 | 37 | * `TRACK_UPDATE` 38 | * `TRACK_UPDATE_REQUEST` 39 | 40 | Every time the current song changes you'll receive data on the WebSocket. Data contains the current song as well as the past 2 songs played, and a variety of information about it such as artist, album, source if any, duration, and if it's a favorite song or not depending if you authenticated or not. 41 | 42 | An example of `OP 1` data looks like this: 43 | 44 | ```json 45 | { 46 | "op": 1, 47 | "d": { 48 | "song": { 49 | "id": 450, 50 | "title": "Trust in you", 51 | "sources": [], 52 | "artists": [ 53 | { 54 | "id": 186, 55 | "name": "sweet ARMS", 56 | "nameRomaji": null, 57 | "image": "sweet-ARMS_image.jpg" 58 | } 59 | ], 60 | "albums": [], 61 | "duration": 273, 62 | "favorite": false 63 | }, 64 | "requester": null, 65 | "event": null, 66 | "startTime": "2018-03-28T21:39:24.431Z", 67 | "lastPlayed": [ 68 | { 69 | "id": 5918, 70 | "title": "おつかれサマー!", 71 | "sources": [], 72 | "artists": [ 73 | { 74 | "id": 1589, 75 | "name": "でんぱ組.inc", 76 | "nameRomaji": null, 77 | "image": null 78 | } 79 | ], 80 | "albums": [ 81 | { 82 | "id": 272, 83 | "name": "おつかれサマー!", 84 | "nameRomaji": null, 85 | "image": null 86 | } 87 | ], 88 | "duration": 273, 89 | "favorite": false 90 | }, 91 | { 92 | "id": 2668, 93 | "title": "つけまつける", 94 | "sources": [], 95 | "artists": [ 96 | { 97 | "id": 1169, 98 | "name": "きゃりーぱみゅぱみゅ", 99 | "nameRomaji": "Kyary Pamyu Pamyu", 100 | "image": "きゃりーぱみゅぱみゅ_image.jpg" 101 | } 102 | ], 103 | "albums": [], 104 | "duration": 0, 105 | "favorite": false 106 | } 107 | ], 108 | "listeners": 92 109 | }, 110 | "t": "TRACK_UPDATE" 111 | } 112 | ``` 113 | 114 | `TRACK_UPDATE` and `TRACK_UPDATE_REQUEST` can be the possible values depending on if you sent `OP 2` or not. 115 | 116 | 117 | The `d.requester` and `d.event` property is either `null` or an `object` containing information about the requester or event: 118 | 119 | ```json 120 | "requester": { 121 | "name": "some-username" 122 | } 123 | ``` 124 | 125 | ```json 126 | "event": { 127 | "name": "some-event-name", 128 | "image": "some-image.jpg" 129 | } 130 | ``` 131 | -------------------------------------------------------------------------------- /docs/.vuepress/config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | title: 'Docs', 3 | description: 'Documentation for everything API and WebSocket related to LISTEN.moe', 4 | head: [ 5 | [ 'link', { rel: 'apple-touch-icon', sizes: '180x180', href: 'https://listen.moe/public/images/icons/apple-touch-icon.png' } ], 6 | [ 'link', { rel: 'icon', type: 'image/png', sizes: '32x32', href: 'https://listen.moe/public/images/icons/favicon-32x32.png' } ], 7 | [ 'link', { rel: 'icon', type: 'image/png', sizes: '16x16', href: 'https://listen.moe/public/images/icons/favicon-16x16.png' } ], 8 | [ 'link', { rel: 'manifest', href: 'https://listen.moe/public/images/icons/manifest.json' } ], 9 | [ 'link', { rel: 'mask-icon', color: '#FF015B', href: 'https://listen.moe/public/images/icons/safari-pinned-tab.svg' } ], 10 | [ 'link', { rel: 'shortcut icon', href: 'https://listen.moe/public/images/icons/favicon.ico' } ], 11 | [ 'meta', { property: 'apple-mobile-web-app-title', name: 'apple-mobile-web-app-title', content: 'LISTEN.moe Docs' } ], 12 | [ 'meta', { property: 'application-name', name: 'application-name', content: 'LISTEN.moe Docs' } ], 13 | [ 'meta', { property: 'msapplication-config', name: 'msapplication-config', content: 'https://listen.moe/public/images/icons/browserconfig.xml' } ], 14 | [ 'meta', { property: 'twitter:card', name: 'twitter:card', content: 'summary' } ], 15 | [ 'meta', { property: 'twitter:site', name: 'twitter:site', content: '@LISTEN_moe' } ], 16 | [ 'meta', { property: 'twitter:creator', name: 'twitter:creator', content: '@LISTEN_moe' } ], 17 | [ 'meta', { property: 'twitter:title', name: 'twitter:title', content: `LISTEN.moe Docs` } ], 18 | [ 'meta', { property: 'twitter:description', name: 'twitter:description', content: 'Documentation for everything API and WebSocket related to LISTEN.moe' } ], 19 | [ 'meta', { property: 'twitter:image', name: 'twitter:image', content: 'https://listen.moe/public/images/share2.jpg' } ], 20 | [ 'meta', { property: 'og:url', property: 'og:url', content: 'https://docs.listen.moe' } ], 21 | [ 'meta', { property: 'og:type', property: 'og:type', content: 'website' } ], 22 | [ 'meta', { property: 'og:title', property: 'og:title', content: `LISTEN.moe Docs` } ], 23 | [ 'meta', { property: 'og:description', property: 'og:description', content: 'Documentation for everything API and WebSocket related to LISTEN.moe' } ], 24 | [ 'meta', { property: 'og:image', property: 'og:image', content: 'https://listen.moe/public/images/share2.jpg' } ], 25 | [ 'meta', { property: 'og:image:secure_url', property: 'og:image:secure_url', content: 'https://listen.moe/public/images/share2.jpg' } ], 26 | [ 'meta', { property: 'og:site_name', property: 'og:site_name', content: 'LISTEN.moe Docs' } ] 27 | ], 28 | markdown: { 29 | lineNumbers: true 30 | }, 31 | themeConfig: { 32 | nav: [ 33 | { text: 'Home', link: '/' }, 34 | { text: 'API Docs', link: '/api/' }, 35 | { text: 'WebSocket Docs', link: '/ws/' }, 36 | { text: 'LISTEN.moe', link: 'https://listen.moe' } 37 | ], 38 | lastUpdated: 'Last Updated', 39 | sidebar: [ 40 | '/', 41 | { 42 | title: 'API', 43 | collapsable: false, 44 | children: [ 45 | 'api/', 46 | 'api/account', 47 | 'api/songs', 48 | 'api/favorites', 49 | 'api/requests', 50 | 'api/artists', 51 | 'api/users', 52 | 'api/posts', 53 | 'api/feeds' 54 | ] 55 | }, 56 | { 57 | title: 'WebSocket', 58 | collapsable: false, 59 | children: [ 60 | 'ws/', 61 | 'ws/spec', 62 | 'ws/usage', 63 | ] 64 | } 65 | ], 66 | sidebarDepth: 2, 67 | repo: 'LISTEN-moe/documentation', 68 | repoLabel: 'Contribute!', 69 | docsDir: 'docs', 70 | editLinks: true, 71 | editLinkText: 'Help us improve this page!' 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /docs/api/favorites.md: -------------------------------------------------------------------------------- 1 | # Favorites 2 | 3 | ## Favorite a song 4 | 5 | ``` 6 | Type: POST 7 | Route: /favorites/{id} 8 | ``` 9 | 10 | URI Parameters 11 | ``` 12 | Name: id 13 | Type: String 14 | Required: True 15 | ``` 16 | 17 | Response `204` 18 | 19 | Possible errors 20 | 21 | ```json 22 | # Code 400 23 | { "message": "No params provided." } 24 | { "message": "Invalid params provided." } 25 | 26 | # Code 403 27 | { "message": "Not authorized." } 28 | 29 | # Code 404 30 | { "message": "No song found." } 31 | ``` 32 | 33 | ## Delete a favorited song 34 | 35 | ``` 36 | Type: DELETE 37 | Route: /favorites/{id} 38 | ``` 39 | 40 | URI Parameters 41 | ``` 42 | Name: id 43 | Type: String 44 | Required: True 45 | ``` 46 | 47 | Response `204` 48 | 49 | Possible errors 50 | 51 | ```json 52 | # Code 400 53 | { "message": "No params provided." } 54 | { "message": "Invalid params provided." } 55 | 56 | # Code 403 57 | { "message": "Not authorized." } 58 | 59 | # Code 404 60 | { "message": "No song found." } 61 | { "message": "No favorite found." } 62 | ``` 63 | 64 | ## Get favorite songs 65 | 66 | ``` 67 | Type: GET 68 | Route: /favorites/{username} 69 | ``` 70 | 71 | URI Parameters 72 | ``` 73 | Name: username 74 | Type: String 75 | Required: True 76 | ``` 77 | 78 | Response `200` 79 | 80 | ```json 81 | { 82 | "message": "Successfully retrieved favorites.", 83 | "favorites": [ 84 | { 85 | "albums": [ 86 | { 87 | "id": 1, 88 | "name": "Märchen EP", 89 | "nameRomaji": null, 90 | "image": "Märchen-EP_cover.jpg", 91 | "releaseDate": "1970-01-01T00:00:00.000Z" 92 | }, 93 | ... 94 | ], 95 | "artists": [ 96 | { 97 | "id": 1, 98 | "name": "Aiobahn", 99 | "nameRomaji": null, 100 | "image": null 101 | }, 102 | ... 103 | ], 104 | "duration": 176, 105 | "groups": [], 106 | "id": 1, 107 | "lastPlayed": "1970-01-01T00:00:00.000Z", 108 | "notes": null, 109 | "source": [], 110 | "tags": [], 111 | "title": "ヘンゼルとグレーテルの森", 112 | "uploader": { 113 | "uuid": , 114 | "username": "testuser", 115 | "displayName": "TestUser" 116 | } 117 | }, 118 | ... 119 | ] 120 | } 121 | # or 122 | { 123 | "message": "This user does not have any favorites.", 124 | "favorites": [] 125 | } 126 | ``` 127 | 128 | Possible errors 129 | 130 | ```json 131 | # Code 400 132 | { "message": "No params provided." } 133 | { "message": "Invalid params provided." } 134 | { "message": "Not a valid username." } 135 | 136 | # Code 403 137 | { "message": "Not authorized." } 138 | 139 | # Code 404 140 | { "message": "No user found." } 141 | ``` 142 | 143 | ## Add tags to a favorite 144 | 145 | Adding tags to a song is a great way of keeping track of a specific group of songs. Only you can see your tags and they are taken into account when fetching the song list. 146 | 147 | ``` 148 | Type: PUT 149 | Route: /favorites/{id}/tags 150 | ``` 151 | 152 | URI Parameters 153 | ``` 154 | Name: id 155 | Type: String 156 | Required: True 157 | ``` 158 | 159 | Body 160 | ```json 161 | { 162 | "tags": [ 163 | "an", 164 | "array", 165 | "of", 166 | "tags" 167 | ] 168 | } 169 | ``` 170 | 171 | Response `200` 172 | 173 | ```json 174 | { 175 | "message": "Successfully updated tags on favorite.", 176 | "tags": [ 177 | "an", 178 | "array", 179 | "of", 180 | "tags" 181 | ] 182 | } 183 | ``` 184 | 185 | Possible errors 186 | 187 | ```json 188 | # Code 400 189 | { "message": "No params provided." } 190 | { "message": "Invalid params provided." } 191 | { "message": "No body provided." } 192 | { "message": "Invalid body provided." } 193 | 194 | # Code 403 195 | { "message": "Not authorized." } 196 | 197 | # Code 404 198 | { "message": "No favorite found." } 199 | ``` 200 | -------------------------------------------------------------------------------- /docs/api/posts.md: -------------------------------------------------------------------------------- 1 | # News 2 | 3 | ## Get all the posts 4 | 5 | ``` 6 | Type: GET 7 | Route: /posts 8 | ``` 9 | 10 | Response `200` 11 | ```json 12 | { 13 | "message": "Successfully retrieved posts.", 14 | "posts": [ 15 | { 16 | "id": 1, 17 | "type": 1, 18 | "slug": "some-slug", 19 | "title": "some-title", 20 | "content": "some-content", 21 | "createdAt": "1970-01-01T00:00:00.000Z", 22 | "editedAt": "1970-01-01T00:00:00.000Z", 23 | "user": { 24 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 25 | "username": "testuser", 26 | "displayName": "TestUser", 27 | "avatarImage": "testuser-512x512.jpg" 28 | } 29 | }, 30 | ... 31 | ] 32 | } 33 | ``` 34 | 35 | ## Get a specific post 36 | 37 | ``` 38 | Type: GET 39 | Route: /posts/{slug} 40 | ``` 41 | 42 | URI Parameters 43 | ``` 44 | Name: slug 45 | Type: String 46 | Required: True 47 | ``` 48 | 49 | Response `200` 50 | ```json 51 | { 52 | "message": "Successfully retrieved post.", 53 | "post": [ 54 | { 55 | "id": 1, 56 | "type": 1, 57 | "slug": "some-slug", 58 | "title": "some-title", 59 | "content": "some-content", 60 | "createdAt": "1970-01-01T00:00:00.000Z", 61 | "editedAt": "1970-01-01T00:00:00.000Z", 62 | "user": { 63 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 64 | "username": "testuser", 65 | "displayName": "TestUser", 66 | "avatarImage": "testuser-512x512.jpg" 67 | }, 68 | "comments": [ 69 | "id": 1, 70 | "parentId": 0, 71 | "content": "some-content", 72 | "createdAt": "1970-01-01T00:00:00.000Z", 73 | "editedAt": "1970-01-01T00:00:00.000Z", 74 | "user": { 75 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 76 | "username": "testuser", 77 | "displayName": "TestUser", 78 | "avatarImage": "testuser-512x512.jpg" 79 | }, 80 | ... 81 | ] 82 | }, 83 | ... 84 | ] 85 | } 86 | ``` 87 | 88 | Possible errors 89 | 90 | ```json 91 | # Code 400 92 | { "message": "No params provided." } 93 | { "message": "Invalid params provided." } 94 | 95 | # Code 404 96 | { "message": "No post found." } 97 | ``` 98 | 99 | ## Post a new comment 100 | 101 | ``` 102 | Type: POST 103 | Route: /posts/{id}/comments 104 | ``` 105 | 106 | URI Parameters 107 | ``` 108 | Name: id 109 | Type: String 110 | Required: True 111 | ``` 112 | 113 | Body 114 | ```json 115 | { 116 | "parentId": 0, 117 | "content": "Your comment." 118 | } 119 | ``` 120 | 121 | Response `200` 122 | ```json 123 | { 124 | "message": "Successfully commented on the post", 125 | "comment": { 126 | "id": 1, 127 | "parentId": 0, 128 | "content": "some-content", 129 | "createdAt": "1970-01-01T00:00:00.000Z", 130 | "editedAt": "1970-01-01T00:00:00.000Z", 131 | "user": { 132 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 133 | "username": "testuser", 134 | "displayName": "TestUser", 135 | "avatarImage": "testuser-512x512.jpg" 136 | } 137 | } 138 | } 139 | ``` 140 | 141 | Possible errors 142 | 143 | ```json 144 | # Code 400 145 | { "message": "No params provided." } 146 | { "message": "Invalid params provided." } 147 | { "message": "No body provided." } 148 | { "message": "Invalid body provided." } 149 | 150 | # Code 404 151 | { "message": "No comment found." } 152 | { "message": "No post found." } 153 | ``` 154 | 155 | ## Get comments on a post 156 | 157 | ``` 158 | Type: GET 159 | Route: /posts/{id}/comments 160 | ``` 161 | 162 | URI Parameters 163 | ``` 164 | Name: id 165 | Type: String 166 | Required: True 167 | ``` 168 | 169 | Response `200` 170 | ```json 171 | { 172 | "message": "Successfully retrieved comments.", 173 | "comments": [ 174 | { 175 | "id": 1, 176 | "parentId": 0, 177 | "content": "some-content", 178 | "createdAt": "1970-01-01T00:00:00.000Z", 179 | "editedAt": "1970-01-01T00:00:00.000Z", 180 | "user": { 181 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 182 | "username": "testuser", 183 | "displayName": "TestUser", 184 | "avatarImage": "testuser-512x512.jpg" 185 | } 186 | }, 187 | ... 188 | ] 189 | } 190 | # or 191 | { 192 | "message": "No comments found.", 193 | "comments": [] 194 | } 195 | ``` 196 | 197 | Possible errors 198 | 199 | ```json 200 | # Code 400 201 | { "message": "No params provided." } 202 | { "message": "Invalid params provided." } 203 | 204 | # Code 404 205 | { "message": "No post found." } 206 | ``` 207 | -------------------------------------------------------------------------------- /docs/api/feeds.md: -------------------------------------------------------------------------------- 1 | # Feeds 2 | 3 | ## Get user's feed 4 | 5 | ``` 6 | Type: GET 7 | Route: /users/{userame}/feed 8 | ``` 9 | 10 | URI Parameters 11 | ``` 12 | Name: username 13 | Type: String 14 | Required: True 15 | ``` 16 | 17 | Response `200` 18 | ```json 19 | 20 | { 21 | "message": "Successfully retrieved feeds.", 22 | "feeds": [ 23 | { 24 | "id": 54323, 25 | "type": 1, 26 | "content": "Feed content", 27 | "createdAt": "2018-06-04T04:25:51.506Z", 28 | "editedAt": "2018-06-04T04:25:51.506Z", 29 | "author": { 30 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 31 | "username": "testuser", 32 | "displayName": "TestUser", 33 | "avatarImage": "testuser-512x512.jpg" 34 | }, 35 | "user": { 36 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5782", 37 | "username": "anothertestuser", 38 | "displayName": "AnotherTestUser", 39 | "avatarImage": "anothertestuser-512x512.jpg" 40 | }, 41 | "followTarget": [], 42 | "song": null, 43 | "upload": null, 44 | "comments": 1 45 | }, 46 | ... 47 | ] 48 | } 49 | ``` 50 | ::: tip 51 | Depending the `type` property returned, the response structure changes a bit. Here's a breakdown of what's different on each case. 52 | ::: 53 | 54 | ``` 55 | Type 1: Someone commented on the user's feed. 56 | Type 2: The user favorited a song 57 | Type 3: The user uploaded a song 58 | Type 4: The user approved an upload (only admins) 59 | ``` 60 | 61 | **When type is 2 or 4** 62 | 63 | ```json 64 | "content": null, 65 | "song": { 66 | "title": "sigh", 67 | "titleRomaji": null, 68 | "artists": [ 69 | { 70 | "id": 2761, 71 | "name": "Luschka", 72 | "nameRomaji": null 73 | } 74 | ] 75 | }, 76 | "upload": null 77 | ``` 78 | 79 | **When type is 3** 80 | 81 | ```json 82 | "content": null, 83 | "song": null, 84 | "upload": { 85 | "title": "ぼなぺてぃーと♡S -Hall staff ver.-", 86 | "titleRomaji": null, 87 | "artists": [ 88 | "桜ノ宮苺香 (CV: 和氣あず未)", 89 | "日向夏帆 (CV: 鬼頭明里)", 90 | "星川麻冬 (CV: 春野 杏)", 91 | "天野美雨 (CV: 種﨑敦美)", 92 | "神崎ひでり (CV: 徳井青空)" 93 | ], 94 | "artistsRomaji": [ 95 | "Maika Sakuranomiya (CV: Azumi Waki)", 96 | "Kaho Hinata (CV: Akari Kito)", 97 | "Hoshikawa Mafuyu (CV: Haruno Anzu)", 98 | "Amano Miu (CV: Atsumi Tanezaki)", 99 | "Hideri Kanzaki (CV: Sora Tokui)" 100 | ], 101 | "artistGroups": [], 102 | "artistGroupsRomaji": [] 103 | }, 104 | ``` 105 | 106 | 107 | ## Delete a feed 108 | 109 | ``` 110 | Type: DELETE 111 | Route: /feeds/{id} 112 | ``` 113 | 114 | URI Parameters 115 | ``` 116 | Name: id 117 | Type: String 118 | Required: True 119 | ``` 120 | 121 | Response `204` 122 | 123 | Possible errors 124 | 125 | ```json 126 | # Code 400 127 | { "message": "No params provided." } 128 | { "message": "Invalid params provided." } 129 | 130 | # Code 403 131 | { "message": "Not authorized." } 132 | 133 | # Code 404 134 | { "message": "No feed found." } 135 | ``` 136 | 137 | ## Get feed comments 138 | 139 | ``` 140 | Type: GET 141 | Route: /feeds/{id}/comments 142 | ``` 143 | 144 | URI Parameters 145 | ``` 146 | Name: id 147 | Type: String 148 | Required: True 149 | ``` 150 | 151 | Response `200` 152 | 153 | ```json 154 | { 155 | "message": "Successfully retrieved comments.", 156 | "comments": [ 157 | { 158 | "id": 1, 159 | "parentId": 0, 160 | "content": "some-content", 161 | "createdAt": "1970-01-01T00:00:00.000Z", 162 | "editedAt": "1970-01-01T00:00:00.000Z", 163 | "user": { 164 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 165 | "username": "testuser", 166 | "displayName": "TestUser", 167 | "avatarImage": "testuser-512x512.jpg" 168 | } 169 | }, 170 | ... 171 | ] 172 | } 173 | 174 | { 175 | "message": "No comments found.", 176 | "comments": [] 177 | } 178 | ``` 179 | 180 | Possible errors 181 | 182 | ```json 183 | # Code 400 184 | { "message": "No params provided." } 185 | { "message": "Invalid params provided." } 186 | 187 | # Code 404 188 | { "message": "No feed found." } 189 | ``` 190 | 191 | ## Post a new comment 192 | 193 | ``` 194 | Type: POST 195 | Route: /feeds/{id}/comments 196 | ``` 197 | 198 | URI Parameters 199 | ``` 200 | Name: id 201 | Type: String 202 | Required: True 203 | ``` 204 | 205 | Body 206 | ```json 207 | { 208 | "parentId": 0, 209 | "content": "Your comment." 210 | } 211 | ``` 212 | 213 | Response `200` 214 | ```json 215 | { 216 | "message": "Successfully commented on the feed", 217 | "comment": { 218 | "id": 1, 219 | "parentId": 0, 220 | "content": "some-content", 221 | "createdAt": "1970-01-01T00:00:00.000Z", 222 | "editedAt": "1970-01-01T00:00:00.000Z", 223 | "user": { 224 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 225 | "username": "testuser", 226 | "displayName": "TestUser", 227 | "avatarImage": "testuser-512x512.jpg" 228 | } 229 | } 230 | } 231 | ``` 232 | 233 | Possible errors 234 | 235 | ```json 236 | # Code 400 237 | { "message": "No params provided." } 238 | { "message": "Invalid params provided." } 239 | { "message": "No body provided." } 240 | { "message": "Invalid body provided." } 241 | 242 | # Code 404 243 | { "message": "No comment found." } 244 | { "message": "No feed found." } 245 | ``` 246 | -------------------------------------------------------------------------------- /docs/api/users.md: -------------------------------------------------------------------------------- 1 | # Users 2 | 3 | ## Get user information 4 | 5 | ``` 6 | Type: GET 7 | Route: /users/{username} 8 | ``` 9 | 10 | URI Parameters 11 | ``` 12 | Name: username 13 | Type: String 14 | Required: True 15 | ``` 16 | 17 | Response `200` 18 | ```json 19 | { 20 | "message": "Successfully retrieved user.", 21 | "user": { 22 | "uuid": "345c45aa-5h76-9zh4-6tr7-6tkl52rn5783", 23 | "email": "testUser@gmail.com", 24 | "username": "testuser", 25 | "displayName": "TestUser", 26 | "avatarImage": null, 27 | "bannerImage": null, 28 | "bio": null, 29 | "roles": [ 30 | { 31 | "id": 5, 32 | "name": "user", 33 | "slug": "user", 34 | "color": null, 35 | "songRequests": 0 36 | }, 37 | ... 38 | ], 39 | "settings": { 40 | "mfa": true 41 | }, 42 | "userSettings": {}, 43 | "requestsRemaining": 5, 44 | "additionalRequests": 0, 45 | "uploadLimit": 30, 46 | "favorites": 0, 47 | "uploads": 0 48 | } 49 | } 50 | ``` 51 | 52 | Possible errors 53 | 54 | ```json 55 | # Code 400 56 | { "message": "No params provided." } 57 | { "message": "Invalid params provided." } 58 | 59 | # Code 404 60 | { "message": "No user found." } 61 | ``` 62 | 63 | ## Update profile biography 64 | 65 | ``` 66 | Type: PATCH 67 | Route: /users/{username} 68 | ``` 69 | 70 | URI Parameters 71 | ``` 72 | Name: username 73 | Type: String 74 | Required: True 75 | ``` 76 | 77 | Body 78 | ```json 79 | { 80 | "bio: "Some info stuff." 81 | } 82 | ``` 83 | Response `204` 84 | 85 | Possible errors 86 | 87 | ```json 88 | # Code 400 89 | { "message": "No params provided." } 90 | { "message": "Invalid params provided." } 91 | { "message": "No body provided." } 92 | 93 | # Code 403 94 | { "message": "Not authorized." } 95 | ``` 96 | 97 | ## Activate 2FA 98 | 99 | This endpoint lets you generate a QR code for you to scan with your 2FA app ([Google Authenticator](https://support.google.com/accounts/answer/1066447?hl=en), [Authy](https://authy.com/)) which generates a one-time token to send via the API on the next request. 100 | 101 | ``` 102 | Type: GET 103 | Route: /users/{username}/mfa 104 | ``` 105 | 106 | URI Parameters 107 | ``` 108 | Name: username 109 | Type: String 110 | Required: True 111 | ``` 112 | 113 | Response `200` 114 | 115 | ```json 116 | { 117 | "otpauthURL": "base64 encoded QR image" 118 | } 119 | ``` 120 | 121 | Possible errors 122 | 123 | ```json 124 | # Code 400 125 | { "message": "No params provided." } 126 | { "message": "Invalid params provided." } 127 | { "message": "MFA already activated." } 128 | 129 | # Code 403 130 | { "message": "Not authorized." } 131 | ``` 132 | 133 | ## Finish activating 2FA 134 | 135 | This endpoint expects the code generated on the previous step by the authenticator app. 136 | 137 | ``` 138 | Type: POST 139 | Route: /users/{username}/mfa 140 | ``` 141 | 142 | URI Parameters 143 | ``` 144 | Name: username 145 | Type: String 146 | Required: True 147 | ``` 148 | 149 | Body 150 | ```json 151 | { 152 | "token": "your_token" 153 | } 154 | ``` 155 | 156 | Response `200` 157 | 158 | ```json 159 | { 160 | "message": "Successfully enabled mfa. You will now be logged out." 161 | } 162 | ``` 163 | 164 | Possible errors 165 | 166 | ```json 167 | # Code 400 168 | { "message": "No params provided." } 169 | { "message": "Invalid params provided." } 170 | { "message": "No body provided." } 171 | { "message": "Invalid body provided." } 172 | { "message": "MFA already activated." } 173 | 174 | # Code 401 175 | { "message": "Invalid OTP provided." } 176 | 177 | # Code 403 178 | { "message": "Not authorized." } 179 | ``` 180 | 181 | ## Disable 2FA 182 | 183 | ``` 184 | Type: DELETE 185 | Route: /users/{username}/mfa?token={token} 186 | ``` 187 | 188 | URI Parameters 189 | ``` 190 | Name: username 191 | Type: String 192 | Required: True 193 | 194 | Name: token 195 | Type: String 196 | Required: True 197 | ``` 198 | 199 | Response `200` 200 | 201 | ```json 202 | { 203 | "message": "Successfully disabled mfa. You will now be logged out." 204 | } 205 | ``` 206 | 207 | Possible errors 208 | 209 | ```json 210 | # Code 400 211 | { "message": "No params provided." } 212 | { "message": "Invalid params provided." } 213 | { "message": "No body provided." } 214 | { "message": "Invalid body provided." } 215 | { "message": "No MFA activated." } 216 | 217 | # Code 403 218 | { "message": "Not authorized." } 219 | ``` 220 | 221 | ## Get user roles 222 | 223 | ``` 224 | Type: GET 225 | Route: /users/{username}/roles 226 | ``` 227 | 228 | URI Parameters 229 | ``` 230 | Name: username 231 | Type: String 232 | Required: True 233 | ``` 234 | 235 | Response `200` 236 | 237 | ```json 238 | { 239 | "message": "Successfully retrieved roles.", 240 | "roles": [ 241 | { 242 | "id": 5, 243 | "name": "user", 244 | "slug": "user", 245 | "color": null, 246 | "songRequests": 0, 247 | "enabled": false, 248 | "userRole": { 249 | "userId": 1, 250 | "roleId": 5 251 | } 252 | }, 253 | ... 254 | ] 255 | } 256 | ``` 257 | 258 | Possible errors 259 | 260 | ```json 261 | # Code 400 262 | { "message": "No params provided." } 263 | { "message": "Invalid params provided." } 264 | 265 | # Code 403 266 | { "message": "Not authorized." } 267 | 268 | # Code 404 269 | { "message": "No user found." } 270 | ``` 271 | --------------------------------------------------------------------------------