Merge pull request #4 from Enstrayed/main
Merge changes into new auth branch
This commit was merged in pull request #4.
This commit is contained in:
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1,4 +1,6 @@
|
||||
node_modules/
|
||||
config.json
|
||||
bun.lockb
|
||||
GITVERSION
|
||||
GITVERSION
|
||||
todo.txt
|
||||
proto.js
|
||||
@@ -1,9 +0,0 @@
|
||||
FROM node:20
|
||||
WORKDIR /app
|
||||
|
||||
RUN git clone https://github.com/enstrayed/enstrayedapi .
|
||||
RUN git config --global --add safe.directory /app
|
||||
RUN git show --oneline -s >> GITVERSION
|
||||
RUN npm install
|
||||
|
||||
ENTRYPOINT [ "node", "index.js" ]
|
||||
105
README.md
Normal file
105
README.md
Normal file
@@ -0,0 +1,105 @@
|
||||
# Enstrayed API
|
||||
This repository contains the code for my personal web API written in JavaScript using the Express framework.
|
||||
|
||||
## Documentation
|
||||
This file contains documentation relevant for development and deployment, but not necessarily usage. Information for all endpoints is available [on my website](https://enstrayed.com/posts/20240409-API-Documentation.html).
|
||||
|
||||
## Issues
|
||||
If you would like to report a bug or security issue, please open a GitHub issue. If you are the operator of a service this application accesses, use the contact information provided during registration with your service to contact me directly.
|
||||
|
||||
## Configuration
|
||||
On startup, this application will look for two files. If either cannot be read, it will exit with an error code.
|
||||
1. `config.json` contains settings required for operation and API keys used for calling external services.
|
||||
2. `GITVERSION` contains the commit that was cloned when the container was built.
|
||||
|
||||
<details> <summary>Configuration Example</summary>
|
||||
|
||||
* `couchdb.host`: Hostname/IP address and port of a CouchDB server.
|
||||
* `couchdb.authorization`: Username & password used to access the CouchDB server, in HTTP Basic authentication format, e.g. `username:password`.
|
||||
* `blog.postsDirectory`: Directory that will be parsed when calling /blogposts. If running in Docker this directory will need to be mounted to the container.
|
||||
* `blog.postsDirUrl`: Location of the posts directory on the web server.
|
||||
* `nowplaying.*.target`: Set to the Last.fm/Jellyfin username to query for playback information.
|
||||
|
||||
```json
|
||||
{
|
||||
"startup": {
|
||||
"apiPort": 8081,
|
||||
"routesDir": "./routes"
|
||||
},
|
||||
|
||||
"couchdb": {
|
||||
"host": "hazeldale:5984",
|
||||
"authorization": ""
|
||||
},
|
||||
|
||||
"mailjet": {
|
||||
"apiKey": "",
|
||||
"senderAddress": "apinotifications@enstrayed.com",
|
||||
"senderName": "API Notifications",
|
||||
|
||||
"authKeysDoc": "mailjet"
|
||||
},
|
||||
|
||||
"blog": {
|
||||
"postsDirectory": "C:/Users/natha/Downloads/proto/posts",
|
||||
"postsDirUrl": "/posts"
|
||||
},
|
||||
|
||||
"nowplaying": {
|
||||
"lastfm": {
|
||||
"apiKey": "",
|
||||
"target": "enstrayed"
|
||||
},
|
||||
"jellyfin": {
|
||||
"apiKey": "",
|
||||
"host": "",
|
||||
"target": ""
|
||||
},
|
||||
"cider": {
|
||||
"apiKeys": [],
|
||||
"hosts": []
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## Docker
|
||||
In production, this application is designed to be run in Docker, and the container built by pulling the latest commit from the main branch. As such, deploying this application is just a matter of creating a directory and copying the Dockerfile:
|
||||
|
||||
> [!IMPORTANT]
|
||||
> Please review the Configuration section of this document for important information. By default, the `config.json` file is expected to be mounted into the container at `/app/config.json`.
|
||||
|
||||
```dockerfile
|
||||
FROM node:20
|
||||
WORKDIR /app
|
||||
|
||||
RUN git clone https://github.com/enstrayed/enstrayedapi .
|
||||
RUN git config --global --add safe.directory /app
|
||||
RUN git show --oneline -s >> GITVERSION
|
||||
RUN npm install
|
||||
|
||||
ENTRYPOINT [ "node", "index.js" ]
|
||||
```
|
||||
|
||||
<details> <summary>Docker Compose File</summary>
|
||||
|
||||
```yaml
|
||||
---
|
||||
services:
|
||||
enstrayedapi:
|
||||
build:
|
||||
context: .
|
||||
image: enstrayedapi
|
||||
container_name: enstrayedapi
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config.json:/app/config.json
|
||||
```
|
||||
|
||||
</details>
|
||||
|
||||
## License
|
||||
If for whatever reason you want to, you are free to adapt this code for your own projects or as reference. However, this software is provided as-is with no warranty or agreement to support it.
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"startup": {
|
||||
"apiPort": 8081,
|
||||
"routesDir": "./routes"
|
||||
},
|
||||
|
||||
"couchdb": {
|
||||
"host": "hazeldale:5984",
|
||||
"authorization": ""
|
||||
},
|
||||
|
||||
"cider": {
|
||||
"targetHosts": ["localhost:10769"],
|
||||
|
||||
"authKeysDoc": "cider"
|
||||
},
|
||||
|
||||
"mailjet": {
|
||||
"apiKey": "",
|
||||
"senderAddress": "apinotifications@enstrayed.com",
|
||||
"senderName": "API Notifications",
|
||||
|
||||
"authKeysDoc": "mailjet"
|
||||
},
|
||||
|
||||
"etyd": {
|
||||
"randomHexLength": 6,
|
||||
"authKeyInDb": "apiAuthKeys.etyd"
|
||||
},
|
||||
|
||||
"blog": {
|
||||
"postsDirectory": "C:/Users/natha/Downloads/proto/posts",
|
||||
"postsDirUrl": "/posts"
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
const { app, db, globalConfig } = require("../index.js") // Get globals from index
|
||||
|
||||
var timeSinceLastCiderQuery = Date.now()-2000;
|
||||
var currentListening = {}
|
||||
var currentListeningHtml = ""
|
||||
|
||||
app.get("/cider", (rreq,rres) => {
|
||||
|
||||
rres.send("<span>Cider endpoint is temporarily unavailable.</span>")
|
||||
})
|
||||
|
||||
// app.get("/cider", (rreq,rres) => { // GET current listening from target
|
||||
|
||||
// if (Date.now() < timeSinceLastCiderQuery+2000) {
|
||||
// rres.send(currentListening); // If it has been <2 seconds since the last request, return the cached result.
|
||||
// } else {
|
||||
// getCurrentListening(globalConfig.cider.targetHosts[0],"json").then(funcRes => {
|
||||
// if (funcRes == 1) {
|
||||
// rres.sendStatus(503) // If there was a problem getting the upstream JSON, return 503 Service Unavailable.
|
||||
// } else {
|
||||
// // Required (I think?) because of CORS.
|
||||
// currentListening = funcRes
|
||||
// rres.send(funcRes)
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
|
||||
// })
|
||||
|
||||
// app.post("/cider", (rreq,rres) => { // POST stop listening on cider target
|
||||
|
||||
// fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${globalConfig.cider.authKeysDoc}`, {
|
||||
// headers: {
|
||||
// "Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
|
||||
// }
|
||||
// }).then(dbRes => dbRes.json()).then(dbRes => {
|
||||
|
||||
// if (dbRes.status == 404) { // If document containing cider auth keys does not exist
|
||||
// console.log(`ERROR: Could not find apiauthkeys/${globalConfig.mailjet.authKeysDoc}`)
|
||||
// rres.sendStatus(500) // Refuse request
|
||||
// } else {
|
||||
// if (dbRes["content"][rreq.get("Authorization").split("_")[0]] === rreq.get("Authorization").split("_")[1]) {
|
||||
|
||||
// fetch(`http://${globalConfig.cider.targetHosts[0]}/stop`).then(fres => { // send GET /stop to cider target
|
||||
// if (fres.status == 204) {
|
||||
// console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 200 KEY:${rreq.get("Authorization")}`)
|
||||
// rres.sendStatus(200) // if that works then 200
|
||||
// } else {
|
||||
// rres.sendStatus(500) // otherwise lol
|
||||
// }
|
||||
// }).catch(ferror => {
|
||||
// rres.sendStatus(503) // and if a problem happens its probably cause cider target is unavailable
|
||||
// })
|
||||
|
||||
// } else {
|
||||
// console.log(`${rreq.get("cf-connecting-ip")} POST /cider returned 401`) // log ip of unauthorized requests
|
||||
// rres.sendStatus(401) // received auth key was not in database
|
||||
// }
|
||||
// }
|
||||
// })
|
||||
|
||||
// })
|
||||
|
||||
// 2024-04-10: Retrieves currentPlayingSong JSON from specified Cider host and
|
||||
// returns JSON/HTML containing the useful bits if successful, returning 1 if not.
|
||||
|
||||
// async function getCurrentListening(host,contentType) { // Host should be hostname/ip & port only.
|
||||
// timeSinceLastCiderQuery = Date.now(); // Save last time function was run, used to indicate when the cache needs refreshed.
|
||||
// return await fetch(`http://${host}/currentPlayingSong`).then(fetchRes => {
|
||||
// if (fetchRes.status == 502) {
|
||||
// return 1 // If the upstream server returns 502 (Bad Gateway) then internally return 1, indicating error.
|
||||
// } else {
|
||||
// return fetchRes.json().then(jsonRes => {
|
||||
// if (jsonRes.info.name == undefined) {
|
||||
// return 1 // If Cider is running but not playing a song this check prevents an undefined variable error.
|
||||
// } else {
|
||||
// if (contentType === "json") {
|
||||
// return {
|
||||
// "songName": jsonRes.info.name,
|
||||
// "artistName": jsonRes.info.artistName,
|
||||
// "albumName": jsonRes.info.albumName,
|
||||
// "songLinkUrl": jsonRes.info.url.songLink,
|
||||
// "endtimeEpochInMs": jsonRes.info.endTime,
|
||||
// "artworkUrl": jsonRes.info.artwork.url.replace("{w}", jsonRes.info.artwork.width).replace("{h}", jsonRes.info.artwork.height)
|
||||
// }
|
||||
// } else if (contentType === "html") {
|
||||
// return `<img src="${jsonRes.info.artwork.url.replace("{w}", jsonRes.info.artwork.width).replace("{h}", jsonRes.info.artwork.height)}" alt="Album Art" id="nowplaying-albumart" style="width: 10em;"> <div class="textlist"><p>I'm listening to</p><h3>${`${jsonRes.info.name} by ${jsonRes.info.artistName}`}</h3><p>from ${jsonRes.info.albumName}</p><a href="${jsonRes.info.url.songLink}" class="noindent">song.link</a></div>`
|
||||
// } else {
|
||||
// return 1
|
||||
// }
|
||||
|
||||
// }
|
||||
// })
|
||||
// }
|
||||
// }).catch(fetchError => {
|
||||
// console.error("Error fetch()ing upstream Cider host: "+fetchError)
|
||||
// return 1 // If something else happens then log it and return 1, indicating error.
|
||||
// })
|
||||
// }
|
||||
|
||||
module.exports = {app} // export routes to be imported by index for execution
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
services:
|
||||
enstrayedapi:
|
||||
build:
|
||||
context: .
|
||||
image: enstrayedapi
|
||||
container_name: enstrayedapi
|
||||
restart: unless-stopped
|
||||
volumes:
|
||||
- ./config.json:/app/config.json
|
||||
@@ -1,11 +1,7 @@
|
||||
const { globalConfig } = require("../index.js")
|
||||
|
||||
async function checkAuthorization(documentToUse,keyToCheck) {
|
||||
return await fetch(`http://${globalConfig.couchdb.host}/apiauthkeys/${documentToUse}`, {
|
||||
headers: {
|
||||
"Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
|
||||
}
|
||||
}).then(fetchRes => {
|
||||
return await fetch(`${globalConfig.couchdbHost}/apiauthkeys/${documentToUse}`).then(fetchRes => {
|
||||
|
||||
if (fetchRes.status === 404) { // If document doesnt exist fail gracefully
|
||||
|
||||
|
||||
26
liberals/nowplaying.js
Normal file
26
liberals/nowplaying.js
Normal file
@@ -0,0 +1,26 @@
|
||||
const { globalConfig } = require("../index.js")
|
||||
|
||||
async function queryLastfm() {
|
||||
return await fetch(`https://ws.audioscrobbler.com/2.0/?format=json&method=user.getrecenttracks&limit=1&api_key=${globalConfig.nowplaying.lastfm.apiKey}&user=${globalConfig.nowplaying.lastfm.target}`).then(response => response.json()).then(response => {
|
||||
if (response["recenttracks"] == undefined) {
|
||||
return 1
|
||||
} else {
|
||||
if (response.recenttracks.track[0]["@attr"] == undefined) {
|
||||
return 1
|
||||
} else {
|
||||
return {
|
||||
"json": {
|
||||
"songName": response.recenttracks.track[0].name,
|
||||
"artistName": response.recenttracks.track[0].artist["#text"],
|
||||
"albumName": response.recenttracks.track[0].album["#text"],
|
||||
"artUrl": response.recenttracks.track[0].image[3]["#text"],
|
||||
"link": response.recenttracks.track[0].url
|
||||
},
|
||||
"html": `<img src="${response.recenttracks.track[0].image[3]["#text"]}" alt="Album Art" style="width: 10em;"> <div class="textlist"> <p>I'm listening to</p> <h3>${response.recenttracks.track[0].name} by ${response.recenttracks.track[0].artist["#text"]}</h3> <p>from ${response.recenttracks.track[0].album["#text"]}</p> <a href="${response.recenttracks.track[0].url}" class="noindent">View on Last.fm</a></div>`
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
module.exports = { queryLastfm }
|
||||
56
package-lock.json
generated
56
package-lock.json
generated
@@ -9,22 +9,43 @@
|
||||
"version": "1.0.0",
|
||||
"license": "UNLICENSED",
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"typescript": "^5.4.3"
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.0.12",
|
||||
"@types/node": "^20.12.3"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.3.tgz",
|
||||
"integrity": "sha512-sD+ia2ubTeWrOu+YMF+MTAB7E+O7qsMqAbMfW7DG3K1URwhZ5hN1pLlRVGbf4wDFzSfikL05M17EyorS86jShw==",
|
||||
"node_modules/@types/bun": {
|
||||
"version": "1.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/bun/-/bun-1.1.5.tgz",
|
||||
"integrity": "sha512-7RprVDMF+1o+EWSo7F1+iJpkfNz+Ikw9K//vwambcY+D1QHXfb9l7jWY1hSBfuFEkW9yFAhkMzP2uTi1pQXoqw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bun-types": "1.1.14"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.12.14",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.12.14.tgz",
|
||||
"integrity": "sha512-scnD59RpYD91xngrQQLGkE+6UrHUPzeKZWhhjBSa3HSkwjbQc38+q3RoIVEwxQGRw3M+j5hpNAM+lgV3cVormg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/ws": {
|
||||
"version": "8.5.10",
|
||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.10.tgz",
|
||||
"integrity": "sha512-vmQSUcfalpIq0R9q7uTo2lXs6eGIpt9wtnLdMv9LVpIjCA/+ufZRozlVoVelIYixx1ugCBKDhn89vnsEGOCx9A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/accepts": {
|
||||
"version": "1.3.8",
|
||||
"resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz",
|
||||
@@ -65,6 +86,17 @@
|
||||
"npm": "1.2.8000 || >= 1.4.16"
|
||||
}
|
||||
},
|
||||
"node_modules/bun-types": {
|
||||
"version": "1.1.14",
|
||||
"resolved": "https://registry.npmjs.org/bun-types/-/bun-types-1.1.14.tgz",
|
||||
"integrity": "sha512-esfxOvECTkjEuUEHBOoOo590Qggf4b9cz5h29AOB2SKt3yZwG3LbAX4iIYwWZX7GnO7vaY5hIdcQygwN0xGdNw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/node": "~20.12.8",
|
||||
"@types/ws": "~8.5.10"
|
||||
}
|
||||
},
|
||||
"node_modules/bytes": {
|
||||
"version": "3.1.2",
|
||||
"resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz",
|
||||
@@ -679,18 +711,6 @@
|
||||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/typescript": {
|
||||
"version": "5.4.3",
|
||||
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.4.3.tgz",
|
||||
"integrity": "sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg==",
|
||||
"bin": {
|
||||
"tsc": "bin/tsc",
|
||||
"tsserver": "bin/tsserver"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.17"
|
||||
}
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
|
||||
@@ -2,13 +2,10 @@
|
||||
"dependencies": {
|
||||
"express": "^4.18.2"
|
||||
},
|
||||
"name": "api",
|
||||
"name": "enstrayedapi",
|
||||
"version": "1.0.0",
|
||||
"description": "api.enstrayed.com",
|
||||
"description": "EnstrayedAPI",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/enstrayed/enstrayedapi.git"
|
||||
@@ -18,7 +15,6 @@
|
||||
"bugs": {
|
||||
"url": "https://github.com/enstrayed/enstrayedapi/issues"
|
||||
},
|
||||
"homepage": "https://api.enstrayed.com",
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.0.12",
|
||||
"@types/node": "^20.12.3"
|
||||
|
||||
@@ -2,11 +2,7 @@ const { app, globalConfig } = require("../index.js") // Get globals from index
|
||||
const { checkToken } = require("../liberals/auth.js")
|
||||
|
||||
app.get("/etyd*", (rreq,rres) => {
|
||||
fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd","")}`, {
|
||||
headers: {
|
||||
"Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
|
||||
}
|
||||
}).then(dbRes => {
|
||||
fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd","")}`).then(dbRes => {
|
||||
if (dbRes.status == 404) {
|
||||
rres.sendStatus(404)
|
||||
} else {
|
||||
@@ -35,21 +31,16 @@ app.delete("/etyd*", (rreq,rres) => {
|
||||
rres.sendStatus(401)
|
||||
} else if (authRes === true) { // Authorization successful
|
||||
|
||||
fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, {
|
||||
headers: {
|
||||
"Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
|
||||
}
|
||||
}).then(dbRes => {
|
||||
fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`).then(dbRes => {
|
||||
|
||||
if (dbRes.status == 404) {
|
||||
rres.sendStatus(404)
|
||||
} else {
|
||||
dbRes.json().then(dbRes => {
|
||||
|
||||
fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, {
|
||||
fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`, {
|
||||
method: "DELETE",
|
||||
headers: {
|
||||
"Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`,
|
||||
"If-Match": dbRes["_rev"] // Using the If-Match header is easiest for deleting entries in couchdb
|
||||
}
|
||||
}).then(fetchRes => {
|
||||
@@ -89,10 +80,7 @@ app.post("/etyd*", (rreq,rres) => {
|
||||
if (rreq.body["url"] == undefined) {
|
||||
rres.sendStatus(400)
|
||||
} else {
|
||||
fetch(`http://${globalConfig.couchdb.host}/etyd${rreq.path.replace("/etyd", "")}`, {
|
||||
headers: {
|
||||
"Authorization": `Basic ${btoa(globalConfig.couchdb.authorization)}`
|
||||
},
|
||||
fetch(`${globalConfig.couchdbHost}/etyd${rreq.path.replace("/etyd", "")}`, {
|
||||
method: "PUT",
|
||||
body: JSON.stringify({
|
||||
"content": {
|
||||
|
||||
@@ -1,11 +1,33 @@
|
||||
const { app, globalConfig } = require("../index.js")
|
||||
const { queryLastfm } = require("../liberals/nowplaying.js")
|
||||
|
||||
var timeSinceLastLastfmQuery = Date.now()-5000
|
||||
var cachedLastfmResult = {}
|
||||
|
||||
const notPlayingAnythingPlaceholder = {
|
||||
"json": {
|
||||
"playing": false
|
||||
},
|
||||
"html": `<span>I'm not currently listening to anything.</span>`
|
||||
}
|
||||
|
||||
app.get("/nowplaying", (rreq,rres) => {
|
||||
if (rreq.query.format === "html") {
|
||||
rres.send("<span>The /nowplaying endpoint is currently under construction.</span>")
|
||||
|
||||
if (Date.now() < timeSinceLastLastfmQuery+5000) {
|
||||
rres.send(cachedLastfmResult[rreq.query.format] ?? cachedLastfmResult.json)
|
||||
} else {
|
||||
rres.send({"message":"The /nowplaying endpoint is currently under construction."})
|
||||
timeSinceLastLastfmQuery = Date.now()
|
||||
queryLastfm().then(response => {
|
||||
if (response == 1) {
|
||||
cachedLastfmResult = notPlayingAnythingPlaceholder
|
||||
} else {
|
||||
cachedLastfmResult = response
|
||||
}
|
||||
|
||||
rres.send(cachedLastfmResult[rreq.query.format] ?? cachedLastfmResult.json)
|
||||
})
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
module.exports = {app}
|
||||
Reference in New Issue
Block a user