@jw988/unifi-access
Version:
A soon-to-be-almost-complete implementation of the UniFi Access API.
144 lines • 6.2 kB
JavaScript
/* Copyright(C) 2019-2025, HJD (https://github.com/hjdhjd). All rights reserved.
*
* ufa.ts: UniFi Access API command line utility.
*/
import { existsSync, readFileSync } from "fs";
import { AccessApi } from "../index.js";
import { homedir } from "os";
import util from "util";
// Create a new Access API instance.
const ufa = new AccessApi();
// 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 = ["ufa.json", homedir() + "/.ufa.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 ./ufa.json or ~/.ufa.json. Credentials must be in JSON form with properties for controller, username, and password defined.");
usage();
}
// Login to the Access controller.
if (!(await ufa.login(config.controller, config.username, config.password))) {
log.error("Invalid login credentials.");
process.exit(0);
}
;
// Bootstrap the controller.
if (!(await ufa.getBootstrap())) {
log.error("Unable to bootstrap the Access 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(ufa.bootstrap, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0));
break;
// Output the controller endpoint.
case "controller":
// Output the controller JSON and we're done.
process.stdout.write(util.inspect(ufa.controller, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0));
break;
// Output the devices.
case "devices":
// Output the devices JSON and we're done.
process.stdout.write(util.inspect(ufa.devices, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0));
break;
// Output the doors.
case "doors":
// Output the doors JSON and we're done.
process.stdout.write(util.inspect(ufa.doors, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0));
break;
// Output realtime events.
case "events":
ufa.on("message", (packet) => {
if (process.argv.length === 5) {
if (!["event", "event_object_id"].includes(process.argv[3])) {
return;
}
if (packet[process.argv[3]]?.toLowerCase() !== process.argv[4]?.toLowerCase()) {
return;
}
}
log.info(util.inspect(packet, { colors: true, depth: null, sorted: true }));
});
break;
// Output the floors.
case "floors":
// Output the floors JSON and we're done.
process.stdout.write(util.inspect(ufa.floors, { colors: true, depth: null, sorted: true }) + "\n", () => process.exit(0));
break;
// Restart devices.
case "restart":
if (process.argv[3] !== "devices") {
usage();
}
// Restart every device.
for (const device of ufa.devices?.filter(x => !x.is_rebooting && x.is_connected) ?? []) {
//eslint-disable-next-line no-await-in-loop
const response = await ufa.retrieve(ufa.getApiEndpoint("device") + "/" + device.unique_id + "/restart", { body: JSON.stringify({}), method: "PUT" });
if (!response?.ok) {
log.error("%s: unable to reboot: %s", ufa.getDeviceName(device), response);
break;
}
log.info("%s: restarted.", ufa.getDeviceName(device));
}
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 controller", process.argv[1]);
log.error("Usage: %s devices", process.argv[1]);
log.error("Usage: %s doors", process.argv[1]);
log.error("Usage: %s events [event | event_object_id] [value] (all parameters are case-sensitive)", process.argv[1]);
log.error("Usage: %s floors", process.argv[1]);
log.error("Usage: %s restart devices", 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 (ufa.bootstrap) {
if (ufa.devices?.length) {
log.error("Devices:");
ufa.devices.map(x => log.error(" %s => %s", (x.alias || x.name) ?? x.display_model, x.unique_id));
}
}
process.exit(1);
}
//# sourceMappingURL=ufa.js.map