Add Instagram metadata support in client and server

This commit is contained in:
2026-01-07 18:36:57 +01:00
parent 904dcb4fc0
commit cc2f7abd03
3 changed files with 53 additions and 0 deletions

View File

@@ -25,6 +25,7 @@ function normalizeDetail(payload: any): EntryDetail {
id: String(payload?.id ?? ''), id: String(payload?.id ?? ''),
title: payload?.title || payload?.meta?.title_en || String(payload?.id ?? 'Untitled'), title: payload?.title || payload?.meta?.title_en || String(payload?.id ?? 'Untitled'),
meta: payload?.meta || {}, meta: payload?.meta || {},
ig_meta: payload?.ig_meta,
items, items,
quiz: Array.isArray(payload?.quiz) ? payload.quiz : [], quiz: Array.isArray(payload?.quiz) ? payload.quiz : [],
ui_hints: payload?.ui_hints || {}, ui_hints: payload?.ui_hints || {},

View File

@@ -64,6 +64,15 @@ export interface EntryDetail {
type?: string; type?: string;
title_en?: string; title_en?: string;
}; };
ig_meta?: {
username?: string;
full_name?: string;
profile_pic_url?: string;
post_url?: string;
profile_url?: string;
post_date?: string;
description?: string;
};
items: EntryItems; items: EntryItems;
quiz: QuizQuestion[]; quiz: QuizQuestion[];
ui_hints?: { ui_hints?: {

View File

@@ -140,6 +140,32 @@ function computeCounts(items: EntryData['items'], quiz: EntryData['quiz']) {
}; };
} }
function extractIgMeta(raw: any): EntryRecord['igMeta'] {
if (!raw || typeof raw !== 'object') return undefined;
const username = raw.username || raw.owner?.username;
const full_name = raw.fullname || raw.full_name || raw.owner?.full_name;
const post_url = raw.post_url || raw.postUrl || raw.permalink;
const profile_pic_url =
raw.profile_pic_url ||
raw.owner?.hd_profile_pic_url_info?.url ||
raw.owner?.profile_pic_url ||
raw.owner?.profile_pic_url_info?.url;
const post_date = raw.post_date || raw.date || raw.taken_at_timestamp || raw.timestamp;
const description = raw.description || raw.caption;
const profile_url = username ? `https://www.instagram.com/${username}/` : undefined;
if (!username && !profile_pic_url && !post_date && !description) return undefined;
return {
username,
full_name,
profile_pic_url,
post_url,
profile_url,
post_date: post_date ? String(post_date) : undefined,
description,
};
}
async function loadEntries() { async function loadEntries() {
entryIndex.clear(); entryIndex.clear();
@@ -159,6 +185,7 @@ async function loadEntries() {
const dir = path.dirname(resolvedMp4); const dir = path.dirname(resolvedMp4);
const baseName = path.basename(resolvedMp4, '.mp4'); const baseName = path.basename(resolvedMp4, '.mp4');
const jsonPath = path.join(dir, `${baseName}.json`); const jsonPath = path.join(dir, `${baseName}.json`);
const mp4MetaPath = path.join(dir, `${baseName}.mp4.json`);
if (!(await fileExists(jsonPath))) { if (!(await fileExists(jsonPath))) {
continue; continue;
@@ -169,6 +196,20 @@ async function loadEntries() {
continue; continue;
} }
let igMeta: EntryRecord['igMeta'];
const resolvedMp4Meta = path.resolve(mp4MetaPath);
if (await fileExists(resolvedMp4Meta)) {
if (ensureWithinDataRoot(resolvedMp4Meta)) {
try {
const raw = await fs.readFile(resolvedMp4Meta, 'utf-8');
const json = JSON.parse(raw);
igMeta = extractIgMeta(json);
} catch (err) {
console.warn(`Failed to parse mp4 metadata ${resolvedMp4Meta}:`, err);
}
}
}
let parsed: EntryData | null = null; let parsed: EntryData | null = null;
try { try {
const raw = await fs.readFile(resolvedJson, 'utf-8'); const raw = await fs.readFile(resolvedJson, 'utf-8');
@@ -196,6 +237,7 @@ async function loadEntries() {
items: parsed.items || { grammar: [], vocab: [], conversation: [], key_phrases: [] }, items: parsed.items || { grammar: [], vocab: [], conversation: [], key_phrases: [] },
quiz: parsed.quiz || [], quiz: parsed.quiz || [],
ui_hints: parsed.ui_hints || { recommended_order: [] }, ui_hints: parsed.ui_hints || { recommended_order: [] },
igMeta,
videoPath: resolvedMp4, videoPath: resolvedMp4,
jsonPath: resolvedJson, jsonPath: resolvedJson,
video_url, video_url,
@@ -214,6 +256,7 @@ function sanitizeEntryResponse(entry: EntryRecord) {
items: entry.items || { grammar: [], vocab: [], conversation: [], key_phrases: [] }, items: entry.items || { grammar: [], vocab: [], conversation: [], key_phrases: [] },
quiz: entry.quiz || [], quiz: entry.quiz || [],
ui_hints: entry.ui_hints || { recommended_order: [] }, ui_hints: entry.ui_hints || { recommended_order: [] },
ig_meta: entry.igMeta,
video_url: entry.video_url, video_url: entry.video_url,
counts: entry.counts, counts: entry.counts,
}; };