fix(sync): 403 InnerTube + WL manquant via fallbacks
- InnerTube 403: la YOUTUBE_API_KEY restreinte par referrer etait rejetee sans Referer envoye; ajout headers navigateur + repli cle publique du client web (probe live: 401 propre = transport OK) - Watch Later: repli lecture via InnerTube (browseId WL, sans ID) quand l'API Data ne renvoie pas d'ID; repli push via editPlaylist; note 'via' affichee dans l'UI; debug cles relatedPlaylists conserve
This commit is contained in:
+29
-7
@@ -106,6 +106,7 @@ import {
|
||||
refreshAccessToken, redirectUriFor, fetchGoogleProfile, fetchGoogleSubscriptions,
|
||||
fetchGoogleLiked, fetchTwitchProfile, fetchTwitchFollows, hasGoogleWriteScope,
|
||||
fetchGoogleWatchLater, pushGoogleWatchLater, fetchInnerTubeHistory,
|
||||
fetchInnerTubeWatchLater, pushInnerTubeWatchLater, fetchGoogleWatchLaterId,
|
||||
} from './oauth.mjs';
|
||||
import { getSearchMode, getYtDlpBin, hasCookiesFile, metricsSnapshot } from './providers/youtube-common.mjs';
|
||||
import { ytScrapeCacheStats } from './providers/youtube.mjs';
|
||||
@@ -660,11 +661,23 @@ r.post('/oauth/:provider/import', authMiddlewareCookieAware, oauthLimiter, async
|
||||
r.get('/oauth/google/watchlater', authMiddlewareCookieAware, oauthLimiter, async (req, res) => {
|
||||
try {
|
||||
const conn = await freshOAuthToken(req.user.id, 'google');
|
||||
const { items } = await fetchGoogleWatchLater(conn.accessToken, 50);
|
||||
return res.json({
|
||||
items, count: items.length,
|
||||
writeGranted: hasGoogleWriteScope(conn.scopes),
|
||||
});
|
||||
const writeGranted = hasGoogleWriteScope(conn.scopes);
|
||||
try {
|
||||
const { items } = await fetchGoogleWatchLater(conn.accessToken, 50);
|
||||
return res.json({ items, count: items.length, writeGranted, via: 'api' });
|
||||
} catch (apiError) {
|
||||
// L'API Data ne renvoie pas d'ID WL pour certains comptes : repli
|
||||
// InnerTube (playlist logique "WL", aucun ID requis).
|
||||
if (apiError?.message !== 'watchlater_not_found' && apiError?.message !== 'youtube_no_channel') throw apiError;
|
||||
try {
|
||||
console.warn(`[oauth] WL Data API indisponible (${apiError.message}), repli InnerTube`);
|
||||
} catch {}
|
||||
const { items } = await fetchInnerTubeWatchLater(conn.accessToken, innertubeApiKey(), 100);
|
||||
return res.json({
|
||||
items, count: items.length, writeGranted, via: 'innertube',
|
||||
note: 'Liste lue via InnerTube (l’API YouTube ne l’expose pas pour ce compte).',
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
const out = { error: error?.message || 'watchlater_fetch_failed' };
|
||||
if (error?.hint) out.hint = error.hint;
|
||||
@@ -719,15 +732,24 @@ r.post('/oauth/google/watchlater', authMiddlewareCookieAware, oauthLimiter, asyn
|
||||
const ids = Array.isArray(req.body?.videoIds) ? req.body.videoIds : [];
|
||||
const clean = [...new Set(ids.map((v) => String(v || '').trim()).filter(Boolean))].slice(0, 50);
|
||||
if (!clean.length) return res.status(400).json({ error: 'videoIds_required' });
|
||||
// ID WL via Data API, sinon repli InnerTube (playlist logique "WL").
|
||||
let useInnertube = false;
|
||||
try {
|
||||
await fetchGoogleWatchLaterId(conn.accessToken);
|
||||
} catch (e) {
|
||||
if (e?.message === 'watchlater_not_found' || e?.message === 'youtube_no_channel') useInnertube = true;
|
||||
else throw e;
|
||||
}
|
||||
let added = 0;
|
||||
let skipped = 0;
|
||||
for (const videoId of clean) {
|
||||
try {
|
||||
await pushGoogleWatchLater(conn.accessToken, videoId);
|
||||
if (useInnertube) await pushInnerTubeWatchLater(conn.accessToken, innertubeApiKey(), videoId);
|
||||
else await pushGoogleWatchLater(conn.accessToken, videoId);
|
||||
added++;
|
||||
} catch { skipped++; }
|
||||
}
|
||||
return res.json({ added, skipped, total: clean.length });
|
||||
return res.json({ added, skipped, total: clean.length, via: useInnertube ? 'innertube' : 'api' });
|
||||
} catch (error) {
|
||||
const status = error?.status || 502;
|
||||
if (status === 403) {
|
||||
|
||||
Reference in New Issue
Block a user