feat(providers): phases 7.3/7.4/7.6/8.1 — provenance, health, NDJSON, contrat unique
CI / build-and-test (push) Successful in 14m43s
CI / build-and-test (push) Successful in 14m43s
7.3: capturedAt/source au registre + 6 adaptateurs + module provenance.ts + ?debug=1 (search-transport.mjs). 7.4: ProviderHealthService + badge source degradee. 7.6: squelettes par provider + snapshots progressifs + transport NDJSON /api/search. 8.1: ProviderAdapter unifie (search enveloppe + channelContent/channelMeta/capabilities) via getProviderAdapter + test de contrat offline.
This commit is contained in:
@@ -75,6 +75,45 @@ export function parseDurationLabel(label) {
|
||||
} catch { return undefined; }
|
||||
}
|
||||
|
||||
/** "il y a 2 mois" / "Streamed 3 weeks ago" / "2 days ago" -> ISO, ou undefined. */
|
||||
export function parseRelativeDate(text, nowMs = Date.now()) {
|
||||
try {
|
||||
const s = String(text || '').toLowerCase().trim();
|
||||
if (!s) return undefined;
|
||||
// Une date absolue ("17 janv. 2024", "2024-01-17") passe deja par Date.parse.
|
||||
if (/\d{4}-\d{2}-\d{2}/.test(s)) {
|
||||
const abs = Date.parse(s);
|
||||
if (Number.isFinite(abs) && abs > 0 && abs < 8.64e15) return new Date(abs).toISOString();
|
||||
}
|
||||
// Fenetre temporelle : YouTube annonce "il y a 2 heures", "3 weeks ago", ...
|
||||
const re = /(\d+)\s*(second|minute|hour|day|week|month|year|seconde|heure|jour|semaine|mois|an|annee|année)/;
|
||||
const m = s.match(re);
|
||||
if (!m) {
|
||||
// "hier" / "yesterday" / "aujourd'hui" / "today" : cas sans quantite.
|
||||
if (/\b(yesterday|hier)\b/.test(s)) return new Date(nowMs - 864e5).toISOString();
|
||||
if (/\b(today|aujourd'hui)\b/.test(s)) return new Date(nowMs).toISOString();
|
||||
if (/\bjust now|just now|maintenant|venir de\b/.test(s)) return new Date(nowMs).toISOString();
|
||||
return undefined;
|
||||
}
|
||||
const n = Math.max(0, Number(m[1]));
|
||||
if (!Number.isFinite(n)) return undefined;
|
||||
const unit = m[2];
|
||||
const table = {
|
||||
second: 1e3, seconde: 1e3,
|
||||
minute: 6e4,
|
||||
hour: 36e5, heure: 36e5,
|
||||
day: 864e5, jour: 864e5,
|
||||
week: 6048e5, semaine: 6048e5,
|
||||
month: 26298e6, mois: 26298e6,
|
||||
year: 315576e5, an: 315576e5, annee: 315576e5, année: 315576e5,
|
||||
};
|
||||
const mult = table[unit];
|
||||
if (!mult) return undefined;
|
||||
const ms = nowMs - (n * mult);
|
||||
return Number.isFinite(ms) && ms > 0 ? new Date(ms).toISOString() : undefined;
|
||||
} catch { return undefined; }
|
||||
}
|
||||
|
||||
function bestThumb(thumbs) {
|
||||
try {
|
||||
const arr = Array.isArray(thumbs) ? thumbs.filter((t) => t?.url) : [];
|
||||
@@ -151,7 +190,12 @@ export function mapVideoNode(n) {
|
||||
type: 'video',
|
||||
...(Number.isFinite(duration) && duration > 0 ? { duration } : {}),
|
||||
...(views !== undefined ? { views } : {}),
|
||||
...(n.published?.text ? { publishedAt: String(n.published.text) } : {}),
|
||||
// Phase 1.9 - `n.published.text` est un LIBELLE RELATIF ("il y a 2 mois"),
|
||||
// que Date.parse() renvoie en NaN. On le convertit en ISO : sans cela le
|
||||
// filtre `period=week` etait inoperant sur YouTube et `sort=date` triait
|
||||
// sur NaN. Si le libelle est incomprehensible, publishedAt reste absent
|
||||
// (jamais de date inventee).
|
||||
...(n.published?.text ? { publishedAt: parseRelativeDate(n.published.text) } : {}),
|
||||
...(authorId ? { channelId: authorId, channelExternalId: authorId, channelUrl: `https://www.youtube.com/channel/${authorId}` } : {}),
|
||||
...(authorName ? { channelHandle: String(authorName) } : {}),
|
||||
...(n.is_live ? { isLive: true } : {}),
|
||||
|
||||
Reference in New Issue
Block a user