Files
jellyPod/src/pkjs/index.js
T

174 lines
6.2 KiB
JavaScript

var Clay = require('@rebble/clay');
var clayConfig = require('./config');
var keys = require('message_keys');
new Clay(clayConfig);
var NavScope = Object.freeze({
NAV_SCOPE_MAIN: 0,
NAV_SCOPE_SEARCH: 1,
NAV_SCOPE_ARTISTS: 2,
NAV_SCOPE_ALBUMS: 3,
NAV_SCOPE_ALBUM_TRACKS: 4,
});
var cfg = JSON.parse(localStorage.getItem('clay-settings'));
var lookupMap = new Map();
Pebble.addEventListener("ready", function () {
// store cache of library info from Jellyfin
if (cfg.PRIV_BASE_URL && cfg.PRIV_API_KEY && cfg.PRIV_USERNAME) {
// get userId from Jellyfin
callAPI("GET", cfg.PRIV_BASE_URL + "/Users", function (userIdResp) {
if (userIdResp === null) { return; }
const userIdObj = JSON.parse(userIdResp);
let userId;
for (const user of userIdObj) {
if (user.Name === cfg.PRIV_USERNAME) {
userId = user.Id;
break;
}
}
// get library response from Jellyfin
callAPI("GET", cfg.PRIV_BASE_URL + "/Items?userId=" + userId + "&recursive=true&includeItemTypes=Audio&sortBy=AlbumArtist,Artist,ProductionYear,Album,IndexNumber,Name", function (libResp) {
if (libResp === null) return;
const libObj = JSON.parse(libResp);
// format response for jellyPod
//// build lookupMap
let lastAlbumInfo = {AlbumArtist: "", AlbumName: "", AlbumYear: 0};
for (const track of libObj.Items) {
if (!track.Name) continue;
let albumArtist = "unknown artist";
if (track.AlbumArtist) {
albumArtist = track.AlbumArtist;
} else if (track.Artists.length > 0) {
albumArtist = track.Artists[0];
}
const albumName = track.Album ? track.Album : "unknown album";
// insert every track into lookupMap
const firstChar = Array.from(albumArtist)[0].toUpperCase();
if (!lookupMap.has(firstChar)) {
lookupMap.set(firstChar, new Map());
}
const artistToAlbumsMap = lookupMap.get(firstChar);
if (!artistToAlbumsMap.has(albumArtist)) {
artistToAlbumsMap.set(albumArtist, new Map());
}
const albumToTracksMap = artistToAlbumsMap.get(albumArtist);
if (!albumToTracksMap.has(albumName)) {
albumToTracksMap.set(albumName, []);
}
albumToTracksMap.get(albumName).push({
TrackName: track.Name,
JfId: track.Id
});
// consider the album to have changed if the album name OR artist changed
if (lastAlbumInfo.AlbumArtist !== albumArtist || lastAlbumInfo.AlbumName !== albumName) {
lastAlbumInfo = {
AlbumArtist: albumArtist,
AlbumName: albumName,
AlbumYear: track.ProductionYear ? track.ProductionYear : 1970
};
}
}
//console.log(JSON.stringify(lookupMap, (k, v) => v instanceof Map ? Object.fromEntries(v) : v));
const readyDict = {};
readyDict[keys.PKJS_READY] = 1;
Pebble.sendAppMessage(readyDict);
});
});
}
});
// PKJS seems to change its mind on how to index message keys, so this is necessary... :/
function payloadGet(payload, name) {
if (payload[name] !== undefined) return payload[name];
if (payload[keys[name]] !== undefined) return payload[keys[name]];
return undefined;
}
Pebble.addEventListener("appmessage", function (dict) {
//console.log("payload", JSON.stringify(dict.payload));
//console.log("expected NAV_SCOPE key", keys.BIDIR_MENU_NAV_SCOPE);
const scope = payloadGet(dict.payload, "BIDIR_MENU_NAV_SCOPE");
if (scope) {
let vanityList;
switch (scope) {
case NavScope.NAV_SCOPE_SEARCH: { // search
vanityList = Array.from(lookupMap.keys());
break;
}
case NavScope.NAV_SCOPE_ARTISTS: { // artists
const subscope = payloadGet(dict.payload, "OUT_MENU_NAV_SUBSCOPE");
if (subscope) {
// allow filtering by starting character
vanityList = Array.from(lookupMap.get(subscope).keys());
} else {
vanityList = Array.from(lookupMap.values()).flatMap((artistMap) => Array.from(artistMap.keys()));
}
break;
}
default:
return;
}
const outDict = {};
for (let i = 0; i < vanityList.length; i++) {
outDict[keys.IN_VANITY_LIST + i] = vanityList[i];
outDict[keys.BIDIR_MENU_NAV_SCOPE] = scope;
}
Pebble.sendAppMessage(outDict);
}
});
function reportAPIFailure() {
console.log("API FAILURE");
}
function callAPI(method, endpoint, callback) {
const xhr = new XMLHttpRequest();
xhr.onload = function () {
switch (xhr.status) {
case 200:
callback(this.responseText);
return;
default:
reportAPIFailure();
}
};
xhr.onerror = function () {
reportAPIFailure();
};
xhr.ontimeout = function () {
reportAPIFailure();
};
xhr.open(method, endpoint, true);
xhr.timeout = 8000;
xhr.setRequestHeader("Accept", "application/json");
xhr.setRequestHeader("Authorization", 'MediaBrowser Token="' + cfg.PRIV_API_KEY + '"');
xhr.send();
}
// On Every Startup
//// Get Session ID
//curl -H "Authorization: MediaBrowser Token=\"TOKEN\"" -X GET 'BASEURL/Sessions'
// Filter by:
// UserId
// Sort by:
// LastActivityDate
// Controls
//// Select Track (pass through a queue)
//curl - H "Authorization: MediaBrowser Token=\"TOKEN\"" - X POST 'BASEURL/Sessions/SESSIONID/Playing?playCommand=PlayNow&itemIds=XXX,YYY'