philtv-js
Version:
A simple library to pair and interact with Philips TV
12 lines • 5.46 kB
JavaScript
Object.defineProperty(exports,Symbol.toStringTag,{value:`Module`});let e=require("node:net"),t=require("consola"),n=require("node:crypto"),r=require("urllib");var i=`2.2.0`;async function a(e,t,n,r={}){try{let i=`${t}/${e}`,a=await n.request(i,{...r});if(!(a.status>=200&&a.status<300))return{success:!1,error:Error(`Request failed with status ${a.status} (${a.url})`)};let o=a.data.toString();try{return{success:!0,data:JSON.parse(o)}}catch{return{success:!0,data:o}}}catch(e){return{success:!1,error:e instanceof Error?e:Error(String(e))}}}function o(e){var t;let n=new r.HttpClient({connect:{rejectUnauthorized:(e==null||(t=e.options)==null?void 0:t.rejectUnauthorized)??!1,timeout:1e3},defaultArgs:{digestAuth:`${e.user}:${e.password}`,contentType:`json`,headers:{}}});return{client:n,request:(t,r={})=>a(t,e.baseUrl,n,r)}}function s(){return new r.HttpClient({connect:{rejectUnauthorized:!1,timeout:1e3},defaultArgs:{contentType:`json`}})}function c(e){return{app_id:`gapp.id`,id:e,device_name:`MyName`,device_os:`Android`,app_name:`PhilTV-js APP`,type:`native`}}function l(e,t){let r=(0,n.createHmac)(`sha1`,e);return r.write(t),r.end(),r.read(`binary`).toString(`base64`)}async function u(e){let t=s();try{let{data:n}=await t.request(`${e}/system`,{dataType:`json`,timeout:2e3,signal:AbortSignal.timeout(2e3)}),r=n,i=r.api_version.Major,a=r.featuring.systemfeatures,o=a.secured_transport===`true`,s=a.pairing_type===`digest_auth_pairing`;return{success:!0,data:{apiVersion:i,systemFeatures:a,isSecureTransport:o,isGoodPairingType:s,isReady:o&&s}}}catch(e){return{success:!1,error:e instanceof Error?e:Error(String(e))}}}var d=class{tvBase;deviceId;apiUrls;deviceInformation;httpClients;startPairingResponse;credentials;apiVersion;pairingRequestUrl;constructor(e){this.tvBase=e,this.apiUrls={secure:`https://${this.tvBase.tvIp}:${this.tvBase.apiPort||1926}`},this.deviceId=(0,n.randomBytes)(16).toString(`hex`),this.deviceInformation=c(this.deviceId)}async init(){var e;let t=await u(this.apiUrls.secure);return t.success?t!=null&&(e=t.data)!=null&&e.isReady?(this.apiVersion=t.data.apiVersion,this.pairingRequestUrl=`${this.apiUrls.secure}/${this.apiVersion}/pair/request`,{success:!0,data:t.data}):{success:!1,error:Error(`TV is not ready`)}:{success:!1,error:t.error}}async startPairing(){try{let{data:e}=await s().request(this.pairingRequestUrl,{method:`POST`,data:{device:this.deviceInformation,scope:[`read`,`write`,`control`]},dataType:`json`}),t=e,n=t.error_id,r=t.error_text;if(n!==`SUCCESS`)return{success:!1,error:Error(`Failed to start pairing: ${r}`)};let i=t.timestamp,a=t.auth_key,c=t.timeout;return this.startPairingResponse={authTimestamp:i,authKey:a,timeout:c},this.credentials={user:this.deviceId,password:a},this.httpClients={digest:o({user:this.credentials.user,password:this.credentials.password,baseUrl:`${this.apiUrls.secure}/${this.apiVersion}`})},{success:!0,data:this.startPairingResponse}}catch(e){return{success:!1,error:e instanceof Error?e:Error(String(e))}}}async completePairing(e){if(!e)return{success:!1,error:Error(`Failed to complete pairing`)};if(this.startPairingResponse){let t=l(Buffer.from(`ZmVay1EQVFOaZhwQ4Kv81ypLAZNczV9sG4KkseXWn1NEk6cXmPKO/MCa9sryslvLCFMnNe4Z4CPXzToowvhHvA==`,`base64`),this.startPairingResponse.authTimestamp.toString()+e),n={device:this.deviceInformation,auth:{auth_AppId:`1`,pin:e,auth_timestamp:this.startPairingResponse.authTimestamp,auth_signature:t}};if(!this.httpClients.digest)return{success:!1,error:Error(`No digest client`)};let r=await this.httpClients.digest.request(`pair/grant`,{method:`POST`,data:n,retry:0});if(!r.success)return r;let i=r.data;return(i==null?void 0:i.error_id)===`SUCCESS`?{success:!0,data:{user:this.credentials.user,password:this.credentials.password,apiUrl:this.apiUrls.secure,apiVersion:this.apiVersion,fullApiUrl:`${this.apiUrls.secure}/${this.apiVersion}`}}:{success:!1,error:Error(`Failed to complete pairing: ${i==null?void 0:i.error_text}`)}}return{success:!1,error:Error(`Failed to complete pairing`)}}};for(let e of[`SIGINT`,`SIGTERM`,`uncaughtException`])process.on(e,()=>{t.consola.info(`OK. Bye.`)});const f=process.argv[2];async function p(){t.consola.box(`\nphiltv-js\nPairing TV\n${i}\n`.trim());let n=f?f.trim():await t.consola.prompt(`Enter TV ip address:`,{type:`text`});(0,e.isIP)(n)||(t.consola.error(`'${n}' is an invalid IP address. Bye.`),process.exit(1));let r=new d({tvIp:n});t.consola.info(`Starting pairing with TV at ${r.apiUrls.secure}`),t.consola.start(`Trying to contact TV...`);let a=await r.init();a.success||(t.consola.error(`
Check if the TV is on and the IP is correct.
Only Philips TVs with API version 6 or higher are supported.
Only secure transport and digest auth pairing are supported (https).\n
${a.success?``:a.error.message}
Bye.
`),process.exit(1)),t.consola.success(`TV is ready with API version`,a.data.apiVersion),t.consola.start(`Trying to pair TV at ${r.pairingRequestUrl}...`);let o=await r.startPairing();o.success||(t.consola.error(`
Failed to start pairing.\n
${o.error.message}\n
Bye.
`),process.exit(1));let s=await(async()=>await t.consola.prompt(`Enter pin code from TV:`,{type:`text`}))(),c=await r.completePairing(s);c.success?(t.consola.success(`Pairing successful`),t.consola.box(JSON.stringify(c.data,null,2))):t.consola.error(`${c.error.message}\nBye.`)}process.env.NODE_ENV!==`test`&&p(),exports.runPairing=p;