@inner-dj/server 0.0.3

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.
Files changed (57) hide show
  1. package/LICENSE +21 -0
  2. package/assets/css/styles.css +3 -0
  3. package/assets/js/entry.js +1 -0
  4. package/assets/js/index.global.js +1 -0
  5. package/assets/js/otplib.js +1 -0
  6. package/assets/js/qr-creator.min.js +1 -0
  7. package/assets/js/qr.js +1 -0
  8. package/assets/js/song-entry.js +1 -0
  9. package/assets/js/totp.js +1 -0
  10. package/assets/js/urls.js +1 -0
  11. package/lib/src/browser/entry.d.ts +1 -0
  12. package/lib/src/browser/entry.js +62 -0
  13. package/lib/src/browser/entry.js.map +1 -0
  14. package/lib/src/browser/index.global.js +10 -0
  15. package/lib/src/browser/otplib.js +10 -0
  16. package/lib/src/browser/qr-creator.min.js +28 -0
  17. package/lib/src/browser/qr.js +1636 -0
  18. package/lib/src/browser/song-entry.d.ts +1 -0
  19. package/lib/src/browser/song-entry.js +44 -0
  20. package/lib/src/browser/song-entry.js.map +1 -0
  21. package/lib/src/browser/totp.d.ts +1 -0
  22. package/lib/src/browser/totp.js +46 -0
  23. package/lib/src/browser/totp.js.map +1 -0
  24. package/lib/src/browser/urls.d.ts +1 -0
  25. package/lib/src/browser/urls.js +15 -0
  26. package/lib/src/browser/urls.js.map +1 -0
  27. package/lib/src/config.d.ts +18 -0
  28. package/lib/src/config.js +51 -0
  29. package/lib/src/config.js.map +1 -0
  30. package/lib/src/index.d.ts +3 -0
  31. package/lib/src/index.js +26 -0
  32. package/lib/src/index.js.map +1 -0
  33. package/lib/src/main.d.ts +2 -0
  34. package/lib/src/main.js +5 -0
  35. package/lib/src/main.js.map +1 -0
  36. package/lib/src/receivers/ireceiver.d.ts +17 -0
  37. package/lib/src/receivers/ireceiver.js +18 -0
  38. package/lib/src/receivers/ireceiver.js.map +1 -0
  39. package/lib/src/receivers/smtp-receiver.d.ts +10 -0
  40. package/lib/src/receivers/smtp-receiver.js +38 -0
  41. package/lib/src/receivers/smtp-receiver.js.map +1 -0
  42. package/lib/src/session-store-migrations.d.ts +2 -0
  43. package/lib/src/session-store-migrations.js +17 -0
  44. package/lib/src/session-store-migrations.js.map +1 -0
  45. package/lib/src/session-store.d.ts +16 -0
  46. package/lib/src/session-store.js +40 -0
  47. package/lib/src/session-store.js.map +1 -0
  48. package/lib/src/web.d.ts +21 -0
  49. package/lib/src/web.js +282 -0
  50. package/lib/src/web.js.map +1 -0
  51. package/package.json +61 -0
  52. package/views/admin.ejs +29 -0
  53. package/views/footer.ejs +7 -0
  54. package/views/head.ejs +24 -0
  55. package/views/index.ejs +28 -0
  56. package/views/instructions.ejs +6 -0
  57. package/views/landing.ejs +30 -0
package/lib/src/web.js ADDED
@@ -0,0 +1,282 @@
1
+ import crypto from 'crypto';
2
+ import jwt from 'jsonwebtoken';
3
+ import express from 'express';
4
+ import expressWs from 'express-ws';
5
+ import session from 'express-session';
6
+ import getYouTubeID from 'get-youtube-id-ng';
7
+ import { verify } from "otplib";
8
+ import { encodePayload } from '@inner-dj/common';
9
+ import { SessionStore } from './session-store.js';
10
+ export class Web {
11
+ config;
12
+ _webserver = null;
13
+ _clients = {};
14
+ _app = null;
15
+ _guides = [];
16
+ constructor(config) {
17
+ this.config = config;
18
+ }
19
+ initialize = async () => {
20
+ const config = this.config;
21
+ const app = this._app = express();
22
+ const wsApp = expressWs(app).app;
23
+ app.set('trust proxy', config.trustProxy);
24
+ app.set('view engine', 'ejs');
25
+ app.set('view options', { outputFunctionName: 'echo' });
26
+ app.use((_, res, next) => {
27
+ res.set('X-Robots-Tag', 'noindex, nofollow');
28
+ next && next();
29
+ });
30
+ app.use('/assets', express.static('assets', { maxAge: '30 days' }));
31
+ app.use((_req, res, next) => {
32
+ crypto.randomBytes(32, (err, randomBytes) => {
33
+ if (err) {
34
+ console.error(err);
35
+ next(err);
36
+ }
37
+ else {
38
+ res.locals['cspNonce'] = randomBytes.toString("hex");
39
+ next();
40
+ }
41
+ });
42
+ });
43
+ app.get('/healthcheck', (_req, res) => {
44
+ res.send('healthy');
45
+ });
46
+ wsApp.ws('/sock', async (ws, req) => {
47
+ const clientInfo = {
48
+ ws,
49
+ clientId: null,
50
+ nowPlaying: null,
51
+ };
52
+ ws.on('message', msg => {
53
+ clearTimeout(timeout);
54
+ const decoded = jwt.verify(msg.toString(), config.jwtSecret);
55
+ const payload = decoded;
56
+ if (!clientInfo.clientId && decoded && payload?.type === 'reg' && payload.body) {
57
+ console.log(`Connection established. Registered ${payload.body}`);
58
+ const key = payload.body;
59
+ clientInfo.clientId = key;
60
+ this._clients[key] === clientInfo || this._clients[key]?.ws?.close();
61
+ this._clients[key] = clientInfo;
62
+ ws.on('close', () => {
63
+ if (this._clients[key] === clientInfo) {
64
+ delete this._clients[key];
65
+ }
66
+ });
67
+ const ack = {
68
+ type: 'ack'
69
+ };
70
+ ws.send(encodePayload(ack, config.jwtSecret));
71
+ return;
72
+ }
73
+ else if (clientInfo.clientId && decoded && payload?.type === 'nowPlaying' && payload.body) {
74
+ console.log(`client ${clientInfo.clientId} playing ${clientInfo.nowPlaying = payload.body}`);
75
+ return;
76
+ }
77
+ ws.close();
78
+ });
79
+ console.debug(`Got websocket connection from ${req.ip}`);
80
+ const timeout = setTimeout(() => {
81
+ console.debug(`Client@${req.ip} took too long to identify itself. Closing.`);
82
+ ws.close();
83
+ }, 5000);
84
+ });
85
+ const db = new SessionStore(config.db);
86
+ const sessionMiddleware = session({
87
+ name: 'sessionId',
88
+ secret: config.sessionSecret,
89
+ resave: true,
90
+ saveUninitialized: false,
91
+ store: db,
92
+ cookie: {
93
+ maxAge: config.cookieTTL,
94
+ httpOnly: true,
95
+ secure: config.secure
96
+ }
97
+ });
98
+ app.get('/logout', sessionMiddleware, (req, res) => {
99
+ req.session.destroy((err) => {
100
+ if (err) {
101
+ console.error(err);
102
+ }
103
+ res.redirect('/');
104
+ });
105
+ });
106
+ app.post('/', sessionMiddleware, express.json(), async (req, res) => {
107
+ const token = req.body?.['code'];
108
+ if (typeof token !== 'string') {
109
+ res.status(400).send({
110
+ status: 'bad',
111
+ reason: '`code` is required'
112
+ });
113
+ return;
114
+ }
115
+ if (!config.public && (await verify({
116
+ secret: config.userOtp,
117
+ token,
118
+ epochTolerance: [45, 15],
119
+ }))?.valid) {
120
+ req.session.admin = false;
121
+ req.session.save(err => {
122
+ if (err) {
123
+ console.error(err);
124
+ res.send({
125
+ status: 'bad',
126
+ reason: 'session'
127
+ });
128
+ return;
129
+ }
130
+ res.send({
131
+ status: 'ok',
132
+ destination: '/landing'
133
+ });
134
+ });
135
+ return;
136
+ }
137
+ if ((await verify({
138
+ secret: config.adminOtp,
139
+ token,
140
+ epochTolerance: 10,
141
+ }))?.valid) {
142
+ req.session.admin = true;
143
+ req.session.save(err => {
144
+ if (err) {
145
+ console.error(err);
146
+ res.send({
147
+ status: 'bad',
148
+ reason: 'session'
149
+ });
150
+ return;
151
+ }
152
+ res.send({
153
+ status: 'ok',
154
+ destination: '/admin'
155
+ });
156
+ });
157
+ return;
158
+ }
159
+ res.status(401).send({
160
+ status: 'bad',
161
+ reason: 'bad token'
162
+ });
163
+ });
164
+ app.get('/', sessionMiddleware, (req, res) => {
165
+ if (!req.query?.['override']) {
166
+ if (config.public || req.session.admin === false) {
167
+ res.redirect('/landing');
168
+ return;
169
+ }
170
+ if (req.session.admin === true) {
171
+ res.redirect('/admin');
172
+ return;
173
+ }
174
+ }
175
+ res.render('index', {
176
+ session,
177
+ page: {
178
+ title: 'Inner DJ',
179
+ titlesuffix: 'Inner DJ',
180
+ description: 'Request a song'
181
+ }
182
+ });
183
+ });
184
+ const requireAdmin = (req, res, next) => {
185
+ if (req.session.admin !== true) {
186
+ res.redirect('/');
187
+ return;
188
+ }
189
+ next();
190
+ };
191
+ const requireAuth = (req, res, next) => {
192
+ if (!config.public && req.session.admin === undefined) {
193
+ res.redirect('/');
194
+ return;
195
+ }
196
+ next();
197
+ };
198
+ app.get('/landing', sessionMiddleware, requireAuth, (_req, res) => {
199
+ res.render('landing', {
200
+ guides: this._guides.join('\n'),
201
+ page: {
202
+ title: 'Request a Song',
203
+ titlesuffix: 'Inner DJ',
204
+ description: 'Request a song'
205
+ }
206
+ });
207
+ });
208
+ app.post('/landing', sessionMiddleware, requireAuth, express.json(), async (req, res) => {
209
+ const url = req.body?.['url'];
210
+ if (!url || typeof url !== 'string') {
211
+ res.status(400).send({
212
+ status: 'bad',
213
+ reason: '`url` is required'
214
+ });
215
+ return;
216
+ }
217
+ if (await this.sendPayload({
218
+ type: 'queue',
219
+ body: url
220
+ })) {
221
+ res.send({
222
+ status: 'ok'
223
+ });
224
+ return;
225
+ }
226
+ res.status(400).send({
227
+ status: 'bad',
228
+ reason: 'must be a YouTube URL'
229
+ });
230
+ });
231
+ app.get('/admin', sessionMiddleware, requireAdmin, (_req, res) => {
232
+ res.render('admin', {
233
+ guides: this._guides.join('\n'),
234
+ page: {
235
+ title: 'How to Request a Song',
236
+ titlesuffix: 'Inner DJ',
237
+ description: 'Instructions for requesting a song'
238
+ }
239
+ });
240
+ });
241
+ app.get('/admin/controls', sessionMiddleware, requireAdmin, (_req, res) => {
242
+ res.send('controls');
243
+ });
244
+ app.get('/admin/token', sessionMiddleware, requireAdmin, (_req, res) => {
245
+ res.send({
246
+ token: config.userOtp
247
+ });
248
+ });
249
+ this._webserver = app.listen(config.port, () => console.log(`running on port ${config.port}`));
250
+ };
251
+ registerRoute = (method, route, handler) => {
252
+ console.debug(`Registered route to ${route} via ${method.toUpperCase()} requests`);
253
+ this._app?.[method](route, handler);
254
+ };
255
+ registerGuide = (html) => {
256
+ return this._guides.push(`<div class="block">${html}</div>`);
257
+ };
258
+ sendPayload = async (payload) => {
259
+ const promises = [];
260
+ const p = encodePayload(payload, this.config.jwtSecret);
261
+ if (payload.type === 'queue' && !getYouTubeID(payload.body)) {
262
+ return false;
263
+ }
264
+ for (let id in this._clients) {
265
+ promises.push(new Promise((resolve, reject) => {
266
+ this._clients[id]?.ws?.send(p, (err) => {
267
+ if (err) {
268
+ reject(err);
269
+ return;
270
+ }
271
+ resolve();
272
+ });
273
+ }));
274
+ }
275
+ await Promise.all(promises);
276
+ return true;
277
+ };
278
+ close = () => {
279
+ this._webserver?.close();
280
+ };
281
+ }
282
+ //# sourceMappingURL=web.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"web.js","sourceRoot":"","sources":["../../src/web.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,61 @@
1
+ {
2
+ "name": "@inner-dj/server",
3
+ "version": "0.0.3",
4
+ "description": "server component of inner-dj",
5
+ "repository": {
6
+ "type": "git",
7
+ "url": "https://codeberg.org/sanin/inner-dj.git"
8
+ },
9
+ "license": "MIT",
10
+ "author": "Cory Sanin",
11
+ "type": "module",
12
+ "exports": {
13
+ ".": {
14
+ "types": "./lib/src/index.d.ts",
15
+ "import": "./lib/src/index.js"
16
+ }
17
+ },
18
+ "main": "./lib/src/index.js",
19
+ "types": "./lib/src/index.d.ts",
20
+ "bin": {
21
+ "inner-dj-server": "./lib/src/main.js"
22
+ },
23
+ "files": [
24
+ "assets/",
25
+ "views/",
26
+ "lib/src",
27
+ "!lib/**/*.js.map"
28
+ ],
29
+ "dependencies": {
30
+ "@inner-dj/discord-receiver": "0.0.3",
31
+ "@inner-dj/twilio-receiver": "0.0.3",
32
+ "better-sqlite3": "^12.10.0",
33
+ "bulma": "^1.0.4",
34
+ "ejs": "^5.0.2",
35
+ "express": "^5.2.1",
36
+ "express-session": "^1.19.0",
37
+ "express-ws": "^5.0.2",
38
+ "get-youtube-id-ng": "1.0.1",
39
+ "jsonwebtoken": "9.0.3",
40
+ "otplib": "^13.4.0",
41
+ "qr-creator": "^1.0.0",
42
+ "yaml": "^2.9.0",
43
+ "@inner-dj/common": "0.0.3"
44
+ },
45
+ "devDependencies": {
46
+ "@sindresorhus/tsconfig": "8.1.0",
47
+ "@types/better-sqlite3": "^7.6.13",
48
+ "@types/express": "^5.0.6",
49
+ "@types/express-session": "^1.19.0",
50
+ "@types/express-ws": "^3.0.6",
51
+ "@types/jsonwebtoken": "^9.0.10",
52
+ "@types/node": "^24.12.4",
53
+ "@types/ws": "^8.18.1",
54
+ "forking-build-shit": "1.1.3",
55
+ "typescript": "^6.0.3"
56
+ },
57
+ "scripts": {
58
+ "build": "tsc && chmod +x lib/src/main.js && cp node_modules/otplib/dist/index.global.js scripts/otplib.js && cp node_modules/qr-creator/dist/qr-creator.min.js scripts/ && build-shit",
59
+ "start": "./lib/src/main.js"
60
+ }
61
+ }
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3
+
4
+ <head>
5
+ <%- include("head", locals) %>
6
+ </head>
7
+
8
+ <body class="preload">
9
+ <div class="mt-5">
10
+ <div class="container">
11
+ <h1 class="title is-1">Inner DJ</h1>
12
+ <div class="box is-size-5">
13
+ <h2 class="title is-2">Request a Song</h2>
14
+ <div class="has-text-centered">
15
+ <div id="qr-container" class="mb-4"></div>
16
+ <p>Or go to <span class="has-text-weight-bold populate-url with-trailing-slash">this site</span> and enter <span id="token" class="has-text-weight-bold is-family-monospace">...</span></p>
17
+ </div>
18
+ </div>
19
+ <%- include("instructions", locals) %>
20
+ <%- include("footer", locals) %>
21
+ </div>
22
+ </div>
23
+ <script src="/assets/js/otplib.js?v1" nonce="<%= cspNonce %>"></script>
24
+ <script src="/assets/js/qr-creator.min.js?v1" nonce="<%= cspNonce %>"></script>
25
+ <script src="/assets/js/totp.js?v1" nonce="<%= cspNonce %>"></script>
26
+ <script src="/assets/js/urls.js?v1" nonce="<%= cspNonce %>"></script>
27
+ </body>
28
+
29
+ </html>
@@ -0,0 +1,7 @@
1
+ <div class="block has-text-centered">
2
+ <p>
3
+ <a href="https://codeberg.org/sanin/inner-dj">Inner DJ</a> by <a href="https://sanin.dev/">Cory Sanin</a>.
4
+ The source code is licensed
5
+ <a href="https://opensource.org/license/mit">MIT</a>.
6
+ </p>
7
+ </div>
package/views/head.ejs ADDED
@@ -0,0 +1,24 @@
1
+ <title><%= page.title %> | <%= page.titlesuffix %></title>
2
+ <meta charset="utf-8" />
3
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
4
+ <meta property="og:title" content="<%= page.title%> | <%= page.titlesuffix%>" />
5
+ <% if (page.description) { %>
6
+ <meta name="description" content="<%= page.description%>" />
7
+ <meta property="og:description" content="<%= page.description%>" />
8
+ <% } %><% if (page.imgSrc?.src) { %>
9
+ <meta name="thumbnail" content="<%= locals.host || ''%><%= page.imgSrc.src%>"/>
10
+ <meta property="og:image" content="<%= locals.host || ''%><%= page.imgSrc.src%>"/>
11
+ <meta property="og:image:alt" content="Inner DJ song request platform"/>
12
+ <link rel="image_src" href="<%= locals.host || ''%><%= page.imgSrc.src%>"/>
13
+ <% } %><% if (page.imgSrc?.width) { %>
14
+ <meta property="og:image:width" content="<%= page.imgSrc.width%>" />
15
+ <% } %><% if (page.imgSrc?.height) { %>
16
+ <meta property="og:image:height" content="<%= page.imgSrc.height%>" />
17
+ <% } %>
18
+ <link rel="shortcut icon" href="/assets/images/svg/innerdj.svg">
19
+ <link rel="stylesheet" href="/assets/css/styles.css?v2.1">
20
+ <script nonce="<%= cspNonce %>">
21
+ document.addEventListener("DOMContentLoaded", function() {
22
+ document.body.classList.remove('preload');
23
+ });
24
+ </script>
@@ -0,0 +1,28 @@
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3
+
4
+ <head>
5
+ <%- include("head", locals) %>
6
+ </head>
7
+
8
+ <body class="preload">
9
+ <div class="mt-5">
10
+ <div class="container">
11
+ <h1 class="title is-1">Inner DJ</h1>
12
+ <div class="box">
13
+ <h2 class="title is-2">Enter Token</h2>
14
+ <div class="grid grid-3">
15
+ <label for="token">Token</label>
16
+ <input id="token" type="text" class="input" />
17
+ <button id="submit" class="button is-primary">
18
+ <span>Submit</span>
19
+ </button>
20
+ </div>
21
+ </div>
22
+ <%- include("footer", locals) %>
23
+ </div>
24
+ </div>
25
+ <script src="/assets/js/entry.js?v1" nonce="<%= cspNonce %>"></script>
26
+ </body>
27
+
28
+ </html>
@@ -0,0 +1,6 @@
1
+ <% if (locals?.guides) { %>
2
+ <div class="box is-size-5">
3
+ <h2 class="title is-2">Other Methods</h2>
4
+ <%- guides %>
5
+ </div>
6
+ <% } %>
@@ -0,0 +1,30 @@
1
+ <!DOCTYPE html>
2
+ <html xmlns="http://www.w3.org/1999/xhtml" lang="en">
3
+
4
+ <head>
5
+ <%- include("head", locals) %>
6
+ </head>
7
+
8
+ <body class="preload">
9
+ <div class="mt-5">
10
+ <div class="container">
11
+ <h1 class="title is-1">Inner DJ</h1>
12
+ <div class="box is-size-5">
13
+ <h2 class="title is-2">Add to the Queue</h2>
14
+ <div class="grid grid-3">
15
+ <label for="url">YouTube Link</label>
16
+ <input id="url" type="url" class="input" placeholder="https://youtu.be/ikqh8LAWDGM"/>
17
+ <button id="submit" class="button is-primary">
18
+ <span>Submit</span>
19
+ </button>
20
+ </div>
21
+ </div>
22
+ <%- include("instructions", locals) %>
23
+ <%- include("footer", locals) %>
24
+ </div>
25
+ </div>
26
+ <script src="/assets/js/song-entry.js?v1" nonce="<%= cspNonce %>"></script>
27
+ <script src="/assets/js/urls.js?v1" nonce="<%= cspNonce %>"></script>
28
+ </body>
29
+
30
+ </html>