Add Instagram metadata support in client and server
This commit is contained in:
@@ -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 || {},
|
||||||
|
|||||||
@@ -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?: {
|
||||||
|
|||||||
@@ -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,
|
||||||
};
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user