Files
ko-grammar/docs/.vitepress/vite-plugins/mp3-downloader.ts
T

111 lines
3.4 KiB
TypeScript

import fs from "node:fs";
import type { IncomingMessage, ServerResponse } from "node:http";
import path from "node:path";
import type { Connect, Plugin, ViteDevServer } from "vite";
import sqlite3 from "better-sqlite3";
import { Tokenizer } from "./tokenizer";
export function mp3DownloaderPlugin(): Plugin {
return {
name: "mp3-downloader",
async configureServer(server: ViteDevServer) {
const db = sqlite3(".sentence-mp3.db");
db.exec(/* sql */ `
CREATE TABLE IF NOT EXISTS sentence (
id INTEGER NOT NULL PRIMARY KEY,
ko TEXT NOT NULL,
original TEXT NOT NULL,
[url] TEXT NOT NULL
);
CREATE UNIQUE INDEX IF NOT EXISTS idx_sentence_u_url ON sentence([url]);
CREATE VIRTUAL TABLE IF NOT EXISTS sentence_fts USING fts5 (
ko,
original UNINDEXED,
[url] UNINDEXED,
content='sentence',
content_rowid='id'
);
-- Triggers to keep the FTS index up to date.
CREATE TRIGGER IF NOT EXISTS t1_ai AFTER INSERT ON sentence BEGIN
INSERT INTO sentence_fts(rowid, ko) VALUES (new.id, new.ko);
END;
CREATE TRIGGER IF NOT EXISTS t1_ad AFTER DELETE ON sentence BEGIN
INSERT INTO sentence_fts(fts_idx, rowid, ko) VALUES('delete', old.id, old.ko);
END;
CREATE TRIGGER IF NOT EXISTS t1_au AFTER UPDATE ON sentence BEGIN
INSERT INTO sentence_fts(fts_idx, rowid, ko) VALUES('delete', old.id, old.ko);
INSERT INTO sentence_fts(rowid, ko) VALUES (new.id, new.ko);
END;
`);
let tokenizer: Tokenizer | null = null;
// Vite dev server middlewares are connect-style: (req, res, next)
server.middlewares.use(async (req, res, next) => {
const reqUrl = req.url;
if (!reqUrl || !reqUrl.startsWith("/uploads/")) return next();
const url = new URL(reqUrl, "http://localhost");
if (!url.pathname.endsWith(".mp3")) return next();
try {
// if (!tokenizer) {
// tokenizer = await Tokenizer.init();
// }
await mp3Downloader(db, tokenizer, url, res, next);
} catch (e) {
console.error(e, reqUrl);
next();
}
});
},
};
}
async function mp3Downloader(
db: ReturnType<typeof sqlite3>,
tokenizer: Tokenizer,
url: URL,
res: ServerResponse<IncomingMessage>,
next: Connect.NextFunction,
) {
const downloadURL = new URL(
`https://www.howtostudykorean.com/wp-content${url.pathname}`,
);
// const text = url.searchParams.get("text");
// if (text) {
// db.prepare(
// "INSERT OR IGNORE INTO sentence ([url], ko, original) VALUES (?, ?, ?)",
// ).run(downloadURL.href, tokenizer.normalize(text), text);
// }
const segs = url.pathname.slice(1).split("/");
const filename = segs.pop()!;
const saveroot = "docs/public";
const saveto = path.join(saveroot, ...segs, filename);
if (fs.existsSync(saveto)) {
return next();
}
fs.mkdirSync(path.join(saveroot, ...segs), { recursive: true });
const r = await fetch(downloadURL, { redirect: "follow" });
if (!r.ok) {
console.error(r);
return next();
}
fs.writeFileSync(saveto, Buffer.from(await r.arrayBuffer()));
// console.log(fs.statSync(saveto));
console.log("saved to", saveto);
res.setHeader("Content-Type", "audio/mpeg");
fs.createReadStream(saveto).pipe(res);
}