Fix Odido multi-account sync: unique device IDs and fresh login before play.
This commit is contained in:
parent
e3601710f8
commit
62435927f5
3 changed files with 54 additions and 12 deletions
|
|
@ -1,10 +1,21 @@
|
||||||
|
import { createHash } from "node:crypto";
|
||||||
|
|
||||||
const BASE_URL = "https://tv.odido.nl";
|
const BASE_URL = "https://tv.odido.nl";
|
||||||
export const ODIDO_USER_AGENT =
|
export const ODIDO_USER_AGENT =
|
||||||
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 " +
|
||||||
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
"(KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36";
|
||||||
|
/** Fallback als er geen username is; liever deviceIdForUsername(). */
|
||||||
const DEFAULT_DEVICE_ID = "MzkzNjIwMTU1NjAxMDNydA==";
|
const DEFAULT_DEVICE_ID = "MzkzNjIwMTU1NjAxMDNydA==";
|
||||||
const DEFAULT_SESSION_CACHE_MS = 20 * 60_000;
|
const DEFAULT_SESSION_CACHE_MS = 20 * 60_000;
|
||||||
|
|
||||||
|
/** Stabiele, per-account device-id zodat sessies elkaar niet wegtrappen. */
|
||||||
|
export function deviceIdForUsername(username: string): string {
|
||||||
|
const hash = createHash("sha256")
|
||||||
|
.update(`odido-mediacluster:${username.trim()}`)
|
||||||
|
.digest("base64url");
|
||||||
|
return hash.slice(0, 24);
|
||||||
|
}
|
||||||
|
|
||||||
export type OdidoCookies = Record<string, string>;
|
export type OdidoCookies = Record<string, string>;
|
||||||
|
|
||||||
export type OdidoChannel = {
|
export type OdidoChannel = {
|
||||||
|
|
@ -153,7 +164,11 @@ export async function login(
|
||||||
const cached = sessions.get(username);
|
const cached = sessions.get(username);
|
||||||
if (cached && cached.expiresAt > Date.now()) return cached.cookies;
|
if (cached && cached.expiresAt > Date.now()) return cached.cookies;
|
||||||
}
|
}
|
||||||
const cookies = await loginWithDevice(username, password, DEFAULT_DEVICE_ID);
|
const cookies = await loginWithDevice(
|
||||||
|
username,
|
||||||
|
password,
|
||||||
|
deviceIdForUsername(username) || DEFAULT_DEVICE_ID
|
||||||
|
);
|
||||||
const ttl = Number.isFinite(sessionCacheMs) && sessionCacheMs > 0
|
const ttl = Number.isFinite(sessionCacheMs) && sessionCacheMs > 0
|
||||||
? sessionCacheMs
|
? sessionCacheMs
|
||||||
: DEFAULT_SESSION_CACHE_MS;
|
: DEFAULT_SESSION_CACHE_MS;
|
||||||
|
|
|
||||||
|
|
@ -145,10 +145,13 @@ export async function refreshChannelMpd(
|
||||||
let lastError: Error | null = null;
|
let lastError: Error | null = null;
|
||||||
for (const account of ordered) {
|
for (const account of ordered) {
|
||||||
try {
|
try {
|
||||||
const cookies =
|
// Altijd verse login: multi-account deelt geen betrouwbare sessie-cache.
|
||||||
opts?.cookies && opts.account?.id === account.id
|
const cookies = await login(
|
||||||
? opts.cookies
|
account.username,
|
||||||
: await login(account.username, account.pin, tweaks.sessionCacheMinutes * 60_000);
|
account.pin,
|
||||||
|
tweaks.sessionCacheMinutes * 60_000,
|
||||||
|
{ force: true }
|
||||||
|
);
|
||||||
const play = await playChannel(cookies, externalId, mediaId);
|
const play = await playChannel(cookies, externalId, mediaId);
|
||||||
const keys = await fetchKeysIfNeeded(
|
const keys = await fetchKeysIfNeeded(
|
||||||
play,
|
play,
|
||||||
|
|
|
||||||
|
|
@ -116,16 +116,18 @@ export async function syncOdidoList(
|
||||||
const perAccount: Array<{ accountId: string; label: string; channels: number }> = [];
|
const perAccount: Array<{ accountId: string; label: string; channels: number }> = [];
|
||||||
const cookiesByAccount = new Map<string, OdidoCookies>();
|
const cookiesByAccount = new Map<string, OdidoCookies>();
|
||||||
|
|
||||||
// 1) Per account: login + zenderlijst → unie (geen dubbele LiveChannel-rijen)
|
// 1) Per account sequentieel: login → QueryAllChannel → unie
|
||||||
for (const account of accounts) {
|
for (const account of accounts) {
|
||||||
let cookies: OdidoCookies;
|
let cookies: OdidoCookies;
|
||||||
try {
|
try {
|
||||||
cookies = await login(
|
cookies = await login(
|
||||||
account.username,
|
account.username,
|
||||||
account.pin,
|
account.pin,
|
||||||
tweaks.sessionCacheMinutes * 60_000
|
tweaks.sessionCacheMinutes * 60_000,
|
||||||
|
{ force: true }
|
||||||
);
|
);
|
||||||
cookiesByAccount.set(account.id, cookies);
|
cookiesByAccount.set(account.id, cookies);
|
||||||
|
console.log(`[odido] sync account ${account.label || account.username}: login ok`);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.warn(`[odido] login mislukt voor ${account.username}:`, err);
|
console.warn(`[odido] login mislukt voor ${account.username}:`, err);
|
||||||
perAccount.push({ accountId: account.id, label: account.label, channels: 0 });
|
perAccount.push({ accountId: account.id, label: account.label, channels: 0 });
|
||||||
|
|
@ -154,7 +156,6 @@ export async function syncOdidoList(
|
||||||
if (!existing.accountIds.includes(account.id)) {
|
if (!existing.accountIds.includes(account.id)) {
|
||||||
existing.accountIds.push(account.id);
|
existing.accountIds.push(account.id);
|
||||||
}
|
}
|
||||||
// Behoud rijkste metadata (logo/channelNO)
|
|
||||||
if (!logoUrl(existing.channel) && logoUrl(channel)) existing.channel = channel;
|
if (!logoUrl(existing.channel) && logoUrl(channel)) existing.channel = channel;
|
||||||
if (
|
if (
|
||||||
(existing.channel.channelNO == null || existing.channel.channelNO === "") &&
|
(existing.channel.channelNO == null || existing.channel.channelNO === "") &&
|
||||||
|
|
@ -170,6 +171,9 @@ export async function syncOdidoList(
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
console.log(
|
||||||
|
`[odido] sync account ${account.label || account.username}: ${count} VIDEO_CHANNEL`
|
||||||
|
);
|
||||||
perAccount.push({ accountId: account.id, label: account.label, channels: count });
|
perAccount.push({ accountId: account.id, label: account.label, channels: count });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -213,8 +217,12 @@ export async function syncOdidoList(
|
||||||
|
|
||||||
const channelOwners: OdidoChannelOwners = {};
|
const channelOwners: OdidoChannelOwners = {};
|
||||||
const entries = [...union.entries()];
|
const entries = [...union.entries()];
|
||||||
|
const accountById = new Map(accounts.map((a) => [a.id, a]));
|
||||||
|
const allAccountIds = accounts.map((a) => a.id);
|
||||||
|
|
||||||
// 2) PlayChannel per unieke zender — eerste account dat lukt (keys/cache)
|
// 2) PlayChannel per unieke zender.
|
||||||
|
// Belangrijk: verse login per poging — na multi-account QueryAllChannel zijn
|
||||||
|
// eerdere cookies vaak ongeldig; probeer owners eerst, daarna overige accounts.
|
||||||
await mapLimit(entries, tweaks.syncConcurrency, async ([externalId, entry]) => {
|
await mapLimit(entries, tweaks.syncConcurrency, async ([externalId, entry]) => {
|
||||||
channelOwners[externalId] = [...entry.accountIds];
|
channelOwners[externalId] = [...entry.accountIds];
|
||||||
const physical = entry.channel.physicalChannels?.[0];
|
const physical = entry.channel.physicalChannels?.[0];
|
||||||
|
|
@ -224,11 +232,23 @@ export async function syncOdidoList(
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tryOrder = [
|
||||||
|
...entry.accountIds,
|
||||||
|
...allAccountIds.filter((id) => !entry.accountIds.includes(id)),
|
||||||
|
];
|
||||||
|
|
||||||
let playOk = false;
|
let playOk = false;
|
||||||
for (const accountId of entry.accountIds) {
|
for (const accountId of tryOrder) {
|
||||||
const cookies = cookiesByAccount.get(accountId);
|
const account = accountById.get(accountId);
|
||||||
if (!cookies) continue;
|
if (!account) continue;
|
||||||
try {
|
try {
|
||||||
|
const cookies = await login(
|
||||||
|
account.username,
|
||||||
|
account.pin,
|
||||||
|
tweaks.sessionCacheMinutes * 60_000,
|
||||||
|
{ force: true }
|
||||||
|
);
|
||||||
|
cookiesByAccount.set(accountId, cookies);
|
||||||
const play = await playChannel(cookies, externalId, mediaId);
|
const play = await playChannel(cookies, externalId, mediaId);
|
||||||
entry.playAccountId = accountId;
|
entry.playAccountId = accountId;
|
||||||
entry.cookies = play.cookies;
|
entry.cookies = play.cookies;
|
||||||
|
|
@ -236,6 +256,10 @@ export async function syncOdidoList(
|
||||||
entry.licenseUrl = play.licenseUrl;
|
entry.licenseUrl = play.licenseUrl;
|
||||||
entry.licenseToken = play.licenseToken;
|
entry.licenseToken = play.licenseToken;
|
||||||
entry.playContentId = play.contentId;
|
entry.playContentId = play.contentId;
|
||||||
|
if (!entry.accountIds.includes(accountId)) {
|
||||||
|
entry.accountIds.push(accountId);
|
||||||
|
channelOwners[externalId] = [...entry.accountIds];
|
||||||
|
}
|
||||||
playOk = true;
|
playOk = true;
|
||||||
break;
|
break;
|
||||||
} catch {
|
} catch {
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue