mirror of
https://git.victorphan.net/basketballcantho/study4.git
synced 2026-08-05 06:13:11 +07:00
Add source code, scraper extension, and result files
This commit is contained in:
@@ -0,0 +1,200 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user