From e0cc8e5aaf3c4cbaab220408b63d3bcabe5a0ac2 Mon Sep 17 00:00:00 2001 From: Evert Prants Date: Fri, 11 Jan 2019 19:36:58 +0200 Subject: [PATCH] API server --- control/api.js | 131 +++++++++++++++ control/config.js | 5 +- control/index.js | 9 +- control/liquidsoap.js | 45 +++++- index.js | 2 + liq/metadata.liq | 37 +++++ liq/scripts/queue | 4 +- liq/scripts/smart | 9 +- liq/view.liq | 2 + package-lock.json | 360 ++++++++++++++++++++++++++++++++++++++++++ package.json | 5 +- 11 files changed, 597 insertions(+), 12 deletions(-) create mode 100644 control/api.js create mode 100644 liq/metadata.liq diff --git a/control/api.js b/control/api.js new file mode 100644 index 0000000..e233205 --- /dev/null +++ b/control/api.js @@ -0,0 +1,131 @@ +const path = require('path') +const express = require('express') +const bodyParser = require('body-parser') + +require('express-async-errors') + +const config = require(path.join(__dirname, 'config.js')) + +const app = express() +const port = config.api.port +const host = config.api.host +const mount = config.api.mount + +app.enable('trust proxy', 1) +app.disable('x-powered-by') + +app.use(bodyParser.urlencoded({ extended: false })) +app.use(bodyParser.json()) + +const apiRouter = express.Router() +const internalRouter = express.Router() + +// Make sure internal router can only be accessed from local network +internalRouter.use(function (req, res, next) { + if (req.ip !== '127.0.0.1' && req.ip !== '::1') { + return res.status(401).end('401 Unauthorized. Accessed internal API endpoint!') + } + + next() +}) + +// Call this here to prevent key from being required on internal endpoints +apiRouter.use('/internal', internalRouter) + +// Key-based authorization for API endpoints +apiRouter.use(function (req, res, next) { + let key = req.query.key + + // Prefer headers for authentication + if (req.headers['authorization'] && typeof req.headers['authorization'] === 'string') { + let auth = req.headers['authorization'] + if (auth.indexOf('Bearer') !== -1) return next(new Error('Invalid authorization header!')) + auth = auth.split(' ')[1] + + if (!auth) return next(new Error('Invalid authorization header!')) + key = auth + } + + if (!key || config.api.keys.indexOf(key) === -1) return res.status(403).end('403 Forbidden') + req.key = key + next() +}) + +module.exports = function (liq, sched) { + // Schedule actions + apiRouter.get('/events', (req, res) => { + res.jsonp({ + calendar: config.calendar.calendar, + events: sched.events + }) + }) + + apiRouter.post('/events/reload', async (req, res) => { + await sched.calendarFetch() + await sched.update() + res.status(204).end() + }) + + // Liquidsoap status + apiRouter.get('/', (req, res) => { + res.jsonp({ + time: new Date(), + authorization: req.key, + liquidsoap: { + status: liq.running, + meta: liq.metadata + } + }) + }) + + apiRouter.get('/meta', (req, res) => { + res.jsonp(liq.metadata) + }) + + // Liquidsoap actions + apiRouter.post('/queue', (req, res) => { + let items = req.body.items + if (typeof items === 'string') items = [items] + else if (!items || !items.length) return res.status(400).end('400 Bad Request') + liq.queue(items) + res.status(202).end('OK') + }) + + apiRouter.post('/skip', (req, res) => { + liq.skip() + res.status(202).end('OK') + }) + + // Internal router (Only accessible from localhost, no matter the host setting) + // Used by liquidsoap + internalRouter.post('/meta', (req, res) => { + let m = req.body + + if (Object.keys(m).length === 0) { + return res.send('(none)') + } + + liq.metadata = m + res.send(JSON.stringify(m, null, 1)) + }) + + app.use(mount, apiRouter) + + // Handle 404 + app.use((req, res, next) => { + res.status(404).end('404 Not Found') + }) + + // Handle errors + app.use((error, req, res, next) => { + console.error(error.stack) + if (app.get('env') === 'production') { + return res.status(500).end('Internal Server Error: ' + error.message) + } + + res.status(500).end(error.stack) + }) + + // And finally, listen on the configured port and host + app.listen(port, host, () => console.log(`API server listening on ${host}:${port}${mount}`)) +} diff --git a/control/config.js b/control/config.js index fc76330..75aef03 100644 --- a/control/config.js +++ b/control/config.js @@ -4,9 +4,10 @@ const configFile = path.join(__dirname, '..', 'config.json') const defvals = { api: { - host: '0.0.0.0', + host: '127.0.0.1', port: 7005, - mount: '/api' + mount: '/api', + keys: [] }, rtmp: { host: 'icynet.eu:1935', diff --git a/control/index.js b/control/index.js index b945300..127bf4d 100644 --- a/control/index.js +++ b/control/index.js @@ -5,13 +5,15 @@ const Scheduler = require(path.join(__dirname, 'schedule.js')) const rl = require(path.join(__dirname, 'console.js')).rl let liq = new Liquidsoap() -liq.start() +if (process.env.LIQUIDSOAP_AUTOSTART !== 'false') liq.start() let scheduler = new Scheduler(config.calendar) scheduler.startTimers() scheduler.on('event-start', (event) => liq.queue(event.descriptor)) +require(path.join(__dirname, 'api.js'))(liq, scheduler) + // User input handler rl.on('line', function (line) { let argv = line.split(' ') @@ -43,7 +45,10 @@ rl.on('line', function (line) { break case 'events': console.log('Refreshing scheduler..') - scheduler.calendarFetch().catch((e) => console.error('Calendar fetch failed!', e.stack)) + Promise.all([ + scheduler.calendarFetch(), + scheduler.update() + ]).catch((e) => console.error('Calendar fetch failed!', e.stack)) break case 'reload': console.log('Reloading configuration..') diff --git a/control/liquidsoap.js b/control/liquidsoap.js index fdff3a4..3f67831 100644 --- a/control/liquidsoap.js +++ b/control/liquidsoap.js @@ -17,9 +17,44 @@ function printProcessOutput (fn) { } } +function sanitizeMetadata (meta) { + let final = {} + for (let key in meta) { + let value = meta[key] + if (!value) continue + if (value === 'true') value = true + if (value === 'false') value = false + if (value.match && value.match(/^\d+\/\d+\/\d+ \d+:\d+:\d+$/)) value = new Date(value) + if (!isNaN(parseFloat(value))) value = parseFloat(value) + if (key === 'start') value = new Date(value * 1000) + + if (value.indexOf && value.indexOf('(dot)') !== -1) { + value = value.replace('(dot)', '.') + } + + if (key.indexOf('-') !== -1) { + key = key.replace(/\-/g, '_') + } + + final[key] = value + } + return final +} + class Liquidsoap { constructor () { this.running = false + this.meta = {} + } + + set metadata (mdt) { + mdt = sanitizeMetadata(mdt) + mdt.is_fallback = (mdt.source && mdt.source === config.liquidsoap.fallback) + this.meta = mdt + } + + get metadata () { + return this.meta } start () { @@ -52,8 +87,7 @@ class Liquidsoap { client.write(split[i] + '\r\n') } - client.write('quit\r\n') - client.end() + client.end('quit\r\n') resolve() }) @@ -65,7 +99,12 @@ class Liquidsoap { let q = [] for (let i in item) { - q.push('queue.push smart:' + item[i]) + let ii = item[i] + if (typeof ii !== 'string') continue + if (ii.indexOf('smart:') !== 0 && ii.indexOf('annotate:') !== 0) { + ii = 'smart:' + ii + } + q.push('queue.push ' + ii) } this.sendCommand(q.join('\n')).catch((e) => console.error('Failed to queue:', e.message)) diff --git a/index.js b/index.js index 357dce9..72b7011 100644 --- a/index.js +++ b/index.js @@ -20,6 +20,8 @@ for (let i in process.argv) { } else if (arg === '-p') { env = 'production' break + } else if (arg === '-s') { + process.env.LIQUIDSOAP_AUTOSTART = 'false' } } diff --git a/liq/metadata.liq b/liq/metadata.liq new file mode 100644 index 0000000..5759ccf --- /dev/null +++ b/liq/metadata.liq @@ -0,0 +1,37 @@ +# send metadata to server and print the response +def send_metadata(m) + data = json_of(m) + # print("Sending metadata: "^data) + ignore(http.post( + data = data, + headers = [("Content-Type", "application/json; charset=utf-8")], + timeout = 10.0, + "http://#{confstr('api.host', 'localhost')}:#{confstr('api.port', '7005')}#{confstr('api.mount', 'api')}/internal/meta" + )) +end + +def update_nowplaying(m) + # Encode all text in UTF-8 + recode = string.recode(out_enc="UTF-8") + def f(x) + (recode(fst(x)),recode(snd(x))) + end + + m = list.map(f,m) + + # Remove pictures from metadata + m = list.remove_assoc("cover", m) + m = list.remove_assoc("picture", m) + + # Append start time to metadata + m = if m != [] then + list.append( + [("start", string_of(time()))] + , m) + else + m + end + + # Publish to API + send_metadata(m) +end diff --git a/liq/scripts/queue b/liq/scripts/queue index de5e9c2..06882a4 100755 --- a/liq/scripts/queue +++ b/liq/scripts/queue @@ -11,7 +11,5 @@ client.once('connect', function () { if (process.argv[2]) { client.write('queue.push smart:' + process.argv[2] + '\r\n') } - client.write('quit\r\n') - client.end() + client.end('quit\r\n') }) - diff --git a/liq/scripts/smart b/liq/scripts/smart index f7bd089..9b3f7af 100755 --- a/liq/scripts/smart +++ b/liq/scripts/smart @@ -15,8 +15,15 @@ function protocol (arg, handleCb) { yt.stdout.on('data', function (chunk) { output += chunk.toString('utf8') }) + yt.on('close', function () { - let data = JSON.parse(output) + try { + let data = JSON.parse(output) + } catch (e) { + console.error(e.message) + process.exit(1) + } + delete data.formats fetchVideo(data, handleCb) }) diff --git a/liq/view.liq b/liq/view.liq index 3bac8c8..c16f18e 100644 --- a/liq/view.liq +++ b/liq/view.liq @@ -3,6 +3,7 @@ %include "protocols.liq" %include "rtmp.liq" %include "utils.liq" +%include "metadata.liq" set("server.telnet", true) set("server.telnet.bind_addr", confstr("control.host", "0.0.0.0")) @@ -30,6 +31,7 @@ add_skip_command(command="skip", source) # Add file cleanup source = on_end(delay=0., file_end_cleanup, source) +source = on_metadata(update_nowplaying, source) # Output to RTMP output.rtmp.live( diff --git a/package-lock.json b/package-lock.json index f680b2c..998952b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,6 +9,15 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" }, + "accepts": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", + "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", + "requires": { + "mime-types": "~2.1.18", + "negotiator": "0.6.1" + } + }, "ansi-regex": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", @@ -28,11 +37,43 @@ "readable-stream": "^2.0.6" } }, + "array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" + }, "balanced-match": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=" }, + "body-parser": { + "version": "1.18.3", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.3.tgz", + "integrity": "sha1-WykhmP/dVTs6DyDe0FkrlWlVyLQ=", + "requires": { + "bytes": "3.0.0", + "content-type": "~1.0.4", + "debug": "2.6.9", + "depd": "~1.1.2", + "http-errors": "~1.6.3", + "iconv-lite": "0.4.23", + "on-finished": "~2.3.0", + "qs": "6.5.2", + "raw-body": "2.3.3", + "type-is": "~1.6.16" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -42,6 +83,11 @@ "concat-map": "0.0.1" } }, + "bytes": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", + "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" + }, "canvas": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.2.0.tgz", @@ -71,6 +117,26 @@ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=" }, + "content-disposition": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", + "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" + }, + "content-type": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", + "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" + }, + "cookie": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", + "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" + }, + "cookie-signature": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", + "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" + }, "core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", @@ -94,11 +160,121 @@ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=" }, + "depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" + }, + "destroy": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", + "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" + }, "detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=" }, + "ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" + }, + "encodeurl": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", + "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" + }, + "escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" + }, + "etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" + }, + "express": { + "version": "4.16.4", + "resolved": "https://registry.npmjs.org/express/-/express-4.16.4.tgz", + "integrity": "sha512-j12Uuyb4FMrd/qQAm6uCHAkPtO8FDTRJZBDd5D2KOL2eLaz1yUNdUB/NOIyq0iU4q4cFarsUCrnFDPBcnksuOg==", + "requires": { + "accepts": "~1.3.5", + "array-flatten": "1.1.1", + "body-parser": "1.18.3", + "content-disposition": "0.5.2", + "content-type": "~1.0.4", + "cookie": "0.3.1", + "cookie-signature": "1.0.6", + "debug": "2.6.9", + "depd": "~1.1.2", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "1.1.1", + "fresh": "0.5.2", + "merge-descriptors": "1.0.1", + "methods": "~1.1.2", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "path-to-regexp": "0.1.7", + "proxy-addr": "~2.0.4", + "qs": "6.5.2", + "range-parser": "~1.2.0", + "safe-buffer": "5.1.2", + "send": "0.16.2", + "serve-static": "1.13.2", + "setprototypeof": "1.1.0", + "statuses": "~1.4.0", + "type-is": "~1.6.16", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "express-async-errors": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/express-async-errors/-/express-async-errors-3.1.1.tgz", + "integrity": "sha512-h6aK1da4tpqWSbyCa3FxB/V6Ehd4EEB15zyQq9qe75OZBp0krinNKuH4rAY+S/U/2I36vdLAUFSjQJ+TFmODng==" + }, + "finalhandler": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", + "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", + "requires": { + "debug": "2.6.9", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "on-finished": "~2.3.0", + "parseurl": "~1.3.2", + "statuses": "~1.4.0", + "unpipe": "~1.0.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "forwarded": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", + "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" + }, + "fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" + }, "fs-minipass": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.5.tgz", @@ -145,6 +321,17 @@ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=" }, + "http-errors": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", + "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", + "requires": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.0", + "statuses": ">= 1.4.0 < 2" + } + }, "iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -180,6 +367,11 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.5.tgz", "integrity": "sha512-RZY5huIKCMRWDUqZlEi72f/lmXKMvuszcMBduliQ3nnWbx9X/ZBQO7DijMEYS9EhHBb2qacRUMtC7svLwe0lcw==" }, + "ipaddr.js": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.8.0.tgz", + "integrity": "sha1-6qM9bd16zo9/b+DJygRA5wZzix4=" + }, "is-fullwidth-code-point": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", @@ -193,6 +385,39 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE=" }, + "media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" + }, + "merge-descriptors": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", + "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" + }, + "methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" + }, + "mime": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", + "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" + }, + "mime-db": { + "version": "1.37.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.37.0.tgz", + "integrity": "sha512-R3C4db6bgQhlIhPU48fUtdVmKnflq+hRdad7IyKhtFj06VPNVdk2RhiYL3UjQIlso8L+YxAtFkobT0VK+S/ybg==" + }, + "mime-types": { + "version": "2.1.21", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.21.tgz", + "integrity": "sha512-3iL6DbwpyLzjR3xHSFNFeb9Nz/M8WDkX33t1GFQnFOllWk8pOrh/LSrB5OXlnlW5P9LH73X6loW/eogc+F5lJg==", + "requires": { + "mime-db": "~1.37.0" + } + }, "minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", @@ -251,6 +476,11 @@ "sax": "^1.2.4" } }, + "negotiator": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", + "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" + }, "node-pre-gyp": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.11.0.tgz", @@ -312,6 +542,14 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, "once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", @@ -339,16 +577,66 @@ "os-tmpdir": "^1.0.0" } }, + "parseurl": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", + "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" + }, "path-is-absolute": { "version": "1.0.1", "resolved": "http://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=" }, + "path-to-regexp": { + "version": "0.1.7", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", + "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" + }, "process-nextick-args": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==" }, + "proxy-addr": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.4.tgz", + "integrity": "sha512-5erio2h9jp5CHGwcybmxmVqHmnCBZeewlfJ0pex+UW7Qny7OOZXTtH56TGNyBizkgiOwhJtMKrVzDTeKcySZwA==", + "requires": { + "forwarded": "~0.1.2", + "ipaddr.js": "1.8.0" + } + }, + "qs": { + "version": "6.5.2", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", + "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==" + }, + "range-parser": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", + "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" + }, + "raw-body": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.3.tgz", + "integrity": "sha512-9esiElv1BrZoI3rCDuOuKCBRbuApGGaDPQfjSflGxdy4oyzqghxu6klEkkVIvBje+FF0BX9coEv8KqW6X/7njw==", + "requires": { + "bytes": "3.0.0", + "http-errors": "1.6.3", + "iconv-lite": "0.4.23", + "unpipe": "1.0.0" + }, + "dependencies": { + "iconv-lite": { + "version": "0.4.23", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.23.tgz", + "integrity": "sha512-neyTUVFtahjf0mB3dZT77u+8O0QB89jFdnBkd5P1JgYPbPaia3gXXOVL2fq8VyU2gMMD7SaN7QukTB/pmXYvDA==", + "requires": { + "safer-buffer": ">= 2.1.2 < 3" + } + } + } + }, "rc": { "version": "1.2.8", "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", @@ -409,16 +697,64 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, + "send": { + "version": "0.16.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", + "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", + "requires": { + "debug": "2.6.9", + "depd": "~1.1.2", + "destroy": "~1.0.4", + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "0.5.2", + "http-errors": "~1.6.2", + "mime": "1.4.1", + "ms": "2.0.0", + "on-finished": "~2.3.0", + "range-parser": "~1.2.0", + "statuses": "~1.4.0" + }, + "dependencies": { + "statuses": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", + "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" + } + } + }, + "serve-static": { + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", + "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", + "requires": { + "encodeurl": "~1.0.2", + "escape-html": "~1.0.3", + "parseurl": "~1.3.2", + "send": "0.16.2" + } + }, "set-blocking": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=" }, + "setprototypeof": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", + "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" + }, "signal-exit": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz", "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=" }, + "statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=" + }, "string-width": { "version": "1.0.2", "resolved": "http://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", @@ -464,11 +800,35 @@ "yallist": "^3.0.2" } }, + "type-is": { + "version": "1.6.16", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", + "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", + "requires": { + "media-typer": "0.3.0", + "mime-types": "~2.1.18" + } + }, + "unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=" }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" + }, "wide-align": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", diff --git a/package.json b/package.json index 568d182..753d62f 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "author": "Evert \"Diamond\" Prants ", "license": "MIT", "dependencies": { - "canvas": "^2.2.0" + "body-parser": "^1.18.3", + "canvas": "^2.2.0", + "express": "^4.16.4", + "express-async-errors": "^3.1.1" } }