@gibme/tablo.tv
Version:
API interface for interacting with a Tablo TV device
228 lines • 9.21 kB
JavaScript
;
// 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