Pop-up dictionary browser extension for language learning. Successor to Yomichan. (PERSONAL FORK)
at upstream/master 110 lines 4.0 kB view raw
1/* 2 * Copyright (C) 2024-2025 Yomitan Authors 3 * 4 * This program is free software: you can redistribute it and/or modify 5 * it under the terms of the GNU General Public License as published by 6 * the Free Software Foundation, either version 3 of the License, or 7 * (at your option) any later version. 8 * 9 * This program is distributed in the hope that it will be useful, 10 * but WITHOUT ANY WARRANTY; without even the implied warranty of 11 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 12 * GNU General Public License for more details. 13 * 14 * You should have received a copy of the GNU General Public License 15 * along with this program. If not, see <https://www.gnu.org/licenses/>. 16 */ 17 18import {BlobWriter, TextReader, TextWriter, Uint8ArrayReader, ZipReader, ZipWriter} from '@zip.js/zip.js'; 19import {readFileSync, readdirSync} from 'fs'; 20import {join} from 'path'; 21import {parseJson} from './json.js'; 22 23/** 24 * Creates a zip archive from the given dictionary directory. 25 * @param {string} dictionaryDirectory 26 * @param {string} [dictionaryName] 27 * @returns {Promise<ArrayBuffer>} 28 */ 29export async function createDictionaryArchiveData(dictionaryDirectory, dictionaryName) { 30 const fileNames = readdirSync(dictionaryDirectory); 31 const zipFileWriter = new BlobWriter(); 32 // Level 0 compression used since decompression in the node environment is not supported. 33 // See dev/lib/zip.js for more details. 34 const zipWriter = new ZipWriter(zipFileWriter, { 35 level: 0, 36 }); 37 for (const fileName of fileNames) { 38 if (/\.json$/.test(fileName)) { 39 const content = readFileSync(join(dictionaryDirectory, fileName), {encoding: 'utf8'}); 40 /** @type {import('dictionary-data').Index} */ 41 const json = parseJson(content); 42 if (fileName === 'index.json' && typeof dictionaryName === 'string') { 43 json.title = dictionaryName; 44 } 45 await zipWriter.add(fileName, new TextReader(JSON.stringify(json, null, 0))); 46 } else { 47 const content = readFileSync(join(dictionaryDirectory, fileName), {encoding: null}); 48 await zipWriter.add(fileName, new Blob([content]).stream()); 49 } 50 } 51 const blob = await zipWriter.close(); 52 return await blob.arrayBuffer(); 53} 54 55/** 56 * @param {import('@zip.js/zip.js').Entry} entry 57 * @returns {Promise<string>} 58 */ 59export async function readArchiveEntryDataString(entry) { 60 if (typeof entry.getData === 'undefined') { throw new Error('Cannot get index data'); } 61 return await entry.getData(new TextWriter()); 62} 63 64/** 65 * @template [T=unknown] 66 * @param {import('@zip.js/zip.js').Entry} entry 67 * @returns {Promise<T>} 68 */ 69export async function readArchiveEntryDataJson(entry) { 70 const indexContent = await readArchiveEntryDataString(entry); 71 return parseJson(indexContent); 72} 73 74/** 75 * @param {ArrayBuffer} data 76 * @returns {Promise<import('@zip.js/zip.js').Entry[]>} 77 */ 78export async function getDictionaryArchiveEntries(data) { 79 const zipFileReader = new Uint8ArrayReader(new Uint8Array(data)); 80 const zipReader = new ZipReader(zipFileReader); 81 return await zipReader.getEntries(); 82} 83 84/** 85 * @template T 86 * @param {import('@zip.js/zip.js').Entry[]} entries 87 * @param {string} fileName 88 * @returns {Promise<T>} 89 */ 90export async function getDictionaryArchiveJson(entries, fileName) { 91 const entry = entries.find((item) => item.filename === fileName); 92 if (typeof entry === 'undefined') { throw new Error(`File not found: ${fileName}`); } 93 return await readArchiveEntryDataJson(entry); 94} 95 96/** 97 * @returns {string} 98 */ 99export function getIndexFileName() { 100 return 'index.json'; 101} 102 103/** 104 * @param {ArrayBuffer} data 105 * @returns {Promise<import('dictionary-data').Index>} 106 */ 107export async function getDictionaryArchiveIndex(data) { 108 const entries = await getDictionaryArchiveEntries(data); 109 return await getDictionaryArchiveJson(entries, getIndexFileName()); 110}