mirror of
https://github.com/versia-pub/server.git
synced 2025-12-06 16:38:19 +01:00
The old directory, packages/database-interface, was confusingly named so it was better to move it here
254 lines
7.9 KiB
TypeScript
254 lines
7.9 KiB
TypeScript
import { Args, Flags } from "@oclif/core";
|
|
import chalk from "chalk";
|
|
import { and, inArray, isNull } from "drizzle-orm";
|
|
import { lookup } from "mime-types";
|
|
import ora from "ora";
|
|
import { unzip } from "unzipit";
|
|
import { Attachment } from "~/classes/database/attachment";
|
|
import { Emoji } from "~/classes/database/emoji";
|
|
import { MediaManager } from "~/classes/media/media-manager";
|
|
import { BaseCommand } from "~/cli/base";
|
|
import { Emojis } from "~/drizzle/schema";
|
|
import { config } from "~/packages/config-manager";
|
|
|
|
type MetaType = {
|
|
emojis: {
|
|
fileName: string;
|
|
emoji: {
|
|
name: string;
|
|
};
|
|
}[];
|
|
};
|
|
|
|
export default class EmojiImport extends BaseCommand<typeof EmojiImport> {
|
|
static override args = {
|
|
path: Args.string({
|
|
description: "Path to the emoji archive (can be an URL)",
|
|
required: true,
|
|
}),
|
|
};
|
|
|
|
static override description =
|
|
"Imports emojis from a zip file (which can be fetched from a zip URL, e.g. for Pleroma emoji packs)";
|
|
|
|
static override examples = [
|
|
"<%= config.bin %> <%= command.id %> https://volpeon.ink/emojis/neocat/neocat.zip",
|
|
"<%= config.bin %> <%= command.id %> export.zip",
|
|
];
|
|
|
|
static override flags = {
|
|
confirm: Flags.boolean({
|
|
description:
|
|
"Ask for confirmation before deleting the emoji (default yes)",
|
|
allowNo: true,
|
|
default: true,
|
|
}),
|
|
};
|
|
|
|
public async run(): Promise<void> {
|
|
const { args } = await this.parse(EmojiImport);
|
|
|
|
// Check if path ends in .zip, warn the user if it doesn't
|
|
if (!args.path.endsWith(".zip")) {
|
|
this.log(
|
|
`${chalk.yellow(
|
|
"⚠",
|
|
)} The path you provided does not end in .zip, this may not be a zip file. Proceeding anyway.`,
|
|
);
|
|
}
|
|
|
|
let file: File | null = null;
|
|
|
|
if (URL.canParse(args.path)) {
|
|
const spinner = ora(
|
|
`Downloading pack from ${chalk.blue(
|
|
chalk.underline(args.path),
|
|
)}`,
|
|
).start();
|
|
|
|
const response = await fetch(args.path, {
|
|
headers: {
|
|
"Accept-Encoding": "identity",
|
|
},
|
|
// @ts-expect-error Proxy is a Bun-specific feature
|
|
proxy: config.http.proxy.address,
|
|
});
|
|
|
|
if (!response.ok) {
|
|
spinner.fail();
|
|
this.log(
|
|
`${chalk.red("✗")} Request returned status code ${chalk.red(
|
|
response.status,
|
|
)}`,
|
|
);
|
|
this.exit(1);
|
|
}
|
|
|
|
const filename =
|
|
new URL(args.path).pathname.split("/").pop() ?? "archive";
|
|
|
|
file = new File([await response.blob()], filename, {
|
|
type:
|
|
response.headers.get("Content-Type") ??
|
|
"application/octet-stream",
|
|
});
|
|
|
|
spinner.succeed();
|
|
} else {
|
|
const bunFile = Bun.file(args.path);
|
|
file = new File(
|
|
[await bunFile.arrayBuffer()],
|
|
args.path.split("/").pop() ?? "archive",
|
|
{
|
|
type: bunFile.type,
|
|
},
|
|
);
|
|
}
|
|
|
|
const unzipSpinner = ora("Unzipping pack").start();
|
|
|
|
const { entries: unzipped } = await unzip(file);
|
|
|
|
unzipSpinner.succeed();
|
|
|
|
const entries = Object.entries(unzipped);
|
|
|
|
// Check if a meta.json file exists
|
|
const metaExists = entries.find(([name]) => name === "meta.json");
|
|
|
|
if (metaExists) {
|
|
this.log(`${chalk.green("✓")} Detected Pleroma meta.json, parsing`);
|
|
}
|
|
|
|
const meta = metaExists
|
|
? ((await metaExists[1].json()) as MetaType)
|
|
: ({
|
|
emojis: entries.map(([name]) => ({
|
|
fileName: name,
|
|
emoji: {
|
|
name: name.split(".")[0],
|
|
},
|
|
})),
|
|
} as MetaType);
|
|
|
|
// Get all emojis that already exist
|
|
const existingEmojis = await Emoji.manyFromSql(
|
|
and(
|
|
isNull(Emojis.instanceId),
|
|
inArray(
|
|
Emojis.shortcode,
|
|
meta.emojis.map((e) => e.emoji.name),
|
|
),
|
|
),
|
|
);
|
|
|
|
// Filter out existing emojis
|
|
const newEmojis = meta.emojis.filter(
|
|
(e) =>
|
|
!existingEmojis.find(
|
|
(ee) => ee.data.shortcode === e.emoji.name,
|
|
),
|
|
);
|
|
|
|
existingEmojis.length > 0 &&
|
|
this.log(
|
|
`${chalk.yellow("⚠")} Emojis with shortcode ${chalk.yellow(
|
|
existingEmojis.map((e) => e.data.shortcode).join(", "),
|
|
)} already exist in the database and will not be imported`,
|
|
);
|
|
|
|
if (newEmojis.length === 0) {
|
|
this.log(`${chalk.red("✗")} No new emojis to import`);
|
|
this.exit(1);
|
|
}
|
|
|
|
this.log(
|
|
`${chalk.green("✓")} Found ${chalk.green(
|
|
newEmojis.length,
|
|
)} new emoji(s)`,
|
|
);
|
|
|
|
const importSpinner = ora("Importing emojis").start();
|
|
|
|
const mediaManager = new MediaManager(config);
|
|
|
|
const successfullyImported: MetaType["emojis"] = [];
|
|
|
|
for (const emoji of newEmojis) {
|
|
importSpinner.text = `Uploading ${chalk.gray(emoji.emoji.name)} (${
|
|
newEmojis.indexOf(emoji) + 1
|
|
}/${newEmojis.length})`;
|
|
const zipEntry = unzipped[emoji.fileName];
|
|
|
|
if (!zipEntry) {
|
|
this.log(
|
|
`${chalk.red(
|
|
"✗",
|
|
)} Could not find file for emoji ${chalk.red(
|
|
emoji.emoji.name,
|
|
)}`,
|
|
);
|
|
continue;
|
|
}
|
|
|
|
const fileName = emoji.fileName.split("/").pop() ?? "emoji";
|
|
const contentType = lookup(fileName) || "application/octet-stream";
|
|
|
|
const newFile = new File([await zipEntry.arrayBuffer()], fileName, {
|
|
type: contentType,
|
|
});
|
|
|
|
const uploaded = await mediaManager
|
|
.addFile(newFile)
|
|
.catch((e: Error) => {
|
|
this.log(
|
|
`${chalk.red("✗")} Error uploading ${chalk.red(
|
|
emoji.emoji.name,
|
|
)}: ${chalk.red(e.message)}`,
|
|
);
|
|
return null;
|
|
});
|
|
|
|
if (!uploaded) {
|
|
continue;
|
|
}
|
|
|
|
await Emoji.insert({
|
|
shortcode: emoji.emoji.name,
|
|
url: Attachment.getUrl(uploaded.path),
|
|
visibleInPicker: true,
|
|
contentType: uploaded.uploadedFile.type,
|
|
});
|
|
|
|
successfullyImported.push(emoji);
|
|
}
|
|
|
|
importSpinner.succeed("Imported emojis");
|
|
|
|
successfullyImported.length > 0 &&
|
|
this.log(
|
|
`${chalk.green("✓")} Successfully imported ${chalk.green(
|
|
successfullyImported.length,
|
|
)} emoji(s)`,
|
|
);
|
|
|
|
newEmojis.length - successfullyImported.length > 0 &&
|
|
this.log(
|
|
`${chalk.yellow("⚠")} Failed to import ${chalk.yellow(
|
|
newEmojis.length - successfullyImported.length,
|
|
)} emoji(s): ${chalk.yellow(
|
|
newEmojis
|
|
.filter((e) => !successfullyImported.includes(e))
|
|
.map((e) => e.emoji.name)
|
|
.join(", "),
|
|
)}`,
|
|
);
|
|
|
|
if (successfullyImported.length === 0) {
|
|
this.exit(1);
|
|
}
|
|
|
|
this.exit(0);
|
|
}
|
|
}
|