diff --git a/server/index.mjs b/server/index.mjs index 9dd0b40..ba422d0 100644 --- a/server/index.mjs +++ b/server/index.mjs @@ -108,7 +108,7 @@ import { fetchGoogleLiked, fetchTwitchProfile, fetchTwitchFollows, hasGoogleWriteScope, fetchGoogleWatchLater, pushGoogleWatchLater, fetchInnerTubeHistory, fetchInnerTubeWatchLaterViaLib, pushInnerTubeWatchLater, fetchGoogleWatchLaterId, - fetchAccountChannels, + fetchAccountChannels, diagnoseInnertube, } from './oauth.mjs'; import { getSearchMode, getYtDlpBin, hasCookiesFile, metricsSnapshot } from './providers/youtube-common.mjs'; import { ytScrapeCacheStats } from './providers/youtube.mjs'; @@ -585,6 +585,28 @@ r.get('/oauth/google/yt-channels', authMiddlewareCookieAware, oauthLimiter, asyn } }); +// Diagnostic : état de chaque endpoint InnerTube authentifié + Data API. +r.get('/oauth/google/diag', authMiddlewareCookieAware, oauthLimiter, async (req, res) => { + try { + const conn = await freshOAuthToken(req.user.id, 'google'); + const dataApi = {}; + try { + await fetchGoogleWatchLaterId(conn.accessToken); + dataApi.watchLaterId = true; + } catch (e) { + dataApi.watchLaterId = false; + dataApi.watchLaterError = String(e?.message || '').slice(0, 80); + } + const innertube = await diagnoseInnertube(conn); + try { + console.log(`[oauth] diag user=${req.user?.id} ${JSON.stringify({ dataApi, innertube }).slice(0, 800)}`); + } catch {} + return res.json({ dataApi, innertube }); + } catch (error) { + return res.status(error?.status || 502).json({ error: error?.message || 'diag_failed' }); + } +}); + r.put('/oauth/google/yt-channel', authMiddlewareCookieAware, oauthLimiter, (req, res) => { try { const channelId = String(req.body?.channelId || '').trim().slice(0, 64) || null; diff --git a/server/oauth.mjs b/server/oauth.mjs index 388e461..533e589 100644 --- a/server/oauth.mjs +++ b/server/oauth.mjs @@ -717,6 +717,50 @@ function textOf(t) { return String(t?.simpleText || ''); } +/** + * Diagnostic InnerTube authentifié : teste plusieurs endpoints et rapporte + * pour chacun ok/count ou le motif YouTube. But : isoler un rejet global + * du compte d'un problème spécifique à l'historique. + */ +export async function diagnoseInnertube(conn) { + const out = {}; + let innertube = null; + try { + innertube = await getAuthedInnertube(conn); + out.session = { ok: true }; + } catch (e) { + out.session = { ok: false, reason: String(e?.message || e).slice(0, 200) }; + return out; + } + const probe = async (name, fn) => { + try { + const r = await fn(); + out[name] = { ok: true, ...r }; + } catch (e) { + out[name] = { ok: false, reason: libErrorDetail(e) }; + } + }; + await probe('subscriptionsFeed', async () => { + const feed = await innertube.getSubscriptionsFeed(); + return { count: Array.isArray(feed?.contents) ? feed.contents.length : 0 }; + }); + await probe('library', async () => { + await innertube.getLibrary(); + return {}; + }); + await probe('accountMenu', async () => { + const data = await innertube.session.actions.execute('/account/account_menu', {}); + const blob = JSON.stringify(data?.data || data || '').slice(0, 200); + return { sample: blob.slice(0, 120) }; + }); + await probe('history', async () => { + const feed = await innertube.getHistory(); + const n = Array.isArray(feed?.sections) ? feed.sections.reduce((a, s) => a + (s?.contents?.length || 0), 0) : 0; + return { count: n }; + }); + return out; +} + /** * Chaînes YouTube du compte (sélecteur façon SmartTube) via account_menu. * La chaîne par défaut peut être une coquille vide (relatedPlaylists sans diff --git a/src/components/library/import/import.component.html b/src/components/library/import/import.component.html index 7e22c5b..357410a 100644 --- a/src/components/library/import/import.component.html +++ b/src/components/library/import/import.component.html @@ -101,7 +101,12 @@ class="rounded-md border border-slate-700 bg-slate-800 px-3 py-1.5 text-sm text-slate-200 transition hover:border-slate-500"> {{ ytChannelsLoading() ? 'Chargement…' : (ytChannels().length ? 'Recharger' : 'Lister mes chaînes') }} + +
{{ diagResult() }}
{{ ytChannelsError() }}