From 5a8ab5dc266a2dcd7661be083e82ba1415f0c81f Mon Sep 17 00:00:00 2001 From: KGT1 Date: Tue, 8 Sep 2020 13:17:55 +0200 Subject: [PATCH] put api client back to node modules and added some things --- index.js | 17 --- jellyfin-apiclient/CONTRIBUTORS.md | 13 -- jellyfin-apiclient/LICENSE.md | 22 --- jellyfin-apiclient/README.md | 71 ---------- jellyfin-apiclient/dist/jellyfin-apiclient.js | 4 - .../dist/jellyfin-apiclient.js.map | 1 - jellyfin-apiclient/jest.setup.js | 2 - jellyfin-apiclient/package.json | 90 ------------ package.json | 12 +- patches/jellyfin-apiclient+1.4.1.patch | 11 ++ src/index.js | 31 ++++ src/jellyfinclientmanager.js | 19 +++ src/messagehandler.js | 134 ++++++++++++++---- src/util.js | 8 ++ 14 files changed, 181 insertions(+), 254 deletions(-) delete mode 100644 index.js delete mode 100644 jellyfin-apiclient/CONTRIBUTORS.md delete mode 100644 jellyfin-apiclient/LICENSE.md delete mode 100644 jellyfin-apiclient/README.md delete mode 100644 jellyfin-apiclient/dist/jellyfin-apiclient.js delete mode 100644 jellyfin-apiclient/dist/jellyfin-apiclient.js.map delete mode 100644 jellyfin-apiclient/jest.setup.js delete mode 100644 jellyfin-apiclient/package.json create mode 100644 patches/jellyfin-apiclient+1.4.1.patch create mode 100644 src/index.js create mode 100644 src/jellyfinclientmanager.js create mode 100644 src/util.js diff --git a/index.js b/index.js deleted file mode 100644 index b32455b..0000000 --- a/index.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict'; - -const DISCORD = require('discord.js'); - -const JELLYFIN_CLIENT = require('jellyfin-apiclient'); - -const CONFIG = require('./config.json'); - - -const DISCORD_CLIENT = new DISCORD.Client(); - -client.on('ready', () => { - console.log('connected to Discord'); -}); - - -client.login('your token here'); diff --git a/jellyfin-apiclient/CONTRIBUTORS.md b/jellyfin-apiclient/CONTRIBUTORS.md deleted file mode 100644 index b2fb13d..0000000 --- a/jellyfin-apiclient/CONTRIBUTORS.md +++ /dev/null @@ -1,13 +0,0 @@ -# Jellyfin Contributors - - - [thornbill](https://github.com/thornbill) - - [cvium](https://github.com/cvium) - - [Oddstr13](https://github.com/oddstr13) - - [Andrei Oanca](https://github.com/OancaAndrei) - -# Emby Contributors - - - [LukePulverenti](https://github.com/LukePulverenti) - - [ebr11](https://github.com/ebr11) - - [softworkz](https://github.com/softworkz) - - [HazCod](https://github.com/HazCod) diff --git a/jellyfin-apiclient/LICENSE.md b/jellyfin-apiclient/LICENSE.md deleted file mode 100644 index 8f5a547..0000000 --- a/jellyfin-apiclient/LICENSE.md +++ /dev/null @@ -1,22 +0,0 @@ -The MIT License - -Copyright (c) Emby https://emby.media - -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. - diff --git a/jellyfin-apiclient/README.md b/jellyfin-apiclient/README.md deleted file mode 100644 index 01d9d05..0000000 --- a/jellyfin-apiclient/README.md +++ /dev/null @@ -1,71 +0,0 @@ -

Jellyfin API Client for JavaScript

-

Part of the Jellyfin Project

- ---- - -

-Logo Banner -
-
- -MIT License - - -Donate - - -Feature Requests - - -Discuss on our Forum - - -Chat on Matrix - - -Join our Subreddit - -

