ambient-display 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.nvmrc +1 -0
- package/.prettierignore +4 -0
- package/.prettierrc +17 -0
- package/CHANGELOG.md +34 -0
- package/README.md +84 -0
- package/ambient.config.js.template +13 -0
- package/env.template +2 -0
- package/eslint.config.js +24 -0
- package/jsconfig.json +19 -0
- package/package.json +67 -0
- package/screenshot.png +0 -0
- package/server/api/index.js +213 -0
- package/server/api/interact.js +234 -0
- package/server/api/utils.js +284 -0
- package/server/comms.js +13 -0
- package/server/config.js +27 -0
- package/server/constants.js +52 -0
- package/server/events.js +41 -0
- package/server/history.js +47 -0
- package/server/index.js +155 -0
- package/server/logs.js +15 -0
- package/server/memo.js +63 -0
- package/server/run.js +5 -0
- package/server/spotify/auth.js +98 -0
- package/server/spotify/index.js +105 -0
- package/server/spotify/sdk.js +217 -0
- package/server/types/comms.js +7 -0
- package/server/types/data.js +94 -0
- package/server/types/index.js +16 -0
- package/server/types/options.js +72 -0
- package/server/utils.js +101 -0
- package/src/app.d.ts +13 -0
- package/src/app.html +12 -0
- package/src/lib/actions/qr.svelte.js +23 -0
- package/src/lib/comms.js +25 -0
- package/src/lib/components/AuthenticateTrigger.svelte +74 -0
- package/src/lib/components/Controls.svelte +91 -0
- package/src/lib/components/ImageLoad.svelte +79 -0
- package/src/lib/components/LoadingIndicator.svelte +75 -0
- package/src/lib/components/MediaItem.svelte +75 -0
- package/src/lib/components/PlayingTracker.svelte +94 -0
- package/src/lib/components/ResultsList.svelte +80 -0
- package/src/lib/components/Toast/Item.svelte +71 -0
- package/src/lib/components/Toast/Manager.svelte +34 -0
- package/src/lib/icons/disc.svg +1 -0
- package/src/lib/index.js +1 -0
- package/src/lib/store.js +146 -0
- package/src/lib/styles.scss +166 -0
- package/src/lib/toast.js +57 -0
- package/src/lib/utils.js +723 -0
- package/src/routes/+layout.server.js +25 -0
- package/src/routes/+layout.svelte +72 -0
- package/src/routes/+page.svelte +381 -0
- package/src/routes/player/+page.svelte +294 -0
- package/static/favicon.ico +0 -0
- package/static/favicon.png +0 -0
- package/static/icons/144.favicon.png +0 -0
- package/static/icons/168.favicon.png +0 -0
- package/static/icons/192.favicon.png +0 -0
- package/static/icons/48.favicon.png +0 -0
- package/static/icons/72.favicon.png +0 -0
- package/static/icons/96.favicon.png +0 -0
- package/static/manifest.json +40 -0
- package/svelte.config.js +19 -0
- package/tools/BuildManifest.js +87 -0
- package/vite.config.js +46 -0
package/.nvmrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
v18.19.0
|
package/.prettierignore
ADDED
package/.prettierrc
ADDED
package/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines.
|
|
4
|
+
|
|
5
|
+
## 1.1.0 (2025-03-04)
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
### Features
|
|
9
|
+
|
|
10
|
+
* added cursor hide ([928c7f8](https://github.com/jthawme/ambient/commit/928c7f8aef867b947d77f198a1bda916ff13167b))
|
|
11
|
+
* added fleshed out adding panel ([0ebbbc2](https://github.com/jthawme/ambient/commit/0ebbbc2189599a94f55ecadcdff18909e1c1dc28))
|
|
12
|
+
* added history system and converted errors ([30977fc](https://github.com/jthawme/ambient/commit/30977fc9444738d7adbd8597656411e2154b9a4f))
|
|
13
|
+
* added screen lock to player ([11f5275](https://github.com/jthawme/ambient/commit/11f52751524d7de83b22ba84887cd330b55b8dda))
|
|
14
|
+
* added sonos fallback plugin ([0acefd8](https://github.com/jthawme/ambient/commit/0acefd8138765705c88c5e0b0c5cc3ad393549b9))
|
|
15
|
+
* introduced events ecosystem and config file injection ([c3ade28](https://github.com/jthawme/ambient/commit/c3ade28661d4709e72f4f1f5505c4564712fe773))
|
|
16
|
+
* removed plugins from root repo ([0b1528e](https://github.com/jthawme/ambient/commit/0b1528e2790cd9f70e71505780317ca19b0ae83f))
|
|
17
|
+
* sewn up the loop of built and running flows ([7b3f21a](https://github.com/jthawme/ambient/commit/7b3f21acce980d42c0e79d0f1bcea2bb5c410c44))
|
|
18
|
+
* switched to a centralised polling system first, to protect against rate limits ([3f33865](https://github.com/jthawme/ambient/commit/3f33865d6b8f528f7e69d6cffd54161f6c5c5d29))
|
|
19
|
+
* updated eco system ([de42b2b](https://github.com/jthawme/ambient/commit/de42b2b5d6341bac2c987c3d48cfdbd0cc8d3791))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
### Bug Fixes
|
|
23
|
+
|
|
24
|
+
* added attempt at memoization for rate limiting ([9530833](https://github.com/jthawme/ambient/commit/95308335f7eb740de69ef78c542e821a5823f87e))
|
|
25
|
+
* added run script as seperate ([46d90ed](https://github.com/jthawme/ambient/commit/46d90edac3fafb18e8dc4be1c985bf13ad34baa2))
|
|
26
|
+
* backed off on certain endpoints ([d95b69c](https://github.com/jthawme/ambient/commit/d95b69c8466ce12cdae871bec2911009b7258383))
|
|
27
|
+
* built in messaging to spotify errors ([0cc61cb](https://github.com/jthawme/ambient/commit/0cc61cb5be2b902df259eb11d6012e60e4a7307c))
|
|
28
|
+
* continued to make the project less reliant on local install ([22060cc](https://github.com/jthawme/ambient/commit/22060cc4f3d84ebe7c47e328182b617cf7c7ea68))
|
|
29
|
+
* fixed issue with persisting sdk ([0f7fc23](https://github.com/jthawme/ambient/commit/0f7fc2309ec9a9c790060687af562cd2c5293eb2))
|
|
30
|
+
* fixed reauth strategy class ([cfa328d](https://github.com/jthawme/ambient/commit/cfa328dc1599c397ff85b2319800dce7d628283b))
|
|
31
|
+
* is main check fix ([77d087d](https://github.com/jthawme/ambient/commit/77d087d4dc68428e875d01c0b0259ecd47b2d681))
|
|
32
|
+
* protect the polling async function ([e5282b6](https://github.com/jthawme/ambient/commit/e5282b60050fc217e6d4861988cc603fbd594696))
|
|
33
|
+
* reintroduced package lock for npm ci ([fb906cc](https://github.com/jthawme/ambient/commit/fb906cccb8fd7c6856e1f2af28bd7cc429f8d236))
|
|
34
|
+
* small fixes ([0fc9688](https://github.com/jthawme/ambient/commit/0fc9688dc24629b47aaf96f27087011032fdaa6c))
|
package/README.md
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Ambient
|
|
2
|
+
|
|
3
|
+
An ambient display screen for spotify, with the configurable ability to also non destructively add songs to the communal queue and control basic playback.
|
|
4
|
+
|
|
5
|
+

|
|
6
|
+
|
|
7
|
+
[Watch a userful video here](https://www.youtube.com/watch?v=0QEBUVzqkY0)
|
|
8
|
+
|
|
9
|
+
## Like a Spotify Jam?
|
|
10
|
+
|
|
11
|
+
Yeah pretty much, in fact much worse. But in _some_ ways better. Gives a more 'there is a Spotify instance running by the host, but if you wanna chuck a couple tunes in, go for it' vibe, rather than full control.
|
|
12
|
+
|
|
13
|
+
It also protects against the DREADED playlist switch. It then also allows the host to disallow jams, it just turns the tighten up a very little amount.
|
|
14
|
+
|
|
15
|
+
## Installing and running
|
|
16
|
+
|
|
17
|
+
### Option 1: Install globally
|
|
18
|
+
|
|
19
|
+
```
|
|
20
|
+
npm i -g ambient-display // or any package manager of course
|
|
21
|
+
|
|
22
|
+
SPOTIFY_CLIENT_ID=[ID_HERE] SPOTIFY_CLIENT_SECRET=[SECRET_HERE] ambient
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
or load a config file from somewhere
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
CONFIG=~/.ambient/ambient.config.js ambient
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
This will build the sveltekit project and then run the express server
|
|
32
|
+
|
|
33
|
+
### Option 2: Install in another project [COMING SOON]
|
|
34
|
+
|
|
35
|
+
Hopefully
|
|
36
|
+
|
|
37
|
+
### Option 3: Pull the project locally
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
npm run build // or any package manager of course
|
|
41
|
+
|
|
42
|
+
node server/run.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
## Config
|
|
46
|
+
|
|
47
|
+
You can configure Ambient with an `ambient.config.js` file in the root of the folder. All types can be found in [this folder](/server/types/)
|
|
48
|
+
|
|
49
|
+
| Key | Type | Description | Default |
|
|
50
|
+
| ------------------------------- | ------------ | ------------------------------------------------------------------------------------------------- | ----------------------------------- |
|
|
51
|
+
| port | number | The port in which the server runs on | `3000` |
|
|
52
|
+
| origin | string | The origin of the server, usually the local ip | `_Your machine's IP address_` |
|
|
53
|
+
| protocol | string | http:// or https:// (is there another?!) | `http://` |
|
|
54
|
+
| playerRoute | string | The path that the player is routed on | `/player` |
|
|
55
|
+
| verbose | boolean | Whether the app shouts a lot more in the stdout | `false` |
|
|
56
|
+
| api | object | See below | |
|
|
57
|
+
| api.market | string | The spotify market to use a lot of the functions in | `GB` |
|
|
58
|
+
| api.searchQueryLimit | number | How many search results of each category to display | `10` |
|
|
59
|
+
| api.centralisedPolling | boolean | Whether to turn the info call onto the server and communicate via websockets | `true` |
|
|
60
|
+
| api.centralisedPollingTimer | number | The internal timer that calls for the info | 5000 |
|
|
61
|
+
| api.canAdd | boolean | Gives users the ability to add to the playback via the dashboard | `true` |
|
|
62
|
+
| api.canControl | boolean | Gives users the ability to control the playback | `true` |
|
|
63
|
+
| spotify | object | See below | |
|
|
64
|
+
| spotify.client_id | string | The spotify Client ID from your app | `process.env.SPOTIFY_CLIENT_ID` |
|
|
65
|
+
| spotify.client_secret | string | The spotify Client Secret from your app | `process.env.SPOTIFY_CLIENT_SECRET` |
|
|
66
|
+
| spotify.routePrefix | string | The prefixed sub route that the spotify mounting sits on | `/spotify` |
|
|
67
|
+
| spotify.routeToken | string | The path that spotify redirects to with the token | `/token` |
|
|
68
|
+
| spotify.authenticatedRedirect | string | The route that the app redirects too after a successful auth with spotify | `/` |
|
|
69
|
+
| spotify.accessTokenJsonLocation | string | The path to save the access token file, for caching | `./server/spotify_auth.json` |
|
|
70
|
+
| spotify.scope | string[] | Any additional scopes to initiate spotify with | `[]` |
|
|
71
|
+
| plugins | PluginItem[] | An array of [plugins](#plugins) to run | `[]` |
|
|
72
|
+
| pluginOptions | object | Refer to any plugin options for what to place here | `{}` |
|
|
73
|
+
| suppressErrors | string[] | Any error events to catch and not send to the frontend, because perhaps a plugin will handle them | `[]` |
|
|
74
|
+
|
|
75
|
+
## Plugins
|
|
76
|
+
|
|
77
|
+
| Name | Description |
|
|
78
|
+
| ------------------------------------------------------------------- | ------------------------------------------------------------------------------------- |
|
|
79
|
+
| [Kiosk Mode](https://github.com/jthawme/ambient-kiosk-mode) | Opens fullscreen kiosk mode of app after running |
|
|
80
|
+
| [Sonos Fallback](https://github.com/jthawme/ambient-sonos-fallback) | Controls sonos directly, if spotify's api errors while using Sonos as playback device |
|
|
81
|
+
|
|
82
|
+
## Reasoning
|
|
83
|
+
|
|
84
|
+
Spotify's API is excellent, but without being approved for the expanded API grant, its limited to really only hobbyist stuff. This means that you can register your own app, and run this locally and have a customised Spotify Jam scenario, because its running through your credentials. Locally served
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
// import * as KioskPlugin from 'ambient-kiosk-mode';
|
|
2
|
+
// import * as SonosFallbackPlugin from 'ambient-sonos-fallback';
|
|
3
|
+
|
|
4
|
+
/** @type {import('./server/types/options.js').Config} */
|
|
5
|
+
export default {
|
|
6
|
+
// plugins: [KioskPlugin, SonosFallbackPlugin],
|
|
7
|
+
|
|
8
|
+
// A nice way to pass in the credentials
|
|
9
|
+
// spotify: {
|
|
10
|
+
// client_id: process.env.SPOTIFY_CLIENT_ID,
|
|
11
|
+
// client_secret: process.env.SPOTIFY_CLIENT_SECRET
|
|
12
|
+
// }
|
|
13
|
+
};
|
package/env.template
ADDED
package/eslint.config.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import prettier from "eslint-config-prettier";
|
|
2
|
+
import js from '@eslint/js';
|
|
3
|
+
import { includeIgnoreFile } from '@eslint/compat';
|
|
4
|
+
import svelte from 'eslint-plugin-svelte';
|
|
5
|
+
import globals from 'globals';
|
|
6
|
+
import { fileURLToPath } from 'node:url';
|
|
7
|
+
const gitignorePath = fileURLToPath(new URL("./.gitignore", import.meta.url));
|
|
8
|
+
|
|
9
|
+
/** @type {import('eslint').Linter.Config[]} */
|
|
10
|
+
export default [
|
|
11
|
+
includeIgnoreFile(gitignorePath),
|
|
12
|
+
js.configs.recommended,
|
|
13
|
+
...svelte.configs["flat/recommended"],
|
|
14
|
+
prettier,
|
|
15
|
+
...svelte.configs['flat/prettier'],
|
|
16
|
+
{
|
|
17
|
+
languageOptions: {
|
|
18
|
+
globals: {
|
|
19
|
+
...globals.browser,
|
|
20
|
+
...globals.node
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
];
|
package/jsconfig.json
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "./.svelte-kit/tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"allowJs": true,
|
|
5
|
+
"checkJs": true,
|
|
6
|
+
"esModuleInterop": true,
|
|
7
|
+
"forceConsistentCasingInFileNames": true,
|
|
8
|
+
"resolveJsonModule": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"sourceMap": true,
|
|
11
|
+
"strict": true,
|
|
12
|
+
"moduleResolution": "bundler"
|
|
13
|
+
}
|
|
14
|
+
// Path aliases are handled by https://svelte.dev/docs/kit/configuration#alias
|
|
15
|
+
// except $lib which is handled by https://svelte.dev/docs/kit/configuration#files
|
|
16
|
+
//
|
|
17
|
+
// If you want to overwrite includes/excludes, make sure to copy over the relevant includes/excludes
|
|
18
|
+
// from the referenced tsconfig.json - TypeScript does not merge them in
|
|
19
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ambient-display",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "Like a spotify jam but much worse, but also much better. If you need it, you'll love it.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ambient": "./server/run.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "NODE_ENV=development concurrently \"npm:dev:*\"",
|
|
11
|
+
"dev:frontend": "vite dev",
|
|
12
|
+
"dev:server": "nodemon --ignore 'server/*.json' server",
|
|
13
|
+
"build": "vite build",
|
|
14
|
+
"preview": "vite preview",
|
|
15
|
+
"check": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json",
|
|
16
|
+
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./jsconfig.json --watch",
|
|
17
|
+
"format": "prettier --write .",
|
|
18
|
+
"lint": "prettier --check . && eslint .",
|
|
19
|
+
"release": "standard-version",
|
|
20
|
+
"release:publish": "npm run release && npm publish"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@eslint/compat": "^1.2.3",
|
|
24
|
+
"@poppanator/sveltekit-svg": "^5.0.0",
|
|
25
|
+
"@sveltejs/adapter-auto": "^3.0.0",
|
|
26
|
+
"@sveltejs/adapter-node": "^5.2.9",
|
|
27
|
+
"@sveltejs/kit": "^2.9.0",
|
|
28
|
+
"@sveltejs/vite-plugin-svelte": "^5.0.0",
|
|
29
|
+
"concurrently": "^9.1.0",
|
|
30
|
+
"eslint": "^9.7.0",
|
|
31
|
+
"eslint-config-prettier": "^9.1.0",
|
|
32
|
+
"eslint-plugin-svelte": "^2.36.0",
|
|
33
|
+
"fs-extra": "^11.2.0",
|
|
34
|
+
"globals": "^15.0.0",
|
|
35
|
+
"nodemon": "^3.1.7",
|
|
36
|
+
"prettier": "^3.3.2",
|
|
37
|
+
"prettier-plugin-svelte": "^3.2.6",
|
|
38
|
+
"sharp": "^0.33.5",
|
|
39
|
+
"sharp-ico": "^0.1.5",
|
|
40
|
+
"standard-version": "^9.5.0",
|
|
41
|
+
"svelte": "^5.0.0",
|
|
42
|
+
"svelte-check": "^4.0.0",
|
|
43
|
+
"svgo": "^3.3.2",
|
|
44
|
+
"typescript": "^5.0.0",
|
|
45
|
+
"vite": "^6.0.0"
|
|
46
|
+
},
|
|
47
|
+
"dependencies": {
|
|
48
|
+
"@spotify/web-api-ts-sdk": "^1.2.0",
|
|
49
|
+
"ambient-kiosk-mode": "^1.0.1",
|
|
50
|
+
"ambient-sonos-fallback": "^1.0.1",
|
|
51
|
+
"bunyan": "^1.8.15",
|
|
52
|
+
"cors": "^2.8.5",
|
|
53
|
+
"deepmerge": "^4.3.1",
|
|
54
|
+
"detect-rpi": "^1.5.0",
|
|
55
|
+
"dotenv": "^16.4.7",
|
|
56
|
+
"express": "^4.21.1",
|
|
57
|
+
"node-fetch": "^3.3.2",
|
|
58
|
+
"node-vibrant": "3.1.6",
|
|
59
|
+
"normalize.css": "^8.0.1",
|
|
60
|
+
"qrcode-svg": "^1.1.0",
|
|
61
|
+
"sass": "^1.82.0",
|
|
62
|
+
"socket.io": "^4.8.1",
|
|
63
|
+
"socket.io-client": "^4.8.1",
|
|
64
|
+
"sonos": "^1.14.1",
|
|
65
|
+
"svelte-ionicons": "^2.0.1"
|
|
66
|
+
}
|
|
67
|
+
}
|
package/screenshot.png
ADDED
|
Binary file
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
import { SpotifyApi } from '@spotify/web-api-ts-sdk';
|
|
2
|
+
import { Router } from 'express';
|
|
3
|
+
import { Server } from 'socket.io';
|
|
4
|
+
|
|
5
|
+
import * as Types from '../types/options.js';
|
|
6
|
+
import { events } from '../events.js';
|
|
7
|
+
import { asyncInterval } from '../utils.js';
|
|
8
|
+
import { log } from '../logs.js';
|
|
9
|
+
import { DEFAULT_OPTIONS, EVENT } from '../constants.js';
|
|
10
|
+
import { apiWrapper, isSpotifyUrl, SpotifyUrl, protect } from './utils.js';
|
|
11
|
+
import { SpotifyInteract } from './interact.js';
|
|
12
|
+
|
|
13
|
+
const cachedInfo = { current: null };
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
*
|
|
17
|
+
* @param {Server} io
|
|
18
|
+
* @param {{current: SpotifyApi | null}} sdk
|
|
19
|
+
* @param {Types.ApiOptions} opts
|
|
20
|
+
* @param {boolean} verbose
|
|
21
|
+
*/
|
|
22
|
+
const run = (io, sdk, opts = {}, verbose = false) => {
|
|
23
|
+
const options = {
|
|
24
|
+
...DEFAULT_OPTIONS.api,
|
|
25
|
+
...opts
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
const app = Router();
|
|
29
|
+
|
|
30
|
+
app.get(
|
|
31
|
+
'/artist/:id',
|
|
32
|
+
apiWrapper(async (req, res) => {
|
|
33
|
+
const response = await SpotifyInteract.artist.topTracks(req.sdk, req.params.id);
|
|
34
|
+
return res.json(response);
|
|
35
|
+
})
|
|
36
|
+
);
|
|
37
|
+
|
|
38
|
+
app.get(
|
|
39
|
+
'/album/:id',
|
|
40
|
+
apiWrapper(async (req, res) => {
|
|
41
|
+
const response = await SpotifyInteract.album.get(req.sdk, req.params.id);
|
|
42
|
+
return res.json(response);
|
|
43
|
+
})
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
app.get(
|
|
47
|
+
'/search',
|
|
48
|
+
apiWrapper(async (req, res) => {
|
|
49
|
+
// Special case for handling if someone has posted a Spotify URL into the search box
|
|
50
|
+
if (isSpotifyUrl(req.query.q)) {
|
|
51
|
+
const { type, id } = SpotifyUrl(req.query.q);
|
|
52
|
+
|
|
53
|
+
switch (type) {
|
|
54
|
+
case 'track': {
|
|
55
|
+
const response = await SpotifyInteract.track.get(req.sdk, id);
|
|
56
|
+
return res.json(response);
|
|
57
|
+
}
|
|
58
|
+
case 'artist': {
|
|
59
|
+
const response = await SpotifyInteract.artist.topTracks(req.sdk, id);
|
|
60
|
+
return res.json(response);
|
|
61
|
+
}
|
|
62
|
+
case 'album': {
|
|
63
|
+
const response = await SpotifyInteract.album.get(req.sdk, id);
|
|
64
|
+
return res.json(response);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const response = await SpotifyInteract.search.query(
|
|
70
|
+
req.sdk,
|
|
71
|
+
req.query.q,
|
|
72
|
+
options.market,
|
|
73
|
+
options.searchQueryLimit
|
|
74
|
+
);
|
|
75
|
+
return res.json(response);
|
|
76
|
+
})
|
|
77
|
+
);
|
|
78
|
+
|
|
79
|
+
app.get(
|
|
80
|
+
'/add',
|
|
81
|
+
protect([options.canAdd]),
|
|
82
|
+
apiWrapper(async (req, res) => {
|
|
83
|
+
const { success } = await SpotifyInteract.queue.add(req.sdk, req.query.uri);
|
|
84
|
+
|
|
85
|
+
if (!success) {
|
|
86
|
+
req.comms.error('Song already in queue');
|
|
87
|
+
} else {
|
|
88
|
+
req.comms.message(`Added <em>${req.query.name ?? 'a track'}</em>`, 'track');
|
|
89
|
+
}
|
|
90
|
+
events.system('add');
|
|
91
|
+
|
|
92
|
+
return res.json({ success });
|
|
93
|
+
})
|
|
94
|
+
);
|
|
95
|
+
|
|
96
|
+
app.get(
|
|
97
|
+
'/info',
|
|
98
|
+
apiWrapper(async (req, res) => {
|
|
99
|
+
const response = await SpotifyInteract.info.get(req.sdk, options.market);
|
|
100
|
+
return res.json(response);
|
|
101
|
+
})
|
|
102
|
+
);
|
|
103
|
+
|
|
104
|
+
app.get(
|
|
105
|
+
'/queue',
|
|
106
|
+
apiWrapper(async (req, res) => {
|
|
107
|
+
const response = await SpotifyInteract.queue.get(req.sdk);
|
|
108
|
+
return res.json(response);
|
|
109
|
+
})
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
app.get(
|
|
113
|
+
'/skipForward',
|
|
114
|
+
protect([options.canControl]),
|
|
115
|
+
apiWrapper(async (req, res) => {
|
|
116
|
+
const response = await SpotifyInteract.player.forward(req.sdk);
|
|
117
|
+
|
|
118
|
+
req.comms.message('Skipped forward');
|
|
119
|
+
events.system('skippedForward');
|
|
120
|
+
|
|
121
|
+
return res.json(response);
|
|
122
|
+
})
|
|
123
|
+
);
|
|
124
|
+
|
|
125
|
+
app.get(
|
|
126
|
+
'/skipBackward',
|
|
127
|
+
protect([options.canControl]),
|
|
128
|
+
apiWrapper(async (req, res) => {
|
|
129
|
+
const response = await SpotifyInteract.player.back(req.sdk);
|
|
130
|
+
|
|
131
|
+
req.comms.message('Skipped back');
|
|
132
|
+
events.system('skippedBackward');
|
|
133
|
+
|
|
134
|
+
return res.json(response);
|
|
135
|
+
})
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
app.get(
|
|
139
|
+
'/play',
|
|
140
|
+
protect([options.canControl]),
|
|
141
|
+
apiWrapper(async (req, res) => {
|
|
142
|
+
const response = await SpotifyInteract.player.play(req.sdk);
|
|
143
|
+
|
|
144
|
+
req.comms.message('Pressed play');
|
|
145
|
+
events.system('play');
|
|
146
|
+
|
|
147
|
+
return res.json(response);
|
|
148
|
+
})
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
app.get(
|
|
152
|
+
'/pause',
|
|
153
|
+
protect([options.canControl]),
|
|
154
|
+
apiWrapper(async (req, res) => {
|
|
155
|
+
const response = await SpotifyInteract.player.pause(req.sdk);
|
|
156
|
+
|
|
157
|
+
req.comms.message('Pressed pause');
|
|
158
|
+
events.system('pause');
|
|
159
|
+
|
|
160
|
+
return res.json(response);
|
|
161
|
+
})
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
app.get('/health', (req, res) => res.json({ success: true, authenticated: !!req.sdk }));
|
|
165
|
+
|
|
166
|
+
if (options.centralisedPolling) {
|
|
167
|
+
io.on('connect', (socket) => {
|
|
168
|
+
if (cachedInfo.current) {
|
|
169
|
+
if (verbose) {
|
|
170
|
+
console.log('Sending client current cached track');
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
socket.emit('info', cachedInfo.current);
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
asyncInterval(async () => {
|
|
178
|
+
try {
|
|
179
|
+
if (verbose) {
|
|
180
|
+
console.log('Running centralised polling');
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
if (!sdk.current) {
|
|
184
|
+
return;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// If there are no clients, don't bother using an API call - or just once if there is no cached info
|
|
188
|
+
if (io.engine.clientsCount > 0 || !cachedInfo.current) {
|
|
189
|
+
const info = await SpotifyInteract.info.get(sdk.current);
|
|
190
|
+
io.emit('info', info);
|
|
191
|
+
|
|
192
|
+
// Allows us to pass to new sockets immediately
|
|
193
|
+
cachedInfo.current = info;
|
|
194
|
+
|
|
195
|
+
if (verbose) {
|
|
196
|
+
console.log('Ran centralised polling');
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
} catch (e) {
|
|
200
|
+
events.error(EVENT.APP_ERROR, e);
|
|
201
|
+
log.error(e);
|
|
202
|
+
|
|
203
|
+
if (verbose) {
|
|
204
|
+
console.log('Error on polling');
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}, options.centralisedPollingTimer);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
return app;
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
export default run;
|