debug(sync): endpoint diag multi-endpoints (Data API + InnerTube) + bouton UI
This commit is contained in:
+23
-1
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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') }}
|
||||
</button>
|
||||
<button type="button" (click)="loadDiag()" [disabled]="diagLoading()"
|
||||
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">
|
||||
{{ diagLoading() ? 'Diagnostic…' : '🩺 Diagnostic complet' }}
|
||||
</button>
|
||||
</div>
|
||||
<pre *ngIf="diagResult()" class="mt-2 max-h-64 overflow-auto whitespace-pre-wrap rounded-lg border border-slate-700 bg-slate-950 p-2 text-xs text-slate-300">{{ diagResult() }}</pre>
|
||||
<div *ngIf="ytChannelsError()" class="mt-2 rounded-lg border border-red-700 bg-red-900/30 p-2 text-xs text-red-200">{{ ytChannelsError() }}</div>
|
||||
<div *ngIf="ytChannels().length" class="mt-3 space-y-2">
|
||||
<label *ngFor="let c of ytChannels()" class="flex cursor-pointer items-center gap-3 rounded-lg border border-slate-700 bg-slate-800/60 px-3 py-2 text-sm transition hover:border-slate-500">
|
||||
|
||||
@@ -249,6 +249,24 @@ export class ImportComponent implements OnInit {
|
||||
ytSelectedChannelId = signal<string>('');
|
||||
ytSavingChannel = signal(false);
|
||||
ytChannelSaved = signal<string | null>(null);
|
||||
diagLoading = signal(false);
|
||||
diagResult = signal<string | null>(null);
|
||||
|
||||
loadDiag(): void {
|
||||
if (this.diagLoading()) return;
|
||||
this.diagLoading.set(true);
|
||||
this.diagResult.set(null);
|
||||
this.api.diag().subscribe({
|
||||
next: (res) => {
|
||||
this.diagResult.set(JSON.stringify(res, null, 1));
|
||||
this.diagLoading.set(false);
|
||||
},
|
||||
error: (e) => {
|
||||
this.diagResult.set(`Erreur diag : ${e?.error?.error || e?.message || 'diag_failed'}`);
|
||||
this.diagLoading.set(false);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
loadYtChannels(): void {
|
||||
if (!this.isConnected('google') || this.ytChannelsLoading()) return;
|
||||
|
||||
@@ -101,6 +101,11 @@ export class OAuthImportService {
|
||||
saveYtChannel(channelId: string | null, pageId: string | null): Observable<{ selected: { channelId: string | null; pageId: string | null } }> {
|
||||
return this.http.put<any>(`/api/oauth/google/yt-channel`, { channelId, pageId });
|
||||
}
|
||||
|
||||
/** Diagnostic : état de chaque endpoint authentifié (Data API + InnerTube). */
|
||||
diag(): Observable<any> {
|
||||
return this.http.get<any>(`/api/oauth/google/diag`);
|
||||
}
|
||||
}
|
||||
|
||||
/** Parse local du `watch-history.json` de Google Takeout (confidentiel : rien n'est uploadé tel quel). */
|
||||
|
||||
Reference in New Issue
Block a user