- -This library is meant to help clients written in JavaScript or TypeScript interact with Jellyfin's REST API. - -## Compatibility - -This library depends on the Fetch and Promise APIs. These will be expected to be polyfilled if used in a browser that doesn't support them. - -## Build Process - -### Dependencies - -- Yarn - -### Getting Started - -1. Clone or download this repository - - ```sh - git clone https://github.com/jellyfin/jellyfin-apiclient-javascript.git - cd jellyfin-apiclient-javascript - ``` - -2. Install build dependencies in the project directory - - ```sh - yarn install - ``` - -3. Build the library for production - - ```sh - yarn build - ``` - -4. Build the library for development - - ```sh - yarn dev - ``` - -## Building Documentation - -This library is documented using [JSDoc](https://jsdoc.app/) style comments. Documentation can be generated in HTML format by running `yarn docs` and viewing the files in any modern browser. The resulting documentation will be saved in the `docs` directory. diff --git a/jellyfin-apiclient/dist/jellyfin-apiclient.js b/jellyfin-apiclient/dist/jellyfin-apiclient.js deleted file mode 100644 index 091625a..0000000 --- a/jellyfin-apiclient/dist/jellyfin-apiclient.js +++ /dev/null @@ -1,4 +0,0 @@ -const fetch = require('node-fetch') - -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["jellyfin-apiclient"]=t():e["jellyfin-apiclient"]=t()}(this,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";function n(e,t){if(!e)throw new Error("obj cannot be null!");e._callbacks=e._callbacks||{};var r=e._callbacks[t];return r||(e._callbacks[t]=[],r=e._callbacks[t]),r}r.r(t);var i={on:function(e,t,r){n(e,t).push(r)},off:function(e,t,r){var i=n(e,t),o=i.indexOf(r);-1!==o&&i.splice(o,1)},trigger:function(e,t){var r={type:t},i=[];i.push(r);for(var o=arguments[2]||[],a=0,s=o.length;a=r.length)return j(t,i);var o=r[n];return t.getDownloadSpeed(o.bytes).then((function(i){return i=20?Promise.reject():function(e){var t=[],r=[],n=e.serverInfo();return n.LocalAddress&&-1===r.indexOf(n.LocalAddress)&&(t.push({url:n.LocalAddress,timeout:0}),r.push(t[t.length-1].url)),n.ManualAddress&&-1===r.indexOf(n.ManualAddress)&&(t.push({url:n.ManualAddress,timeout:100}),r.push(t[t.length-1].url)),n.RemoteAddress&&-1===r.indexOf(n.RemoteAddress)&&(t.push({url:n.RemoteAddress,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(r,n){var i={};i.numAddresses=t.length,i.rejects=0,t.map((function(t){setTimeout((function(){i.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+t),I(e.getUrl("system/info/public",null,t),{method:"GET",accept:"application/json"},15e3).then((function(){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+t),e.serverAddress(t),n())}),(function(){r.resolved||(console.log("Reconnect failed to "+t),r.rejects++,r.rejects>=r.numAddresses&&i())}))}(e,t.url,i,r,n)}),t.timeout)}))}))}(t).catch((function(n){return console.log("error in tryReconnectInternal: "+(n||"")),new Promise((function(n,i){setTimeout((function(){e(t,r+1).then(n,i)}),500)}))}))}(r).then((function(){return console.log("Reconnect succeesed"),e.url=e.url.replace(i,r.serverAddress()),r.fetchWithFailover(e,!1)})).catch((function(t){throw console.log("Reconnect failed"),y(r,e.url,{}),t}))}))}},{key:"fetch",value:function(e,t){if(!e)return Promise.reject("Request cannot be null");if(e.headers=e.headers||{},!1!==t&&this.setRequestHeaders(e.headers),!1===this.enableAutomaticNetworking||"GET"!==e.type){console.log("Requesting url without automatic networking: ".concat(e.url));var r=this;return S(e).then((function(t){return r.lastFetch=(new Date).getTime(),t.status<400?"json"===e.dataType||"application/json"===e.headers.accept?t.json():"text"===e.dataType||0===(t.headers.get("Content-Type")||"").toLowerCase().indexOf("text/")?t.text():t:(y(r,e.url,t),Promise.reject(t))})).catch((function(t){return y(r,e.url,{}),Promise.reject(t)}))}return this.fetchWithFailover(e,!0)}},{key:"setAuthenticationInfo",value:function(e,t){this._currentUser=null,this._serverInfo.AccessToken=e,this._serverInfo.UserId=t,v(this)}},{key:"serverInfo",value:function(e){return e&&(this._serverInfo=e),this._serverInfo}},{key:"getCurrentUserId",value:function(){return this._serverInfo.UserId}},{key:"accessToken",value:function(){return this._serverInfo.AccessToken}},{key:"serverId",value:function(){return this.serverInfo().Id}},{key:"serverName",value:function(){return this.serverInfo().Name}},{key:"ajax",value:function(e,t){return e?this.fetch(e,t):Promise.reject("Request cannot be null")}},{key:"getCurrentUser",value:function(e){if(this._currentUser)return Promise.resolve(this._currentUser);var t=this.getCurrentUserId();if(!t)return Promise.reject();var r,n=this,i=this.getUser(t).then((function(e){return u.setItem("user-".concat(e.Id,"-").concat(e.ServerId),JSON.stringify(e)),n._currentUser=e,e})).catch((function(e){if(!e.status&&t&&n.accessToken()&&(r=T(n,t)))return Promise.resolve(r);throw e}));return!this.lastFetch&&!1!==e&&(r=T(n,t))?Promise.resolve(r):i}},{key:"isLoggedIn",value:function(){var e=this.serverInfo();return!!(e&&e.UserId&&e.AccessToken)}},{key:"logout",value:function(){var e=this;g(this),this.closeWebSocket();var t=function(){var t=e.serverInfo();t&&t.UserId&&t.Id&&u.removeItem("user-".concat(t.UserId,"-").concat(t.Id)),e.setAuthenticationInfo(null,null)};if(this.accessToken()){var r=this.getUrl("Sessions/Logout");return this.ajax({type:"POST",url:r}).then(t,t)}return t(),Promise.resolve()}},{key:"authenticateUserByName",value:function(e,t){var r=this;if(!e)return Promise.reject();var n=this.getUrl("Users/authenticatebyname");return new Promise((function(i,o){var a={Username:e,Pw:t||""};r.ajax({type:"POST",url:n,data:JSON.stringify(a),dataType:"json",contentType:"application/json"}).then((function(e){var t=function(){v(r),i(e)};r.onAuthenticated?r.onAuthenticated(r,e).then(t):t()})).catch(o)}))}},{key:"ensureWebSocket",value:function(){if(!this.isWebSocketOpenOrConnecting()&&this.isWebSocketSupported())try{this.openWebSocket()}catch(e){console.log("Error opening web socket: ".concat(e))}}},{key:"openWebSocket",value:function(){var e=this.accessToken();if(!e)throw new Error("Cannot open web socket without access token.");var t=this.getUrl("socket");t=p(t,"emby/socket","embywebsocket"),t=p(t,"https:","wss:"),t=p(t,"http:","ws:"),t+="?api_key=".concat(e),t+="&deviceId=".concat(this.deviceId()),console.log("opening web socket with url: ".concat(t));var r,n,o=new WebSocket(t);o.onmessage=P.bind(this),o.onopen=E.bind(this),o.onerror=A.bind(this),r=this,(n=o).onclose=function(){console.log("web socket closed"),O(n),r._webSocket===n&&(console.log("nulling out web socket"),r._webSocket=null),setTimeout((function(){i.trigger(r,"websocketclose")}),0)},this._webSocket=o}},{key:"closeWebSocket",value:function(){var e=this._webSocket;e&&e.readyState===WebSocket.OPEN&&e.close()}},{key:"sendWebSocketMessage",value:function(e,t){console.log("Sending web socket message: ".concat(e));var r={MessageType:e};t&&(r.Data=t),r=JSON.stringify(r),this._webSocket.send(r)}},{key:"sendMessage",value:function(e,t){this.isWebSocketOpen()&&this.sendWebSocketMessage(e,t)}},{key:"isMessageChannelOpen",value:function(){return this.isWebSocketOpen()}},{key:"isWebSocketOpen",value:function(){var e=this._webSocket;return!!e&&e.readyState===WebSocket.OPEN}},{key:"isWebSocketOpenOrConnecting",value:function(){var e=this._webSocket;return!!e&&(e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)}},{key:"get",value:function(e){return this.ajax({type:"GET",url:e})}},{key:"getJSON",value:function(e,t){return this.fetch({url:e,type:"GET",dataType:"json",headers:{accept:"application/json"}},t)}},{key:"updateServerInfo",value:function(e,t){if(null==e)throw new Error("server cannot be null");if(this.serverInfo(e),!t)throw new Error("serverUrl cannot be null. serverInfo: ".concat(JSON.stringify(e)));console.log("Setting server address to ".concat(t)),this.serverAddress(t)}},{key:"isWebSocketSupported",value:function(){try{return null!=WebSocket}catch(e){return!1}}},{key:"clearAuthenticationInfo",value:function(){this.setAuthenticationInfo(null,null)}},{key:"encodeName",value:function(e){var t=m({name:e=(e=(e=e.split("/").join("-")).split("&").join("-")).split("?").join("-")});return t.substring(t.indexOf("=")+1).replace("'","%27")}},{key:"getServerTime",value:function(){var e=this.getUrl("GetUTCTime");return this.ajax({type:"GET",url:e})}},{key:"getDownloadSpeed",value:function(e){var t=this.getUrl("Playback/BitrateTest",{Size:e}),r=(new Date).getTime();return this.ajax({type:"GET",url:t,timeout:5e3}).then((function(){var t=((new Date).getTime()-r)/1e3,n=e/t;return Math.round(8*n)}))}},{key:"detectBitrate",value:function(e){if(!e&&this.lastDetectedBitrate&&(new Date).getTime()-(this.lastDetectedBitrateTime||0)<=36e5)return Promise.resolve(this.lastDetectedBitrate);var t=this;return this.getEndpointInfo().then((function(e){return N(t,e)}),(function(e){return N(t,{})}))}},{key:"getItem",value:function(e,t){if(!t)throw new Error("null itemId");var r=e?this.getUrl("Users/".concat(e,"/Items/").concat(t)):this.getUrl("Items/".concat(t));return this.getJSON(r)}},{key:"getRootFolder",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Items/Root"));return this.getJSON(t)}},{key:"getNotificationSummary",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Notifications/".concat(e,"/Summary"));return this.getJSON(t)}},{key:"getNotifications",value:function(e,t){if(!e)throw new Error("null userId");var r=this.getUrl("Notifications/".concat(e),t||{});return this.getJSON(r)}},{key:"markNotificationsRead",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null idList");var n=r?"Read":"Unread",i={UserId:e,Ids:t.join(",")},o=this.getUrl("Notifications/".concat(e,"/").concat(n),i);return this.ajax({type:"POST",url:o})}},{key:"getRemoteImageProviders",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Providers"),e);return this.getJSON(r)}},{key:"getAvailableRemoteImages",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages"),e);return this.getJSON(r)}},{key:"downloadRemoteImage",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Download"),e);return this.ajax({type:"POST",url:r})}},{key:"getRecordingFolders",value:function(e){var t=this.getUrl("LiveTv/Recordings/Folders",{userId:e});return this.getJSON(t)}},{key:"getLiveTvInfo",value:function(e){var t=this.getUrl("LiveTv/Info",e||{});return this.getJSON(t)}},{key:"getLiveTvGuideInfo",value:function(e){var t=this.getUrl("LiveTv/GuideInfo",e||{});return this.getJSON(t)}},{key:"getLiveTvChannel",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Channels/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvChannels",value:function(e){var t=this.getUrl("LiveTv/Channels",e||{});return this.getJSON(t)}},{key:"getLiveTvPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e.channelIds&&e.channelIds.length>1800?this.ajax({type:"POST",url:this.getUrl("LiveTv/Programs"),data:JSON.stringify(e),contentType:"application/json",dataType:"json"}):this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs",e),dataType:"json"})}},{key:"getLiveTvRecommendedPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs/Recommended",e),dataType:"json"})}},{key:"getLiveTvRecordings",value:function(e){var t=this.getUrl("LiveTv/Recordings",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingSeries",value:function(e){var t=this.getUrl("LiveTv/Recordings/Series",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroups",value:function(e){var t=this.getUrl("LiveTv/Recordings/Groups",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroup",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/Groups/".concat(e));return this.getJSON(t)}},{key:"getLiveTvRecording",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Recordings/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvProgram",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Programs/".concat(e),r);return this.getJSON(n)}},{key:"deleteLiveTvRecording",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"cancelLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getLiveTvTimers",value:function(e){var t=this.getUrl("LiveTv/Timers",e||{});return this.getJSON(t)}},{key:"getLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.getJSON(t)}},{key:"getNewLiveTvTimerDefaults",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("LiveTv/Timers/Defaults",e);return this.getJSON(t)}},{key:"createLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"resetLiveTvTuner",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Tuners/".concat(e,"/Reset"));return this.ajax({type:"POST",url:t})}},{key:"getLiveTvSeriesTimers",value:function(e){var t=this.getUrl("LiveTv/SeriesTimers",e||{});return this.getJSON(t)}},{key:"getLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.getJSON(t)}},{key:"cancelLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"createLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"getRegistrationInfo",value:function(e){var t=this.getUrl("Registrations/".concat(e));return this.getJSON(t)}},{key:"getSystemInfo",value:function(e){var t=this.getUrl("System/Info"),r=this;return this.getJSON(t).then((function(e){return r.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getSyncStatus",value:function(){var e=this.getUrl("Sync/"+itemId+"/Status");return this.ajax({url:e,type:"POST",dataType:"json",contentType:"application/json",data:JSON.stringify({TargetId:this.deviceId()})})}},{key:"getPublicSystemInfo",value:function(){var e=this.getUrl("System/Info/Public"),t=this;return this.getJSON(e).then((function(e){return t.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getInstantMixFromItem",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/InstantMix"),t);return this.getJSON(r)}},{key:"getEpisodes",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Episodes"),t);return this.getJSON(r)}},{key:"getDisplayPreferences",value:function(e,t,r){var n=this.getUrl("DisplayPreferences/".concat(e),{userId:t,client:r});return this.getJSON(n)}},{key:"updateDisplayPreferences",value:function(e,t,r,n){var i=this.getUrl("DisplayPreferences/".concat(e),{userId:r,client:n});return this.ajax({type:"POST",url:i,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getSeasons",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Seasons"),t);return this.getJSON(r)}},{key:"getSimilarItems",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/Similar"),t);return this.getJSON(r)}},{key:"getCultures",value:function(){var e=this.getUrl("Localization/cultures");return this.getJSON(e)}},{key:"getCountries",value:function(){var e=this.getUrl("Localization/countries");return this.getJSON(e)}},{key:"getPlaybackInfo",value:function(e,t,r){var n={DeviceProfile:r};return this.ajax({url:this.getUrl("Items/".concat(e,"/PlaybackInfo"),t),type:"POST",data:JSON.stringify(n),contentType:"application/json",dataType:"json"})}},{key:"getLiveStreamMediaInfo",value:function(e){var t={LiveStreamId:e};return this.ajax({url:this.getUrl("LiveStreams/MediaInfo"),type:"POST",data:JSON.stringify(t),contentType:"application/json",dataType:"json"})}},{key:"getIntros",value:function(e){return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/").concat(e,"/Intros")))}},{key:"getDirectoryContents",value:function(e,t){if(!e)throw new Error("null path");if("string"!=typeof e)throw new Error("invalid path");(t=t||{}).path=e;var r=this.getUrl("Environment/DirectoryContents",t);return this.getJSON(r)}},{key:"getNetworkShares",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/NetworkShares",t);return this.getJSON(r)}},{key:"getParentPath",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/ParentPath",t);return this.ajax({type:"GET",url:r,dataType:"text"})}},{key:"getDrives",value:function(){var e=this.getUrl("Environment/Drives");return this.getJSON(e)}},{key:"getNetworkDevices",value:function(){var e=this.getUrl("Environment/NetworkDevices");return this.getJSON(e)}},{key:"cancelPackageInstallation",value:function(e){if(!e)throw new Error("null installationId");var t=this.getUrl("Packages/Installing/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"refreshItem",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/Refresh"),t||{});return this.ajax({type:"POST",url:r})}},{key:"installPlugin",value:function(e,t,r){if(!e)throw new Error("null name");var n={AssemblyGuid:t};r&&(n.version=r);var i=this.getUrl("Packages/Installed/".concat(e),n);return this.ajax({type:"POST",url:i})}},{key:"restartServer",value:function(){var e=this.getUrl("System/Restart");return this.ajax({type:"POST",url:e})}},{key:"shutdownServer",value:function(){var e=this.getUrl("System/Shutdown");return this.ajax({type:"POST",url:e})}},{key:"getPackageInfo",value:function(e,t){if(!e)throw new Error("null name");var r={AssemblyGuid:t},n=this.getUrl("Packages/".concat(e),r);return this.getJSON(n)}},{key:"getVirtualFolders",value:function(){var e="Library/VirtualFolders";return e=this.getUrl(e),this.getJSON(e)}},{key:"getPhysicalPaths",value:function(){var e=this.getUrl("Library/PhysicalPaths");return this.getJSON(e)}},{key:"getServerConfiguration",value:function(){var e=this.getUrl("System/Configuration");return this.getJSON(e)}},{key:"getDevicesOptions",value:function(){var e=this.getUrl("System/Configuration/devices");return this.getJSON(e)}},{key:"getContentUploadHistory",value:function(){var e=this.getUrl("Devices/CameraUploads",{DeviceId:this.deviceId()});return this.getJSON(e)}},{key:"getNamedConfiguration",value:function(e){var t=this.getUrl("System/Configuration/".concat(e));return this.getJSON(t)}},{key:"getScheduledTasks",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("ScheduledTasks",e);return this.getJSON(t)}},{key:"startScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"POST",url:t})}},{key:"getScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/".concat(e));return this.getJSON(t)}},{key:"getNextUpEpisodes",value:function(e){var t=this.getUrl("Shows/NextUp",e);return this.getJSON(t)}},{key:"stopScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getPluginConfiguration",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.getJSON(t)}},{key:"getAvailablePlugins",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};e.PackageType="UserInstalled";var t=this.getUrl("Packages",e);return this.getJSON(t)}},{key:"uninstallPlugin",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"removeVirtualFolder",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders";return r=this.getUrl(r,{refreshLibrary:!!t,name:e}),this.ajax({type:"DELETE",url:r})}},{key:"addVirtualFolder",value:function(e,t,r,n){if(!e)throw new Error("null name");var i={};t&&(i.collectionType=t),i.refreshLibrary=!!r,i.name=e;var o="Library/VirtualFolders";return o=this.getUrl(o,i),this.ajax({type:"POST",url:o,data:JSON.stringify({LibraryOptions:n}),contentType:"application/json"})}},{key:"updateVirtualFolderOptions",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders/LibraryOptions";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Id:e,LibraryOptions:t}),contentType:"application/json"})}},{key:"renameVirtualFolder",value:function(e,t,r){if(!e)throw new Error("null name");var n="Library/VirtualFolders/Name";return n=this.getUrl(n,{refreshLibrary:!!r,newName:t,name:e}),this.ajax({type:"POST",url:n})}},{key:"addMediaPath",value:function(e,t,r,n){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var i="Library/VirtualFolders/Paths",o={Path:t};return r&&(o.NetworkPath=r),i=this.getUrl(i,{refreshLibrary:!!n}),this.ajax({type:"POST",url:i,data:JSON.stringify({Name:e,PathInfo:o}),contentType:"application/json"})}},{key:"updateMediaPath",value:function(e,t){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null pathInfo");var r="Library/VirtualFolders/Paths/Update";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Name:e,PathInfo:t}),contentType:"application/json"})}},{key:"removeMediaPath",value:function(e,t,r){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var n="Library/VirtualFolders/Paths";return n=this.getUrl(n,{refreshLibrary:!!r,path:t,name:e}),this.ajax({type:"DELETE",url:n})}},{key:"deleteUser",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("Users/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"deleteUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");var n=this.getUrl("Users/".concat(e,"/Images/").concat(t));return null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItemImage",value:function(e,t,r){if(!t)throw new Error("null imageType");var n=this.getUrl("Items/".concat(e,"/Images"));return n+="/".concat(t),null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItem",value:function(e){if(!e)throw new Error("null itemId");var t=this.getUrl("Items/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"stopActiveEncodings",value:function(e){var t={deviceId:this.deviceId()};e&&(t.PlaySessionId=e);var r=this.getUrl("Videos/ActiveEncodings",t);return this.ajax({type:"DELETE",url:r})}},{key:"reportCapabilities",value:function(e){var t=this.getUrl("Sessions/Capabilities/Full");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateItemImageIndex",value:function(e,t,r,n){if(!t)throw new Error("null imageType");var i={newIndex:n},o=this.getUrl("Items/".concat(e,"/Images/").concat(t,"/").concat(r,"/Index"),i);return this.ajax({type:"POST",url:o})}},{key:"getItemImageInfos",value:function(e){var t=this.getUrl("Items/".concat(e,"/Images"));return this.getJSON(t)}},{key:"getCriticReviews",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/CriticReviews"),t);return this.getJSON(r)}},{key:"getItemDownloadUrl",value:function(e){if(!e)throw new Error("itemId cannot be empty");var t="Items/".concat(e,"/Download");return this.getUrl(t,{api_key:this.accessToken()})}},{key:"getSessions",value:function(e){var t=this.getUrl("Sessions",e);return this.getJSON(t)}},{key:"uploadUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this;return new Promise((function(i,o){var a=new FileReader;a.onerror=function(){o()},a.onabort=function(){o()},a.onload=function(a){var s=a.target.result.split(",")[1],l=n.getUrl("Users/".concat(e,"/Images/").concat(t));n.ajax({type:"POST",url:l,data:s,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(i,o)},a.readAsDataURL(r)}))}},{key:"uploadItemImage",value:function(e,t,r){if(!e)throw new Error("null itemId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this.getUrl("Items/".concat(e,"/Images"));n+="/".concat(t);var i=this;return new Promise((function(e,t){var o=new FileReader;o.onerror=function(){t()},o.onabort=function(){t()},o.onload=function(o){var a=o.target.result.split(",")[1];i.ajax({type:"POST",url:n,data:a,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(e,t)},o.readAsDataURL(r)}))}},{key:"getInstalledPlugins",value:function(){var e=this.getUrl("Plugins",{});return this.getJSON(e)}},{key:"getUser",value:function(e){if(!e)throw new Error("Must supply a userId");var t=this.getUrl("Users/".concat(e));return this.getJSON(t)}},{key:"getStudio",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Studios/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Genres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getMusicGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("MusicGenres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getArtist",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Artists/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPerson",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Persons/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPublicUsers",value:function(){var e=this.getUrl("users/public");return this.ajax({type:"GET",url:e,dataType:"json"},!1)}},{key:"getUsers",value:function(e){var t=this.getUrl("users",e||{});return this.getJSON(t)}},{key:"getParentalRatings",value:function(){var e=this.getUrl("Localization/ParentalRatings");return this.getJSON(e)}},{key:"getDefaultImageQuality",value:function(e){return"backdrop"===e.toLowerCase()?80:90}},{key:"getUserImageUrl",value:function(e,t){if(!e)throw new Error("null userId");t=t||{};var r="Users/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),t.quality=t.quality||this.getDefaultImageQuality(t.type),this.normalizeImageOptions&&this.normalizeImageOptions(t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getScaledImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,delete t.minScale,this.getUrl(r,t)}},{key:"getThumbImageUrl",value:function(e,t){if(!e)throw new Error("null item");return(t=t||{}).imageType="thumb",e.ImageTags&&e.ImageTags.Thumb?(t.tag=e.ImageTags.Thumb,this.getImageUrl(e.Id,t)):e.ParentThumbItemId?(t.tag=e.ImageTags.ParentThumbImageTag,this.getImageUrl(e.ParentThumbItemId,t)):null}},{key:"updateUserPassword",value:function(e,t,r){if(!e)return Promise.reject();var n=this.getUrl("Users/".concat(e,"/Password"));return this.ajax({type:"POST",url:n,data:JSON.stringify({CurrentPw:t||"",NewPw:r}),contentType:"application/json"})}},{key:"updateEasyPassword",value:function(e,t){if(e){var r=this.getUrl("Users/".concat(e,"/EasyPassword"));return this.ajax({type:"POST",url:r,data:{NewPw:t}})}Promise.reject()}},{key:"resetUserPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Password")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"resetEasyPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/EasyPassword")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"updateServerConfiguration",value:function(e){if(!e)throw new Error("null configuration");var t=this.getUrl("System/Configuration");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateNamedConfiguration",value:function(e,t){if(!t)throw new Error("null configuration");var r=this.getUrl("System/Configuration/".concat(e));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateItem",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("Items/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updatePluginSecurityInfo",value:function(e){var t=this.getUrl("Plugins/SecurityInfo");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"createUser",value:function(e){var t=this.getUrl("Users/New");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json",headers:{accept:"application/json"}})}},{key:"updateUser",value:function(e){if(!e)throw new Error("null user");var t=this.getUrl("Users/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateUserPolicy",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null policy");var r=this.getUrl("Users/".concat(e,"/Policy"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateUserConfiguration",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null configuration");var r=this.getUrl("Users/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateScheduledTaskTriggers",value:function(e,t){if(!e)throw new Error("null id");if(!t)throw new Error("null triggers");var r=this.getUrl("ScheduledTasks/".concat(e,"/Triggers"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updatePluginConfiguration",value:function(e,t){if(!e)throw new Error("null Id");if(!t)throw new Error("null configuration");var r=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getAncestorItems",value:function(e,t){if(!e)throw new Error("null itemId");var r={};t&&(r.userId=t);var n=this.getUrl("Items/".concat(e,"/Ancestors"),r);return this.getJSON(n)}},{key:"getItems",value:function(e,t){var r;return r="string"===c(e).toString().toLowerCase()?this.getUrl("Users/".concat(e,"/Items"),t):this.getUrl("Items",t),this.getJSON(r)}},{key:"getResumableItems",value:function(e,t){return this.isMinServerVersion("3.2.33")?this.getJSON(this.getUrl("Users/".concat(e,"/Items/Resume"),t)):this.getItems(e,Object.assign({SortBy:"DatePlayed",SortOrder:"Descending",Filters:"IsResumable",Recursive:!0,CollapseBoxSetItems:!1,ExcludeLocationTypes:"Virtual"},t))}},{key:"getMovieRecommendations",value:function(e){return this.getJSON(this.getUrl("Movies/Recommendations",e))}},{key:"getUpcomingEpisodes",value:function(e){return this.getJSON(this.getUrl("Shows/Upcoming",e))}},{key:"getUserViews",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,r=this.getUrl("Users/".concat(t||this.getCurrentUserId(),"/Views"),e);return this.getJSON(r)}},{key:"getArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists",t);return this.getJSON(r)}},{key:"getAlbumArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists/AlbumArtists",t);return this.getJSON(r)}},{key:"getGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Genres",t);return this.getJSON(r)}},{key:"getMusicGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("MusicGenres",t);return this.getJSON(r)}},{key:"getPeople",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Persons",t);return this.getJSON(r)}},{key:"getStudios",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Studios",t);return this.getJSON(r)}},{key:"getLocalTrailers",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/LocalTrailers"));return this.getJSON(r)}},{key:"getAdditionalVideoParts",value:function(e,t){if(!t)throw new Error("null itemId");var r={};e&&(r.userId=e);var n=this.getUrl("Videos/".concat(t,"/AdditionalParts"),r);return this.getJSON(n)}},{key:"getThemeMedia",value:function(e,t,r){if(!t)throw new Error("null itemId");var n={};e&&(n.userId=e),n.InheritFromParent=r||!1;var i=this.getUrl("Items/".concat(t,"/ThemeMedia"),n);return this.getJSON(i)}},{key:"getSearchHints",value:function(e){var t=this.getUrl("Search/Hints",e),r=this.serverId();return this.getJSON(t).then((function(e){return e.SearchHints.forEach((function(e){e.ServerId=r})),e}))}},{key:"getSpecialFeatures",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/SpecialFeatures"));return this.getJSON(r)}},{key:"getDateParamValue",value:function(e){function t(e){return e<10?"0".concat(e):e}var r=e;return"".concat(r.getFullYear()).concat(t(r.getMonth()+1)).concat(t(r.getDate())).concat(t(r.getHours())).concat(t(r.getMinutes())).concat(t(r.getSeconds()))}},{key:"markPlayed",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n={};r&&(n.DatePlayed=this.getDateParamValue(r));var i=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t),n);return this.ajax({type:"POST",url:i,dataType:"json"})}},{key:"markUnplayed",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"updateFavoriteStatus",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/FavoriteItems/").concat(t)),i=r?"POST":"DELETE";return this.ajax({type:i,url:n,dataType:"json"})}},{key:"updateUserItemRating",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"),{likes:r});return this.ajax({type:"POST",url:n,dataType:"json"})}},{key:"getItemCounts",value:function(e){var t={};e&&(t.userId=e);var r=this.getUrl("Items/Counts",t);return this.getJSON(r)}},{key:"clearUserItemRating",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,g(this),w(this);var t=this.getUrl("Sessions/Playing");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");var t=e.EventName||"timeupdate",r=h[t]||0,n=(new Date).getTime()-(this.lastPlaybackProgressReport||0),i=e.PositionTicks;if(n=5e7&&(r=0)}if(r<(void 0!==this.reportPlaybackProgressTimeout?this.reportPlaybackProgressTimeout:1e6)&&w(this),this.lastPlaybackProgressOptions=e,this.reportPlaybackProgressPromise)return Promise.resolve();var a,s=this,l=!1,u=function(){s.reportPlaybackProgressPromise===a&&(delete s.lastPlaybackProgressOptions,delete s.reportPlaybackProgressTimeout,delete s.reportPlaybackProgressPromise,delete s.reportPlaybackProgressCancel)},c=Math.max(0,r-n);return a=new Promise((function(e,t){return setTimeout(e,c)})).then((function(){return l?Promise.resolve():function(e){if(u(),!e)throw new Error("null options");s.lastPlaybackProgressReport=(new Date).getTime(),s.lastPlaybackProgressReportTicks=e.PositionTicks;var t=s.getUrl("Sessions/Playing/Progress");return s.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}(s.lastPlaybackProgressOptions)})).finally((function(){u()})),this.reportPlaybackProgressTimeout=r,this.reportPlaybackProgressPromise=a,this.reportPlaybackProgressCancel=function(){l=!0,u()},a}},{key:"reportOfflineActions",value:function(e){if(!e)throw new Error("null actions");var t=this.getUrl("Sync/OfflineActions");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"syncData",value:function(e){if(!e)throw new Error("null data");var t=this.getUrl("Sync/Data");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t,dataType:"json"})}},{key:"getReadySyncItems",value:function(e){if(!e)throw new Error("null deviceId");var t=this.getUrl("Sync/Items/Ready",{TargetId:e});return this.getJSON(t)}},{key:"reportSyncJobItemTransferred",value:function(e){if(!e)throw new Error("null syncJobItemId");var t=this.getUrl("Sync/JobItems/".concat(e,"/Transferred"));return this.ajax({type:"POST",url:t})}},{key:"cancelSyncItems",value:function(e,t){if(!e)throw new Error("null itemIds");var r=this.getUrl("Sync/".concat(t||this.deviceId(),"/Items"),{ItemIds:e.join(",")});return this.ajax({type:"DELETE",url:r})}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,v(this),w(this);var t=this.getUrl("Sessions/Playing/Stopped");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"sendPlayCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r=this.getUrl("Sessions/".concat(e,"/Playing"),t);return this.ajax({type:"POST",url:r})}},{key:"sendCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Command"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendMessageCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Message"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendPlayStateCommand",value:function(e,t,r){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var n=this.getUrl("Sessions/".concat(e,"/Playing/").concat(t),r||{});return this.ajax({type:"POST",url:n})}},{key:"getSyncPlayGroups",value:function(){var e=this.getUrl("SyncPlay/List");return this.ajax({type:"GET",url:e})}},{key:"createSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/New");return this.ajax({type:"POST",url:e})}},{key:"joinSyncPlayGroup",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Join",e);return this.ajax({type:"POST",url:t})}},{key:"leaveSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/Leave");return this.ajax({type:"POST",url:e})}},{key:"sendSyncPlayPing",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Ping",e);return this.ajax({type:"POST",url:t})}},{key:"requestSyncPlayStart",value:function(){var e=this.getUrl("SyncPlay/Play");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlayPause",value:function(){var e=this.getUrl("SyncPlay/Pause");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlaySeek",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Seek",e);return this.ajax({type:"POST",url:t})}},{key:"createPackageReview",value:function(e){var t=this.getUrl("Packages/Reviews/".concat(e.id),e);return this.ajax({type:"POST",url:t})}},{key:"getPackageReviews",value:function(e,t,r,n){if(!e)throw new Error("null packageId");var i={};t&&(i.MinRating=t),r&&(i.MaxRating=r),n&&(i.Limit=n);var o=this.getUrl("Packages/".concat(e,"/Reviews"),i);return this.getJSON(o)}},{key:"getSavedEndpointInfo",value:function(){return this._endPointInfo}},{key:"getEndpointInfo",value:function(){var e=this._endPointInfo;if(e)return Promise.resolve(e);var t=this;return this.getJSON(this.getUrl("System/Endpoint")).then((function(e){return k(t,e),e}))}},{key:"getLatestItems",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/Latest"),e))}},{key:"getFilters",value:function(e){return this.getJSON(this.getUrl("Items/Filters2",e))}},{key:"setSystemInfo",value:function(e){this._serverVersion=e.Version}},{key:"serverVersion",value:function(){return this._serverVersion}},{key:"isMinServerVersion",value:function(e){var t=this.serverVersion();return!!t&&function(e,t){e=e.split("."),t=t.split(".");for(var r=0,n=Math.max(e.length,t.length);ro)return 1}return 0}(t,e)>=0}},{key:"handleMessageReceived",value:function(e){b(this,e)}}])&&d(t.prototype,r),n&&d(t,n),e}();function J(e){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(e,t){for(var r=0;rt.length&&0===e.indexOf(t))}function H(e,t){return q(e,t)?e.substr(t.length):e}function z(e){return e?V(e)?e:"local:".concat(e):null}function K(e){e.Id=z(e.Id),e.SeriesId=z(e.SeriesId),e.SeasonId=z(e.SeasonId),e.AlbumId=z(e.AlbumId),e.ParentId=z(e.ParentId),e.ParentThumbItemId=z(e.ParentThumbItemId),e.ParentPrimaryImageItemId=z(e.ParentPrimaryImageItemId),e.PrimaryImageItemId=z(e.PrimaryImageItemId),e.ParentLogoItemId=z(e.ParentLogoItemId),e.ParentBackdropItemId=z(e.ParentBackdropItemId),e.ParentBackdropImageTags=null}function Q(e,t,r){return e.getLocalFolders(t,r).then((function(r){var n=null;return r.length>0&&(n={Name:e.downloadsTitleText||"Downloads",ServerId:t,Id:"localview",Type:"localview",IsFolder:!0}),Promise.resolve(n)}))}var X=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&D(e,t)}(o,e);var t,r,n,i=M(o);function o(e,t,r,n,a,s,l){var u;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(u=i.call(this,e,t,r,n,a,s)).localAssetManager=l,u}return t=o,(r=[{key:"getPlaybackInfo",value:function(e,t,r){var n=function(){return C.prototype.getPlaybackInfo.call(i,e,t,r)};if(V(e))return this.localAssetManager.getLocalItem(this.serverId(),W(e)).then((function(e){return{MediaSources:e.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}))}}),n);var i=this;return this.localAssetManager.getLocalItem(this.serverId(),e).then((function(o){if(o){var a=o.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}));return i.localAssetManager.fileExists(o.LocalPath).then((function(n){if(n){var o={MediaSources:a};return Promise.resolve(o)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}},{key:"getItems",value:function(e,t){var r,n=this.serverInfo();if(n&&"localview"===t.ParentId)return this.getLocalFolders(n.Id,e).then((function(e){var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(n&&t&&(V(t.ParentId)||V(t.SeriesId)||V(t.SeasonId)||G(t.ParentId)||V(t.AlbumIds)))return this.localAssetManager.getViewItems(n.Id,e,t).then((function(e){e.forEach((function(e){K(e)}));var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(t&&t.ExcludeItemIds&&t.ExcludeItemIds.length){var i=t.ExcludeItemIds.split(",");for(r=0;r0?Promise.resolve(r[0]):Promise.reject()})):V(t)&&(r=this.serverInfo())?this.localAssetManager.getLocalItem(r.Id,W(t)).then((function(e){return K(e.Item),Promise.resolve(e.Item)})):C.prototype.getItem.call(this,e,t)}},{key:"getLocalFolders",value:function(e){var t=this.serverInfo();return e=e||t.UserId,this.localAssetManager.getViews(t.Id,e)}},{key:"getNextUpEpisodes",value:function(e){return e.SeriesId&&V(e.SeriesId)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getNextUpEpisodes.call(this,e)}},{key:"getSeasons",value:function(e,t){return V(e)?(t.SeriesId=e,t.IncludeItemTypes="Season",this.getItems(this.getCurrentUserId(),t)):C.prototype.getSeasons.call(this,e,t)}},{key:"getEpisodes",value:function(e,t){return V(t.SeasonId)||V(t.seasonId)||V(e)?(t.SeriesId=e,t.IncludeItemTypes="Episode",this.getItems(this.getCurrentUserId(),t)):C.prototype.getEpisodes.call(this,e,t)}},{key:"getLatestOfflineItems",value:function(e){e.SortBy="DateCreated",e.SortOrder="Descending";var t=this.serverInfo();return t?this.localAssetManager.getViewItems(t.Id,null,e).then((function(e){return e.forEach((function(e){K(e)})),Promise.resolve(e)})):Promise.resolve([])}},{key:"getThemeMedia",value:function(e,t,r){return G(t)||V(t)||B(t)?Promise.reject():C.prototype.getThemeMedia.call(this,e,t,r)}},{key:"getSpecialFeatures",value:function(e,t){return V(t)?Promise.resolve([]):C.prototype.getSpecialFeatures.call(this,e,t)}},{key:"getSimilarItems",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getSimilarItems.call(this,e,t)}},{key:"updateFavoriteStatus",value:function(e,t,r){return V(t)?Promise.resolve():C.prototype.updateFavoriteStatus.call(this,e,t,r)}},{key:"getScaledImageUrl",value:function(e,t){if(V(e)||t&&t.itemid&&V(t.itemid)){var r=this.serverInfo(),n=W(e);return this.localAssetManager.getImageUrl(r.Id,n,t)}return C.prototype.getScaledImageUrl.call(this,e,t)}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");return V(e.ItemId)?Promise.resolve():C.prototype.reportPlaybackStart.call(this,e)}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo();if(t){var r=this;return this.localAssetManager.getLocalItem(t.Id,W(e.ItemId)).then((function(t){var n=t.Item;return"Video"===n.MediaType||"AudioBook"===n.Type?(n.UserData=n.UserData||{},n.UserData.PlaybackPositionTicks=e.PositionTicks,n.UserData.PlayedPercentage=Math.min(n.RunTimeTicks?(e.PositionTicks||0)/n.RunTimeTicks*100:0,100),r.localAssetManager.addOrUpdateLocalItem(t)):Promise.resolve()}))}return Promise.resolve()}return C.prototype.reportPlaybackProgress.call(this,e)}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo(),r={Date:(new Date).getTime(),ItemId:W(e.ItemId),PositionTicks:e.PositionTicks,ServerId:t.Id,Type:0,UserId:this.getCurrentUserId()};return this.localAssetManager.recordUserAction(r)}return C.prototype.reportPlaybackStopped.call(this,e)}},{key:"getIntros",value:function(e){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getIntros.call(this,e)}},{key:"getInstantMixFromItem",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getInstantMixFromItem.call(this,e,t)}},{key:"getItemDownloadUrl",value:function(e){if(V(e)){var t=this.serverInfo();if(t)return this.localAssetManager.getLocalItem(t.Id,W(e)).then((function(e){return Promise.resolve(e.LocalPath)}))}return C.prototype.getItemDownloadUrl.call(this,e)}}])&&R(t.prototype,r),n&&R(t,n),o}(C);function Y(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};!1!==t.reportCapabilities&&e.reportCapabilities(s),e.enableAutomaticBitrateDetection=t.enableAutomaticBitrateDetection,!1!==t.enableWebSocket&&(console.log("calling apiClient.ensureWebSocket"),e.ensureWebSocket())}function d(e,t,r){return l._getOrAddApiClient(e,t),(l.onLocalUserSignedIn?l.onLocalUserSignedIn.call(l,r):Promise.resolve()).then((function(){i.trigger(l,"localusersignedin",[r])}))}function h(e,t){return ae({type:"GET",url:ie(t,"System/Info"),dataType:"json",headers:{"X-MediaBrowser-Token":e.AccessToken}}).then((function(t){return ne(e,t),Promise.resolve()}),(function(){return e.UserId=null,e.AccessToken=null,Promise.resolve()}))}function v(e){var t={serverId:(e.serverInfo()||{}).Id};return e.logout().then((function(){i.trigger(l,"localusersignedout",[t])}),(function(){i.trigger(l,"localusersignedout",[t])}))}function f(e){if(e.Address&&e.EndpointAddress){var t=e.EndpointAddress.split(":")[0],r=e.Address.split(":");if(r.length>1){var n=r[r.length-1];isNaN(parseInt(n))||(t+=":".concat(n))}return le(t)}return null}function g(e){var t=[],r=[];return!e.manualAddressOnly&&e.LocalAddress&&-1===r.indexOf(e.LocalAddress)&&(t.push({url:e.LocalAddress,mode:Z,timeout:0}),r.push(t[t.length-1].url)),e.ManualAddress&&-1===r.indexOf(e.ManualAddress)&&(t.push({url:e.ManualAddress,mode:ee,timeout:100}),r.push(t[t.length-1].url)),!e.manualAddressOnly&&e.RemoteAddress&&-1===r.indexOf(e.RemoteAddress)&&(t.push({url:e.RemoteAddress,mode:$,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(e,r){var n={};n.numAddresses=t.length,n.rejects=0,t.map((function(t){setTimeout((function(){n.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+e),ae({url:ie(e,"system/info/public"),timeout:2e4,type:"GET",dataType:"json"}).then((function(i){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+e),n({url:e,connectionMode:t,data:i}))}),(function(){console.log("Reconnect failed to "+e),r.resolved||(r.rejects++,r.rejects>=r.numAddresses&&i())}))}(t.url,t.mode,n,e,r)}),t.timeout)}))}))}function p(e,t){var r={ManualAddress:e,LastConnectionMode:ee};return l.connectToServer(r,t).then((function(e){return"Unavailable"===e.State?Promise.reject():e}))}this._apiClients=[],l._minServerVersion="3.2.33",l.appVersion=function(){return n},l.appName=function(){return r},l.capabilities=function(){return s},l.deviceId=function(){return a},l.credentialProvider=function(){return t},l.getServerInfo=function(e){return t.credentials().Servers.filter((function(t){return t.Id===e}))[0]},l.getLastUsedServer=function(){var e=t.credentials().Servers;return e.sort((function(e,t){return(t.DateLastAccessed||0)-(e.DateLastAccessed||0)})),e.length?e[0]:null},l.addApiClient=function(e){l._apiClients.push(e);var r=t.credentials().Servers.filter((function(t){return ue(t.ManualAddress,e.serverAddress())||ue(t.LocalAddress,e.serverAddress())||ue(t.RemoteAddress,e.serverAddress())})),n=r.length?r[0]:e.serverInfo();if(n.DateLastAccessed=(new Date).getTime(),n.LastConnectionMode=ee,n.ManualAddress=e.serverAddress(),e.manualAddressOnly&&(n.manualAddressOnly=!0),e.serverInfo(n),e.onAuthenticated=function(e,t){return u(e,t,{},!0)},!r.length){var o=t.credentials();o.Servers=[n],t.credentials(o)}i.trigger(l,"apiclientcreated",[e])},l.clearData=function(){console.log("connection manager clearing data");var e=t.credentials();e.Servers=[],t.credentials(e)},l._getOrAddApiClient=function(e,t){var s=l.getApiClient(e.Id);return s||(s=new C(t,r,n,o,a),l._apiClients.push(s),s.serverInfo(e),s.onAuthenticated=function(e,t){return u(e,t,{},!0)},i.trigger(l,"apiclientcreated",[s])),console.log("returning instance from getOrAddApiClient"),s},l.getOrCreateApiClient=function(e){var r=t.credentials().Servers.filter((function(t){return ue(t.Id,e)}));if(!r.length)throw new Error("Server not found: ".concat(e));var n=r[0];return l._getOrAddApiClient(n,te(n,n.LastConnectionMode))},l.user=function(e){return new Promise((function(t,r){var n;e&&e.getCurrentUserId()&&e&&e.getCurrentUserId()&&e.getCurrentUser().then((function(e){var r=function(e){return e&&e.PrimaryImageTag?{url:l.getApiClient(e).getUserImageUrl(e.Id,{tag:e.PrimaryImageTag,type:"Primary"}),supportsParams:!0}:{url:null,supportsParams:!1}}(n=e);t({localUser:n,name:n?n.Name:null,imageUrl:r.url,supportsImageParams:r.supportsParams})}))}))},l.logout=function(){for(var e=[],r=0,n=l._apiClients.length;ro)return 1}return 0}(l.minServerVersion(),o.Version)?(console.log("minServerVersion requirement not met. Server version: "+o.Version),n({State:"ServerUpdateNeeded",Servers:[e]})):e.Id&&o.Id!==e.Id?(console.log("http request succeeded, but found a different server Id than what was expected"),re(0,n)):function e(r,n,o,a,s,u){var v=arguments.length>6&&void 0!==arguments[6]?arguments[6]:{},f=t.credentials();if(!1===v.enableAutoLogin)r.UserId=null,r.AccessToken=null;else if(r.AccessToken&&s)return void h(r,a).then((function(){e(r,n,o,a,!1,u,v)}));ne(r,n),r.LastConnectionMode=o,!1!==v.updateDateLastAccessed&&(r.DateLastAccessed=(new Date).getTime());t.addOrUpdateServer(f.Servers,r),t.credentials(f);var g={Servers:[]};g.ApiClient=l._getOrAddApiClient(r,a),g.ApiClient.setSystemInfo(n),g.State=r.AccessToken&&!1!==v.enableAutoLogin?"SignedIn":"ServerSignIn",g.Servers.push(r),g.ApiClient.enableAutomaticBitrateDetection=v.enableAutomaticBitrateDetection,g.ApiClient.updateServerInfo(r,a);var p=function(){u(g),i.trigger(l,"connected",[g])};"SignedIn"===g.State?(c(g.ApiClient,v),g.ApiClient.getCurrentUser().then((function(e){d(r,a,e).then(p,p)}),p)):p()}(e,o,s,a,!0,n,r)}),(function(){re(0,n)}))}))},l.connectToAddress=function(e,t){if(!e)return Promise.reject();e=le(e);var r=[];/^[^:]+:\/\//.test(e)?r.push(e):(r.push("https://".concat(e)),r.push("http://".concat(e)));var n=0;return p(r[n],t).catch((function e(){return console.log("connectToAddress ".concat(r[n]," failed")),++n {\n c.apply(obj, eventArgs);\n });\n }\n};\n","function onCachePutFail(e) {\n console.log(e);\n}\n\nfunction updateCache(instance) {\n const cache = instance.cache;\n if (cache) {\n cache.put('data', new Response(JSON.stringify(instance.localData))).catch(onCachePutFail);\n }\n}\n\nfunction onCacheOpened(result) {\n this.cache = result;\n this.localData = {};\n}\n\nclass AppStore {\n constructor() {\n try {\n if (self.caches) {\n caches.open('embydata').then(onCacheOpened.bind(this));\n }\n } catch (err) {\n console.log(`Error opening cache: ${err}`);\n }\n }\n\n setItem(name, value) {\n localStorage.setItem(name, value);\n const localData = this.localData;\n if (localData) {\n const changed = localData[name] !== value;\n if (changed) {\n localData[name] = value;\n updateCache(this);\n }\n }\n }\n\n static getInstance() {\n if (!AppStore.instance) {\n AppStore.instance = new AppStore();\n }\n\n return AppStore.instance;\n }\n\n getItem(name) {\n return localStorage.getItem(name);\n }\n\n removeItem(name) {\n localStorage.removeItem(name);\n const localData = this.localData;\n if (localData) {\n localData[name] = null;\n delete localData[name];\n updateCache(this);\n }\n }\n}\n\nexport default AppStore.getInstance();\n","import events from './events';\nimport appStorage from './appStorage';\n\n/** Report rate limits in ms for different events */\nconst reportRateLimits = {\n timeupdate: 10000,\n volumechange: 3000\n};\n\nfunction redetectBitrate(instance) {\n stopBitrateDetection(instance);\n\n if (instance.accessToken() && instance.enableAutomaticBitrateDetection !== false) {\n setTimeout(redetectBitrateInternal.bind(instance), 6000);\n }\n}\n\nfunction redetectBitrateInternal() {\n if (this.accessToken()) {\n this.detectBitrate();\n }\n}\n\nfunction stopBitrateDetection(instance) {\n if (instance.detectTimeout) {\n clearTimeout(instance.detectTimeout);\n }\n}\n\nfunction replaceAll(originalString, strReplace, strWith) {\n const reg = new RegExp(strReplace, 'ig');\n return originalString.replace(reg, strWith);\n}\n\nfunction onFetchFail(instance, url, response) {\n events.trigger(instance, 'requestfail', [\n {\n url,\n status: response.status,\n errorCode: response.headers ? response.headers.get('X-Application-Error-Code') : null\n }\n ]);\n}\n\nfunction paramsToString(params) {\n const values = [];\n\n for (const key in params) {\n const value = params[key];\n\n if (value !== null && value !== undefined && value !== '') {\n values.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n }\n }\n return values.join('&');\n}\n\nfunction fetchWithTimeout(url, options, timeoutMs) {\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(reject, timeoutMs);\n\n options = options || {};\n options.credentials = 'same-origin';\n\n fetch(url, options)\n .then((response) => {\n clearTimeout(timeout);\n resolve(response);\n })\n .catch((error) => {\n clearTimeout(timeout);\n reject(error);\n });\n });\n}\n\nfunction getFetchPromise(request) {\n const headers = request.headers || {};\n\n if (request.dataType === 'json') {\n headers.accept = 'application/json';\n }\n\n const fetchRequest = {\n headers,\n method: request.type,\n credentials: 'same-origin'\n };\n\n let contentType = request.contentType;\n\n if (request.data) {\n if (typeof request.data === 'string') {\n fetchRequest.body = request.data;\n } else {\n fetchRequest.body = paramsToString(request.data);\n\n contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';\n }\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n\n fetchRequest.dataType=\"json\"\n fetchRequest.contentType=\"application/json\"\n fetchRequest.url=request.url;\n delete fetchRequest.credentials\n console.log('na hoffe');\n console.log(fetchRequest);\n\n\n if (!request.timeout) {\n return fetch(request.url, fetchRequest);\n }\n\n return fetchWithTimeout(request.url, fetchRequest, request.timeout);\n}\n\nfunction cancelReportPlaybackProgressPromise(instance) {\n if (typeof instance.reportPlaybackProgressCancel === 'function') instance.reportPlaybackProgressCancel();\n}\n\n/**\n * Creates a new api client instance\n * @param {String} serverAddress\n * @param {String} appName\n * @param {String} appVersion\n */\nclass ApiClient {\n constructor(serverAddress, appName, appVersion, deviceName, deviceId) {\n if (!serverAddress) {\n throw new Error('Must supply a serverAddress');\n }\n\n console.debug(`ApiClient serverAddress: ${serverAddress}`);\n console.debug(`ApiClient appName: ${appName}`);\n console.debug(`ApiClient appVersion: ${appVersion}`);\n console.debug(`ApiClient deviceName: ${deviceName}`);\n console.debug(`ApiClient deviceId: ${deviceId}`);\n\n this._serverInfo = {};\n this._serverAddress = serverAddress;\n this._deviceId = deviceId;\n this._deviceName = deviceName;\n this._appName = appName;\n this._appVersion = appVersion;\n }\n\n appName() {\n return this._appName;\n }\n\n setRequestHeaders(headers) {\n const currentServerInfo = this.serverInfo();\n const appName = this._appName;\n const accessToken = currentServerInfo.AccessToken;\n\n const values = [];\n\n if (appName) {\n values.push(`Client=\"${appName}\"`);\n }\n\n if (this._deviceName) {\n values.push(`Device=\"${this._deviceName}\"`);\n }\n\n if (this._deviceId) {\n values.push(`DeviceId=\"${this._deviceId}\"`);\n }\n\n if (this._appVersion) {\n values.push(`Version=\"${this._appVersion}\"`);\n }\n\n if (accessToken) {\n values.push(`Token=\"${accessToken}\"`);\n }\n\n if (values.length) {\n const auth = `MediaBrowser ${values.join(', ')}`;\n //headers.Authorization = auth;\n headers['X-Emby-Authorization'] = auth;\n }\n }\n\n appVersion() {\n return this._appVersion;\n }\n\n deviceName() {\n return this._deviceName;\n }\n\n deviceId() {\n return this._deviceId;\n }\n\n /**\n * Gets the server address.\n */\n serverAddress(val) {\n if (val != null) {\n if (val.toLowerCase().indexOf('http') !== 0) {\n throw new Error(`Invalid url: ${val}`);\n }\n\n const changed = val !== this._serverAddress;\n\n this._serverAddress = val;\n\n this.onNetworkChange();\n\n if (changed) {\n events.trigger(this, 'serveraddresschanged');\n }\n }\n\n return this._serverAddress;\n }\n\n onNetworkChange() {\n this.lastDetectedBitrate = 0;\n this.lastDetectedBitrateTime = 0;\n setSavedEndpointInfo(this, null);\n\n redetectBitrate(this);\n }\n\n /**\n * Creates an api url based on a handler name and query string parameters\n * @param {String} name\n * @param {Object} params\n */\n getUrl(name, params, serverAddress) {\n if (!name) {\n throw new Error('Url name cannot be empty');\n }\n\n let url = serverAddress || this._serverAddress;\n\n if (!url) {\n throw new Error('serverAddress is yet not set');\n }\n\n if (name.charAt(0) !== '/') {\n url += '/';\n }\n\n url += name;\n\n if (params) {\n params = paramsToString(params);\n if (params) {\n url += `?${params}`;\n }\n }\n\n return url;\n }\n\n fetchWithFailover(request, enableReconnection) {\n console.log(`Requesting ${request.url}`);\n\n request.timeout = 30000;\n const instance = this;\n\n return getFetchPromise(request)\n .then((response) => {\n instance.lastFetch = new Date().getTime();\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else if (\n request.dataType === 'text' ||\n (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0\n ) {\n return response.text();\n } else {\n return response;\n }\n } else {\n onFetchFail(instance, request.url, response);\n return Promise.reject(response);\n }\n })\n .catch((error) => {\n if (error) {\n console.log(`Request failed to ${request.url} ${error.toString()}`);\n } else {\n console.log(`Request timed out to ${request.url}`);\n }\n\n // http://api.jquery.com/jQuery.ajax/\n if ((!error || !error.status) && enableReconnection) {\n console.log('Attempting reconnection');\n\n const previousServerAddress = instance.serverAddress();\n\n return tryReconnect(instance)\n .then(() => {\n console.log('Reconnect succeesed');\n request.url = request.url.replace(previousServerAddress, instance.serverAddress());\n\n return instance.fetchWithFailover(request, false);\n })\n .catch((innerError) => {\n console.log('Reconnect failed');\n onFetchFail(instance, request.url, {});\n throw innerError;\n });\n } else {\n console.log('Reporting request failure');\n\n onFetchFail(instance, request.url, {});\n throw error;\n }\n });\n }\n\n /**\n * Wraps around jQuery ajax methods to add additional info to the request.\n */\n fetch(request, includeAuthorization) {\n if (!request) {\n return Promise.reject('Request cannot be null');\n }\n\n request.headers = request.headers || {};\n\n if (includeAuthorization !== false) {\n this.setRequestHeaders(request.headers);\n }\n\n if (this.enableAutomaticNetworking === false || request.type !== 'GET') {\n console.log(`Requesting url without automatic networking: ${request.url}`);\n\n const instance = this;\n return getFetchPromise(request)\n .then((response) => {\n instance.lastFetch = new Date().getTime();\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else if (\n request.dataType === 'text' ||\n (response.headers.get('Content-Type') || '').toLowerCase().indexOf('text/') === 0\n ) {\n return response.text();\n } else {\n return response;\n }\n } else {\n onFetchFail(instance, request.url, response);\n return Promise.reject(response);\n }\n })\n .catch((error) => {\n onFetchFail(instance, request.url, {});\n return Promise.reject(error);\n });\n }\n\n return this.fetchWithFailover(request, true);\n }\n\n setAuthenticationInfo(accessKey, userId) {\n this._currentUser = null;\n\n this._serverInfo.AccessToken = accessKey;\n this._serverInfo.UserId = userId;\n redetectBitrate(this);\n }\n\n serverInfo(info) {\n if (info) {\n this._serverInfo = info;\n }\n\n return this._serverInfo;\n }\n\n /**\n * Gets or sets the current user id.\n */\n getCurrentUserId() {\n return this._serverInfo.UserId;\n }\n\n accessToken() {\n return this._serverInfo.AccessToken;\n }\n\n serverId() {\n return this.serverInfo().Id;\n }\n\n serverName() {\n return this.serverInfo().Name;\n }\n\n /**\n * Wraps around jQuery ajax methods to add additional info to the request.\n */\n ajax(request, includeAuthorization) {\n if (!request) {\n return Promise.reject('Request cannot be null');\n }\n\n return this.fetch(request, includeAuthorization);\n }\n\n /**\n * Gets or sets the current user id.\n */\n getCurrentUser(enableCache) {\n if (this._currentUser) {\n return Promise.resolve(this._currentUser);\n }\n\n const userId = this.getCurrentUserId();\n\n if (!userId) {\n return Promise.reject();\n }\n\n const instance = this;\n let user;\n\n const serverPromise = this.getUser(userId)\n .then((userObject) => {\n appStorage.setItem(`user-${userObject.Id}-${userObject.ServerId}`, JSON.stringify(userObject));\n\n instance._currentUser = userObject;\n return userObject;\n })\n .catch((response) => {\n // if timed out, look for cached value\n if (!response.status) {\n if (userId && instance.accessToken()) {\n user = getCachedUser(instance, userId);\n if (user) {\n return Promise.resolve(user);\n }\n }\n }\n\n throw response;\n });\n\n if (!this.lastFetch && enableCache !== false) {\n user = getCachedUser(instance, userId);\n if (user) {\n return Promise.resolve(user);\n }\n }\n\n return serverPromise;\n }\n\n isLoggedIn() {\n const info = this.serverInfo();\n if (info) {\n if (info.UserId && info.AccessToken) {\n return true;\n }\n }\n\n return false;\n }\n\n /**\n * Logout current user\n */\n logout() {\n stopBitrateDetection(this);\n this.closeWebSocket();\n\n const done = () => {\n const info = this.serverInfo();\n if (info && info.UserId && info.Id) {\n appStorage.removeItem(`user-${info.UserId}-${info.Id}`);\n }\n this.setAuthenticationInfo(null, null);\n };\n\n if (this.accessToken()) {\n const url = this.getUrl('Sessions/Logout');\n\n return this.ajax({\n type: 'POST',\n url\n }).then(done, done);\n }\n\n done();\n return Promise.resolve();\n }\n\n /**\n * Authenticates a user\n * @param {String} name\n * @param {String} password\n */\n authenticateUserByName(name, password) {\n if (!name) {\n return Promise.reject();\n }\n\n const url = this.getUrl('Users/authenticatebyname');\n\n return new Promise((resolve, reject) => {\n const postData = {\n Username: name,\n Pw: password || ''\n };\n\n this.ajax({\n type: 'POST',\n url: url,\n data: JSON.stringify(postData),\n dataType: 'json',\n contentType: 'application/json'\n })\n .then((result) => {\n const afterOnAuthenticated = () => {\n redetectBitrate(this);\n resolve(result);\n };\n\n if (this.onAuthenticated) {\n this.onAuthenticated(this, result).then(afterOnAuthenticated);\n } else {\n afterOnAuthenticated();\n }\n })\n .catch(reject);\n });\n }\n\n ensureWebSocket() {\n if (this.isWebSocketOpenOrConnecting() || !this.isWebSocketSupported()) {\n return;\n }\n\n try {\n this.openWebSocket();\n } catch (err) {\n console.log(`Error opening web socket: ${err}`);\n }\n }\n\n openWebSocket() {\n const accessToken = this.accessToken();\n\n if (!accessToken) {\n throw new Error('Cannot open web socket without access token.');\n }\n\n let url = this.getUrl('socket');\n\n url = replaceAll(url, 'emby/socket', 'embywebsocket');\n url = replaceAll(url, 'https:', 'wss:');\n url = replaceAll(url, 'http:', 'ws:');\n\n url += `?api_key=${accessToken}`;\n url += `&deviceId=${this.deviceId()}`;\n\n console.log(`opening web socket with url: ${url}`);\n\n const webSocket = new WebSocket(url);\n\n webSocket.onmessage = onWebSocketMessage.bind(this);\n webSocket.onopen = onWebSocketOpen.bind(this);\n webSocket.onerror = onWebSocketError.bind(this);\n setSocketOnClose(this, webSocket);\n\n this._webSocket = webSocket;\n }\n\n closeWebSocket() {\n const socket = this._webSocket;\n\n if (socket && socket.readyState === WebSocket.OPEN) {\n socket.close();\n }\n }\n\n sendWebSocketMessage(name, data) {\n console.log(`Sending web socket message: ${name}`);\n\n let msg = { MessageType: name };\n\n if (data) {\n msg.Data = data;\n }\n\n msg = JSON.stringify(msg);\n\n this._webSocket.send(msg);\n }\n\n sendMessage(name, data) {\n if (this.isWebSocketOpen()) {\n this.sendWebSocketMessage(name, data);\n }\n }\n\n isMessageChannelOpen() {\n return this.isWebSocketOpen();\n }\n\n isWebSocketOpen() {\n const socket = this._webSocket;\n\n if (socket) {\n return socket.readyState === WebSocket.OPEN;\n }\n return false;\n }\n\n isWebSocketOpenOrConnecting() {\n const socket = this._webSocket;\n\n if (socket) {\n return socket.readyState === WebSocket.OPEN || socket.readyState === WebSocket.CONNECTING;\n }\n return false;\n }\n\n get(url) {\n return this.ajax({\n type: 'GET',\n url\n });\n }\n\n getJSON(url, includeAuthorization) {\n return this.fetch(\n {\n url,\n type: 'GET',\n dataType: 'json',\n headers: {\n accept: 'application/json'\n }\n },\n includeAuthorization\n );\n }\n\n updateServerInfo(server, serverUrl) {\n if (server == null) {\n throw new Error('server cannot be null');\n }\n\n this.serverInfo(server);\n\n if (!serverUrl) {\n throw new Error(`serverUrl cannot be null. serverInfo: ${JSON.stringify(server)}`);\n }\n console.log(`Setting server address to ${serverUrl}`);\n this.serverAddress(serverUrl);\n }\n\n isWebSocketSupported() {\n try {\n return WebSocket != null;\n } catch (err) {\n return false;\n }\n }\n\n clearAuthenticationInfo() {\n this.setAuthenticationInfo(null, null);\n }\n\n encodeName(name) {\n name = name.split('/').join('-');\n name = name.split('&').join('-');\n name = name.split('?').join('-');\n\n const val = paramsToString({ name });\n return val.substring(val.indexOf('=') + 1).replace(\"'\", '%27');\n }\n\n /**\n * Gets the server time as a UTC formatted string.\n * @returns {Promise} Promise that it's fulfilled on request completion.\n * @since 10.6.0\n */\n getServerTime() {\n const url = this.getUrl('GetUTCTime');\n\n return this.ajax({\n type: 'GET',\n url: url\n });\n }\n\n getDownloadSpeed(byteSize) {\n const url = this.getUrl('Playback/BitrateTest', {\n Size: byteSize\n });\n\n const now = new Date().getTime();\n\n return this.ajax({\n type: 'GET',\n url,\n timeout: 5000\n }).then(() => {\n const responseTimeSeconds = (new Date().getTime() - now) / 1000;\n const bytesPerSecond = byteSize / responseTimeSeconds;\n const bitrate = Math.round(bytesPerSecond * 8);\n\n return bitrate;\n });\n }\n\n detectBitrate(force) {\n if (\n !force &&\n this.lastDetectedBitrate &&\n new Date().getTime() - (this.lastDetectedBitrateTime || 0) <= 3600000\n ) {\n return Promise.resolve(this.lastDetectedBitrate);\n }\n\n const instance = this;\n\n return this.getEndpointInfo().then(\n (info) => detectBitrateWithEndpointInfo(instance, info),\n (info) => detectBitrateWithEndpointInfo(instance, {})\n );\n }\n\n /**\n * Gets an item from the server\n * Omit itemId to get the root folder.\n */\n getItem(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = userId ? this.getUrl(`Users/${userId}/Items/${itemId}`) : this.getUrl(`Items/${itemId}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the root folder from the server\n */\n getRootFolder(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/Root`);\n\n return this.getJSON(url);\n }\n\n getNotificationSummary(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Notifications/${userId}/Summary`);\n\n return this.getJSON(url);\n }\n\n getNotifications(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Notifications/${userId}`, options || {});\n\n return this.getJSON(url);\n }\n\n markNotificationsRead(userId, idList, isRead) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!idList) {\n throw new Error('null idList');\n }\n\n const suffix = isRead ? 'Read' : 'Unread';\n\n const params = {\n UserId: userId,\n Ids: idList.join(',')\n };\n\n const url = this.getUrl(`Notifications/${userId}/${suffix}`, params);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getRemoteImageProviders(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages/Providers`, options);\n\n return this.getJSON(url);\n }\n\n getAvailableRemoteImages(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages`, options);\n\n return this.getJSON(url);\n }\n\n downloadRemoteImage(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const urlPrefix = getRemoteImagePrefix(this, options);\n\n const url = this.getUrl(`${urlPrefix}/RemoteImages/Download`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getRecordingFolders(userId) {\n const url = this.getUrl('LiveTv/Recordings/Folders', { userId: userId });\n\n return this.getJSON(url);\n }\n\n getLiveTvInfo(options) {\n const url = this.getUrl('LiveTv/Info', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvGuideInfo(options) {\n const url = this.getUrl('LiveTv/GuideInfo', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvChannel(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Channels/${id}`, options);\n\n return this.getJSON(url);\n }\n\n getLiveTvChannels(options) {\n const url = this.getUrl('LiveTv/Channels', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvPrograms(options = {}) {\n if (options.channelIds && options.channelIds.length > 1800) {\n return this.ajax({\n type: 'POST',\n url: this.getUrl('LiveTv/Programs'),\n data: JSON.stringify(options),\n contentType: 'application/json',\n dataType: 'json'\n });\n } else {\n return this.ajax({\n type: 'GET',\n url: this.getUrl('LiveTv/Programs', options),\n dataType: 'json'\n });\n }\n }\n\n getLiveTvRecommendedPrograms(options = {}) {\n return this.ajax({\n type: 'GET',\n url: this.getUrl('LiveTv/Programs/Recommended', options),\n dataType: 'json'\n });\n }\n\n getLiveTvRecordings(options) {\n const url = this.getUrl('LiveTv/Recordings', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingSeries(options) {\n const url = this.getUrl('LiveTv/Recordings/Series', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingGroups(options) {\n const url = this.getUrl('LiveTv/Recordings/Groups', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvRecordingGroup(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Recordings/Groups/${id}`);\n\n return this.getJSON(url);\n }\n\n getLiveTvRecording(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Recordings/${id}`, options);\n\n return this.getJSON(url);\n }\n\n getLiveTvProgram(id, userId) {\n if (!id) {\n throw new Error('null id');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`LiveTv/Programs/${id}`, options);\n\n return this.getJSON(url);\n }\n\n deleteLiveTvRecording(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Recordings/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n cancelLiveTvTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n getLiveTvTimers(options) {\n const url = this.getUrl('LiveTv/Timers', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${id}`);\n\n return this.getJSON(url);\n }\n\n getNewLiveTvTimerDefaults(options = {}) {\n const url = this.getUrl('LiveTv/Timers/Defaults', options);\n\n return this.getJSON(url);\n }\n\n createLiveTvTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl('LiveTv/Timers');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n updateLiveTvTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`LiveTv/Timers/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n resetLiveTvTuner(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/Tuners/${id}/Reset`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getLiveTvSeriesTimers(options) {\n const url = this.getUrl('LiveTv/SeriesTimers', options || {});\n\n return this.getJSON(url);\n }\n\n getLiveTvSeriesTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${id}`);\n\n return this.getJSON(url);\n }\n\n cancelLiveTvSeriesTimer(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n createLiveTvSeriesTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl('LiveTv/SeriesTimers');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n updateLiveTvSeriesTimer(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`LiveTv/SeriesTimers/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n getRegistrationInfo(feature) {\n const url = this.getUrl(`Registrations/${feature}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server status\n */\n getSystemInfo(itemId) {\n const url = this.getUrl('System/Info');\n\n const instance = this;\n\n return this.getJSON(url).then((info) => {\n instance.setSystemInfo(info);\n return Promise.resolve(info);\n });\n }\n\n getSyncStatus() {\n const url = this.getUrl('Sync/' + itemId + '/Status');\n\n return this.ajax({\n url: url,\n type: 'POST',\n dataType: 'json',\n contentType: 'application/json',\n data: JSON.stringify({\n TargetId: this.deviceId()\n })\n });\n }\n\n /**\n * Gets the current server status\n */\n getPublicSystemInfo() {\n const url = this.getUrl('System/Info/Public');\n\n const instance = this;\n\n return this.getJSON(url).then((info) => {\n instance.setSystemInfo(info);\n return Promise.resolve(info);\n });\n }\n\n getInstantMixFromItem(itemId, options) {\n const url = this.getUrl(`Items/${itemId}/InstantMix`, options);\n\n return this.getJSON(url);\n }\n\n getEpisodes(itemId, options) {\n const url = this.getUrl(`Shows/${itemId}/Episodes`, options);\n\n return this.getJSON(url);\n }\n\n getDisplayPreferences(id, userId, app) {\n const url = this.getUrl(`DisplayPreferences/${id}`, {\n userId,\n client: app\n });\n\n return this.getJSON(url);\n }\n\n updateDisplayPreferences(id, obj, userId, app) {\n const url = this.getUrl(`DisplayPreferences/${id}`, {\n userId,\n client: app\n });\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(obj),\n contentType: 'application/json'\n });\n }\n\n getSeasons(itemId, options) {\n const url = this.getUrl(`Shows/${itemId}/Seasons`, options);\n\n return this.getJSON(url);\n }\n\n getSimilarItems(itemId, options) {\n const url = this.getUrl(`Items/${itemId}/Similar`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all cultures known to the server\n */\n getCultures() {\n const url = this.getUrl('Localization/cultures');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all countries known to the server\n */\n getCountries() {\n const url = this.getUrl('Localization/countries');\n\n return this.getJSON(url);\n }\n\n getPlaybackInfo(itemId, options, deviceProfile) {\n const postData = {\n DeviceProfile: deviceProfile\n };\n\n return this.ajax({\n url: this.getUrl(`Items/${itemId}/PlaybackInfo`, options),\n type: 'POST',\n data: JSON.stringify(postData),\n contentType: 'application/json',\n dataType: 'json'\n });\n }\n\n getLiveStreamMediaInfo(liveStreamId) {\n const postData = {\n LiveStreamId: liveStreamId\n };\n\n return this.ajax({\n url: this.getUrl('LiveStreams/MediaInfo'),\n type: 'POST',\n data: JSON.stringify(postData),\n contentType: 'application/json',\n dataType: 'json'\n });\n }\n\n getIntros(itemId) {\n return this.getJSON(this.getUrl(`Users/${this.getCurrentUserId()}/Items/${itemId}/Intros`));\n }\n\n /**\n * Gets the directory contents of a path on the server\n */\n getDirectoryContents(path, options) {\n if (!path) {\n throw new Error('null path');\n }\n if (typeof path !== 'string') {\n throw new Error('invalid path');\n }\n\n options = options || {};\n\n options.path = path;\n\n const url = this.getUrl('Environment/DirectoryContents', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets shares from a network device\n */\n getNetworkShares(path) {\n if (!path) {\n throw new Error('null path');\n }\n\n const options = {};\n options.path = path;\n\n const url = this.getUrl('Environment/NetworkShares', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the parent of a given path\n */\n getParentPath(path) {\n if (!path) {\n throw new Error('null path');\n }\n\n const options = {};\n options.path = path;\n\n const url = this.getUrl('Environment/ParentPath', options);\n\n return this.ajax({\n type: 'GET',\n url,\n dataType: 'text'\n });\n }\n\n /**\n * Gets a list of physical drives from the server\n */\n getDrives() {\n const url = this.getUrl('Environment/Drives');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a list of network devices from the server\n */\n getNetworkDevices() {\n const url = this.getUrl('Environment/NetworkDevices');\n\n return this.getJSON(url);\n }\n\n /**\n * Cancels a package installation\n */\n cancelPackageInstallation(installationId) {\n if (!installationId) {\n throw new Error('null installationId');\n }\n\n const url = this.getUrl(`Packages/Installing/${installationId}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Refreshes metadata for an item\n */\n refreshItem(itemId, options) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}/Refresh`, options || {});\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Installs or updates a new plugin\n */\n installPlugin(name, guid, version) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {\n AssemblyGuid: guid\n };\n\n if (version) {\n options.version = version;\n }\n\n const url = this.getUrl(`Packages/Installed/${name}`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Instructs the server to perform a restart.\n */\n restartServer() {\n const url = this.getUrl('System/Restart');\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Instructs the server to perform a shutdown.\n */\n shutdownServer() {\n const url = this.getUrl('System/Shutdown');\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets information about an installable package\n */\n getPackageInfo(name, guid) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {\n AssemblyGuid: guid\n };\n\n const url = this.getUrl(`Packages/${name}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the virtual folder list\n */\n getVirtualFolders() {\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all the paths of the locations in the physical root.\n */\n getPhysicalPaths() {\n const url = this.getUrl('Library/PhysicalPaths');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getServerConfiguration() {\n const url = this.getUrl('System/Configuration');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getDevicesOptions() {\n const url = this.getUrl('System/Configuration/devices');\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the current server configuration\n */\n getContentUploadHistory() {\n const url = this.getUrl('Devices/CameraUploads', {\n DeviceId: this.deviceId()\n });\n\n return this.getJSON(url);\n }\n\n getNamedConfiguration(name) {\n const url = this.getUrl(`System/Configuration/${name}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets the server's scheduled tasks\n */\n getScheduledTasks(options = {}) {\n const url = this.getUrl('ScheduledTasks', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Starts a scheduled task\n */\n startScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/Running/${id}`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets a scheduled task\n */\n getScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/${id}`);\n\n return this.getJSON(url);\n }\n\n getNextUpEpisodes(options) {\n const url = this.getUrl('Shows/NextUp', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Stops a scheduled task\n */\n stopScheduledTask(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`ScheduledTasks/Running/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Gets the configuration of a plugin\n * @param {String} Id\n */\n getPluginConfiguration(id) {\n if (!id) {\n throw new Error('null Id');\n }\n\n const url = this.getUrl(`Plugins/${id}/Configuration`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a list of plugins that are available to be installed\n */\n getAvailablePlugins(options = {}) {\n options.PackageType = 'UserInstalled';\n\n const url = this.getUrl('Packages', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Uninstalls a plugin\n * @param {String} Id\n */\n uninstallPlugin(id) {\n if (!id) {\n throw new Error('null Id');\n }\n\n const url = this.getUrl(`Plugins/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Removes a virtual folder\n * @param {String} name\n */\n removeVirtualFolder(name, refreshLibrary) {\n if (!name) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n name\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Adds a virtual folder\n * @param {String} name\n */\n addVirtualFolder(name, type, refreshLibrary, libraryOptions) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (type) {\n options.collectionType = type;\n }\n\n options.refreshLibrary = refreshLibrary ? true : false;\n options.name = name;\n\n let url = 'Library/VirtualFolders';\n\n url = this.getUrl(url, options);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n LibraryOptions: libraryOptions\n }),\n contentType: 'application/json'\n });\n }\n\n updateVirtualFolderOptions(id, libraryOptions) {\n if (!id) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders/LibraryOptions';\n\n url = this.getUrl(url);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Id: id,\n LibraryOptions: libraryOptions\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Renames a virtual folder\n * @param {String} name\n */\n renameVirtualFolder(name, newName, refreshLibrary) {\n if (!name) {\n throw new Error('null name');\n }\n\n let url = 'Library/VirtualFolders/Name';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n newName,\n name\n });\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Adds an additional mediaPath to an existing virtual folder\n * @param {String} name\n */\n addMediaPath(virtualFolderName, mediaPath, networkSharePath, refreshLibrary) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!mediaPath) {\n throw new Error('null mediaPath');\n }\n\n let url = 'Library/VirtualFolders/Paths';\n\n const pathInfo = {\n Path: mediaPath\n };\n if (networkSharePath) {\n pathInfo.NetworkPath = networkSharePath;\n }\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false\n });\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Name: virtualFolderName,\n PathInfo: pathInfo\n }),\n contentType: 'application/json'\n });\n }\n\n updateMediaPath(virtualFolderName, pathInfo) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!pathInfo) {\n throw new Error('null pathInfo');\n }\n\n let url = 'Library/VirtualFolders/Paths/Update';\n\n url = this.getUrl(url);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify({\n Name: virtualFolderName,\n PathInfo: pathInfo\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Removes a media path from a virtual folder\n * @param {String} name\n */\n removeMediaPath(virtualFolderName, mediaPath, refreshLibrary) {\n if (!virtualFolderName) {\n throw new Error('null virtualFolderName');\n }\n\n if (!mediaPath) {\n throw new Error('null mediaPath');\n }\n\n let url = 'Library/VirtualFolders/Paths';\n\n url = this.getUrl(url, {\n refreshLibrary: refreshLibrary ? true : false,\n path: mediaPath,\n name: virtualFolderName\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Deletes a user\n * @param {String} id\n */\n deleteUser(id) {\n if (!id) {\n throw new Error('null id');\n }\n\n const url = this.getUrl(`Users/${id}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Deletes a user image\n * @param {String} userId\n * @param {String} imageType The type of image to delete, based on the server-side ImageType enum.\n */\n deleteUserImage(userId, imageType, imageIndex) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n let url = this.getUrl(`Users/${userId}/Images/${imageType}`);\n\n if (imageIndex != null) {\n url += `/${imageIndex}`;\n }\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n deleteItemImage(itemId, imageType, imageIndex) {\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n let url = this.getUrl(`Items/${itemId}/Images`);\n\n url += `/${imageType}`;\n\n if (imageIndex != null) {\n url += `/${imageIndex}`;\n }\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n deleteItem(itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}`);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n stopActiveEncodings(playSessionId) {\n const options = {\n deviceId: this.deviceId()\n };\n\n if (playSessionId) {\n options.PlaySessionId = playSessionId;\n }\n\n const url = this.getUrl('Videos/ActiveEncodings', options);\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n reportCapabilities(options) {\n const url = this.getUrl('Sessions/Capabilities/Full');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(options),\n contentType: 'application/json'\n });\n }\n\n updateItemImageIndex(itemId, imageType, imageIndex, newIndex) {\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n const options = { newIndex };\n\n const url = this.getUrl(`Items/${itemId}/Images/${imageType}/${imageIndex}/Index`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getItemImageInfos(itemId) {\n const url = this.getUrl(`Items/${itemId}/Images`);\n\n return this.getJSON(url);\n }\n\n getCriticReviews(itemId, options) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Items/${itemId}/CriticReviews`, options);\n\n return this.getJSON(url);\n }\n\n getItemDownloadUrl(itemId) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n const url = `Items/${itemId}/Download`;\n\n return this.getUrl(url, {\n api_key: this.accessToken()\n });\n }\n\n getSessions(options) {\n const url = this.getUrl('Sessions', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Uploads a user image\n * @param {String} userId\n * @param {String} imageType The type of image to delete, based on the server-side ImageType enum.\n * @param {Object} file The file from the input element\n */\n uploadUserImage(userId, imageType, file) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n if (!file) {\n throw new Error('File must be an image.');\n }\n\n if (!file.type.startsWith('image/')) {\n throw new Error('File must be an image.');\n }\n\n const instance = this;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => {\n reject();\n };\n\n reader.onabort = () => {\n reject();\n };\n\n // Closure to capture the file information.\n reader.onload = (e) => {\n // Split by a comma to remove the url: prefix\n const data = e.target.result.split(',')[1];\n\n const url = instance.getUrl(`Users/${userId}/Images/${imageType}`);\n\n instance\n .ajax({\n type: 'POST',\n url,\n data,\n contentType: `image/${file.name.substring(file.name.lastIndexOf('.') + 1)}`\n })\n .then(resolve, reject);\n };\n\n // Read in the image file as a data URL.\n reader.readAsDataURL(file);\n });\n }\n\n uploadItemImage(itemId, imageType, file) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n if (!imageType) {\n throw new Error('null imageType');\n }\n\n if (!file) {\n throw new Error('File must be an image.');\n }\n\n if (!file.type.startsWith('image/')) {\n throw new Error('File must be an image.');\n }\n\n let url = this.getUrl(`Items/${itemId}/Images`);\n\n url += `/${imageType}`;\n const instance = this;\n\n return new Promise((resolve, reject) => {\n const reader = new FileReader();\n\n reader.onerror = () => {\n reject();\n };\n\n reader.onabort = () => {\n reject();\n };\n\n // Closure to capture the file information.\n reader.onload = (e) => {\n // Split by a comma to remove the url: prefix\n const data = e.target.result.split(',')[1];\n\n instance\n .ajax({\n type: 'POST',\n url,\n data,\n contentType: `image/${file.name.substring(file.name.lastIndexOf('.') + 1)}`\n })\n .then(resolve, reject);\n };\n\n // Read in the image file as a data URL.\n reader.readAsDataURL(file);\n });\n }\n\n /**\n * Gets the list of installed plugins on the server\n */\n getInstalledPlugins() {\n const options = {};\n\n const url = this.getUrl('Plugins', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a user by id\n * @param {String} id\n */\n getUser(id) {\n if (!id) {\n throw new Error('Must supply a userId');\n }\n\n const url = this.getUrl(`Users/${id}`);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a studio\n */\n getStudio(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Studios/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a genre\n */\n getGenre(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Genres/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n getMusicGenre(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`MusicGenres/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets an artist\n */\n getArtist(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Artists/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets a Person\n */\n getPerson(name, userId) {\n if (!name) {\n throw new Error('null name');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Persons/${this.encodeName(name)}`, options);\n\n return this.getJSON(url);\n }\n\n getPublicUsers() {\n const url = this.getUrl('users/public');\n\n return this.ajax(\n {\n type: 'GET',\n url,\n dataType: 'json'\n },\n false\n );\n }\n\n /**\n * Gets all users from the server\n */\n getUsers(options) {\n const url = this.getUrl('users', options || {});\n\n return this.getJSON(url);\n }\n\n /**\n * Gets all available parental ratings from the server\n */\n getParentalRatings() {\n const url = this.getUrl('Localization/ParentalRatings');\n\n return this.getJSON(url);\n }\n\n getDefaultImageQuality(imageType) {\n return imageType.toLowerCase() === 'backdrop' ? 80 : 90;\n }\n\n /**\n * Constructs a url for a user image\n * @param {String} userId\n * @param {Object} options\n * Options supports the following properties:\n * width - download the image at a fixed width\n * height - download the image at a fixed height\n * maxWidth - download the image at a maxWidth\n * maxHeight - download the image at a maxHeight\n * quality - A scale of 0-100. This should almost always be omitted as the default will suffice.\n * For best results do not specify both width and height together, as aspect ratio might be altered.\n */\n getUserImageUrl(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n\n let url = `Users/${userId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n normalizeImageOptions(this, options);\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n\n return this.getUrl(url, options);\n }\n\n /**\n * Constructs a url for an item image\n * @param {String} itemId\n * @param {Object} options\n * Options supports the following properties:\n * type - Primary, logo, backdrop, etc. See the server-side enum ImageType\n * index - When downloading a backdrop, use this to specify which one (omitting is equivalent to zero)\n * width - download the image at a fixed width\n * height - download the image at a fixed height\n * maxWidth - download the image at a maxWidth\n * maxHeight - download the image at a maxHeight\n * quality - A scale of 0-100. This should almost always be omitted as the default will suffice.\n * For best results do not specify both width and height together, as aspect ratio might be altered.\n */\n getImageUrl(itemId, options) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n options = options || {};\n\n let url = `Items/${itemId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n options.quality = options.quality || this.getDefaultImageQuality(options.type);\n\n if (this.normalizeImageOptions) {\n this.normalizeImageOptions(options);\n }\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n\n return this.getUrl(url, options);\n }\n\n getScaledImageUrl(itemId, options) {\n if (!itemId) {\n throw new Error('itemId cannot be empty');\n }\n\n options = options || {};\n\n let url = `Items/${itemId}/Images/${options.type}`;\n\n if (options.index != null) {\n url += `/${options.index}`;\n }\n\n normalizeImageOptions(this, options);\n\n // Don't put these on the query string\n delete options.type;\n delete options.index;\n delete options.minScale;\n\n return this.getUrl(url, options);\n }\n\n getThumbImageUrl(item, options) {\n if (!item) {\n throw new Error('null item');\n }\n\n options = options || {};\n\n options.imageType = 'thumb';\n\n if (item.ImageTags && item.ImageTags.Thumb) {\n options.tag = item.ImageTags.Thumb;\n return this.getImageUrl(item.Id, options);\n } else if (item.ParentThumbItemId) {\n options.tag = item.ImageTags.ParentThumbImageTag;\n return this.getImageUrl(item.ParentThumbItemId, options);\n } else {\n return null;\n }\n }\n\n /**\n * Updates a user's password\n * @param {String} userId\n * @param {String} currentPassword\n * @param {String} newPassword\n */\n updateUserPassword(userId, currentPassword, newPassword) {\n if (!userId) {\n return Promise.reject();\n }\n\n const url = this.getUrl(`Users/${userId}/Password`);\n\n return this.ajax({\n type: 'POST',\n url: url,\n data: JSON.stringify({\n CurrentPw: currentPassword || '',\n NewPw: newPassword\n }),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates a user's easy password\n * @param {String} userId\n * @param {String} newPassword\n */\n updateEasyPassword(userId, newPassword) {\n if (!userId) {\n Promise.reject();\n return;\n }\n\n const url = this.getUrl(`Users/${userId}/EasyPassword`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: {\n NewPw: newPassword\n }\n });\n }\n\n /**\n * Resets a user's password\n * @param {String} userId\n */\n resetUserPassword(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/Password`);\n\n const postData = {};\n\n postData.resetPassword = true;\n\n return this.ajax({\n type: 'POST',\n url,\n data: postData\n });\n }\n\n resetEasyPassword(userId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n const url = this.getUrl(`Users/${userId}/EasyPassword`);\n\n const postData = {};\n\n postData.resetPassword = true;\n\n return this.ajax({\n type: 'POST',\n url,\n data: postData\n });\n }\n\n /**\n * Updates the server's configuration\n * @param {Object} configuration\n */\n updateServerConfiguration(configuration) {\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl('System/Configuration');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n updateNamedConfiguration(name, configuration) {\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`System/Configuration/${name}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n updateItem(item) {\n if (!item) {\n throw new Error('null item');\n }\n\n const url = this.getUrl(`Items/${item.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(item),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates plugin security info\n */\n updatePluginSecurityInfo(info) {\n const url = this.getUrl('Plugins/SecurityInfo');\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(info),\n contentType: 'application/json'\n });\n }\n\n /**\n * Creates a user\n * @param {Object} user\n */\n createUser(user) {\n const url = this.getUrl('Users/New');\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(user),\n contentType: 'application/json',\n headers: {\n accept: 'application/json'\n }\n });\n }\n\n /**\n * Updates a user\n * @param {Object} user\n */\n updateUser(user) {\n if (!user) {\n throw new Error('null user');\n }\n\n const url = this.getUrl(`Users/${user.Id}`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(user),\n contentType: 'application/json'\n });\n }\n\n updateUserPolicy(userId, policy) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!policy) {\n throw new Error('null policy');\n }\n\n const url = this.getUrl(`Users/${userId}/Policy`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(policy),\n contentType: 'application/json'\n });\n }\n\n updateUserConfiguration(userId, configuration) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`Users/${userId}/Configuration`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates the Triggers for a ScheduledTask\n * @param {String} id\n * @param {Object} triggers\n */\n updateScheduledTaskTriggers(id, triggers) {\n if (!id) {\n throw new Error('null id');\n }\n\n if (!triggers) {\n throw new Error('null triggers');\n }\n\n const url = this.getUrl(`ScheduledTasks/${id}/Triggers`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(triggers),\n contentType: 'application/json'\n });\n }\n\n /**\n * Updates a plugin's configuration\n * @param {String} Id\n * @param {Object} configuration\n */\n updatePluginConfiguration(id, configuration) {\n if (!id) {\n throw new Error('null Id');\n }\n\n if (!configuration) {\n throw new Error('null configuration');\n }\n\n const url = this.getUrl(`Plugins/${id}/Configuration`);\n\n return this.ajax({\n type: 'POST',\n url,\n data: JSON.stringify(configuration),\n contentType: 'application/json'\n });\n }\n\n getAncestorItems(itemId, userId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Items/${itemId}/Ancestors`, options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets items based on a query, typically for children of a folder\n * @param {String} userId\n * @param {Object} options\n * Options accepts the following properties:\n * itemId - Localize the search to a specific folder (root if omitted)\n * startIndex - Use for paging\n * limit - Use to limit results to a certain number of items\n * filter - Specify one or more ItemFilters, comma delimeted (see server-side enum)\n * sortBy - Specify an ItemSortBy (comma-delimeted list see server-side enum)\n * sortOrder - ascending/descending\n * fields - additional fields to include aside from basic info. This is a comma delimited list. See server-side enum ItemFields.\n * index - the name of the dynamic, localized index function\n * dynamicSortBy - the name of the dynamic localized sort function\n * recursive - Whether or not the query should be recursive\n * searchTerm - search term to use as a filter\n */\n getItems(userId, options) {\n let url;\n\n if ((typeof userId).toString().toLowerCase() === 'string') {\n url = this.getUrl(`Users/${userId}/Items`, options);\n } else {\n url = this.getUrl('Items', options);\n }\n\n return this.getJSON(url);\n }\n\n getResumableItems(userId, options) {\n if (this.isMinServerVersion('3.2.33')) {\n return this.getJSON(this.getUrl(`Users/${userId}/Items/Resume`, options));\n }\n\n return this.getItems(\n userId,\n Object.assign(\n {\n SortBy: 'DatePlayed',\n SortOrder: 'Descending',\n Filters: 'IsResumable',\n Recursive: true,\n CollapseBoxSetItems: false,\n ExcludeLocationTypes: 'Virtual'\n },\n options\n )\n );\n }\n\n getMovieRecommendations(options) {\n return this.getJSON(this.getUrl('Movies/Recommendations', options));\n }\n\n getUpcomingEpisodes(options) {\n return this.getJSON(this.getUrl('Shows/Upcoming', options));\n }\n\n getUserViews(options = {}, userId) {\n const url = this.getUrl(`Users/${userId || this.getCurrentUserId()}/Views`, options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets artists from an item\n */\n getArtists(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Artists', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets artists from an item\n */\n getAlbumArtists(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Artists/AlbumArtists', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets genres from an item\n */\n getGenres(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Genres', options);\n\n return this.getJSON(url);\n }\n\n getMusicGenres(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('MusicGenres', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets people from an item\n */\n getPeople(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Persons', options);\n\n return this.getJSON(url);\n }\n\n /**\n Gets studios from an item\n */\n getStudios(userId, options) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n options = options || {};\n options.userId = userId;\n\n const url = this.getUrl('Studios', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Gets local trailers for an item\n */\n getLocalTrailers(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/LocalTrailers`);\n\n return this.getJSON(url);\n }\n\n getAdditionalVideoParts(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl(`Videos/${itemId}/AdditionalParts`, options);\n\n return this.getJSON(url);\n }\n\n getThemeMedia(userId, itemId, inherit) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n options.InheritFromParent = inherit || false;\n\n const url = this.getUrl(`Items/${itemId}/ThemeMedia`, options);\n\n return this.getJSON(url);\n }\n\n getSearchHints(options) {\n const url = this.getUrl('Search/Hints', options);\n const serverId = this.serverId();\n\n return this.getJSON(url).then((result) => {\n result.SearchHints.forEach((i) => {\n i.ServerId = serverId;\n });\n return result;\n });\n }\n\n /**\n * Gets special features for an item\n */\n getSpecialFeatures(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/SpecialFeatures`);\n\n return this.getJSON(url);\n }\n\n getDateParamValue(date) {\n function formatDigit(i) {\n return i < 10 ? `0${i}` : i;\n }\n\n const d = date;\n\n return `${d.getFullYear()}${formatDigit(d.getMonth() + 1)}${formatDigit(d.getDate())}${formatDigit(\n d.getHours()\n )}${formatDigit(d.getMinutes())}${formatDigit(d.getSeconds())}`;\n }\n\n markPlayed(userId, itemId, date) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const options = {};\n\n if (date) {\n options.DatePlayed = this.getDateParamValue(date);\n }\n\n const url = this.getUrl(`Users/${userId}/PlayedItems/${itemId}`, options);\n\n return this.ajax({\n type: 'POST',\n url,\n dataType: 'json'\n });\n }\n\n markUnplayed(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/PlayedItems/${itemId}`);\n\n return this.ajax({\n type: 'DELETE',\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Updates a user's favorite status for an item.\n * @param {String} userId\n * @param {String} itemId\n * @param {Boolean} isFavorite\n */\n updateFavoriteStatus(userId, itemId, isFavorite) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/FavoriteItems/${itemId}`);\n\n const method = isFavorite ? 'POST' : 'DELETE';\n\n return this.ajax({\n type: method,\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Updates a user's personal rating for an item\n * @param {String} userId\n * @param {String} itemId\n * @param {Boolean} likes\n */\n updateUserItemRating(userId, itemId, likes) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/Rating`, {\n likes\n });\n\n return this.ajax({\n type: 'POST',\n url,\n dataType: 'json'\n });\n }\n\n getItemCounts(userId) {\n const options = {};\n\n if (userId) {\n options.userId = userId;\n }\n\n const url = this.getUrl('Items/Counts', options);\n\n return this.getJSON(url);\n }\n\n /**\n * Clears a user's personal rating for an item\n * @param {String} userId\n * @param {String} itemId\n */\n clearUserItemRating(userId, itemId) {\n if (!userId) {\n throw new Error('null userId');\n }\n\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n const url = this.getUrl(`Users/${userId}/Items/${itemId}/Rating`);\n\n return this.ajax({\n type: 'DELETE',\n url,\n dataType: 'json'\n });\n }\n\n /**\n * Reports the user has started playing something\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackStart(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n this.lastPlaybackProgressReport = 0;\n this.lastPlaybackProgressReportTicks = null;\n stopBitrateDetection(this);\n\n cancelReportPlaybackProgressPromise(this);\n const url = this.getUrl('Sessions/Playing');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(options),\n contentType: 'application/json',\n url\n });\n }\n\n /**\n * Reports progress viewing an item\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackProgress(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n const eventName = options.EventName || 'timeupdate';\n let reportRateLimitTime = reportRateLimits[eventName] || 0;\n\n const now = new Date().getTime();\n const msSinceLastReport = now - (this.lastPlaybackProgressReport || 0);\n const newPositionTicks = options.PositionTicks;\n\n if (msSinceLastReport < reportRateLimitTime && eventName === 'timeupdate' && newPositionTicks) {\n const expectedReportTicks = 1e4 * msSinceLastReport + (this.lastPlaybackProgressReportTicks || 0);\n if (Math.abs(newPositionTicks - expectedReportTicks) >= 5e7) reportRateLimitTime = 0;\n }\n\n if (\n reportRateLimitTime <\n (this.reportPlaybackProgressTimeout !== undefined ? this.reportPlaybackProgressTimeout : 1e6)\n ) {\n cancelReportPlaybackProgressPromise(this);\n }\n\n this.lastPlaybackProgressOptions = options;\n\n /* eslint-disable-next-line @typescript-eslint/no-misused-promises */\n if (this.reportPlaybackProgressPromise) return Promise.resolve();\n\n let instance = this;\n let promise;\n let cancelled = false;\n\n let resetPromise = function () {\n if (instance.reportPlaybackProgressPromise !== promise) return;\n\n delete instance.lastPlaybackProgressOptions;\n delete instance.reportPlaybackProgressTimeout;\n delete instance.reportPlaybackProgressPromise;\n delete instance.reportPlaybackProgressCancel;\n };\n\n let sendReport = function (lastOptions) {\n resetPromise();\n\n if (!lastOptions) throw new Error('null options');\n\n instance.lastPlaybackProgressReport = new Date().getTime();\n instance.lastPlaybackProgressReportTicks = lastOptions.PositionTicks;\n\n const url = instance.getUrl('Sessions/Playing/Progress');\n return instance.ajax({\n type: 'POST',\n data: JSON.stringify(lastOptions),\n contentType: 'application/json',\n url: url\n });\n };\n\n let delay = Math.max(0, reportRateLimitTime - msSinceLastReport);\n\n promise = new Promise((resolve, reject) => setTimeout(resolve, delay))\n .then(() => {\n if (cancelled) return Promise.resolve();\n return sendReport(instance.lastPlaybackProgressOptions);\n })\n .finally(() => {\n resetPromise();\n });\n\n this.reportPlaybackProgressTimeout = reportRateLimitTime;\n this.reportPlaybackProgressPromise = promise;\n this.reportPlaybackProgressCancel = function () {\n cancelled = true;\n resetPromise();\n };\n\n return promise;\n }\n\n reportOfflineActions(actions) {\n if (!actions) {\n throw new Error('null actions');\n }\n\n const url = this.getUrl('Sync/OfflineActions');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(actions),\n contentType: 'application/json',\n url\n });\n }\n\n syncData(data) {\n if (!data) {\n throw new Error('null data');\n }\n\n const url = this.getUrl('Sync/Data');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(data),\n contentType: 'application/json',\n url,\n dataType: 'json'\n });\n }\n\n getReadySyncItems(deviceId) {\n if (!deviceId) {\n throw new Error('null deviceId');\n }\n\n const url = this.getUrl('Sync/Items/Ready', {\n TargetId: deviceId\n });\n\n return this.getJSON(url);\n }\n\n reportSyncJobItemTransferred(syncJobItemId) {\n if (!syncJobItemId) {\n throw new Error('null syncJobItemId');\n }\n\n const url = this.getUrl(`Sync/JobItems/${syncJobItemId}/Transferred`);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n cancelSyncItems(itemIds, targetId) {\n if (!itemIds) {\n throw new Error('null itemIds');\n }\n\n const url = this.getUrl(`Sync/${targetId || this.deviceId()}/Items`, {\n ItemIds: itemIds.join(',')\n });\n\n return this.ajax({\n type: 'DELETE',\n url\n });\n }\n\n /**\n * Reports a user has stopped playing an item\n * @param {String} userId\n * @param {String} itemId\n */\n reportPlaybackStopped(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n this.lastPlaybackProgressReport = 0;\n this.lastPlaybackProgressReportTicks = null;\n redetectBitrate(this);\n\n cancelReportPlaybackProgressPromise(this);\n const url = this.getUrl('Sessions/Playing/Stopped');\n\n return this.ajax({\n type: 'POST',\n data: JSON.stringify(options),\n contentType: 'application/json',\n url\n });\n }\n\n sendPlayCommand(sessionId, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!options) {\n throw new Error('null options');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Playing`, options);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n sendCommand(sessionId, command) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!command) {\n throw new Error('null command');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Command`);\n\n const ajaxOptions = {\n type: 'POST',\n url\n };\n\n ajaxOptions.data = JSON.stringify(command);\n ajaxOptions.contentType = 'application/json';\n\n return this.ajax(ajaxOptions);\n }\n\n sendMessageCommand(sessionId, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!options) {\n throw new Error('null options');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Message`);\n\n const ajaxOptions = {\n type: 'POST',\n url\n };\n\n ajaxOptions.data = JSON.stringify(options);\n ajaxOptions.contentType = 'application/json';\n\n return this.ajax(ajaxOptions);\n }\n\n sendPlayStateCommand(sessionId, command, options) {\n if (!sessionId) {\n throw new Error('null sessionId');\n }\n\n if (!command) {\n throw new Error('null command');\n }\n\n const url = this.getUrl(`Sessions/${sessionId}/Playing/${command}`, options || {});\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n /**\n * Gets a list of all the active SyncPlay groups from the server.\n * @returns {Promise} A Promise that resolves to the list of active groups.\n * @since 10.6.0\n */\n getSyncPlayGroups() {\n const url = this.getUrl(`SyncPlay/List`);\n\n return this.ajax({\n type: 'GET',\n url: url\n });\n }\n\n /**\n * Creates a SyncPlay group on the server with the current client as member.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n createSyncPlayGroup() {\n const url = this.getUrl(`SyncPlay/New`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Joins the client to a given SyncPlay group on the server.\n * @param {object} options Information about the SyncPlay group to join.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n joinSyncPlayGroup(options = {}) {\n const url = this.getUrl(`SyncPlay/Join`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Leaves the current SyncPlay group.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n leaveSyncPlayGroup() {\n const url = this.getUrl(`SyncPlay/Leave`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Sends a ping to the SyncPlay group on the server.\n * @param {object} options Information about the ping\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n sendSyncPlayPing(options = {}) {\n const url = this.getUrl(`SyncPlay/Ping`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback start for the SyncPlay group\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlayStart() {\n const url = this.getUrl(`SyncPlay/Play`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback pause for the SyncPlay group\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlayPause() {\n const url = this.getUrl(`SyncPlay/Pause`);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n /**\n * Requests a playback seek for the SyncPlay group\n * @param {object} options Object containing the requested seek position.\n * @returns {Promise} A Promise fulfilled upon request completion.\n * @since 10.6.0\n */\n requestSyncPlaySeek(options = {}) {\n const url = this.getUrl(`SyncPlay/Seek`, options);\n\n return this.ajax({\n type: 'POST',\n url: url\n });\n }\n\n createPackageReview(review) {\n const url = this.getUrl(`Packages/Reviews/${review.id}`, review);\n\n return this.ajax({\n type: 'POST',\n url\n });\n }\n\n getPackageReviews(packageId, minRating, maxRating, limit) {\n if (!packageId) {\n throw new Error('null packageId');\n }\n\n const options = {};\n\n if (minRating) {\n options.MinRating = minRating;\n }\n if (maxRating) {\n options.MaxRating = maxRating;\n }\n if (limit) {\n options.Limit = limit;\n }\n\n const url = this.getUrl(`Packages/${packageId}/Reviews`, options);\n\n return this.getJSON(url);\n }\n\n getSavedEndpointInfo() {\n return this._endPointInfo;\n }\n\n getEndpointInfo() {\n const savedValue = this._endPointInfo;\n if (savedValue) {\n return Promise.resolve(savedValue);\n }\n\n const instance = this;\n return this.getJSON(this.getUrl('System/Endpoint')).then((endPointInfo) => {\n setSavedEndpointInfo(instance, endPointInfo);\n return endPointInfo;\n });\n }\n\n getLatestItems(options = {}) {\n return this.getJSON(this.getUrl(`Users/${this.getCurrentUserId()}/Items/Latest`, options));\n }\n\n getFilters(options) {\n return this.getJSON(this.getUrl('Items/Filters2', options));\n }\n\n setSystemInfo(info) {\n this._serverVersion = info.Version;\n }\n\n serverVersion() {\n return this._serverVersion;\n }\n\n isMinServerVersion(version) {\n const serverVersion = this.serverVersion();\n\n if (serverVersion) {\n return compareVersions(serverVersion, version) >= 0;\n }\n\n return false;\n }\n\n handleMessageReceived(msg) {\n onMessageReceivedInternal(this, msg);\n }\n}\n\nfunction setSavedEndpointInfo(instance, info) {\n instance._endPointInfo = info;\n}\n\nfunction getTryConnectPromise(instance, url, state, resolve, reject) {\n console.log('getTryConnectPromise ' + url);\n\n fetchWithTimeout(\n instance.getUrl('system/info/public', null, url),\n {\n method: 'GET',\n accept: 'application/json'\n\n // Commenting this out since the fetch api doesn't have a timeout option yet\n //timeout: timeout\n },\n 15000\n ).then(\n () => {\n if (!state.resolved) {\n state.resolved = true;\n\n console.log('Reconnect succeeded to ' + url);\n instance.serverAddress(url);\n resolve();\n }\n },\n () => {\n if (!state.resolved) {\n console.log('Reconnect failed to ' + url);\n\n state.rejects++;\n if (state.rejects >= state.numAddresses) {\n reject();\n }\n }\n }\n );\n}\n\nfunction tryReconnectInternal(instance) {\n const addresses = [];\n const addressesStrings = [];\n\n const serverInfo = instance.serverInfo();\n if (serverInfo.LocalAddress && addressesStrings.indexOf(serverInfo.LocalAddress) === -1) {\n addresses.push({ url: serverInfo.LocalAddress, timeout: 0 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {\n addresses.push({ url: serverInfo.ManualAddress, timeout: 100 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.RemoteAddress && addressesStrings.indexOf(serverInfo.RemoteAddress) === -1) {\n addresses.push({ url: serverInfo.RemoteAddress, timeout: 200 });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n\n console.log('tryReconnect: ' + addressesStrings.join('|'));\n\n return new Promise((resolve, reject) => {\n const state = {};\n state.numAddresses = addresses.length;\n state.rejects = 0;\n\n addresses.map((url) => {\n setTimeout(() => {\n if (!state.resolved) {\n getTryConnectPromise(instance, url.url, state, resolve, reject);\n }\n }, url.timeout);\n });\n });\n}\n\nfunction tryReconnect(instance, retryCount) {\n retryCount = retryCount || 0;\n\n if (retryCount >= 20) {\n return Promise.reject();\n }\n\n return tryReconnectInternal(instance).catch((err) => {\n console.log('error in tryReconnectInternal: ' + (err || ''));\n\n return new Promise((resolve, reject) => {\n setTimeout(() => {\n tryReconnect(instance, retryCount + 1).then(resolve, reject);\n }, 500);\n });\n });\n}\n\nfunction getCachedUser(instance, userId) {\n const serverId = instance.serverId();\n if (!serverId) {\n return null;\n }\n\n const json = appStorage.getItem(`user-${userId}-${serverId}`);\n\n if (json) {\n return JSON.parse(json);\n }\n\n return null;\n}\n\nfunction onWebSocketMessage(msg) {\n const instance = this;\n msg = JSON.parse(msg.data);\n onMessageReceivedInternal(instance, msg);\n}\n\nconst messageIdsReceived = {};\n\nfunction onMessageReceivedInternal(instance, msg) {\n const messageId = msg.MessageId;\n if (messageId) {\n // message was already received via another protocol\n if (messageIdsReceived[messageId]) {\n return;\n }\n\n messageIdsReceived[messageId] = true;\n }\n\n if (msg.MessageType === 'UserDeleted') {\n instance._currentUser = null;\n } else if (msg.MessageType === 'UserUpdated' || msg.MessageType === 'UserConfigurationUpdated') {\n const user = msg.Data;\n if (user.Id === instance.getCurrentUserId()) {\n instance._currentUser = null;\n }\n } else if (msg.MessageType === 'KeepAlive') {\n console.debug('Received KeepAlive from server.');\n } else if (msg.MessageType === 'ForceKeepAlive') {\n console.debug(`Received ForceKeepAlive from server. Timeout is ${msg.Data} seconds.`);\n instance.sendWebSocketMessage('KeepAlive');\n scheduleKeepAlive(instance, msg.Data);\n }\n\n events.trigger(instance, 'message', [msg]);\n}\n\n/**\n * Starts a poller that sends KeepAlive messages using a WebSocket connection.\n * @param {Object} instance The WebSocket connection.\n * @param {number} timeout The number of seconds after which the WebSocket is considered lost by the server.\n * @returns {number} The id of the interval.\n * @since 10.6.0\n */\nfunction scheduleKeepAlive(instance, timeout) {\n clearKeepAlive(instance);\n instance.keepAliveInterval = setInterval(() => {\n instance.sendWebSocketMessage('KeepAlive');\n }, timeout * 1000 * 0.5);\n return instance.keepAliveInterval;\n}\n\n/**\n * Stops the poller that is sending KeepAlive messages on a WebSocket connection.\n * @param {Object} instance The WebSocket connection.\n * @since 10.6.0\n */\nfunction clearKeepAlive(instance) {\n console.debug('Clearing KeepAlive for', instance);\n if (instance.keepAliveInterval) {\n clearInterval(instance.keepAliveInterval);\n instance.keepAliveInterval = null;\n }\n}\n\nfunction onWebSocketOpen() {\n const instance = this;\n console.log('web socket connection opened');\n events.trigger(instance, 'websocketopen');\n}\n\nfunction onWebSocketError() {\n const instance = this;\n clearKeepAlive(instance);\n events.trigger(instance, 'websocketerror');\n}\n\nfunction setSocketOnClose(apiClient, socket) {\n socket.onclose = () => {\n console.log('web socket closed');\n\n clearKeepAlive(socket);\n if (apiClient._webSocket === socket) {\n console.log('nulling out web socket');\n apiClient._webSocket = null;\n }\n\n setTimeout(() => {\n events.trigger(apiClient, 'websocketclose');\n }, 0);\n };\n}\n\nfunction normalizeReturnBitrate(instance, bitrate) {\n if (!bitrate) {\n if (instance.lastDetectedBitrate) {\n return instance.lastDetectedBitrate;\n }\n\n return Promise.reject();\n }\n\n let result = Math.round(bitrate * 0.7);\n\n // allow configuration of this\n if (instance.getMaxBandwidth) {\n const maxRate = instance.getMaxBandwidth();\n if (maxRate) {\n result = Math.min(result, maxRate);\n }\n }\n\n instance.lastDetectedBitrate = result;\n instance.lastDetectedBitrateTime = new Date().getTime();\n\n return result;\n}\n\nfunction detectBitrateInternal(instance, tests, index, currentBitrate) {\n if (index >= tests.length) {\n return normalizeReturnBitrate(instance, currentBitrate);\n }\n\n const test = tests[index];\n\n return instance.getDownloadSpeed(test.bytes).then(\n (bitrate) => {\n if (bitrate < test.threshold) {\n return normalizeReturnBitrate(instance, bitrate);\n } else {\n return detectBitrateInternal(instance, tests, index + 1, bitrate);\n }\n },\n () => normalizeReturnBitrate(instance, currentBitrate)\n );\n}\n\nfunction detectBitrateWithEndpointInfo(instance, endpointInfo) {\n if (endpointInfo.IsInNetwork) {\n const result = 140000000;\n instance.lastDetectedBitrate = result;\n instance.lastDetectedBitrateTime = new Date().getTime();\n return result;\n }\n\n return detectBitrateInternal(\n instance,\n [\n {\n bytes: 500000,\n threshold: 500000\n },\n {\n bytes: 1000000,\n threshold: 20000000\n },\n {\n bytes: 3000000,\n threshold: 50000000\n }\n ],\n 0\n );\n}\n\nfunction getRemoteImagePrefix(instance, options) {\n let urlPrefix;\n\n if (options.artist) {\n urlPrefix = `Artists/${instance.encodeName(options.artist)}`;\n delete options.artist;\n } else if (options.person) {\n urlPrefix = `Persons/${instance.encodeName(options.person)}`;\n delete options.person;\n } else if (options.genre) {\n urlPrefix = `Genres/${instance.encodeName(options.genre)}`;\n delete options.genre;\n } else if (options.musicGenre) {\n urlPrefix = `MusicGenres/${instance.encodeName(options.musicGenre)}`;\n delete options.musicGenre;\n } else if (options.studio) {\n urlPrefix = `Studios/${instance.encodeName(options.studio)}`;\n delete options.studio;\n } else {\n urlPrefix = `Items/${options.itemId}`;\n delete options.itemId;\n }\n\n return urlPrefix;\n}\n\nfunction normalizeImageOptions(instance, options) {\n let ratio = window.devicePixelRatio || 1;\n\n if (ratio) {\n if (options.minScale) {\n ratio = Math.max(options.minScale, ratio);\n }\n\n if (options.width) {\n options.width = Math.round(options.width * ratio);\n }\n if (options.height) {\n options.height = Math.round(options.height * ratio);\n }\n if (options.maxWidth) {\n options.maxWidth = Math.round(options.maxWidth * ratio);\n }\n if (options.maxHeight) {\n options.maxHeight = Math.round(options.maxHeight * ratio);\n }\n }\n\n options.quality = options.quality || instance.getDefaultImageQuality(options.type);\n\n if (instance.normalizeImageOptions) {\n instance.normalizeImageOptions(options);\n }\n}\n\nfunction compareVersions(a, b) {\n // -1 a is smaller\n // 1 a is larger\n // 0 equal\n a = a.split('.');\n b = b.split('.');\n\n for (let i = 0, length = Math.max(a.length, b.length); i < length; i++) {\n const aVal = parseInt(a[i] || '0');\n const bVal = parseInt(b[i] || '0');\n\n if (aVal < bVal) {\n return -1;\n }\n\n if (aVal > bVal) {\n return 1;\n }\n }\n\n return 0;\n}\n\nexport default ApiClient;\n","import ApiClient from './apiClient';\n\nconst localPrefix = 'local:';\nconst localViewPrefix = 'localview:';\n\nfunction isLocalId(str) {\n return startsWith(str, localPrefix);\n}\n\nfunction isLocalViewId(str) {\n return startsWith(str, localViewPrefix);\n}\n\nfunction isTopLevelLocalViewId(str) {\n return str === 'localview';\n}\n\nfunction stripLocalPrefix(str) {\n let res = stripStart(str, localPrefix);\n res = stripStart(res, localViewPrefix);\n\n return res;\n}\n\nfunction startsWith(str, find) {\n if (str && find && str.length > find.length) {\n if (str.indexOf(find) === 0) {\n return true;\n }\n }\n\n return false;\n}\n\nfunction stripStart(str, find) {\n if (startsWith(str, find)) {\n return str.substr(find.length);\n }\n\n return str;\n}\n\nfunction createEmptyList() {\n const result = {\n Items: [],\n TotalRecordCount: 0\n };\n\n return result;\n}\n\nfunction convertGuidToLocal(guid) {\n if (!guid) {\n return null;\n }\n\n if (isLocalId(guid)) {\n return guid;\n }\n\n return `local:${guid}`;\n}\n\nfunction adjustGuidProperties(downloadedItem) {\n downloadedItem.Id = convertGuidToLocal(downloadedItem.Id);\n downloadedItem.SeriesId = convertGuidToLocal(downloadedItem.SeriesId);\n downloadedItem.SeasonId = convertGuidToLocal(downloadedItem.SeasonId);\n\n downloadedItem.AlbumId = convertGuidToLocal(downloadedItem.AlbumId);\n downloadedItem.ParentId = convertGuidToLocal(downloadedItem.ParentId);\n downloadedItem.ParentThumbItemId = convertGuidToLocal(downloadedItem.ParentThumbItemId);\n downloadedItem.ParentPrimaryImageItemId = convertGuidToLocal(downloadedItem.ParentPrimaryImageItemId);\n downloadedItem.PrimaryImageItemId = convertGuidToLocal(downloadedItem.PrimaryImageItemId);\n downloadedItem.ParentLogoItemId = convertGuidToLocal(downloadedItem.ParentLogoItemId);\n downloadedItem.ParentBackdropItemId = convertGuidToLocal(downloadedItem.ParentBackdropItemId);\n\n downloadedItem.ParentBackdropImageTags = null;\n}\n\nfunction getLocalView(instance, serverId, userId) {\n return instance.getLocalFolders(serverId, userId).then((views) => {\n let localView = null;\n\n if (views.length > 0) {\n localView = {\n Name: instance.downloadsTitleText || 'Downloads',\n ServerId: serverId,\n Id: 'localview',\n Type: 'localview',\n IsFolder: true\n };\n }\n\n return Promise.resolve(localView);\n });\n}\n\n/**\n * Creates a new api client instance\n * @param {String} serverAddress\n * @param {String} clientName s\n * @param {String} applicationVersion\n */\nclass ApiClientCore extends ApiClient {\n constructor(\n serverAddress,\n clientName,\n applicationVersion,\n deviceName,\n deviceId,\n devicePixelRatio,\n localAssetManager\n ) {\n super(serverAddress, clientName, applicationVersion, deviceName, deviceId, devicePixelRatio);\n this.localAssetManager = localAssetManager;\n }\n\n getPlaybackInfo(itemId, options, deviceProfile) {\n const onFailure = () => ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n\n if (isLocalId(itemId)) {\n return this.localAssetManager.getLocalItem(this.serverId(), stripLocalPrefix(itemId)).then((item) => {\n // TODO: This was already done during the sync process, right? If so, remove it\n const mediaSources = item.Item.MediaSources.map((m) => {\n m.SupportsDirectPlay = true;\n m.SupportsDirectStream = false;\n m.SupportsTranscoding = false;\n m.IsLocal = true;\n return m;\n });\n\n return {\n MediaSources: mediaSources\n };\n }, onFailure);\n }\n\n var instance = this;\n return this.localAssetManager.getLocalItem(this.serverId(), itemId).then((item) => {\n if (item) {\n const mediaSources = item.Item.MediaSources.map((m) => {\n m.SupportsDirectPlay = true;\n m.SupportsDirectStream = false;\n m.SupportsTranscoding = false;\n m.IsLocal = true;\n return m;\n });\n\n return instance.localAssetManager.fileExists(item.LocalPath).then((exists) => {\n if (exists) {\n const res = {\n MediaSources: mediaSources\n };\n\n return Promise.resolve(res);\n }\n\n return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n }, onFailure);\n }\n\n return ApiClient.prototype.getPlaybackInfo.call(instance, itemId, options, deviceProfile);\n }, onFailure);\n }\n\n getItems(userId, options) {\n const serverInfo = this.serverInfo();\n let i;\n\n if (serverInfo && options.ParentId === 'localview') {\n return this.getLocalFolders(serverInfo.Id, userId).then((items) => {\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n } else if (\n serverInfo &&\n options &&\n (isLocalId(options.ParentId) ||\n isLocalId(options.SeriesId) ||\n isLocalId(options.SeasonId) ||\n isLocalViewId(options.ParentId) ||\n isLocalId(options.AlbumIds))\n ) {\n return this.localAssetManager.getViewItems(serverInfo.Id, userId, options).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n } else if (options && options.ExcludeItemIds && options.ExcludeItemIds.length) {\n const exItems = options.ExcludeItemIds.split(',');\n\n for (i = 0; i < exItems.length; i++) {\n if (isLocalId(exItems[i])) {\n return Promise.resolve(createEmptyList());\n }\n }\n } else if (options && options.Ids && options.Ids.length) {\n const ids = options.Ids.split(',');\n let hasLocal = false;\n\n for (i = 0; i < ids.length; i++) {\n if (isLocalId(ids[i])) {\n hasLocal = true;\n }\n }\n\n if (hasLocal) {\n return this.localAssetManager.getItemsFromIds(serverInfo.Id, ids).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n const result = {\n Items: items,\n TotalRecordCount: items.length\n };\n\n return Promise.resolve(result);\n });\n }\n }\n\n return ApiClient.prototype.getItems.call(this, userId, options);\n }\n\n getUserViews(options, userId) {\n const instance = this;\n\n options = options || {};\n\n const basePromise = ApiClient.prototype.getUserViews.call(instance, options, userId);\n\n if (!options.enableLocalView) {\n return basePromise;\n }\n\n return basePromise.then((result) => {\n const serverInfo = instance.serverInfo();\n if (serverInfo) {\n return getLocalView(instance, serverInfo.Id, userId).then((localView) => {\n if (localView) {\n result.Items.push(localView);\n result.TotalRecordCount++;\n }\n\n return Promise.resolve(result);\n });\n }\n\n return Promise.resolve(result);\n });\n }\n\n getItem(userId, itemId) {\n if (!itemId) {\n throw new Error('null itemId');\n }\n\n if (itemId) {\n itemId = itemId.toString();\n }\n\n let serverInfo;\n\n if (isTopLevelLocalViewId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return getLocalView(this, serverInfo.Id, userId);\n }\n }\n\n if (isLocalViewId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.getLocalFolders(serverInfo.Id, userId).then((items) => {\n const views = items.filter((item) => item.Id === itemId);\n\n if (views.length > 0) {\n return Promise.resolve(views[0]);\n }\n\n // TODO: Test consequence of this\n return Promise.reject();\n });\n }\n }\n\n if (isLocalId(itemId)) {\n serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager.getLocalItem(serverInfo.Id, stripLocalPrefix(itemId)).then((item) => {\n adjustGuidProperties(item.Item);\n\n return Promise.resolve(item.Item);\n });\n }\n }\n\n return ApiClient.prototype.getItem.call(this, userId, itemId);\n }\n\n getLocalFolders(userId) {\n const serverInfo = this.serverInfo();\n userId = userId || serverInfo.UserId;\n\n return this.localAssetManager.getViews(serverInfo.Id, userId);\n }\n\n getNextUpEpisodes(options) {\n if (options.SeriesId) {\n if (isLocalId(options.SeriesId)) {\n return Promise.resolve(createEmptyList());\n }\n }\n\n return ApiClient.prototype.getNextUpEpisodes.call(this, options);\n }\n\n getSeasons(itemId, options) {\n if (isLocalId(itemId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Season';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n return ApiClient.prototype.getSeasons.call(this, itemId, options);\n }\n\n getEpisodes(itemId, options) {\n if (isLocalId(options.SeasonId) || isLocalId(options.seasonId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Episode';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n // get episodes by recursion\n if (isLocalId(itemId)) {\n options.SeriesId = itemId;\n options.IncludeItemTypes = 'Episode';\n return this.getItems(this.getCurrentUserId(), options);\n }\n\n return ApiClient.prototype.getEpisodes.call(this, itemId, options);\n }\n\n getLatestOfflineItems(options) {\n // Supported options\n // MediaType - Audio/Video/Photo/Book/Game\n // Limit\n // Filters: 'IsNotFolder' or 'IsFolder'\n\n options.SortBy = 'DateCreated';\n options.SortOrder = 'Descending';\n\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager.getViewItems(serverInfo.Id, null, options).then((items) => {\n items.forEach((item) => {\n adjustGuidProperties(item);\n });\n\n return Promise.resolve(items);\n });\n }\n\n return Promise.resolve([]);\n }\n\n getThemeMedia(userId, itemId, inherit) {\n if (isLocalViewId(itemId) || isLocalId(itemId) || isTopLevelLocalViewId(itemId)) {\n return Promise.reject();\n }\n\n return ApiClient.prototype.getThemeMedia.call(this, userId, itemId, inherit);\n }\n\n getSpecialFeatures(userId, itemId) {\n if (isLocalId(itemId)) {\n return Promise.resolve([]);\n }\n\n return ApiClient.prototype.getSpecialFeatures.call(this, userId, itemId);\n }\n\n getSimilarItems(itemId, options) {\n if (isLocalId(itemId)) {\n return Promise.resolve(createEmptyList());\n }\n\n return ApiClient.prototype.getSimilarItems.call(this, itemId, options);\n }\n\n updateFavoriteStatus(userId, itemId, isFavorite) {\n if (isLocalId(itemId)) {\n return Promise.resolve();\n }\n\n return ApiClient.prototype.updateFavoriteStatus.call(this, userId, itemId, isFavorite);\n }\n\n getScaledImageUrl(itemId, options) {\n if (isLocalId(itemId) || (options && options.itemid && isLocalId(options.itemid))) {\n const serverInfo = this.serverInfo();\n const id = stripLocalPrefix(itemId);\n\n return this.localAssetManager.getImageUrl(serverInfo.Id, id, options);\n }\n\n return ApiClient.prototype.getScaledImageUrl.call(this, itemId, options);\n }\n\n reportPlaybackStart(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n return Promise.resolve();\n }\n\n return ApiClient.prototype.reportPlaybackStart.call(this, options);\n }\n\n reportPlaybackProgress(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n const instance = this;\n return this.localAssetManager\n .getLocalItem(serverInfo.Id, stripLocalPrefix(options.ItemId))\n .then((item) => {\n const libraryItem = item.Item;\n\n if (libraryItem.MediaType === 'Video' || libraryItem.Type === 'AudioBook') {\n libraryItem.UserData = libraryItem.UserData || {};\n libraryItem.UserData.PlaybackPositionTicks = options.PositionTicks;\n libraryItem.UserData.PlayedPercentage = Math.min(\n libraryItem.RunTimeTicks\n ? 100 * ((options.PositionTicks || 0) / libraryItem.RunTimeTicks)\n : 0,\n 100\n );\n return instance.localAssetManager.addOrUpdateLocalItem(item);\n }\n\n return Promise.resolve();\n });\n }\n\n return Promise.resolve();\n }\n\n return ApiClient.prototype.reportPlaybackProgress.call(this, options);\n }\n\n reportPlaybackStopped(options) {\n if (!options) {\n throw new Error('null options');\n }\n\n if (isLocalId(options.ItemId)) {\n const serverInfo = this.serverInfo();\n\n const action = {\n Date: new Date().getTime(),\n ItemId: stripLocalPrefix(options.ItemId),\n PositionTicks: options.PositionTicks,\n ServerId: serverInfo.Id,\n Type: 0, // UserActionType.PlayedItem\n UserId: this.getCurrentUserId()\n };\n\n return this.localAssetManager.recordUserAction(action);\n }\n\n return ApiClient.prototype.reportPlaybackStopped.call(this, options);\n }\n\n getIntros(itemId) {\n if (isLocalId(itemId)) {\n return Promise.resolve({\n Items: [],\n TotalRecordCount: 0\n });\n }\n\n return ApiClient.prototype.getIntros.call(this, itemId);\n }\n\n getInstantMixFromItem(itemId, options) {\n if (isLocalId(itemId)) {\n return Promise.resolve({\n Items: [],\n TotalRecordCount: 0\n });\n }\n\n return ApiClient.prototype.getInstantMixFromItem.call(this, itemId, options);\n }\n\n getItemDownloadUrl(itemId) {\n if (isLocalId(itemId)) {\n const serverInfo = this.serverInfo();\n\n if (serverInfo) {\n return this.localAssetManager\n .getLocalItem(serverInfo.Id, stripLocalPrefix(itemId))\n .then((item) => Promise.resolve(item.LocalPath));\n }\n }\n\n return ApiClient.prototype.getItemDownloadUrl.call(this, itemId);\n }\n}\n\nexport default ApiClientCore;\n","import events from './events';\nimport ApiClient from './apiClient';\n\nconst defaultTimeout = 20000;\n\nconst ConnectionMode = {\n Local: 0,\n Remote: 1,\n Manual: 2\n};\n\nfunction getServerAddress(server, mode) {\n switch (mode) {\n case ConnectionMode.Local:\n return server.LocalAddress;\n case ConnectionMode.Manual:\n return server.ManualAddress;\n case ConnectionMode.Remote:\n return server.RemoteAddress;\n default:\n return server.ManualAddress || server.LocalAddress || server.RemoteAddress;\n }\n}\n\nfunction paramsToString(params) {\n const values = [];\n\n for (const key in params) {\n const value = params[key];\n\n if (value !== null && value !== undefined && value !== '') {\n values.push(`${encodeURIComponent(key)}=${encodeURIComponent(value)}`);\n }\n }\n return values.join('&');\n}\n\nfunction resolveFailure(instance, resolve) {\n resolve({\n State: 'Unavailable'\n });\n}\n\nfunction mergeServers(credentialProvider, list1, list2) {\n for (let i = 0, length = list2.length; i < length; i++) {\n credentialProvider.addOrUpdateServer(list1, list2[i]);\n }\n\n return list1;\n}\n\nfunction updateServerInfo(server, systemInfo) {\n server.Name = systemInfo.ServerName;\n\n if (systemInfo.Id) {\n server.Id = systemInfo.Id;\n }\n if (systemInfo.LocalAddress) {\n server.LocalAddress = systemInfo.LocalAddress;\n }\n}\n\nfunction getEmbyServerUrl(baseUrl, handler) {\n return `${baseUrl}/${handler}`;\n}\n\nfunction getFetchPromise(request) {\n const headers = request.headers || {};\n\n if (request.dataType === 'json') {\n headers.accept = 'application/json';\n }\n\n const fetchRequest = {\n headers,\n method: request.type,\n credentials: 'same-origin'\n };\n\n let contentType = request.contentType;\n\n if (request.data) {\n if (typeof request.data === 'string') {\n fetchRequest.body = request.data;\n } else {\n fetchRequest.body = paramsToString(request.data);\n\n contentType = contentType || 'application/x-www-form-urlencoded; charset=UTF-8';\n }\n }\n\n if (contentType) {\n headers['Content-Type'] = contentType;\n }\n\n if (!request.timeout) {\n return fetch(request.url, fetchRequest);\n }\n\n return fetchWithTimeout(request.url, fetchRequest, request.timeout);\n}\n\nfunction fetchWithTimeout(url, options, timeoutMs) {\n console.log(`fetchWithTimeout: timeoutMs: ${timeoutMs}, url: ${url}`);\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(reject, timeoutMs);\n\n options = options || {};\n options.credentials = 'same-origin';\n\n fetch(url, options).then(\n (response) => {\n clearTimeout(timeout);\n\n console.log(`fetchWithTimeout: succeeded connecting to url: ${url}`);\n\n resolve(response);\n },\n (error) => {\n clearTimeout(timeout);\n\n console.log(`fetchWithTimeout: timed out connecting to url: ${url}`);\n\n reject();\n }\n );\n });\n}\n\nfunction ajax(request) {\n if (!request) {\n throw new Error('Request cannot be null');\n }\n\n request.headers = request.headers || {};\n\n console.log(`ConnectionManager requesting url: ${request.url}`);\n\n return getFetchPromise(request).then(\n (response) => {\n console.log(`ConnectionManager response status: ${response.status}, url: ${request.url}`);\n\n if (response.status < 400) {\n if (request.dataType === 'json' || request.headers.accept === 'application/json') {\n return response.json();\n } else {\n return response;\n }\n } else {\n return Promise.reject(response);\n }\n },\n (err) => {\n console.log(`ConnectionManager request failed to url: ${request.url}`);\n throw err;\n }\n );\n}\n\nfunction replaceAll(originalString, strReplace, strWith) {\n const reg = new RegExp(strReplace, 'ig');\n return originalString.replace(reg, strWith);\n}\n\nfunction normalizeAddress(address) {\n // Attempt to correct bad input\n address = address.trim();\n\n // Seeing failures in iOS when protocol isn't lowercase\n address = replaceAll(address, 'Http:', 'http:');\n address = replaceAll(address, 'Https:', 'https:');\n\n return address;\n}\n\nfunction stringEqualsIgnoreCase(str1, str2) {\n return (str1 || '').toLowerCase() === (str2 || '').toLowerCase();\n}\n\nfunction compareVersions(a, b) {\n // -1 a is smaller\n // 1 a is larger\n // 0 equal\n a = a.split('.');\n b = b.split('.');\n\n for (let i = 0, length = Math.max(a.length, b.length); i < length; i++) {\n const aVal = parseInt(a[i] || '0');\n const bVal = parseInt(b[i] || '0');\n\n if (aVal < bVal) {\n return -1;\n }\n\n if (aVal > bVal) {\n return 1;\n }\n }\n\n return 0;\n}\n\nexport default class ConnectionManager {\n constructor(credentialProvider, appName, appVersion, deviceName, deviceId, capabilities) {\n console.log('Begin ConnectionManager constructor');\n\n const self = this;\n this._apiClients = [];\n\n self._minServerVersion = '3.2.33';\n\n self.appVersion = () => appVersion;\n\n self.appName = () => appName;\n\n self.capabilities = () => capabilities;\n\n self.deviceId = () => deviceId;\n\n self.credentialProvider = () => credentialProvider;\n\n self.getServerInfo = (id) => {\n const servers = credentialProvider.credentials().Servers;\n\n return servers.filter((s) => s.Id === id)[0];\n };\n\n self.getLastUsedServer = () => {\n const servers = credentialProvider.credentials().Servers;\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n\n if (!servers.length) {\n return null;\n }\n\n return servers[0];\n };\n\n self.addApiClient = (apiClient) => {\n self._apiClients.push(apiClient);\n\n const existingServers = credentialProvider\n .credentials()\n .Servers.filter(\n (s) =>\n stringEqualsIgnoreCase(s.ManualAddress, apiClient.serverAddress()) ||\n stringEqualsIgnoreCase(s.LocalAddress, apiClient.serverAddress()) ||\n stringEqualsIgnoreCase(s.RemoteAddress, apiClient.serverAddress())\n );\n\n const existingServer = existingServers.length ? existingServers[0] : apiClient.serverInfo();\n existingServer.DateLastAccessed = new Date().getTime();\n existingServer.LastConnectionMode = ConnectionMode.Manual;\n existingServer.ManualAddress = apiClient.serverAddress();\n\n if (apiClient.manualAddressOnly) {\n existingServer.manualAddressOnly = true;\n }\n\n apiClient.serverInfo(existingServer);\n\n apiClient.onAuthenticated = (instance, result) => onAuthenticated(instance, result, {}, true);\n\n if (!existingServers.length) {\n const credentials = credentialProvider.credentials();\n credentials.Servers = [existingServer];\n credentialProvider.credentials(credentials);\n }\n\n events.trigger(self, 'apiclientcreated', [apiClient]);\n };\n\n self.clearData = () => {\n console.log('connection manager clearing data');\n\n const credentials = credentialProvider.credentials();\n credentials.Servers = [];\n credentialProvider.credentials(credentials);\n };\n\n self._getOrAddApiClient = (server, serverUrl) => {\n let apiClient = self.getApiClient(server.Id);\n\n if (!apiClient) {\n apiClient = new ApiClient(serverUrl, appName, appVersion, deviceName, deviceId);\n\n self._apiClients.push(apiClient);\n\n apiClient.serverInfo(server);\n\n apiClient.onAuthenticated = (instance, result) => {\n return onAuthenticated(instance, result, {}, true);\n };\n\n events.trigger(self, 'apiclientcreated', [apiClient]);\n }\n\n console.log('returning instance from getOrAddApiClient');\n return apiClient;\n };\n\n self.getOrCreateApiClient = (serverId) => {\n const credentials = credentialProvider.credentials();\n const servers = credentials.Servers.filter((s) => stringEqualsIgnoreCase(s.Id, serverId));\n\n if (!servers.length) {\n throw new Error(`Server not found: ${serverId}`);\n }\n\n const server = servers[0];\n\n return self._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));\n };\n\n function onAuthenticated(apiClient, result, options, saveCredentials) {\n const credentials = credentialProvider.credentials();\n const servers = credentials.Servers.filter((s) => s.Id === result.ServerId);\n\n const server = servers.length ? servers[0] : apiClient.serverInfo();\n\n if (options.updateDateLastAccessed !== false) {\n server.DateLastAccessed = new Date().getTime();\n }\n server.Id = result.ServerId;\n\n if (saveCredentials) {\n server.UserId = result.User.Id;\n server.AccessToken = result.AccessToken;\n } else {\n server.UserId = null;\n server.AccessToken = null;\n }\n\n credentialProvider.addOrUpdateServer(credentials.Servers, server);\n credentialProvider.credentials(credentials);\n\n // set this now before updating server info, otherwise it won't be set in time\n apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n apiClient.serverInfo(server);\n afterConnected(apiClient, options);\n\n return onLocalUserSignIn(server, apiClient.serverAddress(), result.User);\n }\n\n function afterConnected(apiClient, options = {}) {\n if (options.reportCapabilities !== false) {\n apiClient.reportCapabilities(capabilities);\n }\n apiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n if (options.enableWebSocket !== false) {\n console.log('calling apiClient.ensureWebSocket');\n\n apiClient.ensureWebSocket();\n }\n }\n\n function onLocalUserSignIn(server, serverUrl, user) {\n // Ensure this is created so that listeners of the event can get the apiClient instance\n self._getOrAddApiClient(server, serverUrl);\n\n // This allows the app to have a single hook that fires before any other\n const promise = self.onLocalUserSignedIn ? self.onLocalUserSignedIn.call(self, user) : Promise.resolve();\n\n return promise.then(() => {\n events.trigger(self, 'localusersignedin', [user]);\n });\n }\n\n function validateAuthentication(server, serverUrl) {\n return ajax({\n type: 'GET',\n url: getEmbyServerUrl(serverUrl, 'System/Info'),\n dataType: 'json',\n headers: {\n 'X-MediaBrowser-Token': server.AccessToken\n }\n }).then(\n (systemInfo) => {\n updateServerInfo(server, systemInfo);\n return Promise.resolve();\n },\n () => {\n server.UserId = null;\n server.AccessToken = null;\n return Promise.resolve();\n }\n );\n }\n\n function getImageUrl(localUser) {\n if (localUser && localUser.PrimaryImageTag) {\n const apiClient = self.getApiClient(localUser);\n\n const url = apiClient.getUserImageUrl(localUser.Id, {\n tag: localUser.PrimaryImageTag,\n type: 'Primary'\n });\n\n return {\n url,\n supportsParams: true\n };\n }\n\n return {\n url: null,\n supportsParams: false\n };\n }\n\n self.user = (apiClient) =>\n new Promise((resolve, reject) => {\n let localUser;\n\n function onLocalUserDone(e) {\n if (apiClient && apiClient.getCurrentUserId()) {\n apiClient.getCurrentUser().then((u) => {\n localUser = u;\n const image = getImageUrl(localUser);\n\n resolve({\n localUser,\n name: localUser ? localUser.Name : null,\n imageUrl: image.url,\n supportsImageParams: image.supportsParams\n });\n });\n }\n }\n\n if (apiClient && apiClient.getCurrentUserId()) {\n onLocalUserDone();\n }\n });\n\n self.logout = () => {\n const promises = [];\n\n for (let i = 0, length = self._apiClients.length; i < length; i++) {\n const apiClient = self._apiClients[i];\n\n if (apiClient.accessToken()) {\n promises.push(logoutOfServer(apiClient));\n }\n }\n\n return Promise.all(promises).then(() => {\n const credentials = credentialProvider.credentials();\n\n const servers = credentials.Servers.filter((u) => u.UserLinkType !== 'Guest');\n\n for (let j = 0, numServers = servers.length; j < numServers; j++) {\n const server = servers[j];\n\n server.UserId = null;\n server.AccessToken = null;\n server.ExchangeToken = null;\n }\n });\n };\n\n function logoutOfServer(apiClient) {\n const serverInfo = apiClient.serverInfo() || {};\n\n const logoutInfo = {\n serverId: serverInfo.Id\n };\n\n return apiClient.logout().then(\n () => {\n events.trigger(self, 'localusersignedout', [logoutInfo]);\n },\n () => {\n events.trigger(self, 'localusersignedout', [logoutInfo]);\n }\n );\n }\n\n self.getSavedServers = () => {\n const credentials = credentialProvider.credentials();\n\n const servers = credentials.Servers.slice(0);\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n\n return servers;\n };\n\n self.getAvailableServers = () => {\n console.log('Begin getAvailableServers');\n\n // Clone the array\n const credentials = credentialProvider.credentials();\n\n return Promise.all([findServers()]).then((responses) => {\n const foundServers = responses[0];\n let servers = credentials.Servers.slice(0);\n mergeServers(credentialProvider, servers, foundServers);\n\n servers.sort((a, b) => (b.DateLastAccessed || 0) - (a.DateLastAccessed || 0));\n credentials.Servers = servers;\n credentialProvider.credentials(credentials);\n\n return servers;\n });\n };\n\n function findServers() {\n return new Promise((resolve, reject) => {\n var onFinish = function (foundServers) {\n var servers = foundServers.map((foundServer) => {\n var info = {\n Id: foundServer.Id,\n LocalAddress: convertEndpointAddressToManualAddress(foundServer) || foundServer.Address,\n Name: foundServer.Name\n };\n info.LastConnectionMode = info.ManualAddress ? ConnectionMode.Manual : ConnectionMode.Local;\n return info;\n });\n resolve(servers);\n };\n\n if (window.NativeShell && typeof window.NativeShell.findServers === 'function') {\n window.NativeShell.findServers(1e3).then(onFinish, function () {\n onFinish([]);\n });\n } else {\n resolve([]);\n }\n });\n }\n\n function convertEndpointAddressToManualAddress(info) {\n if (info.Address && info.EndpointAddress) {\n let address = info.EndpointAddress.split(':')[0];\n\n // Determine the port, if any\n const parts = info.Address.split(':');\n if (parts.length > 1) {\n const portString = parts[parts.length - 1];\n\n if (!isNaN(parseInt(portString))) {\n address += `:${portString}`;\n }\n }\n\n return normalizeAddress(address);\n }\n\n return null;\n }\n\n self.connectToServers = (servers, options) => {\n console.log(`Begin connectToServers, with ${servers.length} servers`);\n\n const firstServer = servers.length ? servers[0] : null;\n // See if we have any saved credentials and can auto sign in\n if (firstServer) {\n return self.connectToServer(firstServer, options).then((result) => {\n if (result.State === 'Unavailable') {\n result.State = 'ServerSelection';\n }\n\n console.log('resolving connectToServers with result.State: ' + result.State);\n return result;\n });\n }\n\n return Promise.resolve({\n Servers: servers,\n State: 'ServerSelection'\n });\n };\n\n function getTryConnectPromise(url, connectionMode, state, resolve, reject) {\n console.log('getTryConnectPromise ' + url);\n\n ajax({\n url: getEmbyServerUrl(url, 'system/info/public'),\n timeout: defaultTimeout,\n type: 'GET',\n dataType: 'json'\n }).then(\n (result) => {\n if (!state.resolved) {\n state.resolved = true;\n\n console.log('Reconnect succeeded to ' + url);\n resolve({\n url: url,\n connectionMode: connectionMode,\n data: result\n });\n }\n },\n () => {\n console.log('Reconnect failed to ' + url);\n\n if (!state.resolved) {\n state.rejects++;\n if (state.rejects >= state.numAddresses) {\n reject();\n }\n }\n }\n );\n }\n\n function tryReconnect(serverInfo) {\n const addresses = [];\n const addressesStrings = [];\n\n // the timeouts are a small hack to try and ensure the remote address doesn't resolve first\n\n // manualAddressOnly is used for the local web app that always connects to a fixed address\n if (\n !serverInfo.manualAddressOnly &&\n serverInfo.LocalAddress &&\n addressesStrings.indexOf(serverInfo.LocalAddress) === -1\n ) {\n addresses.push({\n url: serverInfo.LocalAddress,\n mode: ConnectionMode.Local,\n timeout: 0\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (serverInfo.ManualAddress && addressesStrings.indexOf(serverInfo.ManualAddress) === -1) {\n addresses.push({\n url: serverInfo.ManualAddress,\n mode: ConnectionMode.Manual,\n timeout: 100\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n if (\n !serverInfo.manualAddressOnly &&\n serverInfo.RemoteAddress &&\n addressesStrings.indexOf(serverInfo.RemoteAddress) === -1\n ) {\n addresses.push({\n url: serverInfo.RemoteAddress,\n mode: ConnectionMode.Remote,\n timeout: 200\n });\n addressesStrings.push(addresses[addresses.length - 1].url);\n }\n\n console.log('tryReconnect: ' + addressesStrings.join('|'));\n\n return new Promise((resolve, reject) => {\n const state = {};\n state.numAddresses = addresses.length;\n state.rejects = 0;\n\n addresses.map((url) => {\n setTimeout(() => {\n if (!state.resolved) {\n getTryConnectPromise(url.url, url.mode, state, resolve, reject);\n }\n }, url.timeout);\n });\n });\n }\n\n self.connectToServer = (server, options) => {\n console.log('begin connectToServer');\n\n return new Promise((resolve, reject) => {\n options = options || {};\n\n tryReconnect(server).then(\n (result) => {\n const serverUrl = result.url;\n const connectionMode = result.connectionMode;\n result = result.data;\n\n if (compareVersions(self.minServerVersion(), result.Version) === 1) {\n console.log('minServerVersion requirement not met. Server version: ' + result.Version);\n resolve({\n State: 'ServerUpdateNeeded',\n Servers: [server]\n });\n } else if (server.Id && result.Id !== server.Id) {\n console.log(\n 'http request succeeded, but found a different server Id than what was expected'\n );\n resolveFailure(self, resolve);\n } else {\n onSuccessfulConnection(server, result, connectionMode, serverUrl, true, resolve, options);\n }\n },\n () => {\n resolveFailure(self, resolve);\n }\n );\n });\n };\n\n function onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, verifyLocalAuthentication, resolve, options={}) {\n const credentials = credentialProvider.credentials();\n\n if (options.enableAutoLogin === false) {\n server.UserId = null;\n server.AccessToken = null;\n } else if (server.AccessToken && verifyLocalAuthentication) {\n return void validateAuthentication(server, serverUrl).then(function () {\n onSuccessfulConnection(server, systemInfo, connectionMode, serverUrl, false, resolve, options);\n });\n }\n\n updateServerInfo(server, systemInfo);\n\n server.LastConnectionMode = connectionMode;\n\n if (options.updateDateLastAccessed !== false) {\n server.DateLastAccessed = new Date().getTime();\n }\n credentialProvider.addOrUpdateServer(credentials.Servers, server);\n credentialProvider.credentials(credentials);\n\n const result = {\n Servers: []\n };\n\n result.ApiClient = self._getOrAddApiClient(server, serverUrl);\n\n result.ApiClient.setSystemInfo(systemInfo);\n\n result.State = server.AccessToken && options.enableAutoLogin !== false ? 'SignedIn' : 'ServerSignIn';\n\n result.Servers.push(server);\n\n // set this now before updating server info, otherwise it won't be set in time\n result.ApiClient.enableAutomaticBitrateDetection = options.enableAutomaticBitrateDetection;\n\n result.ApiClient.updateServerInfo(server, serverUrl);\n\n const resolveActions = function () {\n resolve(result);\n\n events.trigger(self, 'connected', [result]);\n };\n\n if (result.State === 'SignedIn') {\n afterConnected(result.ApiClient, options);\n\n result.ApiClient.getCurrentUser().then((user) => {\n onLocalUserSignIn(server, serverUrl, user).then(resolveActions, resolveActions);\n }, resolveActions);\n } else {\n resolveActions();\n }\n }\n\n function tryConnectToAddress(address, options) {\n const server = {\n ManualAddress: address,\n LastConnectionMode: ConnectionMode.Manual\n };\n\n return self.connectToServer(server, options).then((result) => {\n // connectToServer never rejects, but resolves with State='Unavailable'\n if (result.State === 'Unavailable') {\n return Promise.reject();\n }\n return result;\n });\n }\n\n self.connectToAddress = function (address, options) {\n if (!address) {\n return Promise.reject();\n }\n\n address = normalizeAddress(address);\n\n let urls = [];\n\n if (/^[^:]+:\\/\\//.test(address)) {\n // Protocol specified - connect as is\n urls.push(address);\n } else {\n urls.push(`https://${address}`);\n urls.push(`http://${address}`);\n }\n\n let i = 0;\n\n function onFail() {\n console.log(`connectToAddress ${urls[i]} failed`);\n\n if (++i < urls.length) {\n return tryConnectToAddress(urls[i], options).catch(onFail);\n }\n\n return Promise.resolve({\n State: 'Unavailable'\n });\n }\n\n return tryConnectToAddress(urls[i], options).catch(onFail);\n };\n\n self.deleteServer = (serverId) => {\n if (!serverId) {\n throw new Error('null serverId');\n }\n\n let server = credentialProvider.credentials().Servers.filter((s) => s.Id === serverId);\n server = server.length ? server[0] : null;\n\n return new Promise((resolve, reject) => {\n function onDone() {\n const credentials = credentialProvider.credentials();\n\n credentials.Servers = credentials.Servers.filter((s) => s.Id !== serverId);\n\n credentialProvider.credentials(credentials);\n resolve();\n }\n\n if (!server.ConnectServerId) {\n onDone();\n return;\n }\n });\n };\n }\n\n connect(options) {\n console.log('Begin connect');\n\n return this.getAvailableServers().then((servers) => {\n return this.connectToServers(servers, options);\n });\n }\n\n handleMessageReceived(msg) {\n const serverId = msg.ServerId;\n if (serverId) {\n const apiClient = this.getApiClient(serverId);\n if (apiClient) {\n if (typeof msg.Data === 'string') {\n try {\n msg.Data = JSON.parse(msg.Data);\n } catch (err) {\n console.log('unable to parse json content: ' + err);\n }\n }\n\n apiClient.handleMessageReceived(msg);\n }\n }\n }\n\n getApiClients() {\n const servers = this.getSavedServers();\n\n for (let i = 0, length = servers.length; i < length; i++) {\n const server = servers[i];\n if (server.Id) {\n this._getOrAddApiClient(server, getServerAddress(server, server.LastConnectionMode));\n }\n }\n\n return this._apiClients;\n }\n\n getApiClient(item) {\n if (!item) {\n throw new Error('item or serverId cannot be null');\n }\n\n // Accept string + object\n if (item.ServerId) {\n item = item.ServerId;\n }\n\n return this._apiClients.filter((a) => {\n const serverInfo = a.serverInfo();\n\n // We have to keep this hack in here because of the addApiClient method\n return !serverInfo || serverInfo.Id === item;\n })[0];\n }\n\n minServerVersion(val) {\n if (val) {\n this._minServerVersion = val;\n }\n\n return this._minServerVersion;\n }\n}\n","import events from './events';\nimport appStorage from './appStorage';\n\nfunction ensure(instance, data) {\n if (!instance._credentials) {\n const json = instance.appStorage.getItem(instance.key) || '{}';\n\n console.log(`credentials initialized with: ${json}`);\n instance._credentials = JSON.parse(json);\n instance._credentials.Servers = instance._credentials.Servers || [];\n }\n}\n\nfunction set(instance, data) {\n if (data) {\n instance._credentials = data;\n instance.appStorage.setItem(instance.key, JSON.stringify(data));\n } else {\n instance.clear();\n }\n\n events.trigger(instance, 'credentialsupdated');\n}\n\nexport default class Credentials {\n constructor(key) {\n this.key = key || 'jellyfin_credentials';\n this.appStorage = appStorage;\n }\n\n clear() {\n this._credentials = null;\n this.appStorage.removeItem(this.key);\n }\n\n credentials(data) {\n if (data) {\n set(this, data);\n }\n\n ensure(this);\n return this._credentials;\n }\n\n addOrUpdateServer(list, server) {\n if (!server.Id) {\n throw new Error('Server.Id cannot be null or empty');\n }\n\n const existing = list.filter(({ Id }) => Id === server.Id)[0];\n\n if (existing) {\n // Merge the data\n existing.DateLastAccessed = Math.max(existing.DateLastAccessed || 0, server.DateLastAccessed || 0);\n\n existing.UserLinkType = server.UserLinkType;\n\n if (server.AccessToken) {\n existing.AccessToken = server.AccessToken;\n existing.UserId = server.UserId;\n }\n if (server.ExchangeToken) {\n existing.ExchangeToken = server.ExchangeToken;\n }\n if (server.RemoteAddress) {\n existing.RemoteAddress = server.RemoteAddress;\n }\n if (server.ManualAddress) {\n existing.ManualAddress = server.ManualAddress;\n }\n if (server.LocalAddress) {\n existing.LocalAddress = server.LocalAddress;\n }\n if (server.Name) {\n existing.Name = server.Name;\n }\n if (server.LastConnectionMode != null) {\n existing.LastConnectionMode = server.LastConnectionMode;\n }\n if (server.ConnectServerId) {\n existing.ConnectServerId = server.ConnectServerId;\n }\n\n return existing;\n } else {\n list.push(server);\n return server;\n }\n }\n}\n","import ApiClient from './apiClient';\nimport ApiClientCore from './apiClientCore';\nimport AppStorage from './appStorage';\nimport ConnectionManager from './connectionManager';\nimport Credentials from './credentials';\nimport Events from './events';\nconst fetch = require('node-fetch');\n\n\nexport default {\n ApiClient,\n ApiClientCore,\n AppStorage,\n ConnectionManager,\n Credentials,\n Events\n};\n"],"sourceRoot":""} \ No newline at end of file diff --git a/jellyfin-apiclient/jest.setup.js b/jellyfin-apiclient/jest.setup.js deleted file mode 100644 index 74a91e0..0000000 --- a/jellyfin-apiclient/jest.setup.js +++ /dev/null @@ -1,2 +0,0 @@ -// Add fetch polyfill for jest -import 'isomorphic-fetch'; \ No newline at end of file diff --git a/jellyfin-apiclient/package.json b/jellyfin-apiclient/package.json deleted file mode 100644 index 6bc1dbc..0000000 --- a/jellyfin-apiclient/package.json +++ /dev/null @@ -1,90 +0,0 @@ -{ - "_from": "jellyfin-apiclient", - "_id": "jellyfin-apiclient@1.4.1", - "_inBundle": false, - "_integrity": "sha512-BTTRucQ4tCLyiZ9kR9nAoxqxYp5/z+MCzkayy9vmMZ5C7jlVVsnxAXuuZjoa+AgXMjohXcM5Ci54myfJM1pRkA==", - "_location": "/jellyfin-apiclient", - "_phantomChildren": {}, - "_requested": { - "type": "tag", - "registry": true, - "raw": "jellyfin-apiclient", - "name": "jellyfin-apiclient", - "escapedName": "jellyfin-apiclient", - "rawSpec": "", - "saveSpec": null, - "fetchSpec": "latest" - }, - "_requiredBy": [ - "#USER", - "/" - ], - "_resolved": "https://registry.npmjs.org/jellyfin-apiclient/-/jellyfin-apiclient-1.4.1.tgz", - "_shasum": "5e544a19bc001b16669eb7ecf46bb7d652365e41", - "_spec": "jellyfin-apiclient", - "_where": "/home/kilian/Documents/GitTests/Jellyfin/jellyfin-discord-music-bot", - "author": "", - "browserslist": [ - "last 2 Firefox versions", - "last 2 Chrome versions", - "last 2 ChromeAndroid versions", - "last 2 Safari versions", - "last 2 iOS versions", - "last 2 Edge versions", - "Chrome 27", - "Chrome 38", - "Chrome 47", - "Chrome 53", - "Chrome 56", - "Chrome 63", - "Firefox ESR" - ], - "bugs": { - "url": "https://github.com/jellyfin/jellyfin-apiclient-javascript/issues" - }, - "bundleDependencies": false, - "dependencies": {}, - "deprecated": false, - "description": "API client for Jellyfin", - "devDependencies": { - "@babel/core": "^7.9.6", - "@babel/preset-env": "^7.9.6", - "@types/jest": "^25.2.1", - "@types/node": "^13.13.5", - "@typescript-eslint/eslint-plugin": "^2.31.0", - "@typescript-eslint/parser": "^2.31.0", - "babel-loader": "^8.0.5", - "eslint": "^6.8.0", - "eslint-plugin-import": "^2.20.2", - "eslint-plugin-jest": "^23.9.0", - "eslint-plugin-promise": "^4.2.1", - "isomorphic-fetch": "^2.2.1", - "jest": "^26.0.1", - "jest-junit": "^10.0.0", - "jsdoc": "^3.6.4", - "prettier": "2.0.5", - "source-map-loader": "^0.2.4", - "ts-jest": "^25.5.0", - "ts-loader": "^7.0.3", - "typescript": "^3.8.3", - "webpack": "^4.43.0", - "webpack-cli": "^3.2.1" - }, - "homepage": "https://github.com/jellyfin/jellyfin-apiclient-javascript#readme", - "license": "MIT", - "main": "dist/jellyfin-apiclient.js", - "name": "jellyfin-apiclient", - "repository": { - "type": "git", - "url": "git+https://github.com/jellyfin/jellyfin-apiclient-javascript.git" - }, - "scripts": { - "build": "webpack --mode production", - "dev": "webpack --mode development", - "docs": "jsdoc src -r -R README.md -d docs", - "lint": "eslint \"src\"", - "prepare": "webpack", - "test": "jest" - }, - "version": "1.4.1" -} diff --git a/package.json b/package.json index b6ef19a..3df1bf6 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,11 @@ "name": "jellyfin-discord-music-bot", "version": "0.0.1", "description": "A Discord Music Bot for the Jellyfin Media Server", - "main": "index.js", + "main": "src/index.js", "scripts": { - "test": "echo \"Error: no test specified\" && exit 1" + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node src/index.js", + "postinstall": "npx patch-package" }, "repository": { "type": "git", @@ -24,6 +26,10 @@ "dependencies": { "@discordjs/opus": "^0.3.2", "discord.js": "^12.3.1", - "jellyfin-apiclient": "^1.4.1" + "jellyfin-apiclient": "^1.4.1", + "node-fetch": "^2.6.0", + "nodejs": "0.0.0", + "window": "^4.2.7", + "ytdl-core": "^3.2.2" } } diff --git a/patches/jellyfin-apiclient+1.4.1.patch b/patches/jellyfin-apiclient+1.4.1.patch new file mode 100644 index 0000000..965cc9f --- /dev/null +++ b/patches/jellyfin-apiclient+1.4.1.patch @@ -0,0 +1,11 @@ +diff --git a/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js b/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js +index c2382cd..1459c6d 100644 +--- a/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js ++++ b/node_modules/jellyfin-apiclient/dist/jellyfin-apiclient.js +@@ -1,2 +1,5 @@ ++const fetch = require('node-fetch') ++const window = require('window') ++ + !function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports["jellyfin-apiclient"]=t():e["jellyfin-apiclient"]=t()}(window,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=0)}([function(e,t,r){"use strict";function n(e,t){if(!e)throw new Error("obj cannot be null!");e._callbacks=e._callbacks||{};var r=e._callbacks[t];return r||(e._callbacks[t]=[],r=e._callbacks[t]),r}r.r(t);var i={on:function(e,t,r){n(e,t).push(r)},off:function(e,t,r){var i=n(e,t),o=i.indexOf(r);-1!==o&&i.splice(o,1)},trigger:function(e,t){var r={type:t},i=[];i.push(r);for(var o=arguments[2]||[],a=0,s=o.length;a=r.length)return j(t,i);var o=r[n];return t.getDownloadSpeed(o.bytes).then((function(i){return i=20?Promise.reject():function(e){var t=[],r=[],n=e.serverInfo();return n.LocalAddress&&-1===r.indexOf(n.LocalAddress)&&(t.push({url:n.LocalAddress,timeout:0}),r.push(t[t.length-1].url)),n.ManualAddress&&-1===r.indexOf(n.ManualAddress)&&(t.push({url:n.ManualAddress,timeout:100}),r.push(t[t.length-1].url)),n.RemoteAddress&&-1===r.indexOf(n.RemoteAddress)&&(t.push({url:n.RemoteAddress,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(r,n){var i={};i.numAddresses=t.length,i.rejects=0,t.map((function(t){setTimeout((function(){i.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+t),I(e.getUrl("system/info/public",null,t),{method:"GET",accept:"application/json"},15e3).then((function(){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+t),e.serverAddress(t),n())}),(function(){r.resolved||(console.log("Reconnect failed to "+t),r.rejects++,r.rejects>=r.numAddresses&&i())}))}(e,t.url,i,r,n)}),t.timeout)}))}))}(t).catch((function(n){return console.log("error in tryReconnectInternal: "+(n||"")),new Promise((function(n,i){setTimeout((function(){e(t,r+1).then(n,i)}),500)}))}))}(r).then((function(){return console.log("Reconnect succeesed"),e.url=e.url.replace(i,r.serverAddress()),r.fetchWithFailover(e,!1)})).catch((function(t){throw console.log("Reconnect failed"),y(r,e.url,{}),t}))}))}},{key:"fetch",value:function(e,t){if(!e)return Promise.reject("Request cannot be null");if(e.headers=e.headers||{},!1!==t&&this.setRequestHeaders(e.headers),!1===this.enableAutomaticNetworking||"GET"!==e.type){console.log("Requesting url without automatic networking: ".concat(e.url));var r=this;return S(e).then((function(t){return r.lastFetch=(new Date).getTime(),t.status<400?"json"===e.dataType||"application/json"===e.headers.accept?t.json():"text"===e.dataType||0===(t.headers.get("Content-Type")||"").toLowerCase().indexOf("text/")?t.text():t:(y(r,e.url,t),Promise.reject(t))})).catch((function(t){return y(r,e.url,{}),Promise.reject(t)}))}return this.fetchWithFailover(e,!0)}},{key:"setAuthenticationInfo",value:function(e,t){this._currentUser=null,this._serverInfo.AccessToken=e,this._serverInfo.UserId=t,v(this)}},{key:"serverInfo",value:function(e){return e&&(this._serverInfo=e),this._serverInfo}},{key:"getCurrentUserId",value:function(){return this._serverInfo.UserId}},{key:"accessToken",value:function(){return this._serverInfo.AccessToken}},{key:"serverId",value:function(){return this.serverInfo().Id}},{key:"serverName",value:function(){return this.serverInfo().Name}},{key:"ajax",value:function(e,t){return e?this.fetch(e,t):Promise.reject("Request cannot be null")}},{key:"getCurrentUser",value:function(e){if(this._currentUser)return Promise.resolve(this._currentUser);var t=this.getCurrentUserId();if(!t)return Promise.reject();var r,n=this,i=this.getUser(t).then((function(e){return l.setItem("user-".concat(e.Id,"-").concat(e.ServerId),JSON.stringify(e)),n._currentUser=e,e})).catch((function(e){if(!e.status&&t&&n.accessToken()&&(r=T(n,t)))return Promise.resolve(r);throw e}));return!this.lastFetch&&!1!==e&&(r=T(n,t))?Promise.resolve(r):i}},{key:"isLoggedIn",value:function(){var e=this.serverInfo();return!!(e&&e.UserId&&e.AccessToken)}},{key:"logout",value:function(){var e=this;g(this),this.closeWebSocket();var t=function(){var t=e.serverInfo();t&&t.UserId&&t.Id&&l.removeItem("user-".concat(t.UserId,"-").concat(t.Id)),e.setAuthenticationInfo(null,null)};if(this.accessToken()){var r=this.getUrl("Sessions/Logout");return this.ajax({type:"POST",url:r}).then(t,t)}return t(),Promise.resolve()}},{key:"authenticateUserByName",value:function(e,t){var r=this;if(!e)return Promise.reject();var n=this.getUrl("Users/authenticatebyname");return new Promise((function(i,o){var a={Username:e,Pw:t||""};r.ajax({type:"POST",url:n,data:JSON.stringify(a),dataType:"json",contentType:"application/json"}).then((function(e){var t=function(){v(r),i(e)};r.onAuthenticated?r.onAuthenticated(r,e).then(t):t()})).catch(o)}))}},{key:"ensureWebSocket",value:function(){if(!this.isWebSocketOpenOrConnecting()&&this.isWebSocketSupported())try{this.openWebSocket()}catch(e){console.log("Error opening web socket: ".concat(e))}}},{key:"openWebSocket",value:function(){var e=this.accessToken();if(!e)throw new Error("Cannot open web socket without access token.");var t=this.getUrl("socket");t=p(t,"emby/socket","embywebsocket"),t=p(t,"https:","wss:"),t=p(t,"http:","ws:"),t+="?api_key=".concat(e),t+="&deviceId=".concat(this.deviceId()),console.log("opening web socket with url: ".concat(t));var r,n,o=new WebSocket(t);o.onmessage=P.bind(this),o.onopen=E.bind(this),o.onerror=A.bind(this),r=this,(n=o).onclose=function(){console.log("web socket closed"),O(n),r._webSocket===n&&(console.log("nulling out web socket"),r._webSocket=null),setTimeout((function(){i.trigger(r,"websocketclose")}),0)},this._webSocket=o}},{key:"closeWebSocket",value:function(){var e=this._webSocket;e&&e.readyState===WebSocket.OPEN&&e.close()}},{key:"sendWebSocketMessage",value:function(e,t){console.log("Sending web socket message: ".concat(e));var r={MessageType:e};t&&(r.Data=t),r=JSON.stringify(r),this._webSocket.send(r)}},{key:"sendMessage",value:function(e,t){this.isWebSocketOpen()&&this.sendWebSocketMessage(e,t)}},{key:"isMessageChannelOpen",value:function(){return this.isWebSocketOpen()}},{key:"isWebSocketOpen",value:function(){var e=this._webSocket;return!!e&&e.readyState===WebSocket.OPEN}},{key:"isWebSocketOpenOrConnecting",value:function(){var e=this._webSocket;return!!e&&(e.readyState===WebSocket.OPEN||e.readyState===WebSocket.CONNECTING)}},{key:"get",value:function(e){return this.ajax({type:"GET",url:e})}},{key:"getJSON",value:function(e,t){return this.fetch({url:e,type:"GET",dataType:"json",headers:{accept:"application/json"}},t)}},{key:"updateServerInfo",value:function(e,t){if(null==e)throw new Error("server cannot be null");if(this.serverInfo(e),!t)throw new Error("serverUrl cannot be null. serverInfo: ".concat(JSON.stringify(e)));console.log("Setting server address to ".concat(t)),this.serverAddress(t)}},{key:"isWebSocketSupported",value:function(){try{return null!=WebSocket}catch(e){return!1}}},{key:"clearAuthenticationInfo",value:function(){this.setAuthenticationInfo(null,null)}},{key:"encodeName",value:function(e){var t=m({name:e=(e=(e=e.split("/").join("-")).split("&").join("-")).split("?").join("-")});return t.substring(t.indexOf("=")+1).replace("'","%27")}},{key:"getServerTime",value:function(){var e=this.getUrl("GetUTCTime");return this.ajax({type:"GET",url:e})}},{key:"getDownloadSpeed",value:function(e){var t=this.getUrl("Playback/BitrateTest",{Size:e}),r=(new Date).getTime();return this.ajax({type:"GET",url:t,timeout:5e3}).then((function(){var t=((new Date).getTime()-r)/1e3,n=e/t;return Math.round(8*n)}))}},{key:"detectBitrate",value:function(e){if(!e&&this.lastDetectedBitrate&&(new Date).getTime()-(this.lastDetectedBitrateTime||0)<=36e5)return Promise.resolve(this.lastDetectedBitrate);var t=this;return this.getEndpointInfo().then((function(e){return N(t,e)}),(function(e){return N(t,{})}))}},{key:"getItem",value:function(e,t){if(!t)throw new Error("null itemId");var r=e?this.getUrl("Users/".concat(e,"/Items/").concat(t)):this.getUrl("Items/".concat(t));return this.getJSON(r)}},{key:"getRootFolder",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Items/Root"));return this.getJSON(t)}},{key:"getNotificationSummary",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Notifications/".concat(e,"/Summary"));return this.getJSON(t)}},{key:"getNotifications",value:function(e,t){if(!e)throw new Error("null userId");var r=this.getUrl("Notifications/".concat(e),t||{});return this.getJSON(r)}},{key:"markNotificationsRead",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null idList");var n=r?"Read":"Unread",i={UserId:e,Ids:t.join(",")},o=this.getUrl("Notifications/".concat(e,"/").concat(n),i);return this.ajax({type:"POST",url:o})}},{key:"getRemoteImageProviders",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Providers"),e);return this.getJSON(r)}},{key:"getAvailableRemoteImages",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages"),e);return this.getJSON(r)}},{key:"downloadRemoteImage",value:function(e){if(!e)throw new Error("null options");var t=L(this,e),r=this.getUrl("".concat(t,"/RemoteImages/Download"),e);return this.ajax({type:"POST",url:r})}},{key:"getRecordingFolders",value:function(e){var t=this.getUrl("LiveTv/Recordings/Folders",{userId:e});return this.getJSON(t)}},{key:"getLiveTvInfo",value:function(e){var t=this.getUrl("LiveTv/Info",e||{});return this.getJSON(t)}},{key:"getLiveTvGuideInfo",value:function(e){var t=this.getUrl("LiveTv/GuideInfo",e||{});return this.getJSON(t)}},{key:"getLiveTvChannel",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Channels/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvChannels",value:function(e){var t=this.getUrl("LiveTv/Channels",e||{});return this.getJSON(t)}},{key:"getLiveTvPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return e.channelIds&&e.channelIds.length>1800?this.ajax({type:"POST",url:this.getUrl("LiveTv/Programs"),data:JSON.stringify(e),contentType:"application/json",dataType:"json"}):this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs",e),dataType:"json"})}},{key:"getLiveTvRecommendedPrograms",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.ajax({type:"GET",url:this.getUrl("LiveTv/Programs/Recommended",e),dataType:"json"})}},{key:"getLiveTvRecordings",value:function(e){var t=this.getUrl("LiveTv/Recordings",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingSeries",value:function(e){var t=this.getUrl("LiveTv/Recordings/Series",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroups",value:function(e){var t=this.getUrl("LiveTv/Recordings/Groups",e||{});return this.getJSON(t)}},{key:"getLiveTvRecordingGroup",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/Groups/".concat(e));return this.getJSON(t)}},{key:"getLiveTvRecording",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Recordings/".concat(e),r);return this.getJSON(n)}},{key:"getLiveTvProgram",value:function(e,t){if(!e)throw new Error("null id");var r={};t&&(r.userId=t);var n=this.getUrl("LiveTv/Programs/".concat(e),r);return this.getJSON(n)}},{key:"deleteLiveTvRecording",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Recordings/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"cancelLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getLiveTvTimers",value:function(e){var t=this.getUrl("LiveTv/Timers",e||{});return this.getJSON(t)}},{key:"getLiveTvTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Timers/".concat(e));return this.getJSON(t)}},{key:"getNewLiveTvTimerDefaults",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("LiveTv/Timers/Defaults",e);return this.getJSON(t)}},{key:"createLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/Timers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"resetLiveTvTuner",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/Tuners/".concat(e,"/Reset"));return this.ajax({type:"POST",url:t})}},{key:"getLiveTvSeriesTimers",value:function(e){var t=this.getUrl("LiveTv/SeriesTimers",e||{});return this.getJSON(t)}},{key:"getLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.getJSON(t)}},{key:"cancelLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"createLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateLiveTvSeriesTimer",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("LiveTv/SeriesTimers/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"getRegistrationInfo",value:function(e){var t=this.getUrl("Registrations/".concat(e));return this.getJSON(t)}},{key:"getSystemInfo",value:function(e){var t=this.getUrl("System/Info"),r=this;return this.getJSON(t).then((function(e){return r.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getSyncStatus",value:function(){var e=this.getUrl("Sync/"+itemId+"/Status");return this.ajax({url:e,type:"POST",dataType:"json",contentType:"application/json",data:JSON.stringify({TargetId:this.deviceId()})})}},{key:"getPublicSystemInfo",value:function(){var e=this.getUrl("System/Info/Public"),t=this;return this.getJSON(e).then((function(e){return t.setSystemInfo(e),Promise.resolve(e)}))}},{key:"getInstantMixFromItem",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/InstantMix"),t);return this.getJSON(r)}},{key:"getEpisodes",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Episodes"),t);return this.getJSON(r)}},{key:"getDisplayPreferences",value:function(e,t,r){var n=this.getUrl("DisplayPreferences/".concat(e),{userId:t,client:r});return this.getJSON(n)}},{key:"updateDisplayPreferences",value:function(e,t,r,n){var i=this.getUrl("DisplayPreferences/".concat(e),{userId:r,client:n});return this.ajax({type:"POST",url:i,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getSeasons",value:function(e,t){var r=this.getUrl("Shows/".concat(e,"/Seasons"),t);return this.getJSON(r)}},{key:"getSimilarItems",value:function(e,t){var r=this.getUrl("Items/".concat(e,"/Similar"),t);return this.getJSON(r)}},{key:"getCultures",value:function(){var e=this.getUrl("Localization/cultures");return this.getJSON(e)}},{key:"getCountries",value:function(){var e=this.getUrl("Localization/countries");return this.getJSON(e)}},{key:"getPlaybackInfo",value:function(e,t,r){var n={DeviceProfile:r};return this.ajax({url:this.getUrl("Items/".concat(e,"/PlaybackInfo"),t),type:"POST",data:JSON.stringify(n),contentType:"application/json",dataType:"json"})}},{key:"getLiveStreamMediaInfo",value:function(e){var t={LiveStreamId:e};return this.ajax({url:this.getUrl("LiveStreams/MediaInfo"),type:"POST",data:JSON.stringify(t),contentType:"application/json",dataType:"json"})}},{key:"getIntros",value:function(e){return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/").concat(e,"/Intros")))}},{key:"getDirectoryContents",value:function(e,t){if(!e)throw new Error("null path");if("string"!=typeof e)throw new Error("invalid path");(t=t||{}).path=e;var r=this.getUrl("Environment/DirectoryContents",t);return this.getJSON(r)}},{key:"getNetworkShares",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/NetworkShares",t);return this.getJSON(r)}},{key:"getParentPath",value:function(e){if(!e)throw new Error("null path");var t={};t.path=e;var r=this.getUrl("Environment/ParentPath",t);return this.ajax({type:"GET",url:r,dataType:"text"})}},{key:"getDrives",value:function(){var e=this.getUrl("Environment/Drives");return this.getJSON(e)}},{key:"getNetworkDevices",value:function(){var e=this.getUrl("Environment/NetworkDevices");return this.getJSON(e)}},{key:"cancelPackageInstallation",value:function(e){if(!e)throw new Error("null installationId");var t=this.getUrl("Packages/Installing/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"refreshItem",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/Refresh"),t||{});return this.ajax({type:"POST",url:r})}},{key:"installPlugin",value:function(e,t,r){if(!e)throw new Error("null name");var n={AssemblyGuid:t};r&&(n.version=r);var i=this.getUrl("Packages/Installed/".concat(e),n);return this.ajax({type:"POST",url:i})}},{key:"restartServer",value:function(){var e=this.getUrl("System/Restart");return this.ajax({type:"POST",url:e})}},{key:"shutdownServer",value:function(){var e=this.getUrl("System/Shutdown");return this.ajax({type:"POST",url:e})}},{key:"getPackageInfo",value:function(e,t){if(!e)throw new Error("null name");var r={AssemblyGuid:t},n=this.getUrl("Packages/".concat(e),r);return this.getJSON(n)}},{key:"getVirtualFolders",value:function(){var e="Library/VirtualFolders";return e=this.getUrl(e),this.getJSON(e)}},{key:"getPhysicalPaths",value:function(){var e=this.getUrl("Library/PhysicalPaths");return this.getJSON(e)}},{key:"getServerConfiguration",value:function(){var e=this.getUrl("System/Configuration");return this.getJSON(e)}},{key:"getDevicesOptions",value:function(){var e=this.getUrl("System/Configuration/devices");return this.getJSON(e)}},{key:"getContentUploadHistory",value:function(){var e=this.getUrl("Devices/CameraUploads",{DeviceId:this.deviceId()});return this.getJSON(e)}},{key:"getNamedConfiguration",value:function(e){var t=this.getUrl("System/Configuration/".concat(e));return this.getJSON(t)}},{key:"getScheduledTasks",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("ScheduledTasks",e);return this.getJSON(t)}},{key:"startScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"POST",url:t})}},{key:"getScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/".concat(e));return this.getJSON(t)}},{key:"getNextUpEpisodes",value:function(e){var t=this.getUrl("Shows/NextUp",e);return this.getJSON(t)}},{key:"stopScheduledTask",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("ScheduledTasks/Running/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"getPluginConfiguration",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.getJSON(t)}},{key:"getAvailablePlugins",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};e.PackageType="UserInstalled";var t=this.getUrl("Packages",e);return this.getJSON(t)}},{key:"uninstallPlugin",value:function(e){if(!e)throw new Error("null Id");var t=this.getUrl("Plugins/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"removeVirtualFolder",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders";return r=this.getUrl(r,{refreshLibrary:!!t,name:e}),this.ajax({type:"DELETE",url:r})}},{key:"addVirtualFolder",value:function(e,t,r,n){if(!e)throw new Error("null name");var i={};t&&(i.collectionType=t),i.refreshLibrary=!!r,i.name=e;var o="Library/VirtualFolders";return o=this.getUrl(o,i),this.ajax({type:"POST",url:o,data:JSON.stringify({LibraryOptions:n}),contentType:"application/json"})}},{key:"updateVirtualFolderOptions",value:function(e,t){if(!e)throw new Error("null name");var r="Library/VirtualFolders/LibraryOptions";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Id:e,LibraryOptions:t}),contentType:"application/json"})}},{key:"renameVirtualFolder",value:function(e,t,r){if(!e)throw new Error("null name");var n="Library/VirtualFolders/Name";return n=this.getUrl(n,{refreshLibrary:!!r,newName:t,name:e}),this.ajax({type:"POST",url:n})}},{key:"addMediaPath",value:function(e,t,r,n){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var i="Library/VirtualFolders/Paths",o={Path:t};return r&&(o.NetworkPath=r),i=this.getUrl(i,{refreshLibrary:!!n}),this.ajax({type:"POST",url:i,data:JSON.stringify({Name:e,PathInfo:o}),contentType:"application/json"})}},{key:"updateMediaPath",value:function(e,t){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null pathInfo");var r="Library/VirtualFolders/Paths/Update";return r=this.getUrl(r),this.ajax({type:"POST",url:r,data:JSON.stringify({Name:e,PathInfo:t}),contentType:"application/json"})}},{key:"removeMediaPath",value:function(e,t,r){if(!e)throw new Error("null virtualFolderName");if(!t)throw new Error("null mediaPath");var n="Library/VirtualFolders/Paths";return n=this.getUrl(n,{refreshLibrary:!!r,path:t,name:e}),this.ajax({type:"DELETE",url:n})}},{key:"deleteUser",value:function(e){if(!e)throw new Error("null id");var t=this.getUrl("Users/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"deleteUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");var n=this.getUrl("Users/".concat(e,"/Images/").concat(t));return null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItemImage",value:function(e,t,r){if(!t)throw new Error("null imageType");var n=this.getUrl("Items/".concat(e,"/Images"));return n+="/".concat(t),null!=r&&(n+="/".concat(r)),this.ajax({type:"DELETE",url:n})}},{key:"deleteItem",value:function(e){if(!e)throw new Error("null itemId");var t=this.getUrl("Items/".concat(e));return this.ajax({type:"DELETE",url:t})}},{key:"stopActiveEncodings",value:function(e){var t={deviceId:this.deviceId()};e&&(t.PlaySessionId=e);var r=this.getUrl("Videos/ActiveEncodings",t);return this.ajax({type:"DELETE",url:r})}},{key:"reportCapabilities",value:function(e){var t=this.getUrl("Sessions/Capabilities/Full");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateItemImageIndex",value:function(e,t,r,n){if(!t)throw new Error("null imageType");var i={newIndex:n},o=this.getUrl("Items/".concat(e,"/Images/").concat(t,"/").concat(r,"/Index"),i);return this.ajax({type:"POST",url:o})}},{key:"getItemImageInfos",value:function(e){var t=this.getUrl("Items/".concat(e,"/Images"));return this.getJSON(t)}},{key:"getCriticReviews",value:function(e,t){if(!e)throw new Error("null itemId");var r=this.getUrl("Items/".concat(e,"/CriticReviews"),t);return this.getJSON(r)}},{key:"getItemDownloadUrl",value:function(e){if(!e)throw new Error("itemId cannot be empty");var t="Items/".concat(e,"/Download");return this.getUrl(t,{api_key:this.accessToken()})}},{key:"getSessions",value:function(e){var t=this.getUrl("Sessions",e);return this.getJSON(t)}},{key:"uploadUserImage",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this;return new Promise((function(i,o){var a=new FileReader;a.onerror=function(){o()},a.onabort=function(){o()},a.onload=function(a){var s=a.target.result.split(",")[1],u=n.getUrl("Users/".concat(e,"/Images/").concat(t));n.ajax({type:"POST",url:u,data:s,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(i,o)},a.readAsDataURL(r)}))}},{key:"uploadItemImage",value:function(e,t,r){if(!e)throw new Error("null itemId");if(!t)throw new Error("null imageType");if(!r)throw new Error("File must be an image.");if(!r.type.startsWith("image/"))throw new Error("File must be an image.");var n=this.getUrl("Items/".concat(e,"/Images"));n+="/".concat(t);var i=this;return new Promise((function(e,t){var o=new FileReader;o.onerror=function(){t()},o.onabort=function(){t()},o.onload=function(o){var a=o.target.result.split(",")[1];i.ajax({type:"POST",url:n,data:a,contentType:"image/".concat(r.name.substring(r.name.lastIndexOf(".")+1))}).then(e,t)},o.readAsDataURL(r)}))}},{key:"getInstalledPlugins",value:function(){var e=this.getUrl("Plugins",{});return this.getJSON(e)}},{key:"getUser",value:function(e){if(!e)throw new Error("Must supply a userId");var t=this.getUrl("Users/".concat(e));return this.getJSON(t)}},{key:"getStudio",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Studios/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Genres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getMusicGenre",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("MusicGenres/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getArtist",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Artists/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPerson",value:function(e,t){if(!e)throw new Error("null name");var r={};t&&(r.userId=t);var n=this.getUrl("Persons/".concat(this.encodeName(e)),r);return this.getJSON(n)}},{key:"getPublicUsers",value:function(){var e=this.getUrl("users/public");return this.ajax({type:"GET",url:e,dataType:"json"},!1)}},{key:"getUsers",value:function(e){var t=this.getUrl("users",e||{});return this.getJSON(t)}},{key:"getParentalRatings",value:function(){var e=this.getUrl("Localization/ParentalRatings");return this.getJSON(e)}},{key:"getDefaultImageQuality",value:function(e){return"backdrop"===e.toLowerCase()?80:90}},{key:"getUserImageUrl",value:function(e,t){if(!e)throw new Error("null userId");t=t||{};var r="Users/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),t.quality=t.quality||this.getDefaultImageQuality(t.type),this.normalizeImageOptions&&this.normalizeImageOptions(t),delete t.type,delete t.index,this.getUrl(r,t)}},{key:"getScaledImageUrl",value:function(e,t){if(!e)throw new Error("itemId cannot be empty");t=t||{};var r="Items/".concat(e,"/Images/").concat(t.type);return null!=t.index&&(r+="/".concat(t.index)),x(this,t),delete t.type,delete t.index,delete t.minScale,this.getUrl(r,t)}},{key:"getThumbImageUrl",value:function(e,t){if(!e)throw new Error("null item");return(t=t||{}).imageType="thumb",e.ImageTags&&e.ImageTags.Thumb?(t.tag=e.ImageTags.Thumb,this.getImageUrl(e.Id,t)):e.ParentThumbItemId?(t.tag=e.ImageTags.ParentThumbImageTag,this.getImageUrl(e.ParentThumbItemId,t)):null}},{key:"updateUserPassword",value:function(e,t,r){if(!e)return Promise.reject();var n=this.getUrl("Users/".concat(e,"/Password"));return this.ajax({type:"POST",url:n,data:JSON.stringify({CurrentPw:t||"",NewPw:r}),contentType:"application/json"})}},{key:"updateEasyPassword",value:function(e,t){if(e){var r=this.getUrl("Users/".concat(e,"/EasyPassword"));return this.ajax({type:"POST",url:r,data:{NewPw:t}})}Promise.reject()}},{key:"resetUserPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/Password")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"resetEasyPassword",value:function(e){if(!e)throw new Error("null userId");var t=this.getUrl("Users/".concat(e,"/EasyPassword")),r={resetPassword:!0};return this.ajax({type:"POST",url:t,data:r})}},{key:"updateServerConfiguration",value:function(e){if(!e)throw new Error("null configuration");var t=this.getUrl("System/Configuration");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateNamedConfiguration",value:function(e,t){if(!t)throw new Error("null configuration");var r=this.getUrl("System/Configuration/".concat(e));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateItem",value:function(e){if(!e)throw new Error("null item");var t=this.getUrl("Items/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updatePluginSecurityInfo",value:function(e){var t=this.getUrl("Plugins/SecurityInfo");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"createUser",value:function(e){var t=this.getUrl("Users/New");return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json",headers:{accept:"application/json"}})}},{key:"updateUser",value:function(e){if(!e)throw new Error("null user");var t=this.getUrl("Users/".concat(e.Id));return this.ajax({type:"POST",url:t,data:JSON.stringify(e),contentType:"application/json"})}},{key:"updateUserPolicy",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null policy");var r=this.getUrl("Users/".concat(e,"/Policy"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateUserConfiguration",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null configuration");var r=this.getUrl("Users/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updateScheduledTaskTriggers",value:function(e,t){if(!e)throw new Error("null id");if(!t)throw new Error("null triggers");var r=this.getUrl("ScheduledTasks/".concat(e,"/Triggers"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"updatePluginConfiguration",value:function(e,t){if(!e)throw new Error("null Id");if(!t)throw new Error("null configuration");var r=this.getUrl("Plugins/".concat(e,"/Configuration"));return this.ajax({type:"POST",url:r,data:JSON.stringify(t),contentType:"application/json"})}},{key:"getAncestorItems",value:function(e,t){if(!e)throw new Error("null itemId");var r={};t&&(r.userId=t);var n=this.getUrl("Items/".concat(e,"/Ancestors"),r);return this.getJSON(n)}},{key:"getItems",value:function(e,t){var r;return r="string"===c(e).toString().toLowerCase()?this.getUrl("Users/".concat(e,"/Items"),t):this.getUrl("Items",t),this.getJSON(r)}},{key:"getResumableItems",value:function(e,t){return this.isMinServerVersion("3.2.33")?this.getJSON(this.getUrl("Users/".concat(e,"/Items/Resume"),t)):this.getItems(e,Object.assign({SortBy:"DatePlayed",SortOrder:"Descending",Filters:"IsResumable",Recursive:!0,CollapseBoxSetItems:!1,ExcludeLocationTypes:"Virtual"},t))}},{key:"getMovieRecommendations",value:function(e){return this.getJSON(this.getUrl("Movies/Recommendations",e))}},{key:"getUpcomingEpisodes",value:function(e){return this.getJSON(this.getUrl("Shows/Upcoming",e))}},{key:"getUserViews",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=arguments.length>1?arguments[1]:void 0,r=this.getUrl("Users/".concat(t||this.getCurrentUserId(),"/Views"),e);return this.getJSON(r)}},{key:"getArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists",t);return this.getJSON(r)}},{key:"getAlbumArtists",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Artists/AlbumArtists",t);return this.getJSON(r)}},{key:"getGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Genres",t);return this.getJSON(r)}},{key:"getMusicGenres",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("MusicGenres",t);return this.getJSON(r)}},{key:"getPeople",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Persons",t);return this.getJSON(r)}},{key:"getStudios",value:function(e,t){if(!e)throw new Error("null userId");(t=t||{}).userId=e;var r=this.getUrl("Studios",t);return this.getJSON(r)}},{key:"getLocalTrailers",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/LocalTrailers"));return this.getJSON(r)}},{key:"getAdditionalVideoParts",value:function(e,t){if(!t)throw new Error("null itemId");var r={};e&&(r.userId=e);var n=this.getUrl("Videos/".concat(t,"/AdditionalParts"),r);return this.getJSON(n)}},{key:"getThemeMedia",value:function(e,t,r){if(!t)throw new Error("null itemId");var n={};e&&(n.userId=e),n.InheritFromParent=r||!1;var i=this.getUrl("Items/".concat(t,"/ThemeMedia"),n);return this.getJSON(i)}},{key:"getSearchHints",value:function(e){var t=this.getUrl("Search/Hints",e),r=this.serverId();return this.getJSON(t).then((function(e){return e.SearchHints.forEach((function(e){e.ServerId=r})),e}))}},{key:"getSpecialFeatures",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/SpecialFeatures"));return this.getJSON(r)}},{key:"getDateParamValue",value:function(e){function t(e){return e<10?"0".concat(e):e}var r=e;return"".concat(r.getFullYear()).concat(t(r.getMonth()+1)).concat(t(r.getDate())).concat(t(r.getHours())).concat(t(r.getMinutes())).concat(t(r.getSeconds()))}},{key:"markPlayed",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n={};r&&(n.DatePlayed=this.getDateParamValue(r));var i=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t),n);return this.ajax({type:"POST",url:i,dataType:"json"})}},{key:"markUnplayed",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/PlayedItems/").concat(t));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"updateFavoriteStatus",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/FavoriteItems/").concat(t)),i=r?"POST":"DELETE";return this.ajax({type:i,url:n,dataType:"json"})}},{key:"updateUserItemRating",value:function(e,t,r){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var n=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"),{likes:r});return this.ajax({type:"POST",url:n,dataType:"json"})}},{key:"getItemCounts",value:function(e){var t={};e&&(t.userId=e);var r=this.getUrl("Items/Counts",t);return this.getJSON(r)}},{key:"clearUserItemRating",value:function(e,t){if(!e)throw new Error("null userId");if(!t)throw new Error("null itemId");var r=this.getUrl("Users/".concat(e,"/Items/").concat(t,"/Rating"));return this.ajax({type:"DELETE",url:r,dataType:"json"})}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,g(this),w(this);var t=this.getUrl("Sessions/Playing");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");var t=e.EventName||"timeupdate",r=h[t]||0,n=(new Date).getTime()-(this.lastPlaybackProgressReport||0),i=e.PositionTicks;if(n=5e7&&(r=0)}if(r<(void 0!==this.reportPlaybackProgressTimeout?this.reportPlaybackProgressTimeout:1e6)&&w(this),this.lastPlaybackProgressOptions=e,this.reportPlaybackProgressPromise)return Promise.resolve();var a,s=this,u=!1,l=function(){s.reportPlaybackProgressPromise===a&&(delete s.lastPlaybackProgressOptions,delete s.reportPlaybackProgressTimeout,delete s.reportPlaybackProgressPromise,delete s.reportPlaybackProgressCancel)},c=Math.max(0,r-n);return a=new Promise((function(e,t){return setTimeout(e,c)})).then((function(){return u?Promise.resolve():function(e){if(l(),!e)throw new Error("null options");s.lastPlaybackProgressReport=(new Date).getTime(),s.lastPlaybackProgressReportTicks=e.PositionTicks;var t=s.getUrl("Sessions/Playing/Progress");return s.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}(s.lastPlaybackProgressOptions)})).finally((function(){l()})),this.reportPlaybackProgressTimeout=r,this.reportPlaybackProgressPromise=a,this.reportPlaybackProgressCancel=function(){u=!0,l()},a}},{key:"reportOfflineActions",value:function(e){if(!e)throw new Error("null actions");var t=this.getUrl("Sync/OfflineActions");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"syncData",value:function(e){if(!e)throw new Error("null data");var t=this.getUrl("Sync/Data");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t,dataType:"json"})}},{key:"getReadySyncItems",value:function(e){if(!e)throw new Error("null deviceId");var t=this.getUrl("Sync/Items/Ready",{TargetId:e});return this.getJSON(t)}},{key:"reportSyncJobItemTransferred",value:function(e){if(!e)throw new Error("null syncJobItemId");var t=this.getUrl("Sync/JobItems/".concat(e,"/Transferred"));return this.ajax({type:"POST",url:t})}},{key:"cancelSyncItems",value:function(e,t){if(!e)throw new Error("null itemIds");var r=this.getUrl("Sync/".concat(t||this.deviceId(),"/Items"),{ItemIds:e.join(",")});return this.ajax({type:"DELETE",url:r})}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");this.lastPlaybackProgressReport=0,this.lastPlaybackProgressReportTicks=null,v(this),w(this);var t=this.getUrl("Sessions/Playing/Stopped");return this.ajax({type:"POST",data:JSON.stringify(e),contentType:"application/json",url:t})}},{key:"sendPlayCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r=this.getUrl("Sessions/".concat(e,"/Playing"),t);return this.ajax({type:"POST",url:r})}},{key:"sendCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Command"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendMessageCommand",value:function(e,t){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null options");var r={type:"POST",url:this.getUrl("Sessions/".concat(e,"/Message"))};return r.data=JSON.stringify(t),r.contentType="application/json",this.ajax(r)}},{key:"sendPlayStateCommand",value:function(e,t,r){if(!e)throw new Error("null sessionId");if(!t)throw new Error("null command");var n=this.getUrl("Sessions/".concat(e,"/Playing/").concat(t),r||{});return this.ajax({type:"POST",url:n})}},{key:"getSyncPlayGroups",value:function(){var e=this.getUrl("SyncPlay/List");return this.ajax({type:"GET",url:e})}},{key:"createSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/New");return this.ajax({type:"POST",url:e})}},{key:"joinSyncPlayGroup",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Join",e);return this.ajax({type:"POST",url:t})}},{key:"leaveSyncPlayGroup",value:function(){var e=this.getUrl("SyncPlay/Leave");return this.ajax({type:"POST",url:e})}},{key:"sendSyncPlayPing",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Ping",e);return this.ajax({type:"POST",url:t})}},{key:"requestSyncPlayStart",value:function(){var e=this.getUrl("SyncPlay/Play");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlayPause",value:function(){var e=this.getUrl("SyncPlay/Pause");return this.ajax({type:"POST",url:e})}},{key:"requestSyncPlaySeek",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},t=this.getUrl("SyncPlay/Seek",e);return this.ajax({type:"POST",url:t})}},{key:"createPackageReview",value:function(e){var t=this.getUrl("Packages/Reviews/".concat(e.id),e);return this.ajax({type:"POST",url:t})}},{key:"getPackageReviews",value:function(e,t,r,n){if(!e)throw new Error("null packageId");var i={};t&&(i.MinRating=t),r&&(i.MaxRating=r),n&&(i.Limit=n);var o=this.getUrl("Packages/".concat(e,"/Reviews"),i);return this.getJSON(o)}},{key:"getSavedEndpointInfo",value:function(){return this._endPointInfo}},{key:"getEndpointInfo",value:function(){var e=this._endPointInfo;if(e)return Promise.resolve(e);var t=this;return this.getJSON(this.getUrl("System/Endpoint")).then((function(e){return k(t,e),e}))}},{key:"getLatestItems",value:function(){var e=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{};return this.getJSON(this.getUrl("Users/".concat(this.getCurrentUserId(),"/Items/Latest"),e))}},{key:"getFilters",value:function(e){return this.getJSON(this.getUrl("Items/Filters2",e))}},{key:"setSystemInfo",value:function(e){this._serverVersion=e.Version}},{key:"serverVersion",value:function(){return this._serverVersion}},{key:"isMinServerVersion",value:function(e){var t=this.serverVersion();return!!t&&function(e,t){e=e.split("."),t=t.split(".");for(var r=0,n=Math.max(e.length,t.length);ro)return 1}return 0}(t,e)>=0}},{key:"handleMessageReceived",value:function(e){b(this,e)}}])&&d(t.prototype,r),n&&d(t,n),e}();function J(e){return(J="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(e){return typeof e}:function(e){return e&&"function"==typeof Symbol&&e.constructor===Symbol&&e!==Symbol.prototype?"symbol":typeof e})(e)}function R(e,t){for(var r=0;rt.length&&0===e.indexOf(t))}function H(e,t){return q(e,t)?e.substr(t.length):e}function z(e){return e?V(e)?e:"local:".concat(e):null}function K(e){e.Id=z(e.Id),e.SeriesId=z(e.SeriesId),e.SeasonId=z(e.SeasonId),e.AlbumId=z(e.AlbumId),e.ParentId=z(e.ParentId),e.ParentThumbItemId=z(e.ParentThumbItemId),e.ParentPrimaryImageItemId=z(e.ParentPrimaryImageItemId),e.PrimaryImageItemId=z(e.PrimaryImageItemId),e.ParentLogoItemId=z(e.ParentLogoItemId),e.ParentBackdropItemId=z(e.ParentBackdropItemId),e.ParentBackdropImageTags=null}function Q(e,t,r){return e.getLocalFolders(t,r).then((function(r){var n=null;return r.length>0&&(n={Name:e.downloadsTitleText||"Downloads",ServerId:t,Id:"localview",Type:"localview",IsFolder:!0}),Promise.resolve(n)}))}var X=function(e){!function(e,t){if("function"!=typeof t&&null!==t)throw new TypeError("Super expression must either be null or a function");e.prototype=Object.create(t&&t.prototype,{constructor:{value:e,writable:!0,configurable:!0}}),t&&D(e,t)}(o,e);var t,r,n,i=M(o);function o(e,t,r,n,a,s,u){var l;return function(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}(this,o),(l=i.call(this,e,t,r,n,a,s)).localAssetManager=u,l}return t=o,(r=[{key:"getPlaybackInfo",value:function(e,t,r){var n=function(){return C.prototype.getPlaybackInfo.call(i,e,t,r)};if(V(e))return this.localAssetManager.getLocalItem(this.serverId(),W(e)).then((function(e){return{MediaSources:e.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}))}}),n);var i=this;return this.localAssetManager.getLocalItem(this.serverId(),e).then((function(o){if(o){var a=o.Item.MediaSources.map((function(e){return e.SupportsDirectPlay=!0,e.SupportsDirectStream=!1,e.SupportsTranscoding=!1,e.IsLocal=!0,e}));return i.localAssetManager.fileExists(o.LocalPath).then((function(n){if(n){var o={MediaSources:a};return Promise.resolve(o)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}return C.prototype.getPlaybackInfo.call(i,e,t,r)}),n)}},{key:"getItems",value:function(e,t){var r,n=this.serverInfo();if(n&&"localview"===t.ParentId)return this.getLocalFolders(n.Id,e).then((function(e){var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(n&&t&&(V(t.ParentId)||V(t.SeriesId)||V(t.SeasonId)||G(t.ParentId)||V(t.AlbumIds)))return this.localAssetManager.getViewItems(n.Id,e,t).then((function(e){e.forEach((function(e){K(e)}));var t={Items:e,TotalRecordCount:e.length};return Promise.resolve(t)}));if(t&&t.ExcludeItemIds&&t.ExcludeItemIds.length){var i=t.ExcludeItemIds.split(",");for(r=0;r0?Promise.resolve(r[0]):Promise.reject()})):V(t)&&(r=this.serverInfo())?this.localAssetManager.getLocalItem(r.Id,W(t)).then((function(e){return K(e.Item),Promise.resolve(e.Item)})):C.prototype.getItem.call(this,e,t)}},{key:"getLocalFolders",value:function(e){var t=this.serverInfo();return e=e||t.UserId,this.localAssetManager.getViews(t.Id,e)}},{key:"getNextUpEpisodes",value:function(e){return e.SeriesId&&V(e.SeriesId)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getNextUpEpisodes.call(this,e)}},{key:"getSeasons",value:function(e,t){return V(e)?(t.SeriesId=e,t.IncludeItemTypes="Season",this.getItems(this.getCurrentUserId(),t)):C.prototype.getSeasons.call(this,e,t)}},{key:"getEpisodes",value:function(e,t){return V(t.SeasonId)||V(t.seasonId)||V(e)?(t.SeriesId=e,t.IncludeItemTypes="Episode",this.getItems(this.getCurrentUserId(),t)):C.prototype.getEpisodes.call(this,e,t)}},{key:"getLatestOfflineItems",value:function(e){e.SortBy="DateCreated",e.SortOrder="Descending";var t=this.serverInfo();return t?this.localAssetManager.getViewItems(t.Id,null,e).then((function(e){return e.forEach((function(e){K(e)})),Promise.resolve(e)})):Promise.resolve([])}},{key:"getThemeMedia",value:function(e,t,r){return G(t)||V(t)||B(t)?Promise.reject():C.prototype.getThemeMedia.call(this,e,t,r)}},{key:"getSpecialFeatures",value:function(e,t){return V(t)?Promise.resolve([]):C.prototype.getSpecialFeatures.call(this,e,t)}},{key:"getSimilarItems",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getSimilarItems.call(this,e,t)}},{key:"updateFavoriteStatus",value:function(e,t,r){return V(t)?Promise.resolve():C.prototype.updateFavoriteStatus.call(this,e,t,r)}},{key:"getScaledImageUrl",value:function(e,t){if(V(e)||t&&t.itemid&&V(t.itemid)){var r=this.serverInfo(),n=W(e);return this.localAssetManager.getImageUrl(r.Id,n,t)}return C.prototype.getScaledImageUrl.call(this,e,t)}},{key:"reportPlaybackStart",value:function(e){if(!e)throw new Error("null options");return V(e.ItemId)?Promise.resolve():C.prototype.reportPlaybackStart.call(this,e)}},{key:"reportPlaybackProgress",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo();if(t){var r=this;return this.localAssetManager.getLocalItem(t.Id,W(e.ItemId)).then((function(t){var n=t.Item;return"Video"===n.MediaType||"AudioBook"===n.Type?(n.UserData=n.UserData||{},n.UserData.PlaybackPositionTicks=e.PositionTicks,n.UserData.PlayedPercentage=Math.min(n.RunTimeTicks?(e.PositionTicks||0)/n.RunTimeTicks*100:0,100),r.localAssetManager.addOrUpdateLocalItem(t)):Promise.resolve()}))}return Promise.resolve()}return C.prototype.reportPlaybackProgress.call(this,e)}},{key:"reportPlaybackStopped",value:function(e){if(!e)throw new Error("null options");if(V(e.ItemId)){var t=this.serverInfo(),r={Date:(new Date).getTime(),ItemId:W(e.ItemId),PositionTicks:e.PositionTicks,ServerId:t.Id,Type:0,UserId:this.getCurrentUserId()};return this.localAssetManager.recordUserAction(r)}return C.prototype.reportPlaybackStopped.call(this,e)}},{key:"getIntros",value:function(e){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getIntros.call(this,e)}},{key:"getInstantMixFromItem",value:function(e,t){return V(e)?Promise.resolve({Items:[],TotalRecordCount:0}):C.prototype.getInstantMixFromItem.call(this,e,t)}},{key:"getItemDownloadUrl",value:function(e){if(V(e)){var t=this.serverInfo();if(t)return this.localAssetManager.getLocalItem(t.Id,W(e)).then((function(e){return Promise.resolve(e.LocalPath)}))}return C.prototype.getItemDownloadUrl.call(this,e)}}])&&R(t.prototype,r),n&&R(t,n),o}(C);function Y(e,t){for(var r=0;r1&&void 0!==arguments[1]?arguments[1]:{};!1!==t.reportCapabilities&&e.reportCapabilities(s),e.enableAutomaticBitrateDetection=t.enableAutomaticBitrateDetection,!1!==t.enableWebSocket&&(console.log("calling apiClient.ensureWebSocket"),e.ensureWebSocket())}function d(e,t,r){return u._getOrAddApiClient(e,t),(u.onLocalUserSignedIn?u.onLocalUserSignedIn.call(u,r):Promise.resolve()).then((function(){i.trigger(u,"localusersignedin",[r])}))}function h(e,t){return ae({type:"GET",url:ie(t,"System/Info"),dataType:"json",headers:{"X-MediaBrowser-Token":e.AccessToken}}).then((function(t){return ne(e,t),Promise.resolve()}),(function(){return e.UserId=null,e.AccessToken=null,Promise.resolve()}))}function v(e){var t={serverId:(e.serverInfo()||{}).Id};return e.logout().then((function(){i.trigger(u,"localusersignedout",[t])}),(function(){i.trigger(u,"localusersignedout",[t])}))}function f(e){if(e.Address&&e.EndpointAddress){var t=e.EndpointAddress.split(":")[0],r=e.Address.split(":");if(r.length>1){var n=r[r.length-1];isNaN(parseInt(n))||(t+=":".concat(n))}return ue(t)}return null}function g(e){var t=[],r=[];return!e.manualAddressOnly&&e.LocalAddress&&-1===r.indexOf(e.LocalAddress)&&(t.push({url:e.LocalAddress,mode:Z,timeout:0}),r.push(t[t.length-1].url)),e.ManualAddress&&-1===r.indexOf(e.ManualAddress)&&(t.push({url:e.ManualAddress,mode:ee,timeout:100}),r.push(t[t.length-1].url)),!e.manualAddressOnly&&e.RemoteAddress&&-1===r.indexOf(e.RemoteAddress)&&(t.push({url:e.RemoteAddress,mode:$,timeout:200}),r.push(t[t.length-1].url)),console.log("tryReconnect: "+r.join("|")),new Promise((function(e,r){var n={};n.numAddresses=t.length,n.rejects=0,t.map((function(t){setTimeout((function(){n.resolved||function(e,t,r,n,i){console.log("getTryConnectPromise "+e),ae({url:ie(e,"system/info/public"),timeout:2e4,type:"GET",dataType:"json"}).then((function(i){r.resolved||(r.resolved=!0,console.log("Reconnect succeeded to "+e),n({url:e,connectionMode:t,data:i}))}),(function(){console.log("Reconnect failed to "+e),r.resolved||(r.rejects++,r.rejects>=r.numAddresses&&i())}))}(t.url,t.mode,n,e,r)}),t.timeout)}))}))}function p(e,t){var r={ManualAddress:e,LastConnectionMode:ee};return u.connectToServer(r,t).then((function(e){return"Unavailable"===e.State?Promise.reject():e}))}this._apiClients=[],u._minServerVersion="3.2.33",u.appVersion=function(){return n},u.appName=function(){return r},u.capabilities=function(){return s},u.deviceId=function(){return a},u.credentialProvider=function(){return t},u.getServerInfo=function(e){return t.credentials().Servers.filter((function(t){return t.Id===e}))[0]},u.getLastUsedServer=function(){var e=t.credentials().Servers;return e.sort((function(e,t){return(t.DateLastAccessed||0)-(e.DateLastAccessed||0)})),e.length?e[0]:null},u.addApiClient=function(e){u._apiClients.push(e);var r=t.credentials().Servers.filter((function(t){return le(t.ManualAddress,e.serverAddress())||le(t.LocalAddress,e.serverAddress())||le(t.RemoteAddress,e.serverAddress())})),n=r.length?r[0]:e.serverInfo();if(n.DateLastAccessed=(new Date).getTime(),n.LastConnectionMode=ee,n.ManualAddress=e.serverAddress(),e.manualAddressOnly&&(n.manualAddressOnly=!0),e.serverInfo(n),e.onAuthenticated=function(e,t){return l(e,t,{},!0)},!r.length){var o=t.credentials();o.Servers=[n],t.credentials(o)}i.trigger(u,"apiclientcreated",[e])},u.clearData=function(){console.log("connection manager clearing data");var e=t.credentials();e.Servers=[],t.credentials(e)},u._getOrAddApiClient=function(e,t){var s=u.getApiClient(e.Id);return s||(s=new C(t,r,n,o,a),u._apiClients.push(s),s.serverInfo(e),s.onAuthenticated=function(e,t){return l(e,t,{},!0)},i.trigger(u,"apiclientcreated",[s])),console.log("returning instance from getOrAddApiClient"),s},u.getOrCreateApiClient=function(e){var r=t.credentials().Servers.filter((function(t){return le(t.Id,e)}));if(!r.length)throw new Error("Server not found: ".concat(e));var n=r[0];return u._getOrAddApiClient(n,te(n,n.LastConnectionMode))},u.user=function(e){return new Promise((function(t,r){var n;e&&e.getCurrentUserId()&&e&&e.getCurrentUserId()&&e.getCurrentUser().then((function(e){var r=function(e){return e&&e.PrimaryImageTag?{url:u.getApiClient(e).getUserImageUrl(e.Id,{tag:e.PrimaryImageTag,type:"Primary"}),supportsParams:!0}:{url:null,supportsParams:!1}}(n=e);t({localUser:n,name:n?n.Name:null,imageUrl:r.url,supportsImageParams:r.supportsParams})}))}))},u.logout=function(){for(var e=[],r=0,n=u._apiClients.length;ro)return 1}return 0}(u.minServerVersion(),o.Version)?(console.log("minServerVersion requirement not met. Server version: "+o.Version),n({State:"ServerUpdateNeeded",Servers:[e]})):e.Id&&o.Id!==e.Id?(console.log("http request succeeded, but found a different server Id than what was expected"),re(0,n)):function e(r,n,o,a,s,l){var v=arguments.length>6&&void 0!==arguments[6]?arguments[6]:{},f=t.credentials();if(!1===v.enableAutoLogin)r.UserId=null,r.AccessToken=null;else if(r.AccessToken&&s)return void h(r,a).then((function(){e(r,n,o,a,!1,l,v)}));ne(r,n),r.LastConnectionMode=o,!1!==v.updateDateLastAccessed&&(r.DateLastAccessed=(new Date).getTime());t.addOrUpdateServer(f.Servers,r),t.credentials(f);var g={Servers:[]};g.ApiClient=u._getOrAddApiClient(r,a),g.ApiClient.setSystemInfo(n),g.State=r.AccessToken&&!1!==v.enableAutoLogin?"SignedIn":"ServerSignIn",g.Servers.push(r),g.ApiClient.enableAutomaticBitrateDetection=v.enableAutomaticBitrateDetection,g.ApiClient.updateServerInfo(r,a);var p=function(){l(g),i.trigger(u,"connected",[g])};"SignedIn"===g.State?(c(g.ApiClient,v),g.ApiClient.getCurrentUser().then((function(e){d(r,a,e).then(p,p)}),p)):p()}(e,o,s,a,!0,n,r)}),(function(){re(0,n)}))}))},u.connectToAddress=function(e,t){if(!e)return Promise.reject();e=ue(e);var r=[];/^[^:]+:\/\//.test(e)?r.push(e):(r.push("https://".concat(e)),r.push("http://".concat(e)));var n=0;return p(r[n],t).catch((function e(){return console.log("connectToAddress ".concat(r[n]," failed")),++n { + + jellyfinClientManager.getJellyfinClient().authenticateUserByName(CONFIG["jellyfin-username"],CONFIG["jellyfin-password"]).then((response)=>{ + console.log(response) + jellyfinClientManager.getJellyfinClient().setAuthenticationInfo(response.AccessToken, response.SessionInfo.UserId); + }); +}) + +discordClient.on('ready', () => { + console.log('connected to Discord'); +}); + +discordClient.on('message', message => { + handleChannelMessage(message); +}); + +discordClient.login(CONFIG.token); \ No newline at end of file diff --git a/src/jellyfinclientmanager.js b/src/jellyfinclientmanager.js new file mode 100644 index 0000000..fc4c4e1 --- /dev/null +++ b/src/jellyfinclientmanager.js @@ -0,0 +1,19 @@ + +const { ApiClient } = require('jellyfin-apiclient'); +const CONFIG = require('../config.json'); +const os = require('os'); + +var jellyfinClient; + +function init(){ + jellyfinClient = new ApiClient(CONFIG["server-adress"], CONFIG["jellyfin-app-name"], "0.0.1", os.hostname(), os.hostname()); +} + +function getJellyfinClient(){ + return jellyfinClient; +} + +module.exports = { + getJellyfinClient, + init +} \ No newline at end of file diff --git a/src/messagehandler.js b/src/messagehandler.js index ce6d0dc..57b1a99 100644 --- a/src/messagehandler.js +++ b/src/messagehandler.js @@ -1,74 +1,146 @@ const CONFIG = require('../config.json'); - const Discord = require('discord.js'); - +const { + checkJellyfinItemIDRegex +} = require('./util'); const { getAudioDispatcher, setAudioDispatcher } = require('./dispachermanager'); const discordclientmanager = require('./discordclientmanager'); +const jellyfinClientManager = require('./jellyfinclientmanager'); const discordClient = discordclientmanager.getDiscordClient(); +var isSummendByPlay = false; + + //random Color of the Jellyfin Logo Gradient function getRandomDiscordColor() { - function randomNumber(b,a){ - return Math.floor((Math.random()*Math.pow(Math.pow((b-a),2),1/2))+(b>a?a:b)) + function randomNumber(b, a) { + return Math.floor((Math.random() * Math.pow(Math.pow((b - a), 2), 1 / 2)) + (b > a ? a : b)) } - const GRANDIENT_START='#AA5CC3'; - const GRANDIENT_END='#00A4DC'; + const GRANDIENT_START = '#AA5CC3'; + const GRANDIENT_END = '#00A4DC'; - let rS=GRANDIENT_START.slice(1,3); - let gS=GRANDIENT_START.slice(3,5); - let bS=GRANDIENT_START.slice(5,7); - rS=parseInt(rS,16); - gS=parseInt(gS,16); - bS=parseInt(bS,16); + let rS = GRANDIENT_START.slice(1, 3); + let gS = GRANDIENT_START.slice(3, 5); + let bS = GRANDIENT_START.slice(5, 7); + rS = parseInt(rS, 16); + gS = parseInt(gS, 16); + bS = parseInt(bS, 16); - let rE=GRANDIENT_END.slice(1,3); - let gE=GRANDIENT_END.slice(3,5); - let bE=GRANDIENT_END.slice(5,7); - rE=parseInt(rE,16); - gE=parseInt(gE,16); - bE=parseInt(bE,16); + let rE = GRANDIENT_END.slice(1, 3); + let gE = GRANDIENT_END.slice(3, 5); + let bE = GRANDIENT_END.slice(5, 7); + rE = parseInt(rE, 16); + gE = parseInt(gE, 16); + bE = parseInt(bE, 16); - return ('#'+('00'+(randomNumber(rS,rE)).toString(16)).substr(-2)+('00'+(randomNumber(gS,gE)).toString(16)).substr(-2)+('00'+(randomNumber(bS,bE)).toString(16)).substr(-2)); + return ('#' + ('00' + (randomNumber(rS, rE)).toString(16)).substr(-2) + ('00' + (randomNumber(gS, gE)).toString(16)).substr(-2) + ('00' + (randomNumber(bS, bE)).toString(16)).substr(-2)); +} + +async function searchForItemID(searchString) { + + let response = await jellyfinClientManager.getJellyfinClient().getSearchHints({ + searchTerm: searchString, + includeItemTypes: "Audio" + }) + + if (response.TotalRecordCount < 1) { + throw "Found no Song" + } else { + return response.SearchHints[0].ItemId + } +} + +function summon(voiceChannel){ + + if (!voiceChannel) { + return message.reply('please join a voice channel to summon me!'); + } + + voiceChannel.join() } function handleChannelMessage(message) { - getRandomDiscordColor() + getRandomDiscordColor() if (message.content.startsWith(CONFIG["discord-prefix"] + 'summon')) { if (message.channel.type === 'dm') { return; } - const voiceChannel = message.member.voice.channel; + summon(message.member.voice.channel); + - if (!voiceChannel) { - return message.reply('please join a voice channel to summon me!'); - } - voiceChannel.join().then(connection => { - const stream = `${CONFIG['server-adress']}/Audio/0751d668d58afb25b755eca639c498ed/universal?UserId=d5ed94520ff542378b368246683ff9de&DeviceId=Jellyfin%20Discord%20Music%20Bot&MaxStreamingBitrate=320000&Container=opus&AudioCodec=opus&api_key=${CONFIG["jellyfin-api-key"]}&TranscodingContainer=ts&TranscodingProtocol=hls`; - setAudioDispatcher(connection.play(stream)); - }); } else if (message.content.startsWith(CONFIG["discord-prefix"] + 'disconnect')) { discordClient.user.client.voice.connections.forEach((element) => { element.disconnect(); }); - } else if (message.content.startsWith(CONFIG["discord-prefix"] + 'pause')) { + + + } else if ((message.content.startsWith(CONFIG["discord-prefix"] + 'pause')) || (message.content.startsWith(CONFIG["discord-prefix"] + 'resume'))) { if (getAudioDispatcher() !== undefined) { if (getAudioDispatcher().paused) getAudioDispatcher().resume(); else getAudioDispatcher().pause(true); } else { - console.log("WHYYYYYYY") - console.log(getAudioDispatcher()); + message.reply("there is nothing playing!") } + + + } else if (message.content.startsWith(CONFIG["discord-prefix"] + 'play')) { + + + if (discordClient.user.client.voice.connections.size < 1) { + discordClient.user.client.voice.connections.size + summon(message.member.voice.channel) + isSummendByPlay=true + } + + async function playThis(){ + + let indexOfItemID = message.content.indexOf(CONFIG["discord-prefix"] + 'play') + (CONFIG["discord-prefix"] + 'play').length + 1; + let argument = message.content.slice(indexOfItemID); + let itemID; + //check if play command was used with itemID + let regexresults = checkJellyfinItemIDRegex(argument); + if (regexresults) { + itemID = regexresults[0]; + } else { + try { + itemID = await searchForItemID(argument); + } catch (e) { + message.reply(e); + } + } + + discordClient.user.client.voice.connections.forEach((element) => { + let stream = `${jellyfinClientManager.getJellyfinClient().serverAddress()}/Audio/${itemID}/universal?UserId=${jellyfinClientManager.getJellyfinClient().getCurrentUserId()}&DeviceId=${jellyfinClientManager.getJellyfinClient().deviceId()}&MaxStreamingBitrate=${element.channel.bitrate.toString()}&Container=opus&AudioCodec=opus&api_key=${jellyfinClientManager.getJellyfinClient().accessToken()}&TranscodingContainer=ts&TranscodingProtocol=hls`; + setAudioDispatcher(element.play(stream)); + element.on("error", (error) => { + console.error(error); + }) + getAudioDispatcher().on("finish",()=>{ + if(isSummendByPlay){ + element.disconnect(); + } + }) + }) + } + + playThis(); + + } else if (message.content.startsWith(CONFIG["discord-prefix"] + 'stop')) { + getAudioDispatcher().pause() + setAudioDispatcher(undefined) + + } else if (message.content.startsWith(CONFIG["discord-prefix"] + 'help')) { const reply = new Discord.MessageEmbed() .setColor(getRandomDiscordColor()) diff --git a/src/util.js b/src/util.js new file mode 100644 index 0000000..de6abd9 --- /dev/null +++ b/src/util.js @@ -0,0 +1,8 @@ +function checkJellyfinItemIDRegex(strgintomatch){ + let regexresult=strgintomatch.match(/([0-9]|[a-f]){32}/); + return regexresult; +} + +module.exports={ + checkJellyfinItemIDRegex +} \ No newline at end of file