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 = ``; } 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("
"), 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); } }