Files
jiang13-bbs/frontend/scripts/fetch-dy-emoji.mjs
freefire 34aee91a13 feat: 论坛 emoji 表情支持
- 新增 dyEmoji 表情库与解析测试、EmojiPicker 选择器与 EmojiImg 组件
- Markdown 编辑器/渲染支持表情图片,新增 public/emoji 资源与生成脚本
- 聊天室与首页适配
2026-09-28 01:28:41 +08:00

130 lines
4.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* 一次性资产脚本:从 github.com/rento666/douyin-emoji 下载表情图片存档,
* 拍平拷贝到 frontend/public/emoji/dy/ 并生成 manifest.json。
*
* 用法:node scripts/fetch-dy-emoji.mjs
* 产物(图片 + manifest.json)需提交进 git;重复执行会先清空目标目录。
*/
import { execFileSync } from "node:child_process";
import { createWriteStream, existsSync, mkdirSync, rmSync, statSync } from "node:fs";
import { readdir, writeFile, cp } from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { fileURLToPath } from "node:url";
import { Readable } from "node:stream";
import { pipeline } from "node:stream/promises";
const REPO_ZIPS = [
// 国内网络下 GitHub 直连常被限速,优先走镜像加速(实测 gh-proxy.com 可达 MB/s 级)
"https://gh-proxy.com/https://github.com/rento666/douyin-emoji/archive/refs/heads/main.zip",
"https://codeload.github.com/rento666/douyin-emoji/zip/refs/heads/main",
"https://github.com/rento666/douyin-emoji/archive/refs/heads/main.zip",
];
const HERE = path.dirname(fileURLToPath(import.meta.url));
const FRONTEND = path.resolve(HERE, "..");
const DEST = path.join(FRONTEND, "public", "emoji", "dy");
async function downloadZip(url, file) {
const res = await fetch(url, { redirect: "follow", signal: AbortSignal.timeout(120_000) });
if (!res.ok || !res.body) throw new Error(`下载失败 HTTP ${res.status}: ${url}`);
await pipeline(Readable.fromWeb(res.body), createWriteStream(file));
}
async function downloadZipWithFallback(file) {
let lastErr;
for (const url of REPO_ZIPS) {
try {
console.log(`尝试 ${url}`);
await downloadZip(url, file);
return;
} catch (e) {
lastErr = e;
console.warn(`下载失败:${e.message}`);
}
}
throw lastErr;
}
function extractZip(zip, dir) {
// Win10+/Linux 自带 bsdtar,可直接解 zip,无需额外依赖
execFileSync("tar", ["-xf", zip, "-C", dir], { stdio: "inherit" });
}
async function collectPng(dir) {
const out = [];
for (const name of await readdir(dir)) {
const p = path.join(dir, name);
if (statSync(p).isDirectory()) out.push(...(await collectPng(p)));
else if (name.toLowerCase().endsWith(".png")) out.push(p);
}
return out;
}
async function main() {
if (!existsSync(DEST)) mkdirSync(DEST, { recursive: true });
const tmp = await readdir(os.tmpdir()).then(() => os.tmpdir());
const work = path.join(tmp, `dy-emoji-${Date.now()}`);
mkdirSync(work, { recursive: true });
const zip = path.join(work, "repo.zip");
console.log("下载仓库压缩包…");
await downloadZipWithFallback(zip);
console.log("解压…");
extractZip(zip, work);
const repo = path.join(work, "douyin-emoji-main", "dy_emojis");
const tosDir = path.join(repo, "tos_emoji");
const fusDir = path.join(repo, "fus_emoji");
if (!existsSync(tosDir) || !existsSync(fusDir)) {
throw new Error(`仓库结构不符合预期:${repo}`);
}
const tosFiles = await collectPng(tosDir);
const fusFiles = await collectPng(fusDir);
if (tosFiles.length === 0 || fusFiles.length === 0) {
throw new Error(`表情文件为空:tos=${tosFiles.length} fus=${fusFiles.length}`);
}
// 拍平拷贝:目标目录清空后写入,文件名唯一性校验
rmSync(DEST, { recursive: true, force: true });
mkdirSync(DEST, { recursive: true });
const seen = new Map();
const tos = [];
const fus = [];
let bytes = 0;
for (const [group, files] of [
["tos", tosFiles],
["fus", fusFiles],
]) {
for (const f of files) {
const key = path.basename(f, ".png");
if (!/^[A-Za-z0-9_-]+$/.test(key)) {
throw new Error(`文件名含非法字符(会破坏短代码):${key}`);
}
if (seen.has(key)) throw new Error(`文件名冲突:${key}(${f} 与 ${seen.get(key)})`);
seen.set(key, f);
await cp(f, path.join(DEST, `${key}.png`));
bytes += statSync(f).size;
(group === "tos" ? tos : fus).push(key);
}
}
tos.sort();
fus.sort();
const manifest = { tos, fus };
await writeFile(path.join(DEST, "manifest.json"), JSON.stringify(manifest), "utf8");
console.log(
`完成:tos=${tos.length} 张,fus=${fus.length} 张,共 ${(bytes / 1024 / 1024).toFixed(1)}MB → ${DEST}`
);
console.log(`manifest.json:${path.join(DEST, "manifest.json")}`);
// 清理临时目录
rmSync(work, { recursive: true, force: true });
}
main().catch((e) => {
console.error(e);
process.exit(1);
});