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": {
|
||||
"@types/chai": "^4.2.14",
|
||||
"@types/jest": "^26.0.20",
|
||||
"@types/mocha": "^8.2.0",
|
||||
"@types/supertest": "^2.0.10",
|
||||
"chai": "^4.2.0",
|
||||
"mocha": "^8.2.1",
|
||||
"jest": "^26.6.3",
|
||||
"nodemon": "^2.0.7",
|
||||
"nyc": "^15.1.0",
|
||||
"supertest": "^6.1.3",
|
||||
"ts-jest": "^26.4.4",
|
||||
"ts-mockito": "^2.6.1",
|
||||
"ts-node": "^9.1.1"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"start": "node ./build/app.js",
|
||||
"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"
|
||||
}
|
||||
}
|
||||
|
||||
30
src/app.ts
30
src/app.ts
@@ -1,26 +1,12 @@
|
||||
import express from "express";
|
||||
import * as Eta from "eta";
|
||||
import sonos from "./sonos";
|
||||
import server from "./server";
|
||||
|
||||
// import { Navidrome } from "./music_service";
|
||||
import makeSonos from "./sonos";
|
||||
const PORT = process.env["PORT"] || 3000;
|
||||
|
||||
const PORT = 3000;
|
||||
const app = server(sonos(process.env["BONOB_SONOS_SEED_HOST"]));
|
||||
|
||||
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}`);
|
||||
});
|
||||
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";
|
||||
|
||||
type Device = {
|
||||
@@ -9,40 +8,49 @@ type Device = {
|
||||
port: number;
|
||||
};
|
||||
|
||||
interface Sonos
|
||||
{
|
||||
export 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 = {
|
||||
export const SONOS_DISABLED: Sonos = {
|
||||
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();
|
||||
return manager
|
||||
.InitializeWithDiscovery(10)
|
||||
.then((it) => (it ? new RealSonos(manager) : SonosDisabled))
|
||||
.catch((_) => {
|
||||
return SonosDisabled;
|
||||
});
|
||||
|
||||
setupDiscovery(manager, sonosSeedHost).then((r) => {
|
||||
if (r) logger.info({ devices: manager.Devices.map(asDevice) });
|
||||
else logger.warn("Failed to auto discover hosts!");
|
||||
});
|
||||
|
||||
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>
|
||||
<% it.devices.forEach(function(d){ %>
|
||||
<li><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
||||
<li class="device"><%= d.name %> (<%= d.ip %>:<%= d.port %>)</li>
|
||||
<% }) %>
|
||||
</ul>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user