philtv-js
Version:
A simple library to pair and interact with Philips TV
2 lines • 9.19 kB
JavaScript
;var crypto=require('crypto'),radash=require('radash'),urllib=require('urllib'),f=require('superstruct'),zod=require('zod');function _interopNamespace(e){if(e&&e.__esModule)return e;var n=Object.create(null);if(e){Object.keys(e).forEach(function(k){if(k!=='default'){var d=Object.getOwnPropertyDescriptor(e,k);Object.defineProperty(n,k,d.get?d:{enumerable:true,get:function(){return e[k]}});}})}n.default=e;return Object.freeze(n)}var f__namespace=/*#__PURE__*/_interopNamespace(f);var O=["COMFORT","GAME","IMMERSIVE","NATURAL","RELAX","STANDARD","VIVID"],x=["ENERGY_ADAPTIVE_BRIGHTNESS","ENERGY_ADAPTIVE_COLORS","KNIGHT_RIDER_ALTERNATING","MODE_RANDOM","RANDOM_PIXEL_FLASH","SPECTRUM_ANALYSER","VU_METER"],A=[0,1,2,3,4,5,6,7,8,9,10],C=["increase","decrease"],m=["0","1","2","3","4","5","6","7","8","9","10"],F=[...A,...m],y=[...A,...m,...C],l={ambilight:{followAudioMode:x,followVideoMode:O,ambilightChangeBrightnessAvailableValues:y,ambilightBrightnessAvailableValues:F}};var S="ZmVay1EQVFOaZhwQ4Kv81ypLAZNczV9sG4KkseXWn1NEk6cXmPKO/MCa9sryslvLCFMnNe4Z4CPXzToowvhHvA==";async function H(a,t,e,i={}){let s=`${t}/${a}`;try{let n=await e.request(s,{...i}),r=n.status>=200&&n.status<300;if(!r)return [new Error(`Request failed with status ${n.status} (${n.url})`),void 0,n];let[o,u]=await radash.tryit(JSON.parse)(n.data.toString()),d=o?n.data.toString():u;return [void 0,d,{ok:r,...n,data:d}]}catch(n){return [n,void 0,void 0]}}function g(a){let t=new urllib.HttpClient({connect:{rejectUnauthorized:a?.options?.rejectUnauthorized??false,timeout:1e3},defaultArgs:{digestAuth:`${a.user}:${a.password}`,contentType:"json",headers:{}}});return {client:t,request:(e,i={})=>H(e,a.baseUrl,t,i)}}function c(){return new urllib.HttpClient({connect:{rejectUnauthorized:false,timeout:1e3},defaultArgs:{contentType:"json"}})}function v(a){return {app_id:"gapp.id",id:a,device_name:"MyName",device_os:"Android",app_name:"PhilTV-js APP",type:"native"}}function V(a){let t=[];function e(i){let{node_id:s,type:n,string_id:r,context:o,data:u}=i;if(t.push({node_id:s,type:n,string_id:r,context:o,data:radash.omit(u,["nodes"])}),u?.nodes)for(let d of u.nodes)e(d);}return e(a),t}function I(a,t){let e=crypto.createHmac("sha1",a);return e.write(t),e.end(),e.read("binary").toString("base64")}async function L(a){let t=c();try{let{data:e}=await t.request(`${a}/system`,{dataType:"json",timeout:2e3,signal:AbortSignal.timeout(2e3)}),{api_version:{Major:i},featuring:{systemfeatures:s}}=e,n=s.secured_transport==="true",r=s.pairing_type==="digest_auth_pairing";return [void 0,{apiVersion:i,systemFeatures:s,isSecureTransport:n,isGoodPairingType:r,isReady:n&&r}]}catch(e){return [e,void 0]}}var p=class{tvBase;deviceId;apiUrls;deviceInformation;httpClients;startPairingResponse;credentials;apiVersion;pairingRequestUrl;constructor(t){this.tvBase=t,this.apiUrls={secure:`https://${this.tvBase.tvIp}:${this.tvBase.apiPort||1926}`},this.deviceId=crypto.randomBytes(16).toString("hex"),this.deviceInformation=v(this.deviceId);}async init(){let[t,e]=await L(this.apiUrls.secure);return t||(this.apiVersion=e?.apiVersion,this.pairingRequestUrl=`${this.apiUrls.secure}/${this.apiVersion}/pair/request`),[t,e]}async startPairing(){let t=c(),{data:e}=await t.request(this.pairingRequestUrl,{method:"POST",data:{device:this.deviceInformation,scope:["read","write","control"]},dataType:"json"}),{timestamp:i,auth_key:s,timeout:n,error_id:r,error_text:o}=e;return r!=="SUCCESS"?[new Error(`Failed to start pairing: ${o}`),void 0]:(this.startPairingResponse={authTimestamp:i,authKey:s,timeout:n},this.credentials={user:this.deviceId,password:s},this.httpClients={digest:g({user:this.credentials.user,password:this.credentials.password,baseUrl:`${this.apiUrls.secure}/${this.apiVersion}`})},[void 0,this.startPairingResponse])}async completePairing(t){if(!t)return [new Error("Failed to complete pairing"),void 0];if(this.startPairingResponse){let e=Buffer.from(S,"base64"),i=this.startPairingResponse?.authTimestamp+t,s=I(e,i),n={device:this.deviceInformation,auth:{auth_AppId:"1",pin:t,auth_timestamp:this.startPairingResponse.authTimestamp,auth_signature:s}};if(!this.httpClients.digest)return [new Error("No digest client"),void 0];let[r,o]=await this.httpClients.digest.request("pair/grant",{method:"POST",data:n,retry:0});return r?[r,void 0]:o?.error_id!=="SUCCESS"?[new Error(`Failed to complete pairing: ${o?.error_text}`),void 0]:[void 0,{user:this.credentials.user,password:this.credentials.password,apiUrl:this.apiUrls.secure,apiVersion:this.apiVersion,fullApiUrl:`${this.apiUrls.secure}/${this.apiVersion}`}]}return [new Error("Failed to complete pairing"),void 0]}};var h=class{digestClient;constructor(t){this.digestClient=g({user:t.user,password:t.password,baseUrl:t.apiUrl});}getDigestClient(){return this.digestClient}renderResponse(t,e){return [t,e]}async getMenuStructure(t={flat:false}){let[e,i,s]=await this.digestClient.request("menuitems/settings/structure");return e?[e,void 0]:t.flat?[void 0,V(i.node),s]:[void 0,i,s]}async getMenuStructureItem(t,e){let[i,s]=await this.getMenuStructure({flat:true}),n=t?"context":"node_id",r=t||e;return i?[i,void 0]:[void 0,s.find(o=>o?.[n]===r)]}async getMenuStructureItemByContext(t){return this.getMenuStructureItem(t,void 0)}async getMenuStructureItemByNodeId(t){return this.getMenuStructureItem(void 0,t)}async setMenuItemSetting(t,e){if(!t)return [new Error("Item not found"),void 0];let[i,s,n]=await this.digestClient.request("menuitems/settings/update",{method:"POST",data:{values:[{value:{Nodeid:t.node_id,data:e}}]}}),r={status:n?.status,statusText:n?.statusText,body:s,item:t,originalResponse:n};return [i,r]}async handleSetMenuItemSetting(t,e){let[i,s]=await this.getMenuStructureItemByContext(t),[n,r]=await this.setMenuItemSetting(s,e);return [i||n,r]}};var b=class extends h{async getSystem(){let[t,e]=await this.digestClient.request("system");return t?this.renderResponse(t,void 0):[void 0,{...e,fullApiVersion:`${e.api_version.Major}.${e.api_version.Minor}.${e.api_version.Patch}`}]}async setAmbilightBrightness(t){let e=f__namespace.enums(l.ambilight.ambilightBrightnessAvailableValues),[i]=e.validate(t);return i?this.renderResponse(i,void 0):this.handleSetMenuItemSetting("ambilight_brightness",{value:Number(t)})}async changeAmbilightBrightness(t){let e=f__namespace.enums(l.ambilight.ambilightChangeBrightnessAvailableValues),[i]=e.validate(t);if(i)return this.renderResponse(i,void 0);let[,s]=await this.getAmbilightBrightnessValue();if(t==="increase"||t==="decrease"){let n=t==="increase"?Number(s)+1:Number(s)-1,r=Math.min(10,Math.max(0,n));return this.setAmbilightBrightness(r)}return this.setAmbilightBrightness(t)}increaseAmbilightBrightness(){return this.changeAmbilightBrightness("increase")}decreaseAmbilightBrightness(){return this.changeAmbilightBrightness("decrease")}getAmbilightConfiguration(){return this.digestClient.request("ambilight/currentconfiguration")}async getAmbilightBrightnessInformation(){let[t,e]=await this.getMenuStructureItemByContext("ambilight_brightness");if(t)return this.renderResponse(t,void 0);let i=e?.node_id,[s,n]=await this.getCurrentSetting(i);return s?this.renderResponse(s,void 0):[void 0,radash.get(n,"0.value",void 0)]}async getAmbilightBrightnessValue(){let[t,e]=await this.getAmbilightBrightnessInformation();return [t,Number(radash.get(e,"data.value",void 0))]}getAmbilightMode(){return this.digestClient.request("ambilight/mode")}async getAmbilightFullInformation(){let[t,e,i,s,n]=await Promise.all([this.getAmbilightConfiguration(),this.getAmbilightBrightnessInformation(),this.getAmbilightMode(),this.getAmbilightCached(),this.getAmbiHue()]),[r,o]=t,[u,d]=e,[P,E]=i,[M,_]=s,[B,N]=n;return [r||u||P||M||B,{configuration:o,mode:E,brightness:d,cached:_,ambiHue:N}]}async setAmbilightCurrentConfiguration(t){return this.digestClient.request("ambilight/currentconfiguration",{method:"POST",data:{styleName:t?.styleName,isExpert:t?.isExpert??false,menuSetting:t?.menuSetting}})}async setAmbilightFollowVideoMode(t){let e=zod.z.enum(l.ambilight.followVideoMode).safeParse(t);return e.error?this.renderResponse(e.error,void 0):this.setAmbilightCurrentConfiguration({styleName:"FOLLOW_VIDEO",menuSetting:t})}async setAmbilightFollowAudioMode(t){let e=zod.z.enum(l.ambilight.followAudioMode).safeParse(t);return e.error?this.renderResponse(e.error,void 0):this.setAmbilightCurrentConfiguration({styleName:"FOLLOW_AUDIO",menuSetting:t})}setAmbilightMode(t){return this.digestClient.request("ambilight/mode",{method:"POST",data:{current:t}})}getAmbilightCached(){return this.digestClient.request("ambilight/cached")}getCurrentActivity(){return this.digestClient.request("activities/current")}getApplications(){return this.digestClient.request("applications")}sendKey(t){return this.digestClient.request("input/key",{method:"POST",data:{key:t}})}getAmbiHue(){return this.digestClient.request("HueLamp/power")}async getCurrentSetting(t){let[,e]=await this.digestClient.request("menuitems/settings/current",{method:"POST",data:{nodes:[{nodeid:t}]}}),i=e?.values;return i?.length>0?[void 0,i]:[new Error("menuitems/settings/current: No values"),void 0]}};
exports.PhilTVApi=b;exports.PhilTVPairing=p;