mirror of
https://github.com/BreizhHardware/Jellystat.git
synced 2026-01-18 16:27:20 +01:00
597 lines
16 KiB
JavaScript
597 lines
16 KiB
JavaScript
const configClass = require("./config");
|
|
const { axios } = require("./axios");
|
|
|
|
class EmbyAPI {
|
|
constructor() {
|
|
this.config = null;
|
|
this.configReady = false;
|
|
this.#checkReadyStatus();
|
|
this.sessionErrorCounter = 0;
|
|
}
|
|
//Helper classes
|
|
#checkReadyStatus() {
|
|
let checkConfigError = setInterval(async () => {
|
|
const success = await this.#fetchConfig();
|
|
if (success) {
|
|
clearInterval(checkConfigError);
|
|
}
|
|
}, 5000); // Check every 5 seconds
|
|
}
|
|
|
|
async #fetchConfig() {
|
|
const _config = await new configClass().getConfig();
|
|
if (!_config.error && _config.state === 2) {
|
|
this.config = _config;
|
|
this.configReady = true;
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
#errorHandler(error, url) {
|
|
if (error.response) {
|
|
console.log("[EMBY-API]: " + this.#httpErrorMessageHandler(error));
|
|
} else {
|
|
console.log("[EMBY-API]", {
|
|
ErrorAt: this.#getErrorLineNumber(error),
|
|
ErrorLines: this.#getErrorLineNumbers(error),
|
|
Message: error.message,
|
|
url: url,
|
|
// StackTrace: this.#getStackTrace(error),
|
|
});
|
|
}
|
|
}
|
|
|
|
#httpErrorMessageHandler(error) {
|
|
let message = "";
|
|
switch (error.response.status) {
|
|
case 400:
|
|
message = "400 Bad Request";
|
|
break;
|
|
case 401:
|
|
message = "401 Unauthorized";
|
|
break;
|
|
case 403:
|
|
message = "403 Access Forbidden";
|
|
break;
|
|
case 404:
|
|
message = `404 URL Not Found : ${error.request.path}`;
|
|
break;
|
|
case 503:
|
|
message = `503 Service Unavailable : ${error.request.path}`;
|
|
break;
|
|
default:
|
|
message = `Unexpected status code: ${error.response.status}`;
|
|
}
|
|
return message;
|
|
}
|
|
|
|
#getErrorLineNumber(error) {
|
|
const stackTrace = this.#getStackTrace(error);
|
|
if (stackTrace.length < 1) {
|
|
return "Unknown";
|
|
}
|
|
const errorLine = stackTrace[1].trim();
|
|
const lineNumber = errorLine.substring(errorLine.lastIndexOf("\\") + 1, errorLine.lastIndexOf(")"));
|
|
return lineNumber;
|
|
}
|
|
|
|
#getErrorLineNumbers(error) {
|
|
const stackTrace = this.#getStackTrace(error);
|
|
let errorLines = [];
|
|
|
|
for (const [index, line] of stackTrace.entries()) {
|
|
if (line.trim().startsWith("at")) {
|
|
const errorLine = line.trim();
|
|
const startSubstring = errorLine.lastIndexOf("\\") == -1 ? errorLine.indexOf("(") + 1 : errorLine.lastIndexOf("\\") + 1;
|
|
const endSubstring = errorLine.lastIndexOf(")") == -1 ? errorLine.length : errorLine.lastIndexOf(")");
|
|
const lineNumber = errorLine.substring(startSubstring, endSubstring);
|
|
errorLines.push({ TraceIndex: index, line: lineNumber });
|
|
}
|
|
}
|
|
|
|
return errorLines;
|
|
}
|
|
|
|
#getStackTrace(error) {
|
|
if (!error.stack) return [];
|
|
const stackTrace = error.stack.split("\n");
|
|
return stackTrace;
|
|
}
|
|
|
|
#delay(ms) {
|
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
}
|
|
|
|
//Functions
|
|
|
|
async getUsers(refreshConfig = false) {
|
|
if (!this.configReady || refreshConfig) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
const url = `${this.config.JF_HOST}/Users`;
|
|
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
|
|
if (Array.isArray(response?.data)) {
|
|
return response?.data || [];
|
|
}
|
|
|
|
console.log("[JELLYFIN-API] : getUsers - " + (response?.data || response));
|
|
|
|
return [];
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getAdmins(refreshConfig = false) {
|
|
try {
|
|
const users = await this.getUsers(refreshConfig);
|
|
return users?.filter((user) => user.Policy.IsAdministrator) || [];
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getItemsByID({ ids, params }) {
|
|
if (!this.configReady) {
|
|
return [];
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/Items?ids=${ids}`;
|
|
let startIndex = params && params.startIndex ? params.startIndex : 0;
|
|
let increment = params && params.increment ? params.increment : 200;
|
|
let limit = params && params.limit !== undefined ? params.limit : increment;
|
|
let recursive = params && params.recursive !== undefined ? params.recursive : true;
|
|
let total = 200;
|
|
|
|
let final_response = [];
|
|
while (startIndex < total || total === undefined) {
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
params: {
|
|
fields: "MediaSources,DateCreated,Genres",
|
|
startIndex: startIndex,
|
|
recursive: recursive,
|
|
limit: limit,
|
|
isMissing: false,
|
|
excludeLocationTypes: "Virtual",
|
|
},
|
|
});
|
|
|
|
total = response?.data?.TotalRecordCount ?? 0;
|
|
startIndex += increment;
|
|
|
|
const result = response?.data?.Items || [];
|
|
|
|
final_response.push(...result);
|
|
if (response.data.TotalRecordCount === undefined || final_response.length >= limit) {
|
|
break;
|
|
}
|
|
|
|
await this.#delay(10);
|
|
}
|
|
|
|
return final_response;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getItemsFromParentId({ id, itemid, params, ws, syncTask, wsMessage }) {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/Items?ParentId=${id}`;
|
|
|
|
let userid;
|
|
if (!userid || userid == null) {
|
|
await new configClass().getPreferedAdmin().then(async (adminid) => {
|
|
if (!adminid || adminid == null) {
|
|
userid = (await this.getAdmins())[0].Id;
|
|
} else {
|
|
userid = adminid;
|
|
}
|
|
});
|
|
}
|
|
url += `&userId=${userid}`;
|
|
|
|
if (itemid && itemid != null) {
|
|
url += `&Ids=${itemid}`;
|
|
}
|
|
|
|
let startIndex = params && params.startIndex !== undefined ? params.startIndex : 0;
|
|
let increment = params && params.increment !== undefined ? params.increment : 200;
|
|
let recursive = params && params.recursive !== undefined ? params.recursive : true;
|
|
let limit = params && params.limit !== undefined ? params.limit : increment;
|
|
let total = startIndex + increment;
|
|
|
|
let AllItems = [];
|
|
while (startIndex < total || total === undefined) {
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
params: {
|
|
fields: "MediaSources,DateCreated,Genres",
|
|
startIndex: startIndex,
|
|
recursive: recursive,
|
|
limit: limit,
|
|
isMissing: false,
|
|
excludeLocationTypes: "Virtual",
|
|
sortBy: "DateCreated",
|
|
sortOrder: "Descending",
|
|
},
|
|
});
|
|
|
|
total = response?.data?.TotalRecordCount || 0;
|
|
startIndex += increment;
|
|
|
|
const result = response?.data?.Items || [];
|
|
|
|
AllItems.push(...result);
|
|
|
|
if (ws && syncTask && wsMessage) {
|
|
ws(syncTask.wsKey, {
|
|
type: "Update",
|
|
message: `${wsMessage} - ${((Math.min(startIndex, total) / total) * 100).toFixed(2)}%`,
|
|
});
|
|
}
|
|
|
|
if (
|
|
response.data.TotalRecordCount === undefined ||
|
|
(params && params.startIndex !== undefined) ||
|
|
AllItems.length >= limit
|
|
) {
|
|
break;
|
|
}
|
|
|
|
await this.#delay(10);
|
|
}
|
|
|
|
return AllItems;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getItemInfo({ itemID, userid }) {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
if (!userid || userid == null) {
|
|
await new configClass().getPreferedAdmin().then(async (adminid) => {
|
|
if (!adminid || adminid == null) {
|
|
userid = (await this.getAdmins())[0].Id;
|
|
} else {
|
|
userid = adminid;
|
|
}
|
|
});
|
|
}
|
|
|
|
let url = `${this.config.JF_HOST}/Items/${itemID}/playbackinfo?userId=${userid}`;
|
|
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
|
|
return response?.data?.MediaSources || 0;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getLibraries() {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/Library/MediaFolders`;
|
|
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
|
|
const libraries =
|
|
response?.data?.Items?.filter((library) => !["boxsets", "playlists"].includes(library.CollectionType)) || [];
|
|
|
|
return libraries;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getSeasons(SeriesId) {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
let url = `${this.config.JF_HOST}/Shows/${SeriesId}/Seasons`;
|
|
try {
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
|
|
return response?.data?.Items?.filter((item) => item.LocationType !== "Virtual") || [];
|
|
} catch (error) {
|
|
this.#errorHandler(error, url);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getEpisodes({ SeriesId, SeasonId }) {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/Shows/${SeriesId}/Episodes?seasonId=${SeasonId}`;
|
|
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
|
|
return response?.data?.Items?.filter((item) => item.LocationType !== "Virtual") || [];
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getRecentlyAdded({ libraryid, limit = 20, userid }) {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
if (!userid || userid == null) {
|
|
let adminid = await new configClass().getPreferedAdmin();
|
|
if (!adminid || adminid == null) {
|
|
const admins = await this.getAdmins();
|
|
if (admins.length > 0) {
|
|
userid = admins[0].Id;
|
|
}
|
|
} else {
|
|
userid = adminid;
|
|
}
|
|
}
|
|
|
|
if (!userid || userid == null) {
|
|
console.log("[JELLYFIN-API]: getRecentlyAdded - No Admins/UserIds found");
|
|
return [];
|
|
}
|
|
|
|
let url = `${this.config.JF_HOST}/Users/${userid}/Items/Latest?Limit=${limit}`;
|
|
|
|
if (libraryid && libraryid != null) {
|
|
url += `&ParentId=${libraryid}`;
|
|
}
|
|
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
params: {
|
|
fields: "MediaSources,DateCreated,Genres",
|
|
},
|
|
});
|
|
|
|
const items = response?.data?.filter((item) => item.LocationType !== "Virtual") || [];
|
|
|
|
return items;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getSessions() {
|
|
if (!this.configReady) {
|
|
return [];
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/sessions`;
|
|
|
|
const response = await axios
|
|
.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
})
|
|
.then((response) => {
|
|
if (this.sessionErrorCounter > 0) {
|
|
console.log("[EMBY-API]: /sessions - Connection restored");
|
|
this.sessionErrorCounter = 0;
|
|
}
|
|
return response;
|
|
})
|
|
.catch((error) => {
|
|
if (this.sessionErrorCounter == 0) {
|
|
this.sessionErrorCounter++;
|
|
console.log("[EMBY-API]: /sessions - Unable to connect. Please check the URL and your network connection");
|
|
}
|
|
return { data: [] };
|
|
});
|
|
let result = response.data && Array.isArray(response.data) ? response.data : [];
|
|
|
|
if (result.length > 0) {
|
|
result = result.filter(
|
|
(session) =>
|
|
session.NowPlayingItem !== undefined &&
|
|
session.NowPlayingItem.Type != "Trailer" &&
|
|
session.NowPlayingItem.ProviderIds["prerolls.video"] == undefined
|
|
);
|
|
}
|
|
return result;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async getInstalledPlugins() {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/plugins`;
|
|
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
return response.data;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
async StatsSubmitCustomQuery(query) {
|
|
if (!this.configReady) {
|
|
const success = await this.#fetchConfig();
|
|
if (!success) {
|
|
return [];
|
|
}
|
|
}
|
|
try {
|
|
let url = `${this.config.JF_HOST}/user_usage_stats/submit_custom_query`;
|
|
|
|
const response = await axios.post(
|
|
url,
|
|
{
|
|
CustomQueryString: query,
|
|
},
|
|
{
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
}
|
|
);
|
|
return response.data.results;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
return [];
|
|
}
|
|
}
|
|
|
|
#isValidUrl(string) {
|
|
try {
|
|
new URL(string);
|
|
return true;
|
|
} catch (err) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
async validateSettings(url, apikey) {
|
|
let result = { isValid: false, status: 400, errorMessage: "Invalid URL", url: url, cleanedUrl: "" };
|
|
try {
|
|
let _url = url.replace(/\/web\/index\.html#!\/home\.html$/, "");
|
|
|
|
_url = _url.replace(/\/$/, "");
|
|
if (!/^https?:\/\//i.test(_url)) {
|
|
_url = "http://" + _url;
|
|
}
|
|
|
|
if (!url.includes("/emby")) {
|
|
_url = _url + "/emby";
|
|
}
|
|
|
|
result.cleanedUrl = _url;
|
|
|
|
console.log(_url, this.#isValidUrl(_url));
|
|
if (!this.#isValidUrl(_url)) {
|
|
return result;
|
|
}
|
|
|
|
const validation_url = _url.replace(/\/$/, "") + "/system/configuration";
|
|
|
|
const response = await axios.get(validation_url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": apikey,
|
|
},
|
|
});
|
|
result.isValid = response.status == 200;
|
|
return result;
|
|
} catch (error) {
|
|
this.#errorHandler(error);
|
|
result.isValid = false;
|
|
result.status = error?.response?.status ?? 400;
|
|
result.errorMessage =
|
|
error?.response != null
|
|
? this.#httpErrorMessageHandler(error)
|
|
: error.code == "ENOTFOUND"
|
|
? "Unable to connect. Please check the URL and your network connection."
|
|
: error.message;
|
|
return result;
|
|
}
|
|
}
|
|
|
|
async systemInfo() {
|
|
if (!this.configReady) {
|
|
return [];
|
|
}
|
|
let url = `${this.config.JF_HOST}/system/info`;
|
|
try {
|
|
const response = await axios.get(url, {
|
|
headers: {
|
|
"X-MediaBrowser-Token": this.config.JF_API_KEY,
|
|
},
|
|
});
|
|
|
|
return response?.data || {};
|
|
} catch (error) {
|
|
this.#errorHandler(error, url);
|
|
return {};
|
|
}
|
|
}
|
|
}
|
|
|
|
module.exports = EmbyAPI;
|