import * as fs from "node:fs"; import mime from "mime-types"; import archiver from "archiver"; import { queueLogger } from "../../logger.js"; import { addFile } from "../../../services/drive/add-file.js"; import { format as dateFormat } from "date-fns"; import { Users, Emojis } from "../../../models/index.js"; import { createTemp, createTempDir } from "../../../misc/create-temp.js"; import { downloadUrl } from "../../../misc/download-url.js"; import config from "../../../config/index.js"; import { IsNull } from "typeorm"; const logger = queueLogger.createSubLogger("export-custom-emojis"); export async function exportCustomEmojis(job) { logger.info("Exporting custom emojis ..."); const user = await Users.findOneBy({ id: job.data.user.id }); if (user == null) { return "skip: User not found"; } const [path, cleanup] = await createTempDir(); logger.info(`Temp dir is ${path}`); const metaPath = `${path}/meta.json`; fs.writeFileSync(metaPath, "", "utf-8"); const metaStream = fs.createWriteStream(metaPath, { flags: "a" }); const writeMeta = (text)=>{ return new Promise((res, rej)=>{ metaStream.write(text, (err)=>{ if (err) { logger.error(err); rej(err); } else { res(); } }); }); }; await writeMeta(`{"metaVersion":2,"host":"${config.host}","exportedAt":"${new Date().toString()}","emojis":[`); const customEmojis = await Emojis.find({ where: { host: IsNull() }, order: { id: "ASC" } }); for (const emoji of customEmojis){ const ext = mime.extension(emoji.type); // there are some restrictions on file names, so to be safe the files are // named after their database id instead of the actual emoji name const fileName = emoji.id + (ext ? '.' + ext : ''); const emojiPath = `${path}/${fileName}`; fs.writeFileSync(emojiPath, "", "binary"); let downloaded = false; try { await downloadUrl(emoji.originalUrl, emojiPath); downloaded = true; } catch (e) { // TODO: 何度か再試行 logger.error(e instanceof Error ? e : new Error(e)); } if (!downloaded) { fs.unlinkSync(emojiPath); } const content = JSON.stringify({ fileName: fileName, downloaded: downloaded, emoji: emoji }); const isFirst = customEmojis.indexOf(emoji) === 0; await writeMeta(isFirst ? content : ",\n" + content); } await writeMeta("]}"); metaStream.end(); // Create archive await new Promise(async (resolve, reject)=>{ try { const [archivePath, archiveCleanup] = await createTemp(); const archiveStream = fs.createWriteStream(archivePath); const archive = archiver("zip", { zlib: { level: 0 } }); archiveStream.on("close", async ()=>{ logger.succ(`Exported to: ${archivePath}`); const fileName = `custom-emojis-${dateFormat(new Date(), "yyyy-MM-dd-HH-mm-ss")}.zip`; const driveFile = await addFile({ user, path: archivePath, name: fileName, force: true }); logger.succ(`Exported to: ${driveFile.id}`); cleanup(); archiveCleanup(); resolve(undefined); }); archive.pipe(archiveStream); archive.directory(path, false); archive.finalize(); } catch (e) { reject(e); } }); return "Success"; }