Files
study4/study4_scraper_extension/popup.js
T
2026-05-25 11:39:14 +07:00

201 lines
7.8 KiB
JavaScript

function downloadData(data, filename, type) {
const blob = new Blob([data], { type: type });
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = filename;
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
}
function escapeCSV(cell) {
let cellStr = cell ? String(cell) : "";
if (cellStr.includes(",") || cellStr.includes("\n") || cellStr.includes('"')) {
cellStr = '"' + cellStr.replace(/"/g, '""') + '"';
}
return cellStr;
}
function convertToCSV(items) {
if (items.length === 0) return "";
const headers = ["Word", "Type", "Pronunciation", "Definition", "Examples", "ImageURL", "UKAudioURL", "USAudioURL"];
let csv = headers.join(",") + "\n";
items.forEach(item => {
let row = [
item.word,
item.type,
item.pronunciation,
item.definition,
item.examples.join(" | "),
item.imageUrl,
item.audio.uk,
item.audio.us
].map(escapeCSV);
csv += row.join(",") + "\n";
});
return csv;
}
function convertToAnkiCSV(items) {
if (items.length === 0) return "";
let csv = "";
items.forEach(item => {
let imgTag = "";
if (item.imageUrl) {
let filename = item.imageUrl.split('/').pop().split('?')[0];
// ensure filename doesn't have weird chars
filename = decodeURIComponent(filename);
imgTag = `<img src="${filename}">`;
}
let ukTag = "";
if (item.audio.uk) {
let params = new URLSearchParams(item.audio.uk.split('?')[1]);
let q = params.get('q') ? params.get('q').replace(/ /g, '_') : 'uk';
ukTag = `[sound:${q}_uk.mp3]`;
}
let usTag = "";
if (item.audio.us) {
let params = new URLSearchParams(item.audio.us.split('?')[1]);
let q = params.get('q') ? params.get('q').replace(/ /g, '_') : 'us';
usTag = `[sound:${q}_us.mp3]`;
}
let row = [
item.word,
item.type,
item.pronunciation,
item.definition,
item.examples.join("<br>"),
imgTag,
ukTag,
usTag
].map(escapeCSV);
csv += row.join(",") + "\n";
});
return csv;
}
document.getElementById('scrape-json').addEventListener('click', () => triggerScrape('json'));
document.getElementById('scrape-csv').addEventListener('click', () => triggerScrape('csv'));
if (document.getElementById('scrape-anki')) {
document.getElementById('scrape-anki').addEventListener('click', () => triggerScrape('anki'));
}
async function fetchAsBlob(url) {
const response = await fetch(url);
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
return await response.blob();
}
async function triggerScrape(format) {
const statusDiv = document.getElementById('status');
statusDiv.textContent = "Đang xử lý...";
try {
let [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
const results = await chrome.scripting.executeScript({
target: { tabId: tab.id },
files: ['content.js']
});
if (results && results[0] && results[0].result) {
const data = results[0].result;
if (data.error) {
statusDiv.textContent = "Lỗi: " + data.error;
return;
}
if (data.length === 0) {
statusDiv.textContent = "Không tìm thấy từ vựng nào trên trang này.";
return;
}
if (format === 'json') {
statusDiv.textContent = `Đã tìm thấy ${data.length} từ. Đang tải xuống JSON...`;
const jsonStr = JSON.stringify(data, null, 2);
downloadData(jsonStr, 'study4_vocab.json', 'application/json');
setTimeout(() => { statusDiv.textContent = "Hoàn tất!"; }, 2000);
} else if (format === 'csv') {
statusDiv.textContent = `Đã tìm thấy ${data.length} từ. Đang tải xuống CSV...`;
const csvStr = convertToCSV(data);
downloadData("\uFEFF" + csvStr, 'study4_vocab.csv', 'text/csv;charset=utf-8');
setTimeout(() => { statusDiv.textContent = "Hoàn tất!"; }, 2000);
} else if (format === 'anki') {
statusDiv.textContent = `Đã tìm thấy ${data.length} từ. Đang chuẩn bị Media (có thể mất ít phút)...`;
const zip = new JSZip();
// 1. Add CSV
const csvStr = convertToAnkiCSV(data);
zip.file("study4_anki.csv", "\uFEFF" + csvStr);
let count = 0;
let total = data.length * 3; // roughly 3 media per word (img, uk, us)
for (let i = 0; i < data.length; i++) {
const item = data[i];
statusDiv.textContent = `Đang tải hình/âm thanh ${i+1}/${data.length}...`;
if (item.imageUrl) {
try {
let filename = decodeURIComponent(item.imageUrl.split('/').pop().split('?')[0]);
const blob = await fetchAsBlob(item.imageUrl);
zip.file(filename, blob);
} catch (e) {
console.error("Failed to load image", item.imageUrl, e);
}
}
if (item.audio.uk) {
try {
let params = new URLSearchParams(item.audio.uk.split('?')[1]);
let q = params.get('q') ? params.get('q').replace(/ /g, '_') : 'uk';
const blob = await fetchAsBlob(item.audio.uk);
zip.file(`${q}_uk.mp3`, blob);
} catch (e) {
console.error("Failed to load UK audio", item.audio.uk, e);
}
}
if (item.audio.us) {
try {
let params = new URLSearchParams(item.audio.us.split('?')[1]);
let q = params.get('q') ? params.get('q').replace(/ /g, '_') : 'us';
const blob = await fetchAsBlob(item.audio.us);
zip.file(`${q}_us.mp3`, blob);
} catch (e) {
console.error("Failed to load US audio", item.audio.us, e);
}
}
}
statusDiv.textContent = `Đang nén file ZIP...`;
const content = await zip.generateAsync({type:"blob"});
const url = URL.createObjectURL(content);
const a = document.createElement("a");
a.href = url;
a.download = "study4_anki_deck.zip";
document.body.appendChild(a);
a.click();
document.body.removeChild(a);
URL.revokeObjectURL(url);
statusDiv.textContent = "Hoàn tất! Hãy giải nén file ZIP, copy hình và âm thanh vào thư mục Anki collection.media, sau đó import file study4_anki.csv";
}
} else {
statusDiv.textContent = "Không thể lấy dữ liệu. Hãy đảm bảo bạn đang ở trang chứa Flashcard Study4.";
}
} catch (e) {
statusDiv.textContent = "Lỗi: " + e.message;
console.error(e);
}
}