Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 21 additions & 11 deletions classes/arr/radarr.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,18 +72,27 @@ class Radarr {
if (raw != null) {
// move through results and populate media cards
await raw.data.reduce(async (memo, md) => {
let noReleaseDate = false;
await memo;
const medCard = new mediaCard();
let releaseDate;
if(!await util.isEmpty(md.digitalRelease)){
let digitalRelease = new Date(md.digitalRelease);
releaseDate = digitalRelease.toISOString().split("T")[0];
noReleaseDate = true;
// Prefer digital, then physical, then theatrical — calendar already scoped the window.
let releaseDate = "No release date";
let hasReleaseDate = false;
let releaseCandidate = null;
if (!(await util.isEmpty(md.digitalRelease))) {
releaseCandidate = md.digitalRelease;
} else if (!(await util.isEmpty(md.physicalRelease))) {
releaseCandidate = md.physicalRelease;
} else if (!(await util.isEmpty(md.inCinemas))) {
releaseCandidate = md.inCinemas;
}
else {
releaseDate = "No digital release date";
noReleaseDate = false;
if (releaseCandidate) {
try {
releaseDate = new Date(releaseCandidate).toISOString().split("T")[0];
hasReleaseDate = true;
} catch (e) {
releaseDate = String(releaseCandidate);
hasReleaseDate = true;
}
}
medCard.tagLine =
md.title + " (" + releaseDate + ")";
Expand Down Expand Up @@ -199,8 +208,9 @@ class Radarr {
// if(medCard.theme.includes("undefined")) medCard.theme="";
// }

// add media card to array, only if not released yet (caters to old movies being released digitally)
if (md.hasFile == false && md.status != "released" && noReleaseDate != false){ //&& !await util.isEmpty(md.digitalRelease) ) {
// Include upcoming movies not yet downloaded. Require some release date
// (digital/physical/theatrical) so taglines are useful; do not require digital-only.
if (md.hasFile == false && hasReleaseDate) {
csrCards.push(medCard);
}

Expand Down
12 changes: 6 additions & 6 deletions classes/arr/sonarr.js
Original file line number Diff line number Diff line change
Expand Up @@ -253,13 +253,13 @@ class Sonarr {

medCard.posterAR = 1.47;

// add media card to array (taking into account premieres option)
if (md.hasFile == false && premieres && md.episodeNumber == 1) {
// add media card to array (taking into account premieres option).
// premieres is a string ("true"/"false") — must compare explicitly; "false" is truthy in JS.
const premieresOnly = premieres == "true";
if (md.hasFile == false && premieresOnly && md.episodeNumber == 1) {
csCards.push(medCard);
} else if (md.hasFile == false && !premieresOnly) {
csCards.push(medCard);
} else {
if (!premieres) {
csCards.push(medCard);
}
}
}, undefined);
}
Expand Down
82 changes: 62 additions & 20 deletions classes/mediaservers/embyJellyfinBase.js
Original file line number Diff line number Diff line change
Expand Up @@ -862,6 +862,45 @@ class EmbyJellyfinBase {
});
}

/** Comma-separated include list → trimmed lowercase tokens (allows spaces inside names). */
static parseIncludeFilterList(raw) {
return String(raw || "")
.split(",")
.map((s) => s.trim().toLowerCase())
.filter(Boolean);
}

/**
* Emby/Jellyfin session display names for user include-filter matching.
* Prefer UserName; also match UserId (GUID) if pasted into the filter field.
*/
static sessionUserMatchKeys(session) {
const keys = [];
const push = (v) => {
const s = String(v || "")
.trim()
.toLowerCase();
if (s && !keys.includes(s)) keys.push(s);
};
push(session && (session.UserName || session.userName));
push(session && (session.UserId || session.userId));
const extra = (session && (session.AdditionalUsers || session.additionalUsers)) || [];
if (Array.isArray(extra)) {
for (const u of extra) {
push(u && (u.UserName || u.userName));
push(u && (u.UserId || u.userId));
}
}
return keys;
}

static sessionMatchesUserFilter(session, wantedUsers) {
if (!wantedUsers || wantedUsers.length === 0) return true;
const keys = EmbyJellyfinBase.sessionUserMatchKeys(session);
if (keys.length === 0) return false;
return wantedUsers.some((want) => keys.includes(want));
}

static pickStreams(item) {
const ms = item.MediaSources && item.MediaSources[0];
if (!ms || !ms.MediaStreams) return { resCodec: "", audioCodec: "" };
Expand Down Expand Up @@ -1017,20 +1056,8 @@ class EmbyJellyfinBase {
return nsCards;
}

const devices = (filterDevices || "")
.toLowerCase()
.replace(/, /g, ",")
.replace(/ ,/g, ",")
.replace(/,+$/, "")
.split(",")
.filter(Boolean);
const users = (filterUsers || "")
.toLowerCase()
.replace(/, /g, ",")
.replace(/ ,/g, ",")
.replace(/,+$/, "")
.split(",")
.filter(Boolean);
const devices = EmbyJellyfinBase.parseIncludeFilterList(filterDevices);
const users = EmbyJellyfinBase.parseIncludeFilterList(filterUsers);

const libNameCache = new Map();
let userIdForLibs = null;
Expand Down Expand Up @@ -1385,11 +1412,12 @@ class EmbyJellyfinBase {
if (wantRemote && medCard.playerLocal === false) okToAdd = true;
if (wantLocal && medCard.playerLocal === true) okToAdd = true;
}
if (users.length > 0 && users[0] !== "") {
const un = (session.UserName || session.userName || "").toLowerCase();
if (!users.includes(un)) okToAdd = false;
if (users.length > 0) {
if (!EmbyJellyfinBase.sessionMatchesUserFilter(session, users)) {
okToAdd = false;
}
}
if (devices.length > 0 && devices[0] !== "") {
if (devices.length > 0) {
if (!EmbyJellyfinBase.sessionDeviceMatchesFilter(session, medCard.playerDevice, devices)) {
okToAdd = false;
}
Expand Down Expand Up @@ -1466,13 +1494,27 @@ class EmbyJellyfinBase {
}
}

// Never bypass user/device include-filters with unfiltered sessions. That made
// "Filter by user(s)" look broken on Emby/Jellyfin (empty filtered set → show everyone).
if (nsCards.length === 0 && fallbackCards.length > 0) {
const hasIncludeFilter = users.length > 0 || devices.length > 0;
if (!hasIncludeFilter) {
const now = new Date();
console.log(
now.toLocaleString() +
" *Now Scrn. - All Emby/Jellyfin sessions excluded by local/remote filter; using unfiltered fallback cards"
);
return fallbackCards;
}
const now = new Date();
console.log(
now.toLocaleString() +
" *Now Scrn. - All Jellyfin sessions filtered; using fallback unfiltered session cards"
" *Now Scrn. - No Emby/Jellyfin sessions matched user/device filter (" +
(users.length ? "users=[" + users.join(",") + "]" : "") +
(users.length && devices.length ? " " : "") +
(devices.length ? "devices=[" + devices.join(",") + "]" : "") +
"); showing none"
);
return fallbackCards;
}

return nsCards;
Expand Down
6 changes: 4 additions & 2 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -1391,6 +1391,8 @@ async function buildTmdbNowShowingListCards() {

/** Library-style slides: cached poster DB first when enabled; live on-demand only as backup. */
function buildLibrarySlideDeckFromPosterCache() {
// Master ON-DEMAND toggle must gate both live fetches and cache-backed library slides.
if (!isOnDemandEnabled) return [];
if (!preferCachedPostersEnabled()) return odCards;
const kind = loadedSettings
? getMediaServerKind(loadedSettings.mediaServerType)
Expand All @@ -1408,7 +1410,7 @@ function buildLibrarySlideDeckFromPosterCache() {
* Now Playing / on-demand network work finishes (first paint on /posters).
*/
async function warmCachedPosterDeckEarlyIfPossible() {
if (!loadedSettings || !preferCachedPostersEnabled()) return;
if (!loadedSettings || !isOnDemandEnabled || !preferCachedPostersEnabled()) return;
if (!cachedPosterDbHasRows()) return;
const kind = getMediaServerKind(loadedSettings.mediaServerType);
const warmCount = Math.min(12, primaryCachedPosterSlideCount());
Expand Down Expand Up @@ -3261,7 +3263,7 @@ async function saveReset(formObject) {
// clear cards
nsCards = [];
odCards = [];
csrCards = [];
csCards = [];
csrCards = [];
picCards = [];
adSlideCards = [];
Expand Down
2 changes: 1 addition & 1 deletion myviews/settings.ejs
Original file line number Diff line number Diff line change
Expand Up @@ -947,7 +947,7 @@
<label for="filterUsers">Filter by user(s)</label>
<input type="text" class="form-control" id="filterUsers" name="filterUsers" aria-describedby="filterUsersHelp"
placeholder="examples -> matt,fred,sally@abc.com" value="<% if(typeof formData !== 'undefined' && errors){%><%=formData.filterUsers%>"<%}else{%><%=settings.filterUsers%>"<%}%>>
<small id="filterUsersHelp" class="form-text text-muted">Enter user names to include (Comma seperated and no spaces. Leave blank for all users)</small>
<small id="filterUsersHelp" class="form-text text-muted">Include only these Emby/Jellyfin/Plex usernames (comma-separated). Use the exact name from the server’s user list (spaces allowed). Leave blank for all users. When set, other household accounts are hidden even if they are the only ones playing.</small>
</div>

<div class="form-group">
Expand Down
34 changes: 34 additions & 0 deletions test/embyUserFilter.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
const EmbyJellyfinBase = require("../classes/mediaservers/embyJellyfinBase");

describe("Emby/Jellyfin now-playing user filter helpers", () => {
test("parseIncludeFilterList trims and allows spaces in names", () => {
expect(
EmbyJellyfinBase.parseIncludeFilterList(" Matt , Mother In Law ,fred")
).toEqual(["matt", "mother in law", "fred"]);
});

test("sessionMatchesUserFilter matches UserName case-insensitively", () => {
const session = { UserName: "Matt", UserId: "abc-123" };
expect(
EmbyJellyfinBase.sessionMatchesUserFilter(session, ["matt"])
).toBe(true);
expect(
EmbyJellyfinBase.sessionMatchesUserFilter(session, ["mother"])
).toBe(false);
});

test("sessionMatchesUserFilter can match UserId GUID", () => {
const session = { UserName: "Matt", UserId: "a490a918d5234bc990088cfee3c4e245" };
expect(
EmbyJellyfinBase.sessionMatchesUserFilter(session, [
"a490a918d5234bc990088cfee3c4e245",
])
).toBe(true);
});

test("empty filter list allows all sessions", () => {
expect(
EmbyJellyfinBase.sessionMatchesUserFilter({ UserName: "Anyone" }, [])
).toBe(true);
});
});
Loading