mirror of
https://git.victorphan.net/basketballcantho/study4.git
synced 2026-08-05 14:23:11 +07:00
201 lines
7.8 KiB
JavaScript
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);
|
|
}
|
|
}
|