UNPKG

@gibme/tablo.tv

Version:

API interface for interacting with a Tablo TV device

228 lines 9.21 kB
"use strict"; // Copyright (c) 2025, Brandon Lehmann <brandonlehmann@gmail.com> // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } return new (P || (P = Promise))(function (resolve, reject) { function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } step((generator = generator.apply(thisArg, _arguments || [])).next()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.TabloAPI = void 0; const fetch_1 = __importDefault(require("@gibme/fetch")); const uuid_1 = require("uuid"); const crypto_1 = require("crypto"); const logger_1 = __importDefault(require("@gibme/logger")); class TabloAPI { /** * Constructs a new instance of the base API to interact with a Tablo device * @param hostOrUri * @param options */ constructor(hostOrUri, options) { var _a, _b, _c, _d, _e; var _f, _g, _h, _j, _k; this.options = options; this.timeout = 2000; (_a = (_f = this.options).ssl) !== null && _a !== void 0 ? _a : (_f.ssl = false); (_b = (_g = this.options).device_id) !== null && _b !== void 0 ? _b : (_g.device_id = (0, uuid_1.v4)()); (_c = (_h = this.options).timeout) !== null && _c !== void 0 ? _c : (_h.timeout = 2000); (_d = (_j = this.options).request_logging) !== null && _d !== void 0 ? _d : (_j.request_logging = false); (_e = (_k = this.options).port) !== null && _e !== void 0 ? _e : (_k.port = 8887); if (hostOrUri.includes('://')) { if (hostOrUri.endsWith('/')) { hostOrUri = hostOrUri.slice(0, -1); } this.base_uri = hostOrUri; this.options.ssl = this.base_uri.startsWith('https'); } else { this.base_uri = `${this.options.ssl ? 'https' : 'http'}://${hostOrUri}:${this.options.port}`; } this.keys = { access_key: this.options.access_key, secret_key: this.options.secret_key }; this.device_id = this.options.device_id; this.timeout = this.options.timeout; } /** * Calculates the end time based upon the specified start time and duration * @param start_time * @param duration * @protected */ calculate_endtime(start_time, duration) { const date = new Date(start_time).getTime(); return new Date(date + (duration * 1000)).toISOString(); } /** * Returns the current hour timestamps * @protected */ get currentHour() { const now = Math.floor((new Date()).getTime() / 1000) * 1000; const start = Math.floor(now / (60 * 60 * 1000)) * 60 * 60 * 1000; const end = start + (60 * 60 * 1000); return { now, start, end }; } /** * Batch operations are much faster than a bunch of single operations. * For example, instead of making 50 requests for the first 50 recordings * returned by Recordings - Get Airings, you can take those 50 paths * and make 1 request to /batch to receive all the same data. * @param endpoints * @param timeout * @protected */ batch(endpoints_1) { return __awaiter(this, arguments, void 0, function* (endpoints, timeout = this.timeout) { var _a; try { return (_a = yield this.post('/batch', undefined, endpoints, timeout)) !== null && _a !== void 0 ? _a : {}; } catch (_b) { return {}; } }); } /** * Performs a DELETE request against the Tablo device * @param endpoint * @param params * @param timeout * @protected */ delete(endpoint_1) { return __awaiter(this, arguments, void 0, function* (endpoint, params = {}, timeout = this.timeout) { const response = yield this.execute('DELETE', endpoint, params, undefined, undefined, timeout); return response.ok; }); } /** * Performs a GET request against the Tablo device * @param endpoint * @param params * @param timeout * @param json * @protected */ get(endpoint_1) { return __awaiter(this, arguments, void 0, function* (endpoint, params = {}, timeout = this.timeout, json = true) { const response = yield this.execute('GET', endpoint, params, undefined, undefined, timeout); if (response.ok) { if (json) { return response.json(); } else { return yield response.text(); } } }); } /** * Performs a PUT request against the Tablo device * @param endpoint * @param params * @param payload * @param timeout * @param json * @protected */ post(endpoint_1) { return __awaiter(this, arguments, void 0, function* (endpoint, params = {}, payload, timeout = this.timeout, json = true) { const response = yield this.execute('POST', endpoint, params, payload, undefined, timeout); if (response.ok) { if (json) { return response.json(); } else { return yield response.text(); } } }); } /** * Executes an API call to the Tablo device * @param method * @param endpoint * @param params * @param payload * @param keys * @param timeout * @private */ execute(method_1, endpoint_1) { return __awaiter(this, arguments, void 0, function* (method, endpoint, params = {}, payload, keys = this.keys, timeout = this.timeout) { const headers = this.generateAuthHeader(method, endpoint, payload ? JSON.stringify(payload) : undefined, keys); const qs = new URLSearchParams(); for (const [key, value] of Object.entries(params)) { qs.set(key, value !== null && value !== void 0 ? value : ''); } let url = `${this.base_uri}${endpoint}`; if (Object.entries(params).length > 0) { url += `?${qs.toString()}`; } if (this.options.request_logging) { logger_1.default.debug('%s %s %s %s', method, JSON.stringify(headers), url, payload ? JSON.stringify(payload) : ''); } return (0, fetch_1.default)(url, { headers, json: (method === 'PATCH' || method === 'POST' || method === 'PUT') ? payload : undefined, method, timeout }); }); } /** * Generates the authentication header required for some of the Tablo device API calls * @param method * @param path * @param body * @param keys * @private */ generateAuthHeader(method, path, body = '', keys = this.keys) { const date = (new Date()).toUTCString(); const body_hash = body ? (0, crypto_1.createHash)('md5') .update(body) .digest('hex') : ''; const message = `${method}\n${path}\n${body_hash}\n${date}`; const hmac_signature = (0, crypto_1.createHmac)('md5', keys.secret_key) .update(message) .digest('hex') .toUpperCase(); return { Authorization: `tablo:${keys.access_key}:${hmac_signature}`, Date: date }; } } exports.TabloAPI = TabloAPI; exports.default = TabloAPI; //# sourceMappingURL=tablo_api.js.map