Add simple listing of sonos devices and basic Dockefile for running it

This commit is contained in:
simojenki
2021-01-29 12:34:02 +11:00
parent da61f079bc
commit 769a3e50d4
14 changed files with 935 additions and 45 deletions

3
.gitignore vendored
View File

@@ -1,3 +1,4 @@
.nyc_output
.vscode
build
node_modules
.nyc_output

28
Dockerfile Normal file
View File

@@ -0,0 +1,28 @@
FROM node:14.15-alpine as build
WORKDIR /bonob
COPY package.json .
COPY yarn.lock .
COPY tsconfig.json .
COPY src .
RUN yarn install && \
yarn build
FROM node:14.15-alpine
EXPOSE 3000
WORKDIR /bonob
COPY package.json .
COPY yarn.lock .
COPY --from=build /bonob/build/* ./
COPY web web
RUN yarn install --prod
CMD ["node", "./app.js"]

View File

@@ -1,2 +0,0 @@
"use strict";
console.log("Hello World!!!");

View File

@@ -6,24 +6,30 @@
"author": "simojenki <simojenki@users.noreply.github.com>",
"license": "GPL-3.0-only",
"dependencies": {
"@svrooij/sonos": "^2.3.0",
"@types/express": "^4.17.11",
"@types/node": "^14.14.22",
"axios": "^0.21.1",
"eta": "^1.12.1",
"express": "^4.17.1",
"typescript": "^4.1.3"
"ts-md5": "^1.2.7",
"typescript": "^4.1.3",
"winston": "^3.3.3"
},
"devDependencies": {
"@types/chai": "^4.2.14",
"@types/mocha": "^8.2.0",
"chai": "^4.2.0",
"ts-node": "^9.1.1",
"mocha": "^8.2.1",
"nodemon": "^2.0.7",
"nyc": "^15.1.0",
"ts-mockito": "^2.6.1"
"ts-mockito": "^2.6.1",
"ts-node": "^9.1.1"
},
"scripts": {
"build": "tsc",
"start": "node ./bin/app.js",
"dev": "ts-node ./src/app.ts",
"start": "node ./build/app.js",
"dev": "nodemon ./src/app.ts",
"test": "nyc ./node_modules/.bin/_mocha 'tests/**/*.test.ts'",
"testw": "mocha --require ts-node/register --watch --watch-files tests/**/* tests/**/*.test.ts"
}

View File

@@ -1,11 +1,26 @@
import {createServer} from './utils/server'
import express from "express";
import * as Eta from "eta";
createServer()
.then(server => {
server.listen(3000, () => {
console.info(`Listening on http://localhost:3000`)
})
})
.catch(err => {
console.error(`Error: ${err}`)
})
// import { Navidrome } from "./music_service";
import makeSonos from "./sonos";
const PORT = 3000;
makeSonos().then((sonos) => {
const app = express();
app.use(express.static("./web/public"));
app.engine("eta", Eta.renderFile);
app.set("view engine", "eta");
app.set("views", "./web/views");
app.get("/", (_, res) => {
res.render("index", {
devices: sonos.devices(),
});
});
app.listen(PORT, () => {
console.info(`Listening on ${PORT}`);
});
});

24
src/logger.ts Normal file
View File

@@ -0,0 +1,24 @@
import { createLogger, format, transports } from 'winston';
export function debugIt<T>(thing: T): T {
logger.debug(thing);
return thing;
}
const logger = createLogger({
level: 'debug',
format: format.combine(
format.timestamp({
format: 'YYYY-MM-DD HH:mm:ss'
}),
format.errors({ stack: true }),
format.splat(),
format.json()
),
defaultMeta: { service: 'bonob' },
transports: [
new transports.Console()
]
});
export default logger;

20
src/music_service.ts Normal file
View File

@@ -0,0 +1,20 @@
import axios from "axios";
import { Md5 } from "ts-md5/dist/md5";
const s = "foobar100";
const navidrome = process.env["BONOB_NAVIDROME_URL"];
const u = process.env["BONOB_USER"];
const t = Md5.hashStr(`${process.env["BONOB_PASSWORD"]}${s}`);
export class Navidrome {
ping = (): Promise<boolean> =>
axios
.get(
`${navidrome}/rest/ping.view?u=${u}&t=${t}&s=${s}&v=1.16.1.0&c=myapp`
)
.then((_) => true)
.catch((e) => {
console.log(e);
return false;
});
}

48
src/sonos.ts Normal file
View File

@@ -0,0 +1,48 @@
import { SonosManager } from "@svrooij/sonos";
import logger from "./logger";
type Device = {
name: string;
group: string;
ip: string;
port: number;
};
interface Sonos
{
devices: () => Device[];
}
class RealSonos implements Sonos {
manager: SonosManager;
constructor(manager: SonosManager) {
this.manager = manager;
}
devices = (): Device[] => {
const devices = this.manager.Devices.map((d) => ({
name: d.Name,
group: d.GroupName || "",
ip: d.Host,
port: d.Port,
}));
logger.debug({ devices })
return devices;
}
}
const SonosDisabled: Sonos = {
devices: () => [],
};
export default function (): Promise<Sonos> {
const manager = new SonosManager();
return manager
.InitializeWithDiscovery(10)
.then((it) => (it ? new RealSonos(manager) : SonosDisabled))
.catch((_) => {
return SonosDisabled;
});
}

View File

@@ -1,10 +0,0 @@
import express from 'express'
import {Express} from 'express-serve-static-core'
export async function createServer(): Promise<Express> {
const server = express()
server.get('/', (_, res) => {
res.send('Hello world!!!')
})
return server
}

View File

@@ -4,7 +4,7 @@
/* Basic Options */
// "incremental": true, /* Enable incremental compilation */
"target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"target": "ES6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */
"module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */
"lib": ["es2019"], /* Specify library files to be included in the compilation. */
// "allowJs": true, /* Allow javascript files to be compiled. */
@@ -14,8 +14,8 @@
// "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */
// "sourceMap": true, /* Generates corresponding '.map' file. */
// "outFile": "./", /* Concatenate and emit output to single file. */
"outDir": "./bin", /* Redirect output structure to the directory. */
"rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
"outDir": "./build", /* Redirect output structure to the directory. */
"rootDir": ".", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
// "composite": true, /* Enable project compilation */
// "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */
// "removeComments": true, /* Do not emit comments to output. */

6
web/views/devices.eta Normal file
View File

@@ -0,0 +1,6 @@
<ul>
<% it.devices.forEach(function(d){ %>
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
<% }) %>
</ul>

11
web/views/index.eta Normal file
View File

@@ -0,0 +1,11 @@
<% layout('./layout') %>
<div id="content">
<h1>bonob</h1>
<h2>Devices</h2>
<ul>
<% it.devices.forEach(function(d){ %>
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
<% }) %>
</ul>
</div>

9
web/views/layout.eta Normal file
View File

@@ -0,0 +1,9 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title><%= it.title || "bonob" %></title>
</head>
<body>
<%~ it.body %>
</body>
</html>

762
yarn.lock

File diff suppressed because it is too large Load Diff