mirror of
https://github.com/wkulhanek/bonob.git
synced 2025-12-21 17:33:29 +01:00
Ability to auto-discover sonos devices or find by using a seed ip
This commit is contained in:
@@ -1,4 +0,0 @@
|
|||||||
{
|
|
||||||
"require": "./register.js",
|
|
||||||
"reporter": "dot"
|
|
||||||
}
|
|
||||||
17
.nyrc.json
17
.nyrc.json
@@ -1,17 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@istanbuljs/nyc-config-typescript",
|
|
||||||
"include": [
|
|
||||||
"src/**/*.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules/"
|
|
||||||
],
|
|
||||||
"extension": [
|
|
||||||
".ts"
|
|
||||||
],
|
|
||||||
"reporter": [
|
|
||||||
"text-summary",
|
|
||||||
"html"
|
|
||||||
],
|
|
||||||
"report-dir": "./coverage"
|
|
||||||
}
|
|
||||||
4
jest.config.js
Normal file
4
jest.config.js
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
module.exports = {
|
||||||
|
preset: 'ts-jest',
|
||||||
|
testEnvironment: 'node',
|
||||||
|
};
|
||||||
10
package.json
10
package.json
@@ -18,19 +18,21 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/chai": "^4.2.14",
|
"@types/chai": "^4.2.14",
|
||||||
|
"@types/jest": "^26.0.20",
|
||||||
"@types/mocha": "^8.2.0",
|
"@types/mocha": "^8.2.0",
|
||||||
|
"@types/supertest": "^2.0.10",
|
||||||
"chai": "^4.2.0",
|
"chai": "^4.2.0",
|
||||||
"mocha": "^8.2.1",
|
"jest": "^26.6.3",
|
||||||
"nodemon": "^2.0.7",
|
"nodemon": "^2.0.7",
|
||||||
"nyc": "^15.1.0",
|
"supertest": "^6.1.3",
|
||||||
|
"ts-jest": "^26.4.4",
|
||||||
"ts-mockito": "^2.6.1",
|
"ts-mockito": "^2.6.1",
|
||||||
"ts-node": "^9.1.1"
|
"ts-node": "^9.1.1"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
"start": "node ./build/app.js",
|
|
||||||
"dev": "nodemon ./src/app.ts",
|
"dev": "nodemon ./src/app.ts",
|
||||||
"test": "nyc ./node_modules/.bin/_mocha 'tests/**/*.test.ts'",
|
"test": "jest",
|
||||||
"testw": "mocha --require ts-node/register --watch --watch-files tests/**/* tests/**/*.test.ts"
|
"testw": "mocha --require ts-node/register --watch --watch-files tests/**/* tests/**/*.test.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
30
src/app.ts
30
src/app.ts
@@ -1,26 +1,12 @@
|
|||||||
import express from "express";
|
import sonos from "./sonos";
|
||||||
import * as Eta from "eta";
|
import server from "./server";
|
||||||
|
|
||||||
// import { Navidrome } from "./music_service";
|
const PORT = process.env["PORT"] || 3000;
|
||||||
import makeSonos from "./sonos";
|
|
||||||
|
|
||||||
const PORT = 3000;
|
const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]));
|
||||||
|
|
||||||
makeSonos().then((sonos) => {
|
app.listen(PORT, () => {
|
||||||
const app = express();
|
console.info(`Listening on ${PORT}`);
|
||||||
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}`);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default app;
|
||||||
|
|||||||
22
src/server.ts
Normal file
22
src/server.ts
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
import express, { Express } from "express";
|
||||||
|
import * as Eta from "eta";
|
||||||
|
import { Sonos } from "./sonos";
|
||||||
|
|
||||||
|
function server(sonos: Sonos): Express {
|
||||||
|
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(),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
return app;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default server;
|
||||||
70
src/sonos.ts
70
src/sonos.ts
@@ -1,5 +1,4 @@
|
|||||||
import { SonosManager } from "@svrooij/sonos";
|
import { SonosManager, SonosDevice } from "@svrooij/sonos";
|
||||||
|
|
||||||
import logger from "./logger";
|
import logger from "./logger";
|
||||||
|
|
||||||
type Device = {
|
type Device = {
|
||||||
@@ -9,40 +8,49 @@ type Device = {
|
|||||||
port: number;
|
port: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
interface Sonos
|
export interface Sonos {
|
||||||
{
|
|
||||||
devices: () => Device[];
|
devices: () => Device[];
|
||||||
}
|
}
|
||||||
|
|
||||||
class RealSonos implements Sonos {
|
export const SONOS_DISABLED: 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: () => [],
|
devices: () => [],
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function (): Promise<Sonos> {
|
const asDevice = (sonosDevice: SonosDevice) => ({
|
||||||
|
name: sonosDevice.Name,
|
||||||
|
group: sonosDevice.GroupName || "",
|
||||||
|
ip: sonosDevice.Host,
|
||||||
|
port: sonosDevice.Port,
|
||||||
|
});
|
||||||
|
|
||||||
|
const setupDiscovery = (manager: SonosManager, sonosSeedHost?: string): Promise<boolean> => {
|
||||||
|
if (sonosSeedHost == undefined || sonosSeedHost == "") {
|
||||||
|
logger.info("Trying to auto discover sonos devices");
|
||||||
|
return manager.InitializeWithDiscovery(10);
|
||||||
|
} else {
|
||||||
|
logger.info(`Trying to discover sonos devices using seed ${sonosSeedHost}`);
|
||||||
|
return manager.InitializeFromDevice(sonosSeedHost);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export function autoDiscoverySonos(sonosSeedHost?: string): Sonos {
|
||||||
const manager = new SonosManager();
|
const manager = new SonosManager();
|
||||||
return manager
|
|
||||||
.InitializeWithDiscovery(10)
|
setupDiscovery(manager, sonosSeedHost).then((r) => {
|
||||||
.then((it) => (it ? new RealSonos(manager) : SonosDisabled))
|
if (r) logger.info({ devices: manager.Devices.map(asDevice) });
|
||||||
.catch((_) => {
|
else logger.warn("Failed to auto discover hosts!");
|
||||||
return SonosDisabled;
|
});
|
||||||
});
|
|
||||||
|
return {
|
||||||
|
devices: () => manager.Devices.map(asDevice),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function sonos(sonosSeedHost?: string): Sonos {
|
||||||
|
switch (sonosSeedHost) {
|
||||||
|
case "disabled":
|
||||||
|
return SONOS_DISABLED;
|
||||||
|
default:
|
||||||
|
return autoDiscoverySonos(sonosSeedHost);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +0,0 @@
|
|||||||
import { expect } from "chai";
|
|
||||||
|
|
||||||
describe("something", () => {
|
|
||||||
it("fails", () => {
|
|
||||||
expect(true).equal(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
46
tests/index.test.ts
Normal file
46
tests/index.test.ts
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
import request from "supertest";
|
||||||
|
import makeServer from "../src/server";
|
||||||
|
import { SONOS_DISABLED, Sonos } from "../src/sonos";
|
||||||
|
|
||||||
|
describe("index", () => {
|
||||||
|
describe("when sonos integration is disabled", () => {
|
||||||
|
const server = makeServer(SONOS_DISABLED);
|
||||||
|
|
||||||
|
describe("devices list", () => {
|
||||||
|
it("should be empty", async () => {
|
||||||
|
const res = await request(server).get("/").send();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(res.text).not.toMatch(/class=device/)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("when sonos integration is enabled", () => {
|
||||||
|
const fakeSonos: Sonos = {
|
||||||
|
devices: () => [{
|
||||||
|
name: "device1",
|
||||||
|
group: "group1",
|
||||||
|
ip: "172.0.0.1",
|
||||||
|
port: 4301
|
||||||
|
},{
|
||||||
|
name: "device2",
|
||||||
|
group: "group2",
|
||||||
|
ip: "172.0.0.2",
|
||||||
|
port: 4302
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
|
||||||
|
const server = makeServer(fakeSonos);
|
||||||
|
|
||||||
|
describe("devices list", () => {
|
||||||
|
it("should contain the devices returned from sonos", async () => {
|
||||||
|
const res = await request(server).get("/").send();
|
||||||
|
|
||||||
|
expect(res.status).toEqual(200);
|
||||||
|
expect(res.text).toMatch(/device1\s+\(172.0.0.1:4301\)/)
|
||||||
|
expect(res.text).toMatch(/device2\s+\(172.0.0.2:4302\)/)
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
<ul>
|
<ul>
|
||||||
<% it.devices.forEach(function(d){ %>
|
<% it.devices.forEach(function(d){ %>
|
||||||
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
<li class="device"><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
||||||
<% }) %>
|
<% }) %>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user