UNPKG

koishi-plugin-waifu-bangdream

Version:
620 lines (613 loc) 26.5 kB
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 __commonJS = (cb, mod) => function __require() { return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; 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/locales/zh-CN.yml var require_zh_CN = __commonJS({ "src/locales/zh-CN.yml"(exports2, module2) { module2.exports = { avoidNtr: "是否避免用户抽中他人的老婆", onlyActiveUser: "是否只让用户抽中活跃的群友", activeDays: "活跃天数最小值 (onlyActiveUser 判断活跃的标准)", maxTimes: "每日换老婆次数上限 (对换老婆指令生效,0 为不限制)", excludeUsers: { $description: "排除的用户", $value: { uid: "平台名与用户 ID, 格式: platform:userId", note: "备注 (可不填)" } }, commands: { waifu: { description: "娶群友", messages: { marriages: "{quote}你今日的群友老婆是:{name} {avatar}", "members-too-few": "潜在的老婆太少啦,快去拐骗(?)点回来吧!或者骗出来聊天!", "times-too-many": "{quote}渣男,你今天没老婆了!" } }, "force-marry": { description: "强娶群友", messages: { "force-marry": "{quote}好啦好啦,群友「{name}」已经被你强娶回家了 {avatar}", "no-target": "{quote}强娶谁呀~", "already-marriage": "{quote}你已经娶了老婆,今天别再娶啦!", "members-too-few": "能娶的群友没有这位,或许可以先把对方骗出来聊天?!", "target-self": "{quote}娶自己干嘛!" } }, propose: { description: "向群友求婚", messages: { success: "{quote}群友「{name}」答应了你,愿意当你今日的对象!", failure: "{quote}对方拒绝了", "no-target": "{quote}向谁求婚呀~", "already-marriage": "{quote}你已经有对象了,今天别再求婚啦!", "already-marriage2": "{quote} 你已经有对象了,怎么能接受别人求婚呢!", "members-too-few": "能求婚的群友没有这位,或许可以先把对方骗出来聊天?!", "target-self": "{quote}向自己求婚干嘛!", request: "{targetAt} {targetAvatar}<br/>群友「{name}」向你求婚,那么...你愿意嫁给ta吗?在{time}秒内发送【{agree}】或者【{reject}】,回应对方哦!" } }, divorce: { description: "和群友离婚", messages: { "not-married": "{quote}你还是只单身狗,不能离婚!", divorcement: "{quote}你已经离婚,江湖有缘再相见~" } }, "change-waifu": { description: "换老婆", messages: { "not-married": "{quote}你没有老婆,不能换!", "change-success": "{quote}换好了!你今日的老婆是:{name}{avatar}", "last-times": "{quote}再换你就没老婆了!你今日的老婆是:{name}{avatar}", "times-too-many": "{quote}渣男,你今天没老婆了!", "members-too-few": "能换的群友没有了!" } }, "waifu-query": { description: "查询老婆", messages: { "not-married": "{quote}你没有老婆", marriages: "{quote}你今日的群友老婆是:{name} {avatar}" } } } }; } }); // src/index.ts var src_exports = {}; __export(src_exports, { Config: () => Config, apply: () => apply, assetUrl: () => assetUrl, inject: () => inject, name: () => name, usage: () => usage }); module.exports = __toCommonJS(src_exports); var import_koishi = require("koishi"); // src/utils.ts function getMaxAge() { const currentTime = Date.now(); const tomorrowMidnight = /* @__PURE__ */ new Date(); tomorrowMidnight.setHours(24, 0, 0, 0); return tomorrowMidnight.getTime() - currentTime; } __name(getMaxAge, "getMaxAge"); // src/index.ts var fs = __toESM(require("node:fs")); var path = __toESM(require("node:path")); // package.json var package_default = { name: "koishi-plugin-waifu-bangdream", description: "Marry with your groupmate, with BanG Dream Border!", version: "1.1.7", main: "lib/index.js", typings: "lib/index.d.ts", files: [ "lib", "dist", "assets" ], license: "MIT", keywords: [ "chatbot", "koishi", "plugin", "waifu", "marry", "wife", "bangdream" ], repository: { type: "git", url: "https://github.com/StarFreedomX/koishi-plugin-waifu-BanGDream.git" }, peerDependencies: { koishi: "^4.17.7" }, devDependencies: { "@koishijs/cache": "^2.1.0", koishi: "^4.17.7" }, koishi: { description: { en: "Marry with your groupmate", zh: "改自koishi-plugin-waifu,添加了BanG Dream边框" }, browser: true }, packageManager: "yarn@4.5.3", dependencies: { jimp: "^1.6.0" } }; // src/index.ts var import_jimp = require("jimp"); var name = "waifu-bangdream"; var inject = { required: ["cache"], optional: ["database"] }; var usage = ` <h2>娶群友</h2> 本插件fork自<a href="/market?keyword=koishi-plugin-waifu">koishi-plugin-waifu</a><br/> 在原仓库的基础上添加了BanG Dream!的随机边框!<br/> `; var assetUrl; var Config = import_koishi.Schema.intersect([ import_koishi.Schema.object({ avoidNtr: import_koishi.Schema.boolean().default(false), onlyActiveUser: import_koishi.Schema.boolean().default(false), activeDays: import_koishi.Schema.natural().default(7), excludeUsers: import_koishi.Schema.array(import_koishi.Schema.object({ uid: import_koishi.Schema.string().required(), note: import_koishi.Schema.string() })).role("table").default([{ uid: "red:2854196310", note: "Q群管家" }]), maxTimes: import_koishi.Schema.natural().default(2) }).i18n({ "zh-CN": require_zh_CN() }), import_koishi.Schema.object({ forceMarry: import_koishi.Schema.boolean().description("是否启用强娶指令").default(false), propose: import_koishi.Schema.boolean().description("是否启用求婚指令").default(false), divorce: import_koishi.Schema.boolean().description("是否启用离婚指令").default(false), changeWaifu: import_koishi.Schema.boolean().description("是否启用换老婆指令").experimental().default(false), waifuQuery: import_koishi.Schema.boolean().description("是否启用查老婆指令").experimental().default(false) }).description("附加指令") ]); function apply(ctx, cfg) { ctx.i18n.define("zh-CN", require_zh_CN()); initAssets(); ctx.guild().on("message-created", async (session) => { if ((0, import_koishi.isNullable)(session.userId) || session.userId === "0") return; const member = { user: session.event.user, ...session.event.member }; await ctx.cache.set(`waifu_members_${session.gid}`, session.userId, member, 4 * import_koishi.Time.day); await ctx.cache.set(`waifu_members_active_${session.gid}`, session.userId, "", cfg.activeDays * import_koishi.Time.day); }); ctx.on("guild-member-removed", (session) => { if ((0, import_koishi.isNullable)(session.userId) || session.userId === "0") return; ctx.cache.delete(`waifu_members_${session.gid}`, session.userId); ctx.cache.delete(`waifu_members_active_${session.gid}`, session.userId); }); async function getMemberList(session, gid) { let result = []; try { const { data, next } = await session.bot.getGuildMemberList(session.guildId); result = data; if (next) { const { data: data2 } = await session.bot.getGuildMemberList(session.guildId, next); result.push(...data2); } } catch { } if (!result.length) { for await (const value of ctx.cache.values(`waifu_members_${gid}`)) { result.push(value); } } return result; } __name(getMemberList, "getMemberList"); async function getMemberInfo(member, gid, userId, id, platform) { let name2 = member?.nick || member?.user?.nick || member?.user?.name; const avatar = member?.avatar || member?.user?.avatar; if (!name2 && ctx.database) { const user = await ctx.database.getUser(platform, id); if (user?.name) { name2 = user.name; } } name2 ||= id; return [name2, await drawBanGDream(gid, id, avatar)]; } __name(getMemberInfo, "getMemberInfo"); function initAssets() { assetUrl = path.join(ctx.baseDir, "data/waifu/assets"); if (!fs.existsSync(assetUrl)) fs.mkdirSync(assetUrl, { recursive: true }); const defaultAssetsDir = path.join(__dirname, "../assets"); const localVersionFile = path.join(assetUrl, "plugin_version.json"); const pluginVersion = package_default.version; let localVersion = "0"; if (fs.existsSync(localVersionFile)) { try { localVersion = JSON.parse(fs.readFileSync(localVersionFile, "utf-8")).version || "0"; } catch { } } if (pluginVersion > localVersion) { try { if (fs.existsSync(defaultAssetsDir)) { fs.cpSync(defaultAssetsDir, assetUrl, { recursive: true, force: true }); } fs.writeFileSync(localVersionFile, JSON.stringify({ version: pluginVersion })); } catch (err) { console.error("initAssets copy failed:", err); } } } __name(initAssets, "initAssets"); ctx.command("waifu").alias("marry", "娶群友", "今日老婆").action(async ({ session }) => { if (!session.guildId) { return session.text(".members-too-few"); } const { gid } = session; const target = await ctx.cache.get(`waifu_marriages_${gid}`, session.userId); if (target) { let selected2; try { selected2 = await session.bot.getGuildMember(session.guildId, target); } catch { } try { const member = await ctx.cache.get(`waifu_members_${gid}`, target); if (!selected2) { selected2 = member; } else { selected2.nick ??= member.nick; selected2.user ??= member.user; selected2.user.name ??= member.user.name; } } catch { } try { selected2 ??= { user: await session.bot.getUser(target) }; } catch { } const [name3, avatar2] = await getMemberInfo(selected2, gid, session.userId, target, session.platform); return session.text(".marriages", { quote: import_koishi.h.quote(session.messageId), name: name3, avatar: avatar2 && import_koishi.h.image(avatar2) }); } const waifuTimes = await ctx.cache.get(`waifu_times_${gid}`, session.userId); if (waifuTimes && waifuTimes > cfg.maxTimes && cfg.maxTimes != 0) return session.text(".times-too-many"); const excludes = cfg.excludeUsers.map(({ uid }) => uid); excludes.push(session.uid, session.sid); const memberList = await getMemberList(session, gid); let list = memberList.filter((v) => { return v.user && !excludes.includes(`${session.platform}:${v.user.id}`) && !v.user.isBot; }); if (cfg.onlyActiveUser) { let activeList = []; for await (const value of ctx.cache.keys(`waifu_members_active_${gid}`)) { activeList.push(value); } list = list.filter((v) => activeList.find((active) => active === v.user.id)); } if (list.length === 0) return session.text(".members-too-few"); let selected = import_koishi.Random.pick(list); let selectedId = selected.user.id; const selectedTarget = await ctx.cache.get(`waifu_marriages_${gid}`, selectedId); if (selectedTarget) { selected = import_koishi.Random.pick(list); selectedId = selected.user.id; } if (cfg.avoidNtr) { let i = 0; while (true) { const selectedTarget2 = await ctx.cache.get(`waifu_marriages_${gid}`, selectedId); if (selectedTarget2) { selected = import_koishi.Random.pick(list); selectedId = selected.user.id; } else { break; } i++; if (i > list.length) return session.text(".members-too-few"); } } const maxAge = getMaxAge(); await ctx.cache.set(`waifu_marriages_${gid}`, session.userId, selectedId, maxAge); await ctx.cache.set(`waifu_marriages_${gid}`, selectedId, session.userId, maxAge); await ctx.cache.set(`waifu_times_${gid}`, session.userId, waifuTimes ? waifuTimes + 1 : 1, maxAge); const [name2, avatar] = await getMemberInfo(selected, gid, session.userId, selectedId, session.platform); return session.text(".marriages", { quote: import_koishi.h.quote(session.messageId), name: name2, avatar: avatar && import_koishi.h.image(avatar) }); }); if (cfg.forceMarry) { ctx.command("force-marry <target:user>").alias("强娶").action(async ({ session }, target) => { if (!session.guildId) { return session.text(".members-too-few"); } if (!target) { return session.text(".no-target", { quote: import_koishi.h.quote(session.messageId) }); } const targetId = target.slice(session.platform.length + 1); if (targetId === session.userId) return session.text(".target-self"); const { gid } = session; const marriage = await ctx.cache.get(`waifu_marriages_${gid}`, session.userId); if (marriage) { return session.text(".already-marriage", { quote: import_koishi.h.quote(session.messageId) }); } const memberList = await getMemberList(session, gid); const selected = memberList.find((u) => u.user.id == targetId); if (!selected) return session.text(".members-too-few"); const selectedId = selected.user.id; const maxAge = getMaxAge(); const times = await ctx.cache.get(`waifu_times_${gid}`, session.userId) || 0; await Promise.all([ ctx.cache.set(`waifu_marriages_${gid}`, session.userId, selectedId, maxAge), ctx.cache.set(`waifu_marriages_${gid}`, selectedId, session.userId, maxAge), ctx.cache.set(`waifu_times_${gid}`, session.userId, times + 1, maxAge) ]); const [name2, avatar] = await getMemberInfo(selected, gid, session.userId, selectedId, session.platform); return session.text(".force-marry", { quote: import_koishi.h.quote(session.messageId), name: name2, avatar: avatar && import_koishi.h.image(avatar) }); }); } if (cfg.propose) { ctx.command("propose <target:user>").alias("求婚").action(async ({ session }, target) => { if (!session.guildId) { return session.text(".members-too-few"); } if (!target) { return session.text(".no-target", { quote: import_koishi.h.quote(session.messageId) }); } const targetId = target.slice(session.platform.length + 1); if (targetId === session.userId) return session.text(".target-self"); const { gid } = session; const marriage = await ctx.cache.get(`waifu_marriages_${gid}`, session.userId); if (marriage) { return session.text(".already-marriage", { quote: import_koishi.h.quote(session.messageId) }); } const memberList = await getMemberList(session, gid); const selected = memberList.find((u) => u.user.id == targetId); if (!selected) return session.text(".members-too-few"); const selectedId = selected.user.id; const [name2, avatar] = await getMemberInfo(selected, gid, session.userId, selectedId, session.platform); await session.send( session.text(".request", { targetAt: import_koishi.h.at(selected.user.id), targetAvatar: import_koishi.h.image(avatar), name: session.username, agree: "我愿意", reject: "我拒绝", time: "90" }) ); let timeoutId; const sourceMessageId = session.messageId; const sourceUserId = session.userId; const dispose = ctx.platform(session.platform).user(selected.user.id).guild(session.guildId).on("message-created", async ({ elements, text, send }) => { const reply = import_koishi.h.select(elements, "text").join("").trim(); if (reply === "我愿意") { dispose(); clearTimeout(timeoutId); const isMarriaged = await ctx.cache.get(`waifu_marriages_${gid}`, selectedId); if (isMarriaged) { await send( text("commands.propose.messages.already-marriage2", { quote: import_koishi.h.at(selectedId) }) ); return; } const maxAge = getMaxAge(); await ctx.cache.set(`waifu_marriages_${gid}`, sourceUserId, selectedId, maxAge); await ctx.cache.set(`waifu_marriages_${gid}`, selectedId, sourceUserId, maxAge); await send( text("commands.propose.messages.success", { quote: import_koishi.h.quote(sourceMessageId), name: name2 }) ); } else if (reply === "我拒绝") { dispose(); clearTimeout(timeoutId); await send( text("commands.propose.messages.failure", { quote: import_koishi.h.quote(sourceMessageId) }) ); } }); timeoutId = setTimeout(() => { dispose(); }, 90 * import_koishi.Time.second); }); } if (cfg.divorce) { ctx.command("divorce").alias("离婚").action(async ({ session }) => { const { gid } = session; const marriage = await ctx.cache.get(`waifu_marriages_${gid}`, session.userId); if (!marriage) { return session.text(".not-married", { quote: import_koishi.h.quote(session.messageId) }); } else { await Promise.all([ ctx.cache.delete(`waifu_marriages_${gid}`, marriage), ctx.cache.delete(`waifu_marriages_${gid}`, session.userId) ]); return session.text(".divorcement", { quote: import_koishi.h.quote(session.messageId) }); } }); } if (cfg.changeWaifu) { ctx.command("change-waifu").alias("换老婆").action(async ({ session }) => { if (!session.guildId) { return session.text(".members-too-few"); } const { gid } = session; const marriage = await ctx.cache.get(`waifu_marriages_${gid}`, session.userId); const times = await ctx.cache.get(`waifu_times_${gid}`, session.userId) || 0; if (times > cfg.maxTimes && cfg.maxTimes != 0) { if (marriage) { await Promise.all([ ctx.cache.delete(`waifu_marriages_${gid}`, marriage), ctx.cache.delete(`waifu_marriages_${gid}`, session.userId) ]); } return session.text(".times-too-many", { quote: import_koishi.h.quote(session.messageId) }); } else if (!marriage) { return session.text(".not-married", { quote: import_koishi.h.quote(session.messageId) }); } const excludes = cfg.excludeUsers.map(({ uid }) => uid); excludes.push(session.uid, session.sid); const memberList = await getMemberList(session, gid); let list = memberList.filter((v) => { return v.user && !excludes.includes(`${session.platform}:${v.user.id}`) && !v.user.isBot; }); if (cfg.onlyActiveUser) { let activeList = []; for await (const value of ctx.cache.keys(`waifu_members_active_${gid}`)) { activeList.push(value); } list = list.filter((v) => activeList.find((active) => active === v.user.id)); } if (list.length === 0) return session.text(".members-too-few"); let selected = import_koishi.Random.pick(list); let selectedId = selected.user.id; const selectedTarget = await ctx.cache.get(`waifu_marriages_${gid}`, selectedId); if (selectedTarget) { selected = import_koishi.Random.pick(list); selectedId = selected.user.id; } if (cfg.avoidNtr) { let i = 0; while (true) { const selectedTarget2 = await ctx.cache.get(`waifu_marriages_${gid}`, selectedId); if (selectedTarget2) { selected = import_koishi.Random.pick(list); selectedId = selected.user.id; } else { break; } i++; if (i > list.length) return session.text(".members-too-few"); } } await Promise.all([ ctx.cache.delete(`waifu_marriages_${gid}`, marriage), ctx.cache.delete(`waifu_marriages_${gid}`, session.userId) ]); const maxAge = getMaxAge(); await Promise.all([ ctx.cache.set(`waifu_marriages_${gid}`, session.userId, selectedId, maxAge), ctx.cache.set(`waifu_marriages_${gid}`, selectedId, session.userId, maxAge), //请求次数+1 ctx.cache.set(`waifu_times_${gid}`, session.userId, times + 1, maxAge) ]); console.log(times); const [name2, avatar] = await getMemberInfo(selected, gid, session.userId, selectedId, session.platform); if (times == cfg.maxTimes && cfg.maxTimes != 0) { return session.text(".last-times", { quote: import_koishi.h.quote(session.messageId), name: name2, avatar: avatar && import_koishi.h.image(avatar) }); } else if (times < cfg.maxTimes || cfg.maxTimes == 0) { return session.text(".change-success", { quote: import_koishi.h.quote(session.messageId), name: name2, avatar: avatar && import_koishi.h.image(avatar) }); } else { return session.text("Error"); } }); } if (cfg.waifuQuery) { ctx.command("waifu-query").alias("查老婆").action(async ({ session }) => { if (!session.guildId) { return session.text(".not-married"); } const { gid } = session; const target = await ctx.cache.get(`waifu_marriages_${gid}`, session.userId); if (target) { let selected; try { selected = await session.bot.getGuildMember(session.guildId, target); } catch { } try { const member = await ctx.cache.get(`waifu_members_${gid}`, target); if (!selected) { selected = member; } else { selected.nick ??= member.nick; selected.user ??= member.user; selected.user.name ??= member.user.name; } } catch { } try { selected ??= { user: await session.bot.getUser(target) }; } catch { } const [name2, avatar] = await getMemberInfo(selected, gid, session.userId, target, session.platform); return session.text(".marriages", { quote: import_koishi.h.quote(session.messageId), name: name2, avatar: avatar && import_koishi.h.image(avatar) }); } else { return session.text(".not-married"); } }); } async function drawBanGDream(gid, userId, avatar, options) { if (!avatar) { return ""; } if (!options) { const cacheImageOption = await ctx.cache.get(`waifu_image_${gid}`, userId); if (!cacheImageOption) { const colors = ["cool", "pure", "happy", "powerful"]; const bands = ["ppp", "ag", "pp", "r", "hhw", "ras", "mnk", "go"]; const starTypes = ["normal_star", "color_star"]; options = { color: import_koishi.Random.pick(colors), band: import_koishi.Random.pick(bands), starNum: import_koishi.Random.pick([1, 2, 3, 4, 5]), starType: "", border: "" }; options.starType = options.starNum < 3 ? starTypes[0] : import_koishi.Random.pick(starTypes); options.border = `card-${options.starNum}${options.starNum == 1 ? `-${options.color}` : ""}`; await ctx.cache.set(`waifu_image_${gid}`, userId, options, getMaxAge()); } else { options = cacheImageOption; } } const [image, colorImage, bandImage, starImage, borderImage] = await Promise.all([ import_jimp.Jimp.read(avatar), import_jimp.Jimp.read(`${assetUrl}/${options.color}.png`), import_jimp.Jimp.read(`${assetUrl}/${options.band}.png`), import_jimp.Jimp.read(`${assetUrl}/${options.starType}.png`), import_jimp.Jimp.read(`${assetUrl}/${options.border}.png`) ]); const zoom = 2; image.cover({ w: 500 * zoom, h: 500 * zoom }); borderImage.cover({ w: 500 * zoom, h: 500 * zoom }); image.composite(borderImage); colorImage.cover({ w: 130 * zoom, h: 130 * zoom }); image.composite(colorImage, image.width - colorImage.width - 3 * zoom, 5.5); bandImage.width > bandImage.height ? bandImage.resize({ w: 120 * zoom }) : bandImage.resize({ h: 120 * zoom }); image.composite(bandImage, 15 * zoom, 15 * zoom); starImage.resize({ w: 90 * zoom }); const step = 60 * zoom; let hei = 410 * zoom; let times = options.starNum; while (times > 0) { image.composite(starImage, 10 * zoom, hei); hei -= step; times--; } return `data:image/png;base64,${(await image.getBuffer("image/jpeg")).toString("base64")}`; } __name(drawBanGDream, "drawBanGDream"); } __name(apply, "apply"); // Annotate the CommonJS export names for ESM import in node: 0 && (module.exports = { Config, apply, assetUrl, inject, name, usage });