forked from UKSOURCE/ipv6
fix: 21 agenda , key invite
This commit is contained in:
@@ -0,0 +1,206 @@
|
||||
import axios from "axios";
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
interface Speaker {
|
||||
name: string;
|
||||
title: string;
|
||||
bio: string;
|
||||
initials?: string;
|
||||
}
|
||||
|
||||
interface RawSession {
|
||||
id: string;
|
||||
title: string;
|
||||
start: string;
|
||||
end: string;
|
||||
track: string;
|
||||
location: string;
|
||||
description: string;
|
||||
speaker: Speaker | null;
|
||||
}
|
||||
|
||||
interface EnrichedSession extends RawSession {
|
||||
trackLabel: string;
|
||||
durationMinutes: number;
|
||||
timeICT: string;
|
||||
timeUTC: string;
|
||||
}
|
||||
|
||||
interface SummitAgendaData {
|
||||
event: {
|
||||
title: string;
|
||||
theme: string;
|
||||
date: string;
|
||||
location: string;
|
||||
sourceUrl: string;
|
||||
crawledAt: string;
|
||||
};
|
||||
summary: {
|
||||
totalSessions: number;
|
||||
sessionsWithSpeakers: number;
|
||||
tracksBreakdown: Record<string, number>;
|
||||
uniqueSpeakers: Array<{ name: string; title: string }>;
|
||||
};
|
||||
sessions: EnrichedSession[];
|
||||
}
|
||||
|
||||
const TRACK_LABELS: Record<string, string> = {
|
||||
ceremony: "Ceremonies",
|
||||
keynote: "Keynotes & Address",
|
||||
technical: "Technical & Infrastructure",
|
||||
panel: "Forums & Panels",
|
||||
networking: "Breaks & Networking"
|
||||
};
|
||||
|
||||
function formatTime(dateStr: string, timeZone: string): string {
|
||||
const date = new Date(dateStr);
|
||||
return date.toLocaleTimeString("en-US", {
|
||||
timeZone,
|
||||
hour: "numeric",
|
||||
minute: "2-digit",
|
||||
hour12: true
|
||||
}).replace(":", ".").toLowerCase();
|
||||
}
|
||||
|
||||
function calculateDuration(startStr: string, endStr: string): number {
|
||||
const start = new Date(startStr).getTime();
|
||||
const end = new Date(endStr).getTime();
|
||||
return Math.round((end - start) / (1000 * 60));
|
||||
}
|
||||
|
||||
export async function crawlSummitAgenda(targetUrl = "https://apacv6.org/ai-summit-2026/"): Promise<SummitAgendaData> {
|
||||
console.log(`[Crawler] Bắt đầu cào dữ liệu từ: ${targetUrl}...`);
|
||||
|
||||
// 1. Fetch main page HTML
|
||||
const baseUrl = targetUrl.endsWith("/") ? targetUrl : targetUrl + "/";
|
||||
const htmlResponse = await axios.get(baseUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
});
|
||||
const html = htmlResponse.data;
|
||||
|
||||
// 2. Fetch app.js containing the dataset
|
||||
const appJsUrl = `${baseUrl}app.js`;
|
||||
console.log(`[Crawler] Tải mã nguồn kịch bản dữ liệu từ: ${appJsUrl}...`);
|
||||
const appJsResponse = await axios.get(appJsUrl, {
|
||||
headers: {
|
||||
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
|
||||
}
|
||||
});
|
||||
const jsContent = appJsResponse.data;
|
||||
|
||||
// 3. Extract the `sessions` array from app.js
|
||||
const sessionsMatch = jsContent.match(/const\s+sessions\s*=\s*(\[\s*\{[\s\S]*?\}\s*\]);/);
|
||||
if (!sessionsMatch || !sessionsMatch[1]) {
|
||||
throw new Error("Không thể trích xuất mảng dữ liệu 'sessions' từ app.js!");
|
||||
}
|
||||
|
||||
let rawSessions: RawSession[] = [];
|
||||
try {
|
||||
// Safely evaluate or parse JS object array
|
||||
const rawSessionsJsonStr = sessionsMatch[1];
|
||||
// Use Function constructor in isolated scope to parse JS array notation
|
||||
const parseFn = new Function(`return ${rawSessionsJsonStr};`);
|
||||
rawSessions = parseFn();
|
||||
} catch (err) {
|
||||
console.error("[Crawler] Lỗi khi parse dữ liệu sessions:", err);
|
||||
throw err;
|
||||
}
|
||||
|
||||
console.log(`[Crawler] Đã trích xuất thành công ${rawSessions.length} phiên (sessions).`);
|
||||
|
||||
// 4. Enrich sessions with friendly format and timings
|
||||
const enrichedSessions: EnrichedSession[] = rawSessions.map((session) => {
|
||||
const duration = calculateDuration(session.start, session.end);
|
||||
const startIct = formatTime(session.start, "Asia/Bangkok");
|
||||
const endIct = formatTime(session.end, "Asia/Bangkok");
|
||||
const startUtc = formatTime(session.start, "UTC");
|
||||
const endUtc = formatTime(session.end, "UTC");
|
||||
|
||||
// Fix known typo on live for Tran Quy
|
||||
let speaker = session.speaker;
|
||||
if (speaker && (speaker.name.includes("Ho So Chuyen") || session.id === "session-16")) {
|
||||
speaker = {
|
||||
name: "Mr. Tran Quy",
|
||||
title: "",
|
||||
bio: "Senior technical lead and infrastructure specialist, demonstrating next-generation network configurations.",
|
||||
initials: "TQ"
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
...session,
|
||||
speaker,
|
||||
trackLabel: TRACK_LABELS[session.track] || session.track,
|
||||
durationMinutes: duration,
|
||||
timeICT: `${startIct} - ${endIct} ICT`,
|
||||
timeUTC: `${startUtc} - ${endUtc} UTC`
|
||||
};
|
||||
});
|
||||
|
||||
// 5. Summary statistics
|
||||
const tracksBreakdown: Record<string, number> = {};
|
||||
const uniqueSpeakersMap = new Map<string, { name: string; title: string }>();
|
||||
|
||||
enrichedSessions.forEach((s) => {
|
||||
tracksBreakdown[s.track] = (tracksBreakdown[s.track] || 0) + 1;
|
||||
if (s.speaker && s.speaker.name && !s.speaker.name.toLowerCase().startsWith("tbd")) {
|
||||
uniqueSpeakersMap.set(s.speaker.name, {
|
||||
name: s.speaker.name,
|
||||
title: s.speaker.title
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
const agendaResult: SummitAgendaData = {
|
||||
event: {
|
||||
title: "IPv6 Enhanced for AI & Cloud Summit 2026",
|
||||
theme: "AI-driven data centres, cloud technologies, Net 5.5G and next-generation connectivity solutions",
|
||||
date: "2026-08-27",
|
||||
location: "Hanoi, Vietnam",
|
||||
sourceUrl: targetUrl,
|
||||
crawledAt: new Date().toISOString()
|
||||
},
|
||||
summary: {
|
||||
totalSessions: enrichedSessions.length,
|
||||
sessionsWithSpeakers: enrichedSessions.filter((s) => s.speaker !== null).length,
|
||||
tracksBreakdown,
|
||||
uniqueSpeakers: Array.from(uniqueSpeakersMap.values())
|
||||
},
|
||||
sessions: enrichedSessions
|
||||
};
|
||||
|
||||
return agendaResult;
|
||||
}
|
||||
|
||||
// CLI Execution entry point
|
||||
async function main() {
|
||||
try {
|
||||
const data = await crawlSummitAgenda();
|
||||
|
||||
// Ensure output directory exists
|
||||
const outputDir = path.join(process.cwd(), "data");
|
||||
if (!fs.existsSync(outputDir)) {
|
||||
fs.mkdirSync(outputDir, { recursive: true });
|
||||
}
|
||||
|
||||
const outputPath = path.join(outputDir, "summit-2026-agenda.json");
|
||||
fs.writeFileSync(outputPath, JSON.stringify(data, null, 2), "utf-8");
|
||||
|
||||
console.log(`\n======================================================`);
|
||||
console.log(`🎉 CÀO DỮ LIỆU AGENDA THÀNH CÔNG!`);
|
||||
console.log(`📁 File kết quả đã lưu tại: ${outputPath}`);
|
||||
console.log(`📊 Tổng số sessions: ${data.summary.totalSessions}`);
|
||||
console.log(`🎤 Số diễn giả đã xác định: ${data.summary.uniqueSpeakers.length}`);
|
||||
console.log(`======================================================\n`);
|
||||
} catch (error) {
|
||||
console.error("❌ Lỗi cào dữ liệu:", error);
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
Reference in New Issue
Block a user