koishi-plugin-memes-api
Version:
表情包制作插件调用 API 版
1,269 lines (1,239 loc) • 49.6 kB
JavaScript
;
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __export = (target, all) => {
for (var name2 in all)
__defProp(target, name2, { get: all[name2], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
// If the importer is in node compatibility mode or this is not an ESM
// file that has been converted to a CommonJS file using a Babel-
// compatible transform (i.e. "__esModule" has not been set), then set
// "default" to the CommonJS "module.exports" for node compatibility.
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.tsx
var index_exports = {};
__export(index_exports, {
Config: () => Config,
apply: () => apply11,
inject: () => inject,
name: () => name,
usage: () => usage
});
module.exports = __toCommonJS(index_exports);
var import_meme_generator_rs_api3 = require("meme-generator-rs-api");
// src/commands/generate.ts
var import_koishi2 = require("koishi");
// src/utils/error.ts
var import_meme_generator_rs_api = require("meme-generator-rs-api");
// src/utils/arg-parse.ts
var ArgSyntaxError = class _ArgSyntaxError extends SyntaxError {
constructor(type, char, index) {
const message = (() => {
switch (type) {
case _ArgSyntaxError.Type.UnexpectedChar:
return `Unexpected char ${char} in input string at index ${index}, consider use backslash to escape`;
case _ArgSyntaxError.Type.UnterminatedQuote:
return `Unterminated quote ${char} in input string at index ${index}`;
}
})();
super(message);
this.type = type;
this.char = char;
this.index = index;
}
static {
__name(this, "ArgSyntaxError");
}
};
((ArgSyntaxError2) => {
let Type;
((Type2) => {
Type2[Type2["UnexpectedChar"] = 0] = "UnexpectedChar";
Type2[Type2["UnterminatedQuote"] = 1] = "UnterminatedQuote";
})(Type = ArgSyntaxError2.Type || (ArgSyntaxError2.Type = {}));
function getI18NKey(e) {
const kPfx = `memes-api.errors.syntax-error.`;
switch (e.type) {
case 0 /* UnexpectedChar */:
return `${kPfx}unexpected-char`;
case 1 /* UnterminatedQuote */:
return `${kPfx}unterminated-quote`;
}
}
ArgSyntaxError2.getI18NKey = getI18NKey;
__name(getI18NKey, "getI18NKey");
})(ArgSyntaxError || (ArgSyntaxError = {}));
var quotePairs = {
'"': '"',
"'": "'",
"`": "`",
"“": "”",
"‘": "’"
};
var quotes = [...new Set(Object.entries(quotePairs).flat())];
function splitArgString(argString) {
const args = [];
const currentArgChars = [];
let inQuote = null;
let outQuote = null;
let escapeNext = false;
let lastInQuoteIndex = -1;
for (let i = 0; i < argString.length; i += 1) {
const char = argString[i];
if (escapeNext) {
currentArgChars.push(char);
escapeNext = false;
continue;
}
if (char === "\\") {
escapeNext = true;
continue;
}
if (inQuote) {
if (char === outQuote) {
inQuote = null;
outQuote = null;
} else if (char === inQuote) {
throw new ArgSyntaxError(0 /* UnexpectedChar */, char, i);
} else {
currentArgChars.push(char);
}
continue;
}
if (char in quotePairs) {
inQuote = char;
outQuote = quotePairs[char];
lastInQuoteIndex = i;
} else if (/^\s$/.test(char)) {
if (currentArgChars.length) {
args.push(currentArgChars.join(""));
currentArgChars.length = 0;
}
} else {
currentArgChars.push(char);
}
}
if (currentArgChars.length) args.push(currentArgChars.join(""));
if (inQuote) {
throw new ArgSyntaxError(
1 /* UnterminatedQuote */,
inQuote,
lastInQuoteIndex
);
}
return args;
}
__name(splitArgString, "splitArgString");
function escapeArgs(args, extraShouldQuote) {
return args.map((arg) => {
const needQuote = (() => {
for (const q of quotes) if (arg.includes(q)) return true;
if (extraShouldQuote) {
for (const q of extraShouldQuote) if (arg.includes(q)) return true;
}
return false;
})();
if (!needQuote) return arg;
for (const q of quotes) arg.replaceAll(q, `\\${q}`);
return `"${arg}"`;
}).join(" ");
}
__name(escapeArgs, "escapeArgs");
// src/utils/common.ts
var import_koishi = require("koishi");
function checkInRange(value, min, max) {
return value >= min && value <= max;
}
__name(checkInRange, "checkInRange");
function constructBlobFromFileResp(resp) {
return new Blob([resp.data], { type: resp.type });
}
__name(constructBlobFromFileResp, "constructBlobFromFileResp");
function formatRange(min, max) {
return min === max ? min.toString() : `${min} ~ ${max}`;
}
__name(formatRange, "formatRange");
function formatKeywords(keywords) {
return keywords.map((v) => `“${v}”`).join("、");
}
__name(formatKeywords, "formatKeywords");
function listJoin(list, splitter) {
const newList = [];
for (const item of list) {
newList.push(item);
newList.push(splitter);
}
newList.pop();
return newList;
}
__name(listJoin, "listJoin");
function listFlatJoin(list, splitter) {
const newList = [];
for (let i = 0; i < list.length - 1; i += 1) {
newList.push(...list[i]);
newList.push(...splitter);
}
newList.push(...list[list.length - 1]);
return newList;
}
__name(listFlatJoin, "listFlatJoin");
function transformRegex(pythonRegex) {
return pythonRegex.replace(/\(\?P<(?<n>\w+?)>/g, "(?<$<n>>");
}
__name(transformRegex, "transformRegex");
function extractContentPlaintext(content) {
let el;
try {
el = import_koishi.h.parse(content);
} catch (e) {
return content;
}
const textBuffer = [];
const visit = /* @__PURE__ */ __name((e) => {
if (e.children.length) {
for (const child of e.children) visit(child);
}
if (e.type === "text") {
const t = e.attrs.content;
if (t) textBuffer.push(t);
}
}, "visit");
for (const child of el) visit(child);
return textBuffer.join("");
}
__name(extractContentPlaintext, "extractContentPlaintext");
function replaceBracketVar(v, res) {
return v.replace(/(?<l>[^\{])?\{(?<v>.+?)\}(?<r>[^\}])?/g, (...args) => {
const { l, v: v2, r } = args[args.length - 1];
const index = parseInt(v2);
let resolved;
if (!isNaN(index)) {
resolved = res[index] ?? v2;
} else if (res.groups && v2 in res.groups) {
resolved = res.groups[v2];
} else {
resolved = v2;
}
return `${l ?? ""}${extractContentPlaintext(resolved)}${r ?? ""}`;
});
}
__name(replaceBracketVar, "replaceBracketVar");
function isVersionMeets(version, minVersion) {
const parts = version.split(".").map((v) => parseInt(v, 10));
for (let i = 0; i < minVersion.length; i++) {
const part = isNaN(parts[i]) ? 0 : parts[i] ?? 0;
const minPart = minVersion[i] ?? 0;
if (part < minPart) return false;
}
return true;
}
__name(isVersionMeets, "isVersionMeets");
// src/utils/user-info.ts
var GetAvatarFailedError = class extends Error {
constructor(platform, userId) {
super(`Failed to get avatar for user ${userId} on platform ${platform}.`);
this.platform = platform;
this.userId = userId;
this.name = "FetchAvatarFailedError";
}
static {
__name(this, "GetAvatarFailedError");
}
};
async function apply(ctx, config) {
ctx.$.getInfoFromID = async (session, userId, forceFallback) => {
const platformSpecific = {
onebot: /* @__PURE__ */ __name(async () => {
const url = `http://q.qlogo.cn/headimg_dl?dst_uin=${userId}&spec=640`;
const bot = session.bot;
if (session.isDirect) {
const data2 = await bot.internal.getStrangerInfo(userId);
return {
url,
userInfo: { name: data2.nickname, gender: data2.sex || "unknown" }
};
}
const data = await bot.internal.getGroupMemberInfo(session.guildId, userId);
return {
url,
userInfo: { name: data.card || data.nickname, gender: data.sex || "unknown" }
};
}, "onebot")
};
const fallback = /* @__PURE__ */ __name(async () => {
if (session.event.user?.id === userId && session.event.user?.avatar) {
return {
url: session.event.user.avatar,
userInfo: {
name: session.username || session.userId || "",
gender: "unknown"
}
};
} else if (session.bot.getUser) {
const user = await session.bot.getUser(userId, session.guildId);
if (user.avatar) {
return {
url: user.avatar,
userInfo: { name: user.nick || user.name || "", gender: "unknown" }
};
}
}
throw new TypeError(
`User ${userId} in platform ${session.platform} has no avatar`
);
}, "fallback");
const specificFunc = platformSpecific[session.platform];
const func = !specificFunc || forceFallback ? fallback : platformSpecific[session.platform];
try {
return await func();
} catch (e) {
ctx.logger.error(e);
if (func !== fallback) {
ctx.logger.warn(
`Failed to get user info from platform specific method, falling back to universal`
);
return ctx.$.getInfoFromID(session, userId, true);
}
throw new GetAvatarFailedError(session.platform, userId);
}
};
}
__name(apply, "apply");
// src/utils/error.ts
async function apply2(ctx, config) {
ctx.$.handleMemeError = (session, e) => {
if (e instanceof import_meme_generator_rs_api.MemeError.Detailed) {
const { code, data } = e.data;
switch (code) {
case 510:
return session.text("memes-api.errors.image-decode", [data.error]);
case 520:
return session.text("memes-api.errors.image-encode", [data.error]);
case 530:
return session.text("memes-api.errors.image-asset-missing", [data.path]);
case 540:
return session.text("memes-api.errors.deserialize", [data.error]);
case 550:
return session.text("memes-api.errors.image-number-mismatch", [
formatRange(data.min, data.max),
data.actual
]);
case 551:
return session.text("memes-api.errors.text-number-mismatch", [
formatRange(data.min, data.max),
data.actual
]);
case 560:
return session.text("memes-api.errors.text-over-length");
case 570:
return session.text("memes-api.errors.meme-feedback", [data.feedback]);
}
}
if (e.httpStatus === 404) {
return session.text("memes-api.errors.no-such-meme");
}
return session.text("memes-api.errors.other-error", [e.httpStatus, e.message]);
};
ctx.$.handleResolveArgsError = (session, e) => {
if (!(e instanceof ArgSyntaxError)) throw e;
ctx.logger.warn(e.message);
return config.silentShortcut && session.memesApi.shortcut ? void 0 : session.text(ArgSyntaxError.getI18NKey(e), e);
};
ctx.$.handleResolveImagesAndInfosError = (session, e) => {
if (e instanceof GetAvatarFailedError) {
return config.silentShortcut && session.memesApi.shortcut && config.moreSilent ? void 0 : session.text("memes-api.errors.can-not-get-avatar", e);
}
ctx.logger.warn(e);
return config.silentShortcut && session.memesApi.shortcut && config.moreSilent ? void 0 : session.text("memes-api.errors.download-image-failed");
};
ctx.$.handleRenderError = (session, e) => {
ctx.logger.warn(e);
if (!(e instanceof import_meme_generator_rs_api.MemeError)) throw e;
return config.silentShortcut && session.memesApi.shortcut && (config.moreSilent || // or arg error
e instanceof import_meme_generator_rs_api.MemeError.Detailed && [551, 552, 560].includes(e.data.code)) ? void 0 : ctx.$.handleMemeError(session, e);
};
ctx.$.handleError = (session, e) => {
ctx.logger.warn(e);
if (e instanceof ArgSyntaxError) {
return session.text(ArgSyntaxError.getI18NKey(e), e);
}
if (e instanceof import_meme_generator_rs_api.MemeError) {
return ctx.$.handleMemeError(session, e);
}
throw e;
};
}
__name(apply2, "apply");
// src/utils/meme-manage.ts
async function apply3(ctx, config) {
ctx.$.infos = {};
ctx.$.apiVersion = "0.0.0";
ctx.$.fetchInfos = async () => {
const infoArr = await ctx.$.api.getInfos();
return infoArr.reduce(
(acc, info) => {
acc[info.key] = info;
return acc;
},
{}
);
};
ctx.$.updateInfos = async () => {
ctx.$.apiVersion = await ctx.$.api.getVersion();
ctx.$.infos = await ctx.$.fetchInfos();
return ctx.$.infos;
};
}
__name(apply3, "apply");
// src/utils/index.ts
async function apply4(ctx, config) {
await apply3(ctx, config);
await apply2(ctx, config);
await apply(ctx, config);
}
__name(apply4, "apply");
// src/commands/generate.ts
async function apply5(ctx, config) {
const cmdGenerate = ctx.$.cmd.subcommand(".generate").action(async ({ session }) => {
if (session?.memesApi.inGenerateSubCommand) return;
return session?.execute("help meme.generate");
});
const generateSubCommands = [];
ctx.$.resolveArgs = async (session, args) => {
if (!session.userId) {
throw new TypeError("userId not found in session");
}
const imageInfos = [];
const texts = [];
const names = [];
if (session.quote?.elements) {
const visit2 = /* @__PURE__ */ __name((e) => {
if (e.children.length) {
for (const child of e.children) visit2(child);
}
if (e.type === "img") {
const src = e.attrs.src;
if (src) imageInfos.push({ src });
}
}, "visit");
for (const child of session.quote.elements) visit2(child);
}
const textBuffer = [];
const resolveBuffer = /* @__PURE__ */ __name(() => {
if (!textBuffer.length) return;
const bufferTexts = splitArgString(textBuffer.join("")).filter((v) => {
if (v === "自己" || v === "@自己") {
imageInfos.push({ userId: session.userId });
return false;
}
if (v.startsWith("@")) {
const userId = v.slice(1);
imageInfos.push({ userId });
return false;
}
if (v.startsWith("#")) {
const name2 = v.slice(1);
names.push(name2);
return false;
}
return true;
});
textBuffer.length = 0;
texts.push(...bufferTexts);
}, "resolveBuffer");
const visit = /* @__PURE__ */ __name((e) => {
if (e.children.length) {
for (const child of e.children) visit(child);
}
if (e.type === "text") {
const t = e.attrs.content;
if (t) textBuffer.push(t);
return;
}
resolveBuffer();
switch (e.type) {
case "img": {
const src = e.attrs.src;
if (src) imageInfos.push({ src });
break;
}
case "at": {
const userId = e.attrs.id;
if (userId) imageInfos.push({ userId });
break;
}
}
}, "visit");
for (const child of args) visit(child);
resolveBuffer();
return { imageInfos, texts, names };
};
ctx.$.resolveImagesAndInfos = async (session, imageInfos, existingNames) => {
const imageInfoKeys = imageInfos.map((v) => JSON.stringify(v));
const imageMap = {};
const userInfoMap = {};
const tasks = [...new Set(imageInfoKeys)].map(async (key) => {
const index = imageInfoKeys.indexOf(key);
const info = imageInfos[index];
let url;
let userInfo;
if ("src" in info) {
url = info.src;
userInfo = {};
} else if ("userId" in info) {
;
({ url, userInfo } = await ctx.$.getInfoFromID(session, info.userId));
} else {
throw new Error("Invalid image info");
}
imageMap[key] = constructBlobFromFileResp(await ctx.http.file(url));
userInfoMap[key] = userInfo;
});
await Promise.all(tasks);
const images = imageInfoKeys.map((key) => imageMap[key]);
const userInfos = imageInfoKeys.map((key) => userInfoMap[key]);
const names = [
...existingNames ?? [],
...userInfos.slice(existingNames?.length ?? 0).map((x) => x.name ?? session.author.nick ?? session.username)
];
const gender = userInfos.find((x) => x.gender)?.gender ?? "unknown";
return { images, names, gender };
};
ctx.$.checkAndCountToGenerate = async (session) => {
;
(session.memesApi ??= {}).inGenerateSubCommand = true;
const fatherRet = await session.execute("meme.generate", true);
delete session.memesApi.inGenerateSubCommand;
return fatherRet.length ? fatherRet : void 0;
};
ctx.$.uploadImages = async (images) => {
const pLimit = (await import("p-limit")).default;
const sem = pLimit(config.requestConcurrency);
return Promise.all(
images.map(
(x) => sem(() => ctx.$.api.uploadImageMultipart(x)).then((x2) => x2.image_id)
)
);
};
ctx.$.uploadImagesAndProcess = async (meme, { images, names, gender }, options) => {
const imageIds = await ctx.$.uploadImages(images);
const imagesReq = imageIds.map((id, k) => ({ id, name: names[k] }));
if (options && meme.params.options.some((v) => v.name === "gender") && !("gender" in options)) {
options.gender = gender;
}
return imagesReq;
};
ctx.$.normalizeOptionType = (type) => {
if (type === "float") return "number";
return type;
};
ctx.$.checkOptions = async (session, options, info) => {
const {
params: { options: memeOpts }
} = info;
for (const opt of memeOpts) {
if (!(opt.name in options)) continue;
if (opt.minimum || opt.maximum) {
const curr = parseFloat(options[opt.name]);
if (isNaN(curr)) {
return session.text("memes-api.errors.option-type-mismatch.number", [
opt.name
]);
}
if (opt.minimum && curr < opt.minimum) {
return session.text("memes-api.errors.option-number-too-small", [
opt.name,
opt.minimum
]);
}
if (opt.maximum && curr > opt.maximum) {
return session.text("memes-api.errors.option-number-too-big", [
opt.name,
opt.maximum
]);
}
}
}
};
ctx.$.uploadImgAndRenderMeme = async (meme, texts, uploadInfo, options) => {
const imgResp = await ctx.$.api.renderMeme(meme.key, {
texts,
images: await ctx.$.uploadImagesAndProcess(meme, uploadInfo, options),
options
});
return await ctx.$.api.getImage(imgResp.image_id);
};
const registerGenerateOptions = /* @__PURE__ */ __name((cmd, options) => {
for (const opt of options) {
const {
type,
name: name2,
description,
parser_flags: { short_aliases: sa, long_aliases: la },
choices
} = opt;
const kType = ctx.$.normalizeOptionType(type);
const cfg = { aliases: [...sa, ...la] };
if (choices && choices.length) {
cfg.type = choices;
}
cmd.option(name2, `[${name2}:${kType}] ${description}`, cfg);
}
return cmd;
}, "registerGenerateOptions");
const registerGenerateCmd = /* @__PURE__ */ __name((meme) => {
const { key, keywords } = meme;
const subCmd = cmdGenerate.subcommand(`.${key} [args:el]`, { strictOptions: true, hidden: true });
for (const kw of keywords) {
try {
subCmd.alias(`.${kw}`);
} catch (e) {
ctx.logger.warn(`Failed to register alias ${kw} for meme ${key}`);
ctx.logger.warn(e);
}
}
registerGenerateOptions(subCmd, meme.params.options);
return subCmd.action(async ({ session, options }, args) => {
if (!session || !session.userId) return;
if (config.generateSubCommandCountToFather) {
const msg = await ctx.$.checkAndCountToGenerate(session);
if (msg) return msg;
}
if (options) {
const err = await ctx.$.checkOptions(session, options, meme);
if (err) return err;
}
let resolvedArgs;
try {
resolvedArgs = await ctx.$.resolveArgs(session, args ?? []);
} catch (e) {
return ctx.$.handleResolveArgsError(session, e);
}
const { imageInfos, texts, names } = resolvedArgs;
const {
params: {
min_images: minImages,
max_images: maxImages,
min_texts: minTexts,
max_texts: maxTexts,
default_texts: defaultTexts
}
} = meme;
const autoUseAvatar = !!(config.autoUseSenderAvatarWhenOnlyOne && !imageInfos.length && minImages === 1 || config.autoUseSenderAvatarWhenOneLeft && imageInfos.length && imageInfos.length + 1 === minImages);
if (autoUseAvatar) {
imageInfos.unshift({ userId: session.userId });
}
if (!texts.length && config.autoUseDefaultTexts) {
texts.push(...defaultTexts);
}
if (!checkInRange(imageInfos.length, minImages, maxImages)) {
return config.silentShortcut && session.memesApi.shortcut ? void 0 : session.text("memes-api.errors.image-number-mismatch", [
formatRange(minImages, maxImages),
imageInfos.length
]);
}
if (!checkInRange(texts.length, minTexts, maxTexts)) {
return config.silentShortcut && session.memesApi.shortcut ? void 0 : session.text("memes-api.errors.text-number-mismatch", [
formatRange(minTexts, maxTexts),
texts.length
]);
}
let uploadInfo;
try {
uploadInfo = await ctx.$.resolveImagesAndInfos(session, imageInfos, names);
} catch (e) {
return ctx.$.handleResolveImagesAndInfosError(session, e);
}
let res;
try {
res = await ctx.$.uploadImgAndRenderMeme(meme, texts, uploadInfo, options);
} catch (e) {
return ctx.$.handleRenderError(session, e);
}
return import_koishi2.h.image(await res.arrayBuffer(), res.type);
});
}, "registerGenerateCmd");
ctx.$.reRegisterGenerateCommands = async () => {
for (const cmd of generateSubCommands) cmd.dispose();
generateSubCommands.length = 0;
generateSubCommands.push(
...Object.values(ctx.$.infos).map((v) => registerGenerateCmd(v))
);
};
}
__name(apply5, "apply");
// src/commands/info.ts
var import_koishi3 = require("koishi");
async function apply6(ctx, config) {
const subCmd = ctx.$.cmd.subcommand(".info <query:string>", { checkArgCount: true });
if (config.enableShortcut) {
subCmd.alias("表情详情").alias("表情帮助").alias("表情示例");
}
subCmd.action(async ({ session }, query) => {
if (!session) return;
let info;
if (query in ctx.$.infos) {
info = ctx.$.infos[query];
} else {
let searchRes;
try {
searchRes = await ctx.$.api.searchMemes(query, true);
} catch (e) {
return ctx.$.handleError(session, e);
}
if (!searchRes.length) {
return session?.text("memes-api.errors.no-such-meme", [query]);
}
let exactMatch;
if (searchRes.length === 1) {
exactMatch = ctx.$.infos[searchRes[0]];
} else {
const found = searchRes.find((x) => {
const info2 = ctx.$.infos[x];
return info2.keywords.includes(query);
});
if (found) {
exactMatch = ctx.$.infos[found];
}
}
if (!exactMatch) {
let img;
try {
const keys = await ctx.$.api.getKeys();
const notExistKeys = keys.filter(
(x) => !searchRes.includes(x) || !(x in ctx.$.infos)
);
if (notExistKeys.length >= keys.length) {
return session?.text("memes-api.errors.no-such-meme", [query]);
}
const imgId = await ctx.$.api.renderList({
meme_properties: {},
exclude_memes: notExistKeys,
add_category_icon: config.listAddCategoryIcon,
sort_by: config.listSortByRs,
sort_reverse: config.listSortReverse,
text_template: config.searchListTextTemplate
});
img = await ctx.$.api.getImage(imgId.image_id);
} catch (e) {
return ctx.$.handleError(session, e);
}
return [
...session.i18n("memes-api.info.multiple-tip-head"),
import_koishi3.h.image(await img.arrayBuffer(), img.type),
...session.i18n("memes-api.info.multiple-tip-tail")
];
}
const name2 = searchRes[0];
if (!(name2 in ctx.$.infos)) {
return session?.text("memes-api.errors.no-such-meme", [query]);
}
info = ctx.$.infos[name2];
}
const p = info.params;
const msg = [
session.i18n("memes-api.info.key", [info.key]),
session.i18n("memes-api.info.keywords", [formatKeywords(info.keywords)])
];
if (info.shortcuts.length) {
msg.push(
session.i18n("memes-api.info.shortcuts", [
formatKeywords(info.shortcuts.map((v) => v.humanized ?? v.pattern))
])
);
}
if (p.max_images) {
msg.push(
session.i18n("memes-api.info.image-num", [
formatRange(p.min_images, p.max_images)
])
);
}
if (p.max_texts) {
msg.push(
session.i18n("memes-api.info.text-num", [
formatRange(p.min_texts, p.max_texts)
]),
session.i18n("memes-api.info.default-texts", [formatKeywords(p.default_texts)])
);
}
if (p.options.length) {
const optInfos = p.options.map((v) => {
return session.i18n("memes-api.info.option", [
[v.name, ...v.parser_flags.short_aliases, ...v.parser_flags.long_aliases].map((v2) => v2.length > 1 ? `--${v2}` : `-${v2}`).join(session.text("memes-api.info.option-sep")),
v.type === "boolean" ? "" : ` [${v.name}: ${v.type}]`,
v.description
]);
});
msg.push(
session.i18n("memes-api.info.options", [
listJoin(optInfos, [import_koishi3.h.text("\n")]).flat()
])
);
}
let previewImg;
try {
const preview = await ctx.$.api.renderPreview(info.key);
previewImg = await ctx.$.api.getImage(preview.image_id);
} catch (e) {
return ctx.$.handleError(session, e);
}
msg.push(
session.i18n("memes-api.info.preview", [
import_koishi3.h.image(await previewImg.arrayBuffer(), previewImg.type)
])
);
return listFlatJoin(msg, ["\n"]);
});
}
__name(apply6, "apply");
// src/commands/list.ts
var import_koishi5 = require("koishi");
// src/config.ts
var import_koishi4 = require("koishi");
var import_meme_generator_rs_api2 = require("meme-generator-rs-api");
// src/locales/zh-CN.yml
var zh_CN_default = { commands: { "memes-api": { description: "制作各种沙雕表情包" }, "memes-api.list": { description: "查看表情列表" }, "memes-api.info": { description: "查看表情详情(支持模糊搜索及按标签搜索)" }, "memes-api.generate": { description: "生成表情包(每个表情生成指令会注册为本指令的子指令)", usage: "可使用 `自己`,`@自己`,`@某人`,`@用户ID` 格式的参数使用对应用户的头像作为图片参数输入。\n可在使用指令的同时回复消息,被回复消息中的图片将会作为图片参数输入。\n每张图片会附带一个图片名,使用用户头像作为输入时,会将用户昵称作为图片名,否则使用发送者昵称作为图片名。\n可使用且可使用多次 `#名称` 格式的参数覆盖原位置上的图片名。", examples: 'meme generate 5000兆 我去 洛天依\nmeme generate rua -圆 @自己\nmeme generate steam消息 #一大块小饼干 "Visual Studio Code"' }, "memes-api.random": { description: "生成随机表情" } }, "memes-api": { errors: { "syntax-error": { "unexpected-char": "参数语法错误,遇到意外字符 {char} ( 索引 {index} ),如果是左引号请考虑使用反斜杠转义。", "unterminated-quote": "参数语法错误,遇到未闭合引号 {char} ( 索引 {index} )。" }, "can-not-get-avatar": "无法获取平台 {platform} 中 ID 为 {userId} 的用户的头像信息。", "download-image-failed": "下载图片失败。", "option-type-mismatch": { number: "选项 {0} 类型不符,应为数值。" }, "option-number-too-small": "选项 {0} 数值过小,应大于 {1}。", "option-number-too-big": "选项 {0} 数值过大,应小于 {1}。", "no-such-meme": "未找到表情 {0}。", "image-number-mismatch": "输入图片数量不符,图片数量应为 {0},但当前为 {1}。", "text-number-mismatch": "输入文字数量不符,文字数量应为 {0},但当前为 {1}。", "image-decode": "图片解码失败:{0}。", "image-encode": "图片编码失败:{0}。", "image-asset-missing": "缺少图片资源:{0}。", deserialize: "表情选项解析出错:{0}。", "text-over-length": "输入文字过长。", "meme-feedback": "{0}。", "other-error": "发生其他错误:({0}) {1}。" }, list: { tip: "触发方式:“关键词 + 图片/文字”\n发送 “表情详情 + 关键词” 查看表情参数和预览\n目前支持的表情列表:\n{0}", "tip-no-shortcut": "触发指令:“meme generate <关键词/序号> [...图片/文字]”\n发送指令 “meme info <关键词/序号>” 查看表情参数和预览\n目前支持的表情列表:\n{0}" }, info: { "multiple-tip-head": "搜索到多个可能结果:", "multiple-tip-tail": "如有您想要的结果,请修改参数重新查询。", key: "表情名:{0}", keywords: "关键词:{0}", shortcuts: "快捷指令:{0}", "image-num": "需要图片数目:{0}", "text-num": "需要文字数目:{0}", "default-texts": "默认文字:{0}", option: " * {0}{1} - {2}", "option-sep": " | ", options: "可选参数:\n{0}", preview: "表情预览:\n{0}" }, random: { "no-suitable-meme": "找不到符合当前已提供参数信息的表情。", info: "关键词:{0}" } }, _config: [[[[{ $desc: "生成指令配置", enableShortcut: "是否注册类似原版 `memes` 插件的触发指令。 \n例:`meme generate 5000兆 我去 洛天依` -> `5000兆 我去 洛天依`" }, { shortcutUsePrefix: "表情生成快捷指令是否需要携带指令前缀。", silentShortcut: "是否禁用使用原版触发指令时的 参数错误提示。" }], { moreSilent: "是否禁用使用原版触发指令时的 **所有** 错误提示。" }], { autoUseDefaultTexts: "是否在用户未提供文字时自动使用默认文字。", autoUseSenderAvatarWhenOnlyOne: "是否在仅需求一张图片且用户未提供时自动使用发送者头像。", autoUseSenderAvatarWhenOneLeft: "是否在用户已提供图片但距离最少需求图片数仅差 1 时自动使用发送者头像。" }], { $desc: "表情列表配置", listSortByRs: { $desc: "表情排序方式。", $inner: ["表情名", "关键词", "关键词拼音", "创建时间", "修改时间"] }, listSortReverse: "是否倒序排列。", listNewTimeDelta: "表情添加时间在该时间间隔(单位为天)以内时,在列表中添加新表情标识。", listNewStrategy: { $desc: "新表情标识的显示策略。", $inner: ["创建时间", "修改时间"] }, listTextTemplate: "表情列表显示文字模板。 \n可用变量:`{index}`(序号)、`{key}`(表情名)、`{keywords}`(关键词)、`{shortcuts}`(快捷指令)、`{tags}`(标签)", searchListTextTemplate: "表情信息模糊搜索出现多个结果时显示的文字模板。可用变量同上。", listAddCategoryIcon: "是否添加图标以表示表情类型,即“图片表情包”和“文字表情包”。" }, { $desc: "其他指令配置", randomMemeShowInfo: "使用 `meme random` 指令时是否同时发出表情关键词。", generateSubCommandCountToFather: "执行生成表情子指令时是否同时增加父指令的调用计数(对于 `rate-limit` 插件)。", randomCommandCountToGenerate: "执行 `meme random` 指令时是否同时增加 `meme generate` 指令的调用计数(对于 `rate-limit` 插件)。" }, { $desc: "请求配置", requestConfig: { timeout: "等待连接建立的最长时间。", proxyAgent: "使用的代理服务器地址。", keepAlive: "是否保持连接。", endpoint: "要连接的服务器地址。", headers: "要附加的额外请求头。" }, requestConcurrency: "请求并发数。" }] };
// src/config.ts
var NewStrategy = /* @__PURE__ */ ((NewStrategy2) => {
NewStrategy2["DateCreated"] = "date_created";
NewStrategy2["DateModified"] = "date_modified";
return NewStrategy2;
})(NewStrategy || {});
var shortcutCmdConfig = import_koishi4.Schema.object({
enableShortcut: import_koishi4.Schema.boolean().default(true)
});
var shortcutCmdCfgWithSilent = import_koishi4.Schema.intersect([
shortcutCmdConfig,
import_koishi4.Schema.union([
import_koishi4.Schema.object({
enableShortcut: import_koishi4.Schema.const(true),
shortcutUsePrefix: import_koishi4.Schema.boolean().default(true),
silentShortcut: import_koishi4.Schema.boolean().default(false)
}),
import_koishi4.Schema.object({})
])
]);
var shortcutCmdCfgWithMoreSilent = import_koishi4.Schema.intersect([
shortcutCmdCfgWithSilent,
import_koishi4.Schema.union([
import_koishi4.Schema.object({
enableShortcut: import_koishi4.Schema.const(true),
silentShortcut: import_koishi4.Schema.const(true).required(),
moreSilent: import_koishi4.Schema.boolean().default(false)
}),
import_koishi4.Schema.object({})
])
]);
var GenerateCommandConfig = import_koishi4.Schema.intersect([
shortcutCmdCfgWithMoreSilent,
import_koishi4.Schema.object({
autoUseDefaultTexts: import_koishi4.Schema.boolean().default(true),
autoUseSenderAvatarWhenOnlyOne: import_koishi4.Schema.boolean().default(true),
autoUseSenderAvatarWhenOneLeft: import_koishi4.Schema.boolean().default(true)
})
]);
var ListConfig = import_koishi4.Schema.object({
listSortByRs: import_koishi4.Schema.union(import_meme_generator_rs_api2.memeListSortByVals).default("keywords_pinyin"),
listSortReverse: import_koishi4.Schema.boolean().default(false),
listNewTimeDelta: import_koishi4.Schema.natural().min(1).default(30),
listNewStrategy: import_koishi4.Schema.union(Object.values(NewStrategy)).default(
"date_created" /* DateCreated */
),
listTextTemplate: import_koishi4.Schema.string().default("{keywords}"),
searchListTextTemplate: import_koishi4.Schema.string().default("{key}({keywords})"),
listAddCategoryIcon: import_koishi4.Schema.boolean().default(true)
});
var OtherCommandConfig = import_koishi4.Schema.object({
randomMemeShowInfo: import_koishi4.Schema.boolean().default(true),
generateSubCommandCountToFather: import_koishi4.Schema.boolean().default(false),
randomCommandCountToGenerate: import_koishi4.Schema.boolean().default(false)
});
var RequestConfig = import_koishi4.Schema.object({
requestConfig: import_koishi4.HTTP.createConfig("http://127.0.0.1:2233"),
requestConcurrency: import_koishi4.Schema.natural().min(1).default(8)
});
var Config = import_koishi4.Schema.intersect([
GenerateCommandConfig,
ListConfig,
OtherCommandConfig,
RequestConfig
]).i18n({
"zh-CN": zh_CN_default._config,
zh: zh_CN_default._config
});
// src/commands/list.ts
async function apply7(ctx, config) {
const subCmd = ctx.$.cmd.subcommand(".list");
if (config.enableShortcut) {
subCmd.alias("表情包制作").alias("表情列表").alias("头像表情包").alias("文字表情包");
}
subCmd.action(async ({ session }) => {
if (!session) return;
const nowTimestamp = (/* @__PURE__ */ new Date()).getTime();
const timeDeltaMs = config.listNewTimeDelta * import_koishi5.Time.day;
const properties = {};
for (const [key, info] of Object.entries(ctx.$.infos)) {
const compareTimeStr = config.listNewStrategy === "date_created" /* DateCreated */ ? info.date_created : info.date_modified;
const compareTimestamp = new Date(compareTimeStr).getTime();
if (nowTimestamp - compareTimestamp <= timeDeltaMs) {
;
(properties[key] ??= {}).new = true;
}
}
let imgBlob;
try {
const keys = await ctx.$.api.getKeys();
const notExistKeys = keys.filter((x) => !(x in ctx.$.infos));
const img = await ctx.$.api.renderList({
exclude_memes: notExistKeys,
meme_properties: properties,
sort_by: config.listSortByRs,
sort_reverse: config.listSortReverse,
text_template: config.listTextTemplate,
add_category_icon: config.listAddCategoryIcon
});
imgBlob = await ctx.$.api.getImage(img.image_id);
} catch (e) {
return ctx.$.handleError(session, e);
}
const msgParams = [import_koishi5.h.image(await imgBlob.arrayBuffer(), imgBlob.type)];
return config.enableShortcut ? session.i18n("memes-api.list.tip", msgParams) : session.i18n("memes-api.list.tip-no-shortcut", msgParams);
});
}
__name(apply7, "apply");
// src/commands/random.ts
var import_koishi6 = require("koishi");
async function apply8(ctx, config) {
const subCmd = ctx.$.cmd.subcommand(".random [args:el]");
if (config.enableShortcut) {
subCmd.alias("随机表情");
}
subCmd.action(async ({ session, options }, args) => {
if (!session || !session.userId) return;
if (config.randomCommandCountToGenerate) {
const msg = await ctx.$.checkAndCountToGenerate(session);
if (msg) return msg;
}
let resolvedArgs;
try {
resolvedArgs = await ctx.$.resolveArgs(session, args ?? []);
} catch (e) {
return ctx.$.handleResolveArgsError(session, e);
}
const { imageInfos, texts, names } = resolvedArgs;
const autoUse = !imageInfos.length && !texts.length;
if (autoUse) imageInfos.push({ userId: session.userId });
const checkOptionExists = /* @__PURE__ */ __name((info) => {
if (!options) return true;
const {
params: { options: memeOpts }
} = info;
return !Object.keys(options).some(
(k) => (
// 在没找着时返回 true
!memeOpts.some((x) => x.name === k)
)
);
}, "checkOptionExists");
const castOpt = /* @__PURE__ */ __name((opt) => {
if (!options || !(opt.name in options)) return void 0;
const { type } = opt;
const raw = options[opt.name];
switch (type) {
case "boolean":
return ["true", "1"].includes(`${raw}`.toLowerCase());
case "integer":
return parseInt(raw, 10);
case "float":
return parseFloat(raw);
default:
return `${raw}`;
}
}, "castOpt");
const castOptions = /* @__PURE__ */ __name((info) => {
const newOpts = {};
if (!options) return {};
for (const k in options) {
const opt = info.params.options.find((x) => x.name === k);
if (opt) {
newOpts[k] = castOpt(opt);
}
}
return newOpts;
}, "castOptions");
const suitableMemes = Object.values(ctx.$.infos).filter((info) => {
const {
params: {
min_images: minImages,
max_images: maxImages,
min_texts: minTexts,
max_texts: maxTexts
}
} = info;
return checkInRange(imageInfos.length, minImages, maxImages) && (autoUse || checkInRange(texts.length, minTexts, maxTexts)) && checkOptionExists(info);
});
if (!suitableMemes.length) {
return session.text("memes-api.random.no-suitable-meme");
}
let uploadInfo;
try {
uploadInfo = await ctx.$.resolveImagesAndInfos(session, imageInfos, names);
} catch (e) {
return ctx.$.handleResolveImagesAndInfosError(session, e);
}
while (suitableMemes.length) {
const index = import_koishi6.Random.int(0, suitableMemes.length);
const info = suitableMemes[index];
suitableMemes.splice(index, 1);
let res;
try {
const opts = castOptions(info);
res = await ctx.$.uploadImgAndRenderMeme(info, texts, uploadInfo, opts);
} catch (e) {
ctx.logger.warn(e);
continue;
}
const el = [import_koishi6.h.image(await res.arrayBuffer(), res.type)];
if (config.randomMemeShowInfo) {
el.unshift(
...session.i18n("memes-api.random.info", [formatKeywords(info.keywords)])
);
}
return el;
}
return session.text("memes-api.random.no-suitable-meme");
});
}
__name(apply8, "apply");
// src/commands/shortcut.ts
var import_koishi7 = require("koishi");
async function apply9(ctx, config) {
if (!config.enableShortcut) return;
const shortcuts = [];
ctx.$.refreshShortcuts = async () => {
const tmpKeywords = [];
const tmpRegExps = [];
for (const name2 in ctx.$.infos) {
const info = ctx.$.infos[name2];
info.keywords.forEach((keyword) => {
tmpKeywords.push({ name: name2, keyword });
});
info.shortcuts.forEach((s) => {
tmpRegExps.push({ name: name2, ...s, pattern: transformRegex(s.pattern) });
});
}
const tmpShortcuts = [
...tmpKeywords.sort((a, b) => b.keyword.length - a.keyword.length).map(({ name: name2, keyword }) => {
return { name: name2, pattern: (0, import_koishi7.escapeRegExp)(keyword) };
}),
...tmpRegExps
];
shortcuts.length = 0;
shortcuts.push(...tmpShortcuts);
};
const resolveArgs = /* @__PURE__ */ __name((shortcut, res) => {
const args = [];
if (shortcut.names) {
args.push(...shortcut.names?.map((x) => `#${replaceBracketVar(x, res)}`));
}
if (shortcut.texts) {
args.push(...shortcut.texts?.map((x) => replaceBracketVar(x, res)));
}
const options = shortcut.options ? [
...Object.entries(shortcut.options).map(
([key, value]) => `${key.length > 1 ? "--" : "-"}${key} ${escapeArgs([replaceBracketVar(value, res)])}`
)
].join(" ") : "";
return [options, args.join(" ")].filter(Boolean).join(" ");
}, "resolveArgs");
ctx.middleware(async (session, next) => {
const { content } = session;
if (!content) return next();
const cmdPrefixRegex = (() => {
if (config.shortcutUsePrefix) {
const cmdPfxCfg = session.resolve(ctx.root.config.prefix);
const cmdPfx = cmdPfxCfg instanceof Array ? cmdPfxCfg : [cmdPfxCfg ?? ""];
const hasEmptyPfx = cmdPfx.includes("");
const cmdPfxNotEmpty = cmdPfx.filter(Boolean);
if (cmdPfxNotEmpty.length) {
return `(?:${cmdPfxNotEmpty.map(import_koishi7.escapeRegExp).join("|")})${hasEmptyPfx ? "?" : ""}`;
}
}
return "";
})();
for (const shortcut of shortcuts) {
const { name: name2, pattern } = shortcut;
const res = new RegExp(`^${cmdPrefixRegex}${pattern}`).exec(content);
if (res) {
;
(session.memesApi ??= {}).shortcut = true;
return session.execute(
`meme.generate.${name2} ${resolveArgs(shortcut, res)} ${content.slice(res.index + res[0].length)}`
);
}
}
return next();
});
}
__name(apply9, "apply");
// src/commands/index.ts
async function apply10(ctx, config) {
ctx.$.cmd = ctx.command("memes-api").alias("memes").alias("meme");
await apply5(ctx, config);
await apply9(ctx, config);
await apply8(ctx, config);
await apply7(ctx, config);
await apply6(ctx, config);
}
__name(apply10, "apply");
// src/index.tsx
var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
var name = "memes-api";
var usage = `
<style>
.memes-api-usage {
background-color: var(--k-side-bg);
padding: 1px 24px;
border-radius: 4px;
border-left: 4px solid var(--k-color-primary);
}
.memes-api-usage a {
color: var(--k-color-primary-tint);
}
.memes-api-usage a:hover {
color: var(--primary);
}
.k-comment.success > ul > li:has(a[href^='/commands/memes-api/generate/']) {
display: none;
}
.k-comment.success > ul > li:has(a[href^='/commands/memes-api/generate'])::after {
content: '(子命令已隐藏)';
font-size: 12px;
color: var(--k-text-normal);
}
</style>
<div class="memes-api-usage">
好消息,memes-api v2 已经初步支持 [meme-generator-rs](https://github.com/MemeCrafters/meme-generator-rs) 🎉
v2 版本将仅支持 meme-generator-rs,如要使用旧版 meme-generator,请回退到 v1 版本。
查看 [部署文档](https://github.com/MemeCrafters/meme-generator-rs/wiki/%E6%9C%AC%E5%9C%B0%E5%AE%89%E8%A3%85)
或者看看我做的 [视频教程](https://www.bilibili.com/video/BV1hBVsz6EvV/ '关注 LgCookie 喵,关注 LgCookie 谢谢喵') 了解如何部署后端。
目前插件还是处于 可能可以正常使用 的状态,
如果有 Bug 请积极 [反馈](https://github.com/lgc-KoiDev/koishi-plugin-memes-api/issues),
[这里](https://github.com/lgc-KoiDev/koishi-plugin-memes-api#-%E9%85%8D%E7%BD%AE--%E4%BD%BF%E7%94%A8) 也有一些暂缓实现的功能,如果真的很想要可以催催我!
感谢各位的支持与使用~~~!🤗❤️
</div>
`.trim();
var inject = {
required: ["http"],
optional: ["notifier"]
};
async function apply11(ctx, config) {
ctx.i18n.define("zh-CN", zh_CN_default);
ctx.i18n.define("zh", zh_CN_default);
ctx = ctx.isolate("$");
ctx.set("$", {});
ctx.$.api = new import_meme_generator_rs_api3.MemeAPI(ctx.http.extend(config.requestConfig));
await apply4(ctx, config);
ctx.inject(["notifier"], () => {
ctx.$.notifier = ctx.notifier.create();
});
ctx.$.notifier?.update({ type: "primary", content: "插件初始化中……" });
try {
await ctx.$.updateInfos();
} catch (e) {
ctx.logger.warn("Failed to fetch data from backend, plugin will not work");
ctx.logger.warn(e);
const is404 = e instanceof import_meme_generator_rs_api3.MemeError && e.httpStatus === 404;
ctx.$.notifier?.update({
type: "danger",
content: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
"从后端获取相关信息失败,插件将不会工作!",
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
is404 ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: "你或许还没有在使用 meme-generator-rs?请参考插件介绍迁移到 meme-generator-rs 哦。更多信息请查看日志。" }) : /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, { children: "请检查你的请求设置以及 meme-generator 的部署状态,更多信息请查看日志。" })
] })
});
return;
}
try {
await apply10(ctx, config);
await ctx.$.reRegisterGenerateCommands();
await ctx.$.refreshShortcuts?.();
} catch (e) {
try {
ctx.$.cmd?.dispose();
} catch (_) {
}
ctx.logger.warn("Failed to initialize, plugin will not work");
ctx.logger.warn(e);
ctx.$.notifier?.update({
type: "danger",
content: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
"注册插件指令时出错,插件将不会工作!",
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {}),
"更多信息请查看日志。"
] })
});
return;
}
ctx.$.$public = {
get api() {
return ctx.$.api;
},
get infos() {
return ctx.$.infos;
},
get apiVersion() {
return ctx.$.apiVersion;
}
};
ctx.set("memesApi", ctx.$.$public);
const memeCount = Object.keys(ctx.$.infos).length;
const minVersion = [0, 2, 2];
const versionMeets = isVersionMeets(ctx.$.apiVersion, minVersion);
ctx.$.notifier?.update({
type: versionMeets ? "success" : "warning",
content: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("p", { children: [
versionMeets ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(import_jsx_runtime.Fragment, {}) : /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [
"警告:后端版本需大于等于 ",
minVersion.join("."),
",否则插件可能无法正常使用!",
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("br", {})
] }),
"插件初始化完毕,后端版本 ",
ctx.$.apiVersion,
",共载入 ",
memeCount,
" 个表情。"
] })
});
ctx.logger.info(
`Plugin initialized successfully, backend version ${ctx.$.apiVersion}, loaded ${memeCount} memes`
);
}
__name(apply11, "apply");
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
Config,
apply,
inject,
name,
usage
});