koishi-plugin-mizuki-bot
Version:
1,373 lines (1,354 loc) • 85.5 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 name3 in all)
__defProp(target, name3, { get: all[name3], 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.ts
var src_exports = {};
__export(src_exports, {
Config: () => Config,
apply: () => apply2,
inject: () => inject,
name: () => name2
});
module.exports = __toCommonJS(src_exports);
var import_koishi5 = require("koishi");
// src/database.ts
var database_exports = {};
__export(database_exports, {
apply: () => apply,
name: () => name
});
var name = "Database";
function apply(ctx) {
ctx.model.extend("mzk_user", {
id: "unsigned",
platform: "string",
user_id: "string",
nickname: "string",
skland_cred: "string",
skland_uid: "string",
skland_token: "string",
skland_last_refresh: "timestamp",
skland_last_attendent: "timestamp"
}, {
primary: "id",
autoInc: true
});
ctx.model.extend("mzk_jellyfish_box", {
user_id: "unsigned",
last_catch_time: "timestamp",
last_refresh_time: "timestamp",
jellyfish: "json",
decoration: "json",
salinity: "double",
temperature: "double",
draw_style: "string"
}, {
primary: "user_id",
autoInc: true,
foreign: {
user_id: ["mzk_user", "entity_id"]
}
});
ctx.model.extend("mzk_jellyfish_meta", {
id: "string",
name: "string",
group: "string",
description: "string",
reproductive_rate: "double",
living_location: "json",
protected: "boolean",
draw: "json"
});
ctx.model.extend("mzk_jellyfish_event_meta", {
id: "string",
name: "string",
description: "string",
type: "string",
probability: "double",
relation: "json"
});
}
__name(apply, "apply");
// src/commands/jellyfish_box.tsx
var import_koishi2 = require("koishi");
var import_lodash5 = __toESM(require("lodash"));
// src/utils.ts
var import_lodash = __toESM(require("lodash"));
function NormalizeProbabilities(probabilities) {
const total = probabilities.reduce((sum, prob) => sum + prob, 0);
return probabilities.map((prob) => prob / total);
}
__name(NormalizeProbabilities, "NormalizeProbabilities");
function RandomChooseWithWeights(weights, choices) {
if (choices.length !== weights.length) {
throw new Error("weights and choices arrays must have the same length.");
}
const normalizedProbabilities = NormalizeProbabilities(weights);
const cumulativeProbabilities = normalizedProbabilities.reduce((acc, prob, index) => {
acc.push((acc[index - 1] || 0) + prob);
return acc;
}, []);
const random3 = Math.random();
return choices[cumulativeProbabilities.findIndex((p) => p > random3)];
}
__name(RandomChooseWithWeights, "RandomChooseWithWeights");
function RandomChoose(choices) {
return choices[import_lodash.default.random(choices.length - 1)];
}
__name(RandomChoose, "RandomChoose");
// src/draw/default.ts
var import_koishi = require("koishi");
var import_path = __toESM(require("path"));
// src/draw/utils.tsx
var import_lodash2 = __toESM(require("lodash"));
var import_jsx_runtime = require("@satorijs/element/jsx-runtime");
var ImageBuffer = /* @__PURE__ */ __name(async ({ buffer }) => {
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("img", { src: "data:image/png;base64," + buffer.toString("base64") });
}, "ImageBuffer");
var ProcessJellyfishImage = /* @__PURE__ */ __name(async (ctx, image_path) => {
const { Canvas, loadImage } = ctx.skia;
const image = await loadImage(image_path);
const canvas = new Canvas(image.width, image.height);
const skia_ctx = canvas.getContext("2d");
skia_ctx.imageSmoothingQuality = "high";
skia_ctx.drawImage(image, 0, 0);
const image_data = skia_ctx.getImageData(0, 0, canvas.width, canvas.height);
const { width, height, data } = image_data;
let top = height, bottom = 0, left = width, right = 0;
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const alpha = data[(y * width + x) * 4 + 3];
if (alpha > 0) {
if (x < left) left = x;
if (x > right) right = x;
if (y < top) top = y;
if (y > bottom) bottom = y;
}
}
}
let crop_width = right - left + 1;
let crop_height = bottom - top + 1;
if (crop_width % 2 !== 0) crop_width += 1;
if (crop_height % 2 !== 0) crop_height += 1;
const max = import_lodash2.default.max([crop_width, crop_height]) || Math.max(crop_width, crop_height);
const dx = (max - crop_width) / 2;
const dy = (max - crop_height) / 2;
const crop_canvas = new Canvas(max, max);
const crop_ctx = crop_canvas.getContext("2d");
crop_ctx.imageSmoothingQuality = "high";
crop_ctx.drawImage(image, left, top, crop_width, crop_height, dx, dy, crop_width, crop_height);
return crop_canvas;
}, "ProcessJellyfishImage");
// src/draw/default.ts
var import_lodash3 = __toESM(require("lodash"));
var logger = new import_koishi.Logger("mizuki-bot-draw-normal");
var drawRoundRect = /* @__PURE__ */ __name((ctx, x, y, width, height, radius) => {
ctx.save();
ctx.beginPath();
ctx.roundRect(x, y, width, height, radius);
ctx.closePath();
ctx.clip();
ctx.fillRect(x, y, width, height);
ctx.restore();
}, "drawRoundRect");
var drawPacman = /* @__PURE__ */ __name(async (koishiCtx, dotCount) => {
const { Canvas } = koishiCtx.skia;
const canvas = new Canvas(240, 24);
const ctx = canvas.getContext("2d");
ctx.fillStyle = "#e0e97d";
const centerX = 12;
const centerY = 12;
const radius = 12;
const eyeX = centerX + 2;
const eyeY = centerY - 7;
const eyeRadius = 1.5;
ctx.beginPath();
ctx.moveTo(centerX, centerY);
ctx.arc(centerX, centerY, radius, 0.2 * Math.PI, 1.8 * Math.PI, false);
ctx.closePath();
ctx.fill();
ctx.globalCompositeOperation = "destination-out";
ctx.beginPath();
ctx.arc(eyeX, eyeY, eyeRadius, 0, 2 * Math.PI, false);
ctx.fill();
ctx.globalCompositeOperation = "source-over";
const dotRadius = 6;
const dotSpacing = 15;
ctx.fillStyle = "#8aabfd";
for (let i = 1; i <= dotCount; i++) {
const dotX = centerX + radius + i * dotSpacing;
const dotY = centerY;
ctx.beginPath();
ctx.arc(dotX, dotY, dotRadius, 0, 2 * Math.PI, false);
ctx.fill();
}
return canvas;
}, "drawPacman");
var DrawDefaultThemeBox = /* @__PURE__ */ __name(async (koishiCtx, config, session, jellyfishBox, newJelly, events, isCatch = false) => {
const { Canvas, loadImage, FontLibrary } = koishiCtx.skia;
const root = import_path.default.join(koishiCtx.baseDir, "data/mizuki-bot");
const imageRoot = import_path.default.join(root, "image");
const fontRoot = import_path.default.join(root, "font");
const jellyRoot = import_path.default.join(imageRoot, "jellyfish/default");
const themeRoot = import_path.default.join(imageRoot, "/theme/default");
const help = [
{
id: "help1",
name: "水母箱 -h",
description: "查看水母箱指令介绍"
},
...!isCatch ? [{
id: "help2",
name: "水母箱 抓水母",
description: "抓几只水母进水母箱"
}] : []
];
FontLibrary.use("unifont", import_path.default.join(fontRoot, "unifont-16.0.02.otf"));
FontLibrary.use("fusion-pixel-12px", import_path.default.join(fontRoot, "fusion-pixel-12px-proportional-zh_hans.otf"));
FontLibrary.use("noto-sans-sc-regular", import_path.default.join(fontRoot, "NotoSansSC-Regular.ttf"));
FontLibrary.use("noto-sans-sc-medium", import_path.default.join(fontRoot, "NotoSansSC-Medium.ttf"));
FontLibrary.use("noto-sans-sc-bold", import_path.default.join(fontRoot, "NotoSansSC-Bold.ttf"));
const newJellyCanvas = await DrawNewJellyfishCardCanvas(koishiCtx, config, newJelly, jellyRoot);
const eventsCanvas = await DrawGeneralCardCanvas(koishiCtx, config, "事件列表", events);
const helpCanvas = await DrawGeneralCardCanvas(koishiCtx, config, "指令帮助", help);
const height = 608 + (newJellyCanvas ? newJellyCanvas.height + 16 : 0) + (eventsCanvas ? eventsCanvas.height + 16 : 0) + (helpCanvas ? helpCanvas.height + 16 : 0) + 16;
const canvas = new Canvas(768, height);
const ctx = canvas.getContext("2d");
ctx.imageSmoothingQuality = "high";
ctx.fillStyle = config.theme.backgroundColor;
ctx.fillRect(0, 0, 768, height);
const backgroundJelly = await loadImage(import_path.default.join(themeRoot, "background_jelly.png"));
for (let y = 0; y < height; y += backgroundJelly.height) {
for (let x = 0; x < 768; x += backgroundJelly.width) {
ctx.drawImage(backgroundJelly, x, y, backgroundJelly.width, backgroundJelly.height);
}
}
const trDeco = await loadImage(import_path.default.join(themeRoot, "tr_deco.png"));
ctx.drawImage(trDeco, 768 - trDeco.width, 0);
const defaultUserAvatar = import_path.default.join(themeRoot, "default_avatar.png");
const avatarImage = await loadImage(session.event.user?.avatar || defaultUserAvatar);
const avatarSize = 96;
const avatarCanvas = new Canvas(avatarSize, avatarSize);
const avatarCtx = avatarCanvas.getContext("2d");
avatarCtx.imageSmoothingQuality = "high";
const radius = avatarCanvas.width / 2;
avatarCtx.beginPath();
avatarCtx.arc(radius, radius, radius, 0, Math.PI * 2);
avatarCtx.closePath();
avatarCtx.clip();
avatarCtx.drawImage(avatarImage, 0, 0, avatarSize, avatarSize);
ctx.drawCanvas(avatarCanvas, 80, 64);
const user = await koishiCtx.database.get("mzk_user", jellyfishBox.user_id);
const username = user[0]?.nickname ?? session.event.user?.name ?? session.userId ?? "用户";
ctx.font = "48px noto-sans-sc-bold";
ctx.fillStyle = config.theme.name;
ctx.textAlign = "left";
const nameMetrics = ctx.measureText(username);
ctx.fillText(username, 80 + avatarSize + 24, 64 + nameMetrics.actualBoundingBoxAscent);
const pacmanX = 80 + avatarSize + 24;
const pacmanY = 64 + nameMetrics.actualBoundingBoxAscent + 8;
const jellyCount = jellyfishBox.jellyfish.reduce((sum, jelly) => sum + jelly.number, 0);
const pacmanCount = Math.floor(jellyCount / 20);
const pacmanCanvas = await drawPacman(koishiCtx, pacmanCount);
ctx.drawImage(pacmanCanvas, pacmanX, pacmanY);
ctx.fillStyle = "#7986cb";
ctx.font = "24px noto-sans-sc-medium";
ctx.textAlign = "right";
const date = "〇 " + (/* @__PURE__ */ new Date()).toLocaleDateString();
const dateMetrics = ctx.measureText(date);
ctx.fillText(date, 688, 150 + dateMetrics.actualBoundingBoxAscent);
const boxCanvas = new Canvas(672, 416);
const { width: backW, height: backH } = boxCanvas;
const boxCtx = boxCanvas.getContext("2d");
boxCtx.imageSmoothingQuality = "high";
boxCtx.fillStyle = config.theme.boxBackground;
drawRoundRect(boxCtx, 4, 4, backW - 8, backH - 8, 8);
const centerX = backW / 2;
const centerY = backH / 2;
ctx.fillStyle = config.theme.boxOutline;
drawRoundRect(ctx, 48, 192, 672, 416, 12);
const meta = await koishiCtx.database.get("mzk_jellyfish_meta", {});
const jellys = jellyfishBox.jellyfish;
for (let i = 0; i < jellys.length; i++) {
const jelly = jellys[i];
const { id, number } = jelly;
const jellyImagePath = import_path.default.join(jellyRoot, `${id}.png`);
try {
const jellyCanvas = await ProcessJellyfishImage(koishiCtx, jellyImagePath);
const jellyMeta = meta.find((meta2) => meta2.id === id);
const drawSize = jellyMeta?.draw.size ?? 1;
for (let j = 0; j < number; j++) {
const w = jellyCanvas.width;
const h = jellyCanvas.height;
const ratio = 32 / Math.max(w, h) || drawSize;
const drawW = w * ratio;
const drawH = h * ratio;
const x = centerX + import_lodash3.default.random(-centerX + drawW, centerX - drawW);
const y = centerY + import_lodash3.default.random(-centerY + drawH, centerY - drawH);
const direction = import_lodash3.default.random(-180, 180) * (Math.PI / 180);
boxCtx.save();
boxCtx.translate(x, y);
boxCtx.rotate(direction);
boxCtx.drawCanvas(jellyCanvas, -drawW / 2, -drawH / 2, drawW, drawH);
boxCtx.restore();
}
} catch (error) {
logger.error("打开水母图片失败", error);
continue;
}
}
ctx.drawCanvas(boxCanvas, 48, 192);
let currentHeight = 608 + 16;
if (newJellyCanvas) {
ctx.drawCanvas(newJellyCanvas, 48, currentHeight);
currentHeight += newJellyCanvas.height + 16;
}
if (eventsCanvas) {
ctx.drawCanvas(eventsCanvas, 48, currentHeight);
currentHeight += eventsCanvas.height + 16;
}
if (helpCanvas) {
ctx.drawCanvas(helpCanvas, 48, currentHeight);
currentHeight += helpCanvas.height + 16;
}
await canvas.saveAs(import_path.default.join(root, "/temp/output.png"));
return await canvas.toBuffer("png");
}, "DrawDefaultThemeBox");
var DrawCardCanvas = /* @__PURE__ */ __name(async (koishiCtx, config, width, height, title, content) => {
const { Canvas } = koishiCtx.skia;
const canvas = new Canvas(width, height);
const ctx = canvas.getContext("2d");
ctx.fillStyle = config.theme.cardBackground;
drawRoundRect(ctx, 0, 0, width, height, 16);
ctx.fillStyle = config.theme.title;
ctx.font = "30px noto-sans-sc-medium";
ctx.textAlign = "left";
ctx.fillText(title, 24, 36 + 2);
ctx.drawCanvas(content, 0, 38);
return canvas;
}, "DrawCardCanvas");
var DrawJellyfishCardContent = /* @__PURE__ */ __name(async (koishiCtx, config, newJelly, jellyRoot, withBackground = false, showNumber = true) => {
const { Canvas } = koishiCtx.skia;
if (!newJelly || !jellyRoot) {
return null;
}
const width = 768 - 48 * 2;
const height = 162;
const contentCanvas = new Canvas(width, height);
const ctx = contentCanvas.getContext("2d");
if (withBackground) {
ctx.fillStyle = config.theme.cardBackground;
drawRoundRect(ctx, 0, 0, width, height, 16);
}
const newJellyImage = await ProcessJellyfishImage(koishiCtx, import_path.default.join(jellyRoot, `${newJelly.id}.png`));
const meta = await koishiCtx.database.get("mzk_jellyfish_meta", { id: newJelly.id });
const jellyMeta = meta[0];
if (!jellyMeta) {
return null;
}
const name3 = jellyMeta.name;
const iconCanvas = new Canvas(128, 128);
const iconCtx = iconCanvas.getContext("2d");
iconCtx.fillStyle = config.theme.jellyIconBorder;
drawRoundRect(iconCtx, 0, 0, 128, 128, 8);
iconCtx.fillStyle = config.theme.jellyIconBackground;
drawRoundRect(iconCtx, 4, 4, 120, 120, 4);
iconCtx.drawCanvas(newJellyImage, 0, 0, newJellyImage.width, newJellyImage.height, 12, 12, 104, 104);
ctx.drawCanvas(iconCanvas, 24, 16);
ctx.save();
ctx.beginPath();
ctx.roundRect(0, 0, width, height, 16);
ctx.closePath();
ctx.clip();
ctx.globalAlpha = 0.3;
ctx.translate(width - 100, height / 2);
ctx.rotate(Math.PI / -4);
const tx = -100, ty = -60;
const tw = 220, th = 220;
ctx.fillStyle = "red";
ctx.drawCanvas(newJellyImage, 0, 0, newJellyImage.width, newJellyImage.height, tx, ty, tw, th);
ctx.restore();
ctx.fillStyle = config.theme.jellyName;
ctx.font = "30px noto-sans-sc-medium";
ctx.textAlign = "left";
ctx.fillText(name3, 24 + 128 + 16, 46);
ctx.fillStyle = config.theme.eventDescription;
ctx.font = "22px noto-sans-sc-regular";
ctx.textAlign = "left";
const description = jellyMeta.description;
const maxWidth = width - (24 + 128 + 16 + 24);
const lineHeight = 26;
const maxLines = 2;
const lines = [];
let currentLine = "";
const chars = description;
for (let i = 0; i < chars.length; i++) {
const potentialLine = currentLine + chars[i];
const metrics = ctx.measureText(potentialLine);
if (metrics.width > maxWidth) {
if (lines.length >= maxLines) {
currentLine += "...";
lines.push(currentLine);
break;
}
lines.push(currentLine);
currentLine = chars[i];
} else {
currentLine = potentialLine;
if (i === chars.length - 1) {
lines.push(currentLine);
}
}
}
lines.forEach((line, index) => {
ctx.fillText(line, 24 + 128 + 16, 72 + index * lineHeight + 4);
});
ctx.fillStyle = showNumber ? config.theme.jellyName : config.theme.eventDescription;
ctx.font = "24px noto-sans-sc-medium";
ctx.textAlign = "left";
const number = showNumber ? `×${newJelly.number}` : "稀有度:";
ctx.fillText(number, 24 + 128 + 16, 72 + lines.length * lineHeight + 4);
const group = jellyMeta.group;
const groupColor = config.theme.groups[group] ?? config.theme.eventDescription;
ctx.fillStyle = groupColor;
ctx.font = "24px noto-sans-sc-medium";
ctx.textAlign = "left";
const groupDx = 24 + 128 + 16 + ctx.measureText(number).width + 8;
ctx.fillText(`${group.toUpperCase()}`, groupDx, 72 + lines.length * lineHeight + 4);
return contentCanvas;
}, "DrawJellyfishCardContent");
var DrawNewJellyfishCardCanvas = /* @__PURE__ */ __name(async (koishiCtx, config, newJelly, jellyRoot) => {
if (!newJelly || !jellyRoot) {
return null;
}
const contentCanvas = await DrawJellyfishCardContent(koishiCtx, config, newJelly, jellyRoot);
if (!contentCanvas) {
return null;
}
return await DrawCardCanvas(koishiCtx, config, 672, 200, "新增", contentCanvas);
}, "DrawNewJellyfishCardCanvas");
var DrawGeneralCardCanvas = /* @__PURE__ */ __name(async (koishiCtx, config, title, events) => {
const { Canvas } = koishiCtx.skia;
if (!events || events.length === 0) {
return null;
}
const width = 768 - 48 * 2;
const height = 38 + events.length * (26 + 36) + 16;
const canvas = new Canvas(width, height);
const ctx = canvas.getContext("2d");
ctx.fillStyle = config.theme.cardBackground;
drawRoundRect(ctx, 0, 0, width, height, 16);
ctx.fillStyle = config.theme.title;
ctx.font = "30px noto-sans-sc-medium";
ctx.textAlign = "left";
ctx.fillText(title, 24, 38);
let currentHeight = 38 + 30 + 8;
for (let i = 0; i < events.length; i++) {
const event = events[i];
ctx.fillStyle = config.theme.eventTitle;
ctx.font = "30px noto-sans-sc-medium";
ctx.textAlign = "left";
ctx.fillText(event.name, 24, currentHeight);
currentHeight += 30 - 4;
ctx.fillStyle = config.theme.eventDescription;
ctx.font = "22px noto-sans-sc-regular";
ctx.textAlign = "left";
ctx.fillText(event.description, 24, currentHeight);
currentHeight += 24 + 12;
}
return canvas;
}, "DrawGeneralCardCanvas");
var DrawDefaultThemeStatistics = /* @__PURE__ */ __name(async (koishiCtx, config, session, jellyfishBox) => {
const { Canvas, loadImage, FontLibrary } = koishiCtx.skia;
const meta = await koishiCtx.database.get("mzk_jellyfish_meta", {}, ["id", "group"]);
const flag = !!jellyfishBox;
const jellies = jellyfishBox ? import_lodash3.default.clone(jellyfishBox.jellyfish) : meta.map((meta2) => ({ id: meta2.id, number: 0 }));
const selectMeta = /* @__PURE__ */ __name((id) => meta.find((meta2) => meta2.id === id), "selectMeta");
const groupPriority = {
perfect: 5,
great: 4,
good: 3,
normal: 2,
special: 1
};
const getGroupPriority = /* @__PURE__ */ __name((group) => groupPriority[group] ?? 0, "getGroupPriority");
jellies.sort((a, b) => {
const aMeta = selectMeta(a.id);
const bMeta = selectMeta(b.id);
return getGroupPriority(bMeta?.group) - getGroupPriority(aMeta?.group);
});
const jellySpeciesCount = import_lodash3.default.size(jellies);
const singleCol = jellySpeciesCount <= 10;
const width = 708 * import_lodash3.default.ceil(jellySpeciesCount / 10) + (singleCol ? 12 : 0);
const height = 84 + 24 + 24 + (162 + 8) * Math.min(10, jellySpeciesCount);
const root = import_path.default.join(koishiCtx.baseDir, "data/mizuki-bot");
const imageRoot = import_path.default.join(root, "image");
const fontRoot = import_path.default.join(root, "font");
const jellyRoot = import_path.default.join(imageRoot, "jellyfish/default");
const themeRoot = import_path.default.join(imageRoot, "/theme/default");
FontLibrary.use("noto-sans-sc-regular", import_path.default.join(fontRoot, "NotoSansSC-Regular.ttf"));
FontLibrary.use("noto-sans-sc-medium", import_path.default.join(fontRoot, "NotoSansSC-Medium.ttf"));
FontLibrary.use("noto-sans-sc-bold", import_path.default.join(fontRoot, "NotoSansSC-Bold.ttf"));
const canvas = new Canvas(width, height);
const ctx = canvas.getContext("2d");
ctx.fillStyle = config.theme.backgroundColor;
ctx.fillRect(0, 0, width, height);
const backgroundImage = await loadImage(import_path.default.join(themeRoot, "background_jelly.png"));
for (let y = 0; y < height; y += backgroundImage.height) {
for (let x = 0; x < width; x += backgroundImage.width) {
ctx.drawImage(backgroundImage, x, y, backgroundImage.width, backgroundImage.height);
}
}
const date = "〇 " + (/* @__PURE__ */ new Date()).toLocaleDateString();
ctx.fillStyle = config.theme.date;
ctx.font = "24px noto-sans-sc-medium";
ctx.textAlign = "left";
ctx.fillText(date, 48, 54);
let username = "";
if (jellyfishBox) {
const user = await koishiCtx.database.get("mzk_user", jellyfishBox.user_id);
username = user[0]?.nickname ?? session.event.user?.name ?? session.userId ?? "用户";
}
ctx.fillStyle = config.theme.title;
ctx.font = "32px noto-sans-sc-bold";
ctx.textAlign = "left";
const title = flag ? "水母统计表@" + username : "水母图鉴";
ctx.fillText(title, 48, 54 + 32);
for (let i = 0; i < jellies.length; i++) {
const row = i % 10;
const col = import_lodash3.default.floor(i / 10);
const jelly = jellies[i];
const jellyCanvas = await DrawJellyfishCardContent(koishiCtx, config, jelly, jellyRoot, true, flag);
if (!jellyCanvas) {
continue;
}
const dx = 24 + col * (672 + 24);
const dy = 84 + 24 + row * (162 + 8);
ctx.drawCanvas(jellyCanvas, dx, dy);
}
return canvas.toBuffer("png");
}, "DrawDefaultThemeStatistics");
// src/user/user.ts
var import_lodash4 = __toESM(require("lodash"));
var GetUser = /* @__PURE__ */ __name(async (ctx, id, platform) => {
const user = await ctx.database.get("mzk_user", {
platform,
user_id: id
});
if (import_lodash4.default.size(user) === 0) {
return await ctx.database.create("mzk_user", {
platform,
user_id: id
});
}
return user[0];
}, "GetUser");
// src/commands/jellyfish_box.tsx
var import_jsx_runtime2 = require("@satorijs/element/jsx-runtime");
var logger2 = new import_koishi2.Logger("mizuki-bot-jellyfish");
var resolveSessionUid = /* @__PURE__ */ __name((session) => session.event.user?.id ?? session.userId, "resolveSessionUid");
var PromptNickname = /* @__PURE__ */ __name(async (ctx, session) => {
const uid = resolveSessionUid(session);
if (session.platform === "qq" && !session.event.user?.name && uid) {
const user = await GetUser(ctx, uid, session.platform);
if (!user.nickname) {
const botName = session.bot.user?.name ?? "机器人";
session.send(`
检测到这是你第一次认领水母箱,请先@我输入昵称,让机器人知道如何称呼你
例如:@${botName} 海月离
设置完成后你可以通过“/叫我 xx”来更新`);
let name3 = await session.prompt();
if (!name3) return { success: false };
if (name3.startsWith("/叫我 ")) {
name3 = name3.slice(4);
}
await ctx.database.set("mzk_user", user.id, {
nickname: name3
});
return { success: true, name: name3 };
}
}
return { success: true, name: session.event.user?.name ?? session.userId ?? "用户" };
}, "PromptNickname");
async function CommandJellyfishBox(config, ctx, session) {
const uid = resolveSessionUid(session);
if (!uid) return "无法获取你的用户信息,请稍后再试";
const { success } = await PromptNickname(ctx, session);
if (!success) return "输入超时或取消,已退出";
const jellyfish_box = await GetJellyfishBox(ctx, uid, session.platform);
const events = await CalculateBoxEvents(ctx, jellyfish_box);
const buffer = await DrawDefaultThemeBox(ctx, config, session, jellyfish_box, void 0, events);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageBuffer, { buffer }) });
}
__name(CommandJellyfishBox, "CommandJellyfishBox");
async function CommandJellyfishBoxCatch(config, ctx, session) {
const { success } = await PromptNickname(ctx, session);
if (!success) return "输入超时或取消,已退出";
const uid = resolveSessionUid(session);
if (!uid) return "无法获取你的用户信息,请稍后再试";
const platform = session.platform;
let jellyfish_box = await GetJellyfishBox(ctx, uid, platform);
const last_catch_time = jellyfish_box.last_catch_time;
if (uid !== config.test_account && last_catch_time.getTime() + 2 * 60 * 60 * 1e3 > Date.now()) {
const remain = (last_catch_time.getTime() + 2 * 60 * 60 * 1e3 - Date.now()) / 1e3;
const hours = Math.floor(remain / 3600);
const minutes = Math.floor(remain % 3600 / 60);
const seconds = Math.floor(remain % 60);
return `别抓啦,过${hours ? hours + "小时" : ""}${minutes ? minutes + "分" : ""}${seconds}秒再来吧!`;
}
const jellyfish_num = GetJellyfishInBoxCount(jellyfish_box);
if (jellyfish_num >= 256) {
return "别抓啦,水母箱里的水母太多了!";
}
const events = await CalculateBoxEvents(ctx, jellyfish_box);
jellyfish_box = await GetJellyfishBox(ctx, uid, platform);
let added = {
id: "",
number: 0
};
let catch_num = 0;
if (jellyfish_box.jellyfish.length === 0) {
catch_num = import_lodash5.default.random(4, 6);
} else {
if (jellyfish_num < 10)
catch_num = import_lodash5.default.random(3, 4);
else if (jellyfish_num < 20)
catch_num = import_lodash5.default.random(2, 3);
else if (jellyfish_num < 50)
catch_num = import_lodash5.default.random(1, 2);
else
catch_num = 1;
}
const group = ["perfect", "great", "good", "normal", "special"];
let group_probability = [0.02, 0.08, 0.5, 0.4, 0];
if ((/* @__PURE__ */ new Date()).getMonth() === 3 && (/* @__PURE__ */ new Date()).getDate() === 22) {
group_probability = [0.02, 0.08, 0.45, 0.25, 0.2];
}
const selected_group = RandomChooseWithWeights(group_probability, group);
const group_meta = await ctx.database.get("mzk_jellyfish_meta", {
group: selected_group
});
const selected_jellyfish = RandomChoose(group_meta);
const jellyfish_index = jellyfish_box.jellyfish.findIndex((jelly) => jelly.id === selected_jellyfish.id);
if (jellyfish_index === -1) {
jellyfish_box.jellyfish.push({
id: selected_jellyfish.id,
number: catch_num
});
} else {
jellyfish_box.jellyfish[jellyfish_index].number += catch_num;
}
added = {
id: selected_jellyfish.id,
number: catch_num
};
jellyfish_box.last_catch_time = new Date(Date.now());
await ctx.database.set("mzk_jellyfish_box", jellyfish_box.user_id, {
last_catch_time: jellyfish_box.last_catch_time,
jellyfish: jellyfish_box.jellyfish
});
logger2.info(`用户${uid}在${platform}抓到了${catch_num}只${selected_jellyfish.name}`);
const buffer = await DrawDefaultThemeBox(ctx, config, session, jellyfish_box, added, events, true);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageBuffer, { buffer }) });
}
__name(CommandJellyfishBoxCatch, "CommandJellyfishBoxCatch");
var CommandJellyfishBoxStatistics = /* @__PURE__ */ __name(async (config, ctx, session) => {
const uid = resolveSessionUid(session);
if (!uid) return "无法获取你的用户信息,请稍后再试";
const platform = session.platform;
const jellyfish_box = await GetJellyfishBox(ctx, uid, platform);
const buffer = await DrawDefaultThemeStatistics(ctx, config, session, jellyfish_box);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageBuffer, { buffer });
}, "CommandJellyfishBoxStatistics");
var CommandJellyfishBoxCatalogue = /* @__PURE__ */ __name(async (config, ctx, session) => {
const buffer = await DrawDefaultThemeStatistics(ctx, config, session);
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(ImageBuffer, { buffer });
}, "CommandJellyfishBoxCatalogue");
var CommandJellyfishBoxSetStyle = /* @__PURE__ */ __name(async (config, ctx, session, style) => {
return "暂不支持更换样式";
}, "CommandJellyfishBoxSetStyle");
var GetJellyfishBox = /* @__PURE__ */ __name(async (ctx, id, platform) => {
let query = [];
const user = await GetUser(ctx, id, platform);
query = await ctx.database.get("mzk_jellyfish_box", {
user_id: user.id
});
if (query.length === 0) {
query.push(await ctx.database.create("mzk_jellyfish_box", {
user_id: user.id,
last_catch_time: new Date(Date.now() - 2 * 60 * 60 * 1e3),
last_refresh_time: new Date(Date.now() - 2 * 60 * 60 * 1e3),
jellyfish: [],
decoration: [],
salinity: 25,
temperature: 25,
draw_style: "normal"
}));
}
return query[0];
}, "GetJellyfishBox");
var GetJellyfishInBoxCount = /* @__PURE__ */ __name((jellyfish_box) => {
return jellyfish_box.jellyfish.reduce((pre, cur) => pre + cur.number, 0);
}, "GetJellyfishInBoxCount");
var GetJellyfishReduceWeight = /* @__PURE__ */ __name((group) => {
switch (group) {
case "normal":
return 16;
case "good":
return 8;
case "great":
return 4;
case "perfect":
return 2;
case "special":
return 1;
default:
return 1;
}
}, "GetJellyfishReduceWeight");
var CalculateBoxEvents = /* @__PURE__ */ __name(async (ctx, jellyfish_box) => {
const HOUR_MS = 60 * 60 * 1e3;
let refresh = false;
let refresh_number = 0;
const last_time = jellyfish_box.last_refresh_time;
if (last_time.getTime() + HOUR_MS < Date.now()) {
refresh = true;
refresh_number = Math.floor((Date.now() - last_time.getTime()) / HOUR_MS);
if (refresh_number > 168) refresh_number = 24;
else if (refresh_number > 72) refresh_number = 12;
}
if (jellyfish_box.user_id === ctx.config.test_account) {
refresh = true;
refresh_number = 10;
}
if (!refresh) return [];
const events = [];
const jellyfish_num = GetJellyfishInBoxCount(jellyfish_box);
const ids = jellyfish_box.jellyfish.map((jellyfish) => jellyfish.id);
const jellyfish_meta = await ctx.database.get("mzk_jellyfish_meta", { id: { $in: ids } });
for (const item of jellyfish_box.jellyfish) {
let num_to_add = 0;
const meta = jellyfish_meta.find((m) => m.id === item.id);
if (!meta || meta.reproductive_rate === 0) continue;
const reproductive_rate = meta.reproductive_rate;
let rate = reproductive_rate / 30 / 24 * item.number * refresh_number;
if (jellyfish_num > 50) {
rate = rate / jellyfish_num;
}
if (rate > 1) {
num_to_add = Math.floor(rate);
rate -= num_to_add;
}
if (rate > 0 && RandomChooseWithWeights([rate, 1 - rate], [true, false])) {
num_to_add += 1;
}
if (num_to_add > 0) {
item.number += num_to_add;
events.push({
"id": "reproductive",
"name": "繁殖",
"description": `在水母箱中繁殖出了${num_to_add}只新的${meta.name}`
});
}
}
;
const breeding_candidates = jellyfish_box.jellyfish.filter((item) => item.number >= 2).map((item) => ({
item,
meta: jellyfish_meta.find((m) => m.id === item.id)
})).filter((candidate) => !!candidate.meta);
const event_meta = await ctx.database.get("mzk_jellyfish_event_meta", {});
const available_event_meta = breeding_candidates.length === 0 ? event_meta.filter((event) => event.type !== "breed") : event_meta;
const single_display_event_ids = /* @__PURE__ */ new Set(["happy", "eat"]);
const displayed_event_ids = /* @__PURE__ */ new Set();
let event_probabilities_sum = available_event_meta.map((item) => item.probability).reduce((pre, cur) => pre + cur, 0) * refresh_number;
let event_number = 0;
if (event_probabilities_sum > 1) {
event_number += Math.floor(event_probabilities_sum);
}
event_probabilities_sum -= event_number;
if (event_probabilities_sum > 0 && RandomChooseWithWeights([event_probabilities_sum, 1 - event_probabilities_sum], [true, false])) {
event_number += 1;
}
if (event_number > 0) {
for (let i = 0; i < event_number; i++) {
const event = RandomChoose(available_event_meta);
if (single_display_event_ids.has(event.id)) {
if (displayed_event_ids.has(event.id)) {
continue;
}
displayed_event_ids.add(event.id);
}
switch (event.type) {
case "breed": {
const candidate = RandomChoose(breeding_candidates);
candidate.item.number += 1;
events.push({
"id": event.id,
"name": event.name,
"description": `两只${candidate.meta.name}繁殖出了一只新的${candidate.meta.name}`
});
continue;
}
case "reduce": {
const reduce_candidates = jellyfish_box.jellyfish.map((item) => ({
item,
meta: jellyfish_meta.find((m) => m.id === item.id)
})).filter((candidate) => !!candidate.meta && candidate.item.number > 0);
if (reduce_candidates.length === 0) {
continue;
}
const selected = RandomChooseWithWeights(
reduce_candidates.map((candidate) => candidate.item.number * GetJellyfishReduceWeight(candidate.meta.group)),
reduce_candidates
);
selected.item.number -= 1;
if (selected.item.number <= 0) {
const index = jellyfish_box.jellyfish.findIndex((item) => item.id === selected.item.id);
if (index !== -1) {
jellyfish_box.jellyfish.splice(index, 1);
}
}
events.push({
"id": event.id,
"name": event.name,
"description": `${selected.meta.name}遇到旋涡被冲走了`
});
continue;
}
case "add":
break;
case "change":
break;
default:
break;
}
events.push({
"id": event.id,
"name": event.name,
"description": event.description
});
}
}
if (import_lodash5.default.size(events) !== 0) jellyfish_box.last_refresh_time = new Date(Date.now());
await ctx.database.set("mzk_jellyfish_box", jellyfish_box.user_id, {
last_refresh_time: jellyfish_box.last_refresh_time,
jellyfish: jellyfish_box.jellyfish
});
return events;
}, "CalculateBoxEvents");
var parseListParams = /* @__PURE__ */ __name((params) => {
const result = [];
let i = 0;
const all_values = /* @__PURE__ */ new Set(["all", "全部", "所有"]);
while (i < params.length) {
const name3 = params[i];
let num = 1;
if (i + 1 < params.length) {
const next = params[i + 1];
if (!isNaN(Number(next))) {
num = Number(next);
i += 1;
} else if (all_values.has(next.toLowerCase())) {
num = "all";
i += 1;
}
}
result.push({
name: name3,
number: num
});
i += 1;
}
return result;
}, "parseListParams");
var validateAndGenerateDropList = /* @__PURE__ */ __name(async (ctx, drop_list, jellyfish_box) => {
const group_values = /* @__PURE__ */ new Set(["normal", "good", "great", "perfect", "special"]);
const meta = await ctx.database.get("mzk_jellyfish_meta", {});
const all_jellyfish_id_set = new Set(meta.map((jelly) => jelly.id));
const all_jellyfish_id_to_name = new Map(meta.map((jelly) => [jelly.id, jelly.name]));
const errors = [];
const result = [];
for (const item of drop_list) {
if (group_values.has(item.name)) {
if (item.number !== "all") {
errors.push(new Error(`要放生${item.name}时数量必须为all`));
continue;
}
const searched_meta_id = meta.filter((jelly) => jelly.group === item.name).map((jelly) => jelly.id);
const meta_search = new Set(searched_meta_id);
const jelly_search = jellyfish_box.jellyfish.filter((jelly) => meta_search.has(jelly.id));
if (import_lodash5.default.size(jelly_search) === 0) {
errors.push(new Error(`水母箱里没有稀有度为${item.name}的水母`));
continue;
} else {
result.push(...jelly_search.map((jelly) => ({
id: jelly.id,
name: all_jellyfish_id_to_name.get(jelly.id) ?? jelly.id,
num: jelly.number
})));
}
} else {
let item_id = item.name;
if (all_jellyfish_id_set.has(item.name)) {
item_id = item.name;
} else {
const jellyfish_meta = meta.find((jelly) => jelly.name === item.name);
if (!jellyfish_meta) {
errors.push(new Error(`这片大地(hai)没有名为${item.name}的水母`));
continue;
}
item_id = jellyfish_meta.id;
}
const index = jellyfish_box.jellyfish.findIndex((jelly) => jelly.id === item_id);
if (index === -1) {
errors.push(new Error(`水母箱里没有${item.name}`));
continue;
}
if (item.number !== "all" && jellyfish_box.jellyfish[index].number < item.number) {
errors.push(new Error(`水母箱里的${item.name}数量不够`));
continue;
}
result.push({
id: item_id,
name: all_jellyfish_id_to_name.get(item_id) ?? item_id,
num: item.number
});
}
}
return { errors, result };
}, "validateAndGenerateDropList");
async function CommandJellyfishBoxDrop(config, ctx, session, params) {
const uid = resolveSessionUid(session);
if (!uid) return "无法获取你的用户信息,请稍后再试";
const platform = session.platform;
logger2.info(params);
if (!params[0]) {
return "请添加水母名称以及数量\n例:“水母箱 放生 普通水母 10”";
}
const drop_list = parseListParams(params);
logger2.info(JSON.stringify(drop_list));
const jellyfish_box = await GetJellyfishBox(ctx, uid, platform);
const { errors: validate_errors, result: drop_result } = await validateAndGenerateDropList(ctx, drop_list, jellyfish_box);
if (import_lodash5.default.size(validate_errors) > 0) {
return `放生失败,请检查输入的信息:
${validate_errors.map((error) => error.message).join("\n")}`;
}
for (const item of drop_result) {
const index = jellyfish_box.jellyfish.findIndex((jelly) => jelly.id === item.id);
if (index === -1) {
continue;
}
const num_has = jellyfish_box.jellyfish[index].number;
jellyfish_box.jellyfish[index].number -= item.num === "all" ? num_has : item.num;
}
for (let i = jellyfish_box.jellyfish.length - 1; i >= 0; i--) {
if (jellyfish_box.jellyfish[i].number === 0) {
jellyfish_box.jellyfish.splice(i, 1);
}
}
await ctx.database.set("mzk_jellyfish_box", jellyfish_box.user_id, {
jellyfish: jellyfish_box.jellyfish
});
const message = `成功放生${drop_result.map((item) => item.num === "all" ? `全部${item.name}` : `${item.name}×${item.num}`).join("、")}`;
return message;
}
__name(CommandJellyfishBoxDrop, "CommandJellyfishBoxDrop");
// src/commands/testCommand.tsx
var CommandTest = /* @__PURE__ */ __name(async (ctx, session) => {
return "测试";
}, "CommandTest");
// src/index.ts
var fs4 = __toESM(require("fs/promises"));
var path5 = __toESM(require("path"));
// src/commands/callme.tsx
var CommandCallMe = /* @__PURE__ */ __name(async (ctx, session, newName) => {
try {
const uid = session.event.user?.id ?? session.userId;
if (!uid) {
return "更新昵称失败,无法获取你的用户信息。";
}
const platform = session.platform;
const user = await GetUser(ctx, uid, platform);
await ctx.database.set("mzk_user", user.id, {
nickname: newName
});
return `已将你的昵称更新为 ${newName}`;
} catch (error) {
const message = error instanceof Error ? error.message : String(error);
return `更新昵称失败,请检查输入的信息。
错误信息:${message}`;
}
}, "CommandCallMe");
// src/skland/api.ts
var import_axios = __toESM(require("axios"));
var import_koishi4 = require("koishi");
var import_qrcode = __toESM(require("qrcode"));
// src/skland/helper.ts
var import_node_crypto2 = require("node:crypto");
var import_pako = __toESM(require("pako"));
var import_date_fns = require("date-fns");
// src/skland/constant.ts
var SKLAND_SM_CONFIG = {
organization: "UWXspnCCJN4sfYlNfqps",
appId: "default",
publicKey: "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCmxMNr7n8ZeT0tE1R9j/mPixoinPkeM+k4VGIn/s0k7N5rJAfnZ0eMER+QhwFvshzo0LNmeUkpR8uIlU/GEVr8mN28sKmwd2gpygqj0ePnBmOW4v0ZVwbSYK+izkhVFk2V/doLoMbWy6b+UnA8mkjvg0iYWRByfRsK2gdl7llqCwIDAQAB",
protocol: "https",
apiHost: "fp-it.portal101.cn",
apiPath: "/deviceprofile/v4"
};
var DES_RULE = {
appId: {
cipher: "DES",
is_encrypt: 1,
key: "uy7mzc4h",
obfuscated_name: "xx"
},
box: {
is_encrypt: 0,
obfuscated_name: "jf"
},
canvas: {
cipher: "DES",
is_encrypt: 1,
key: "snrn887t",
obfuscated_name: "yk"
},
clientSize: {
cipher: "DES",
is_encrypt: 1,
key: "cpmjjgsu",
obfuscated_name: "zx"
},
organization: {
cipher: "DES",
is_encrypt: 1,
key: "78moqjfc",
obfuscated_name: "dp"
},
os: {
cipher: "DES",
is_encrypt: 1,
key: "je6vk6t4",
obfuscated_name: "pj"
},
platform: {
cipher: "DES",
is_encrypt: 1,
key: "pakxhcd2",
obfuscated_name: "gm"
},
plugins: {
cipher: "DES",
is_encrypt: 1,
key: "v51m3pzl",
obfuscated_name: "kq"
},
pmf: {
cipher: "DES",
is_encrypt: 1,
key: "2mdeslu3",
obfuscated_name: "vw"
},
protocol: {
is_encrypt: 0,
obfuscated_name: "protocol"
},
referer: {
cipher: "DES",
is_encrypt: 1,
key: "y7bmrjlc",
obfuscated_name: "ab"
},
res: {
cipher: "DES",
is_encrypt: 1,
key: "whxqm2a7",
obfuscated_name: "hf"
},
rtype: {
cipher: "DES",
is_encrypt: 1,
key: "x8o2h2bl",
obfuscated_name: "lo"
},
sdkver: {
cipher: "DES",
is_encrypt: 1,
key: "9q3dcxp2",
obfuscated_name: "sc"
},
status: {
cipher: "DES",
is_encrypt: 1,
key: "2jbrxxw4",
obfuscated_name: "an"
},
subVersion: {
cipher: "DES",
is_encrypt: 1,
key: "eo3i2puh",
obfuscated_name: "ns"
},
svm: {
cipher: "DES",
is_encrypt: 1,
key: "fzj3kaeh",
obfuscated_name: "qr"
},
time: {
cipher: "DES",
is_encrypt: 1,
key: "q2t3odsk",
obfuscated_name: "nb"
},
timezone: {
cipher: "DES",
is_encrypt: 1,
key: "1uv05lj5",
obfuscated_name: "as"
},
tn: {
cipher: "DES",
is_encrypt: 1,
key: "x9nzj1bp",
obfuscated_name: "py"
},
trees: {
cipher: "DES",
is_encrypt: 1,
key: "acfs0xo4",
obfuscated_name: "pi"
},
ua: {
cipher: "DES",
is_encrypt: 1,
key: "k92crp1t",
obfuscated_name: "bj"
},
url: {
cipher: "DES",
is_encrypt: 1,
key: "y95hjkoo",
obfuscated_name: "cf"
},
version: {
is_encrypt: 0,
obfuscated_name: "version"
},
vpw: {
cipher: "DES",
is_encrypt: 1,
key: "r9924ab5",
obfuscated_name: "ca"
}
};
var BROWSER_ENV = {
plugins: "MicrosoftEdgePDFPluginPortableDocumentFormatinternal-pdf-viewer1,MicrosoftEdgePDFViewermhjfbmdgcfjbbpaeojofohoefgiehjai1",
ua: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/129.0.0.0 Safari/537.36 Edg/129.0.0.0",
canvas: "259ffe69",
// 基于浏览器的canvas获得的值,不知道复用行不行
timezone: -480,
// 时区,应该是固定值吧
platform: "Win32",
url: "https://www.skland.com/",
// 固定值
referer: "",
res: "1920_1080_24_1.25",
// 屏幕宽度_高度_色深_window.devicePixelRatio
clientSize: "0_0_1080_1920_1920_1080_1920_1080",
status: "0011"
// 不知道在干啥
};
// src/skland/crypto.ts
var import_node_crypto = require("node:crypto");
var import_crypto_js = __toESM(require("crypto-js"));
var import_node_forge = __toESM(require("node-forge"));
var crypto = import_node_crypto.webcrypto;
async function md5(string) {
return import_crypto_js.default.MD5(string).toString();
}
__name(md5, "md5");
async function hmacSha256(key, data) {
return import_crypto_js.default.HmacSHA256(data, key).toString();
}
__name(hmacSha256, "hmacSha256");
async function encryptAES(message, key) {
const iv = new TextEncoder().encode("0102030405060708");
const data = new TextEncoder().encode(message);
const cryptoKey = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(key),
{ name: "AES-CBC" },
false,
["encrypt"]
);
const encrypted = await crypto.subtle.encrypt(
{
name: "AES-CBC",
iv
},
cryptoKey,
data
);
return Array.from(new Uint8Array(encrypted)).map((b) => b.toString(16).padStart(2, "0")).join("");
}
__name(encryptAES, "encryptAES");
function padData(data) {
const blockSize = 8;
const padLength = blockSize - data.length % blockSize;
return data + "\0".repeat(padLength);
}
__name(padData, "padData");
async function encryptDES(message, key) {
const inputStr = padData(String(message));
const keyWordArray = import_crypto_js.default.enc.Utf8.parse(key);
const dataWordArray = import_crypto_js.default.enc.Utf8.parse(inputStr);
const encrypted = import_crypto_js.default.TripleDES.encrypt(dataWordArray, keyWordArray, {
mode: import_crypto_js.default.mode.ECB,
padding: import_crypto_js.default.pad.NoPadding
});
return encrypted.toString();
}
__name(encryptDES, "encryptDES");
async function encryptObjectByDESRules(object, rules) {
const result = {};
for (const i in object) {
if (i in rules) {
const rule = rules[i];
if (rule.is_encrypt === 1)
result[rule.obfuscated_name] = await encryptDES(object[i], rule.key);
else
result[rule.obfuscated_name] = object[i];
} else {
result[i] = object[i];
}
}
return result;
}
__name(encryptObjectByDESRules, "encryptObjectByDESRules");
async function encryptRSA(message, publicKey) {
const formatPublickey = publicKey.match(/.{1,64}/g)?.join("\n") || "";
const pk = `-----BEGIN PUBLIC KEY-----
${formatPublickey}
-----END PUBLIC KEY-----`;
const publicKeyForge = import_node_forge.default.pki.publicKeyFromPem(pk);
const encrypted = publicKeyForge.encrypt(message, "RSAES-PKCS1-V1_5");
return import_node_forge.default.util.encode64(encrypted);
}
__name(encryptRSA, "encryptRSA");
// src/skland/helper.ts
var import_koishi3 = require("koishi");
var crypto2 = import_node_crypto2.webcrypto;
var getErrorMessage = /* @__PURE__ */ __name((error) => error instanceof Error ? error.message : String(error), "getErrorMessage");
var getErrorStack = /* @__PURE__ */ __name((error) => error instanceof Error ? error.stack : void 0, "getErrorStack");
var isStatus403Error = /* @__PURE__ */ __name((error) => {
if (!error || typeof error !== "object") return false;
const response = error.response;
return response?.status === 403;
}, "isStatus403Error");
var stringify = /* @__PURE__ */ __name((obj) => JSON.stringify(obj).replace(/":"/g, '": "').replace(/","/g, '", "'), "stringify");
function gzipObject(o) {
const jsonStr = stringify(o);
const encoded = new TextEncoder().encode(jsonStr);
const compressed = import_pako.default.gzip(encoded, {
level: 2
});
compressed.set([19], 9);
return btoa(String.fromCharCode(...compressed));
}
__name(gzipObject, "gzipObject");
async function getSmId() {
const now = /* @__PURE__ */ new Date();
const _time = (0, import_date_fns.format)(now, "yyyyMMddHHmmss");
const uid = crypto2.randomUUID();
const uidMd5 = md5(uid);
const v = `${_time + uidMd5}00`;
const smsk_web = (await md5(`smsk_web_${v}`)).substring(0, 14);
return `${v + smsk_web}0`;
}
__name(getSmId, "getSmId");
function getTn(o) {
const sortedKeys = Object.keys(o).sort();
const resultList = [];
for (const key of sortedKeys) {
let v = o[key];
if (typeof v === "number")
v = String(v * 1e4);
else if (typeof v === "object" && v !== null)
v = getTn(v);
resultList.push(v);
}
return resultList.join("");
}
__name(getTn, "getTn");
var SM_CONFIG = SKLAND_SM_CONFIG;
var devices_info_url = `${SKLAND_SM_CONFIG.protocol}://${SKLAND_SM_CONFIG.apiHost}${SKLAND_SM_CONFIG.apiPath}`;
async function getDid() {
const uid = crypto2.randomUUID();
const priId = (await md5(uid)).substring(0, 16);
const ep = await encryptRSA(uid, SM_CONFIG.publicKey);
const browser = {
...BROWSER_ENV,
vpw: crypto2.randomUUID(),
svm: Date.now(),
trees: crypto2.randomUUID(),
pmf: Date.now()
};
const desTarget = {
...browser,
protocol: 102,
organization: SM_CONFIG.organization,
appId: SM_CONFIG.appId,
os: "web",
version: "3.0.0",
sdkver: "3.0.0",
box: "",
// 首次请求为空
rtype: "all",
smid: await getSmId(),
subVersion: "1.0.0",
time: 0
};
desTarget.tn = await md5(getTn(desTarget));
const desResult = await encryptObjectByDESRules(desTarget, DES_RULE);
const gzipResult = gzipObject(desResult);
const aesResult = await encryptAES(gzipResult, priId);
const body = {
appId: "default",
compress: 2,
data: aesResult,
encode: 5,
ep,
organization: SM_CONFIG.organization,
os: "web"
};
const response = await fetch(devices_info_url, {
method: "POST",
headers: {
"Content-Type": "application/json"
},