unifi-protect
Version:
A complete implementation of the UniFi Protect API.
133 lines • 5.49 kB
JavaScript
/* Copyright(C) 2019-2025, HJD (https://github.com/hjdhjd). All rights reserved.
*
* ufp.ts: UniFi Protect API command line utility.
*/
import { ProtectApi } from "../index.js";
import { existsSync, readFileSync } from "fs";
import { homedir } from "os";
import util from "util";
// Create a new Protect API instance.
const ufp = new ProtectApi();
// Log utilities.
const log = {
/* eslint-disable no-console */
// eslint-disable-next-line @typescript-eslint/no-unused-vars
debug: (message, ...parameters) => { },
error: (message, ...parameters) => console.error(util.format(message, ...parameters)),
info: (message, ...parameters) => console.log(util.format(message, ...parameters)),
warn: (message, ...parameters) => console.log(util.format(message, ...parameters))
/* eslint-enable no-console */
};
// Read our credentials.
let config;
try {
// We look for credentials in the local directory as well as in the user's home directory.
const configFile = ["ufp.json", homedir() + "/.ufp.json"].find(path => existsSync(path));
if (!configFile) {
throw new Error;
}
// Credentials must be in JSON form with properties for the controller, username, and password.
config = JSON.parse(readFileSync(configFile, "utf8"));
if (!config.controller || !config.username || !config.password) {
throw new Error;
}
}
catch (e) {
// Inform the user we don't know what to connect to.
log.error("No credentials found in ./ufp.json or ~/.ufp.json. Credentials must be in JSON form with properties for controller, username, and password defined.");
usage();
}
// Login to the Protect controller.
if (!(await ufp.login(config.controller, config.username, config.password))) {
log.error("Invalid login credentials.");
process.exit(0);
}
;
// Bootstrap the controller.
if (!(await ufp.getBootstrap())) {
log.error("Unable to bootstrap the Protect controller.");
process.exit(0);
}
// Command line processing.
switch (process.argv.length) {
case 0:
case 1:
case 2:
usage();
break;
default:
switch (process.argv[2]?.toLowerCase()) {
// Output the bootstrap.
case "bootstrap":
// Output the bootstrap JSON and we're done.
process.stdout.write(util.inspect(ufp.bootstrap, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0));
break;
// Output realtime events.
case "events":
ufp.on("message", (packet) => {
if (process.argv.length === 5) {
if (packet.header[process.argv[3]]?.toLowerCase() !== process.argv[4]?.toLowerCase()) {
return;
}
}
log.info(util.inspect(packet, { colors: true, depth: null, sorted: true }));
});
break;
// Restart devices.
case "restart":
if (process.argv[3] !== "cameras") {
usage();
}
// Restart every camera.
for (const camera of ufp.bootstrap?.cameras.filter(x => !x.isRebooting && x.state === "CONNECTED") ?? []) {
//eslint-disable-next-line no-await-in-loop
const response = await ufp.retrieve(ufp.getApiEndpoint(camera.modelKey) + "/" + camera.id + "/reboot", { body: JSON.stringify({}), method: "POST" });
if (!response?.ok) {
log.error("%s: unable to reboot: %s", ufp.getDeviceName(camera), response);
break;
}
log.info("%s: restarted.", ufp.getDeviceName(camera));
}
process.exit(0);
break;
// Unknown command.
default:
usage();
break;
}
;
break;
}
// Usage information.
function usage() {
log.error("Usage: %s bootstrap", process.argv[1]);
log.error("Usage: %s events [action | id | modelKey | other_event_header_property] [value] (all parameters are case-sensitive)", process.argv[1]);
log.error("Usage: %s restart cameras", process.argv[1]);
log.error("");
// If we're bootstrapped, we also want to inform users what the various device identifiers are to make filtering events easier.
if (ufp.bootstrap) {
if (ufp.bootstrap.cameras.length) {
log.error("Cameras:");
ufp.bootstrap.cameras.map(x => log.error(" %s => %s", x.name ?? x.marketName, x.id));
}
if (ufp.bootstrap.chimes.length) {
log.error("Chimes:");
ufp.bootstrap.chimes.map(x => log.error(" %s => %s", x.name ?? x.marketName, x.id));
}
if (ufp.bootstrap.lights.length) {
log.error("Lights:");
ufp.bootstrap.lights.map(x => log.error(" %s => %s", x.name ?? x.marketName, x.id));
}
if (ufp.bootstrap.sensors.length) {
log.error("Sensors:");
ufp.bootstrap.sensors.map(x => log.error(" %s => %s", x.name ?? x.marketName, x.id));
}
if (ufp.bootstrap.viewers.length) {
log.error("Viewers:");
ufp.bootstrap.viewers.map(x => log.error(" %s => %s", x.name ?? x.marketName, x.id));
}
}
process.exit(1);
}
//# sourceMappingURL=ufp.js.map