koishi-plugin-msbao 0.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 团子布丁
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/lib/image.png ADDED
Binary file
package/lib/index.d.ts ADDED
@@ -0,0 +1,35 @@
1
+ import { Context, Schema } from 'koishi';
2
+ export interface Config {
3
+ enabled: boolean;
4
+ whitelistMode: boolean;
5
+ whitelist: string[];
6
+ admins: string[];
7
+ apiKey: string;
8
+ ms: {
9
+ useGlobalwlist: boolean;
10
+ selfWhitelist: string[];
11
+ queryInterval?: number;
12
+ images?: string[];
13
+ };
14
+ URL: {
15
+ enabled: boolean;
16
+ Lists: Array<{
17
+ name: string;
18
+ websites: string[];
19
+ useGlobalwlist: boolean;
20
+ selfWhitelist: string[];
21
+ }>;
22
+ };
23
+ Key: {
24
+ enabled: boolean;
25
+ keywords: Array<{
26
+ listening: string;
27
+ reply: string;
28
+ useGlobalwlist: boolean;
29
+ selfWhitelist: string[];
30
+ }>;
31
+ };
32
+ }
33
+ export declare const Config: Schema<Config>;
34
+ export declare const name = "msbao";
35
+ export declare function apply(ctx: Context, config: Config): void;
package/lib/index.js ADDED
@@ -0,0 +1,247 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
3
+ var __getOwnPropNames = Object.getOwnPropertyNames;
4
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
5
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
6
+ var __export = (target, all) => {
7
+ for (var name2 in all)
8
+ __defProp(target, name2, { get: all[name2], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var src_exports = {};
22
+ __export(src_exports, {
23
+ Config: () => Config,
24
+ apply: () => apply,
25
+ name: () => name
26
+ });
27
+ module.exports = __toCommonJS(src_exports);
28
+ var import_koishi = require("koishi");
29
+ var import_tms = require("maplestory-openapi/tms");
30
+ var import_url = require("url");
31
+ var import_path = require("path");
32
+ var import_fs = require("fs");
33
+ var Config = import_koishi.Schema.object({
34
+ enabled: import_koishi.Schema.boolean().description("插件开关").default(true),
35
+ whitelistMode: import_koishi.Schema.boolean().description("是否开启白名单模式(开启后仅白名单群生效)").default(true),
36
+ whitelist: import_koishi.Schema.array(import_koishi.Schema.string().description("白名单群号")).role("table").default([]),
37
+ admins: import_koishi.Schema.array(import_koishi.Schema.string().description("管理员QQ号")).role("table").default([]),
38
+ URL: import_koishi.Schema.object({
39
+ enabled: import_koishi.Schema.boolean().description("查询功能开关").default(true),
40
+ Lists: import_koishi.Schema.array(
41
+ import_koishi.Schema.object({
42
+ name: import_koishi.Schema.string().description("触发指令"),
43
+ websites: import_koishi.Schema.array(import_koishi.Schema.string().description("网址")).role("table"),
44
+ useGlobalwlist: import_koishi.Schema.boolean().default(true).description("是否套用全局白名单"),
45
+ selfWhitelist: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default([]).description("独立白名单")
46
+ })
47
+ ).description("查询指令列表")
48
+ }),
49
+ Key: import_koishi.Schema.object({
50
+ enabled: import_koishi.Schema.boolean().description("关键词回复功能开关").default(false),
51
+ keywords: import_koishi.Schema.array(
52
+ import_koishi.Schema.object({
53
+ listening: import_koishi.Schema.string().description("监听词"),
54
+ reply: import_koishi.Schema.string().description("回复语句"),
55
+ useGlobalwlist: import_koishi.Schema.boolean().default(false).description("是否套用全局白名单"),
56
+ selfWhitelist: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default([]).description("独立白名单")
57
+ })
58
+ ).description("关键词与回复语句映射表")
59
+ }),
60
+ apiKey: import_koishi.Schema.string().description("Nexon-API 密钥").default(""),
61
+ ms: import_koishi.Schema.object({
62
+ useGlobalwlist: import_koishi.Schema.boolean().default(true).description("是否套用全局白名单"),
63
+ selfWhitelist: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default([]).description("独立白名单"),
64
+ queryInterval: import_koishi.Schema.number().default(100).description("查询间隔(毫秒)"),
65
+ images: import_koishi.Schema.array(import_koishi.Schema.string()).role("table").default(["image.png"]).description("随消息一起发出的图片文件名(放在插件根目录,可带子目录)")
66
+ })
67
+ });
68
+ var name = "msbao";
69
+ function apply(ctx, config) {
70
+ if (!config.enabled) return;
71
+ const isAdmin = /* @__PURE__ */ __name((session) => config.admins.includes(session.userId), "isAdmin");
72
+ function canUse(session, item) {
73
+ if (!session.guildId) return true;
74
+ if (item.useGlobalwlist !== false) {
75
+ return !config.whitelistMode || config.whitelist.includes(session.channelId);
76
+ }
77
+ const self = item.selfWhitelist || [];
78
+ if (self.length === 0) return true;
79
+ return self.includes(session.channelId);
80
+ }
81
+ __name(canUse, "canUse");
82
+ if (config.URL.enabled) {
83
+ for (const cmd of config.URL.Lists) {
84
+ const commandName = cmd.name.trim();
85
+ if (!commandName) continue;
86
+ ctx.command(commandName, `TBD`).action(({ session }) => {
87
+ if (!canUse(session, cmd)) return;
88
+ if (!cmd.websites?.length) return "暂无或忘了(";
89
+ return cmd.websites.join("\n");
90
+ });
91
+ }
92
+ }
93
+ if (config.Key.enabled) {
94
+ ctx.on("message", async (session) => {
95
+ for (const kw of config.Key.keywords) {
96
+ if (!canUse(session, kw)) continue;
97
+ if (new RegExp(kw.listening, "i").test(session.content)) {
98
+ await session.send(kw.reply);
99
+ break;
100
+ }
101
+ }
102
+ });
103
+ }
104
+ ctx.command("listweb").action(async ({ session }) => {
105
+ if (!isAdmin(session)) return "";
106
+ if (!config.URL.Lists.length) return "暂无或忘了(";
107
+ return "=== 指令 ===\n" + config.URL.Lists.map((i) => `【${i.name}】
108
+ ${i.websites.join("\n")}`).join("\n");
109
+ });
110
+ ctx.command("listkey").action(async ({ session }) => {
111
+ if (!isAdmin(session)) return "";
112
+ if (!config.Key.keywords.length) return "None";
113
+ return "=== 监听 ===\n" + config.Key.keywords.map((k) => `监听:${k.listening} → 回复:${k.reply}`).join("\n");
114
+ });
115
+ ctx.command("listall").action(async ({ session }) => {
116
+ if (!isAdmin(session)) return "";
117
+ let msg = "=== 当前网页查询指令 ===\n";
118
+ if (!config.URL.Lists.length) msg += "(暂无)\n";
119
+ else config.URL.Lists.forEach((i) => {
120
+ msg += `【${i.name}】
121
+ ${i.websites.join("\n")}
122
+ `;
123
+ });
124
+ msg += "\n=== 当前关键词监听 ===\n";
125
+ if (!config.Key.keywords.length) msg += "(暂无)\n";
126
+ else config.Key.keywords.forEach((k) => {
127
+ msg += `监听:${k.listening} → 回复:${k.reply}
128
+ `;
129
+ });
130
+ return msg.trimEnd();
131
+ });
132
+ const api = new import_tms.MapleStoryApi(config.apiKey);
133
+ ctx.command("%查询 <name:string>", "查询TMS角色信息").alias("%查詢").action(async ({ session }, name2) => {
134
+ if (!canUse(session, config.ms)) return "";
135
+ if (!name2) return "请提供角色名, 用法: %查询 角色名(仅限TMS)";
136
+ try {
137
+ const character = await api.getCharacter(name2);
138
+ const ocid = character.ocid;
139
+ if (!ocid) return "查询失败,请检查角色名";
140
+ const basic = await api.getCharacterBasic(ocid);
141
+ if (!basic) return "查询失败,请检查角色名";
142
+ const encoded = encodeURIComponent(name2);
143
+ return `${basic.characterName} (${basic.worldName}@${basic.characterGuildName || "无公会"})
144
+ ${basic.characterClass} | Lv.${basic.characterLevel} (${basic.characterExpRate + "%"})
145
+
146
+ 详细信息:
147
+ https://maplescouter.com/info?name=${encoded}`;
148
+ } catch (err) {
149
+ if (err.constructor.name === "MapleStoryApiError") {
150
+ return `查询失败,请检查角色名(仅限TMS)`;
151
+ }
152
+ return "查询失败,请稍后再试或联系开发者(布丁@2482457432 )";
153
+ }
154
+ });
155
+ ctx.command("%查岗 <name:string>", "查看角色最近7天经验变化").alias("%查崗").action(async ({ session }, name2) => {
156
+ if (!canUse(session, config.ms)) return "";
157
+ if (!name2) return "请提供角色名";
158
+ try {
159
+ let getTstDate = function(offsetDay) {
160
+ const d = new Date(tst.getTime() + offsetDay * 864e5);
161
+ d.setHours(0, 0, 0, 0);
162
+ return { year: d.getUTCFullYear(), month: d.getUTCMonth() + 1, day: d.getUTCDate() };
163
+ };
164
+ __name(getTstDate, "getTstDate");
165
+ const character = await api.getCharacter(name2);
166
+ const ocid = character.ocid;
167
+ if (!ocid) return "查询失败,请检查角色名";
168
+ const tst = new Date(Date.now() + 8 * 36e5);
169
+ const dates = [null];
170
+ for (let i = 1; i <= 7; i++) dates.push(getTstDate(-i));
171
+ const basics = [];
172
+ for (let i = 0; i < dates.length; i++) {
173
+ const date = dates[i];
174
+ const dateStr = date ? `${date.year}-${String(date.month).padStart(2, "0")}-${String(date.day).padStart(2, "0")}` : "latest";
175
+ try {
176
+ await new Promise((r) => setTimeout(r, config.ms.queryInterval ?? 100));
177
+ const b = date === null ? await api.getCharacterBasic(ocid) : await api.getCharacterBasic(ocid, date);
178
+ basics.push(b);
179
+ } catch (e) {
180
+ basics.push(null);
181
+ }
182
+ }
183
+ const valid = basics.filter((b) => b);
184
+ if (valid.length < 2) return "网络错误(";
185
+ const dailyDiffs = [];
186
+ for (let i = 0; i < valid.length - 1; i++) {
187
+ const curr = valid[i];
188
+ const prev = valid[i + 1];
189
+ if (curr.characterLevel === prev.characterLevel) {
190
+ dailyDiffs.push(Number(curr.characterExpRate) - Number(prev.characterExpRate));
191
+ }
192
+ }
193
+ const avgDiff = dailyDiffs.length ? dailyDiffs.reduce((a, b) => a + b, 0) / dailyDiffs.length : 0;
194
+ const avgDiffStr = avgDiff.toFixed(3);
195
+ const currentRate = Number(valid[0].characterExpRate);
196
+ const gap = 100 - currentRate;
197
+ const gapStr = gap.toFixed(3);
198
+ const predictDays = avgDiff <= 0 ? "∞" : Math.max(1, Math.ceil(gap / avgDiff)).toString();
199
+ const upgradeDate = new Date(Date.now() + parseInt(predictDays) * 864e5);
200
+ const upgradeStr = `${upgradeDate.getFullYear()}-${String(upgradeDate.getMonth() + 1).padStart(2, "0")}-${String(upgradeDate.getDate()).padStart(2, "0")}`;
201
+ const head = valid[0];
202
+ let lines = `${head.characterName}·${head.characterClass} (${head.worldName}@${head.characterGuildName || "无公会"})
203
+ 经验变化:
204
+ `;
205
+ for (let i = 0; i < valid.length - 1; i++) {
206
+ const curr = valid[i];
207
+ const prev = valid[i + 1];
208
+ if (curr.characterLevel > prev.characterLevel) {
209
+ lines += `${i === 0 ? "目 前" : `${i}天前`}: Lv.${curr.characterLevel} (${curr.characterExpRate}%)
210
+ `;
211
+ } else {
212
+ const diff = (Number(curr.characterExpRate) - Number(prev.characterExpRate)).toFixed(3);
213
+ const sign = diff.startsWith("-") ? "" : "+";
214
+ lines += `${i === 0 ? "目 前" : `${i}天前`}: Lv.${curr.characterLevel} (${curr.characterExpRate}%)[${sign}${diff}%]
215
+ `;
216
+ }
217
+ }
218
+ lines += `----------------------
219
+ 日均+${avgDiffStr}%/天
220
+ 预计升级还需: ${predictDays} 天
221
+ 预计升级日期: ${upgradeStr}
222
+
223
+ (如若升级则不计算日均增长,可能出现预计数据报错)
224
+ (当日数据可能不准确,下午6点完成更新)`;
225
+ const candidates = config.ms.images?.map((s) => s.trim()).filter(Boolean) || [];
226
+ const existFiles = candidates.map((f) => (0, import_path.resolve)(__dirname, f)).filter((f) => (0, import_fs.existsSync)(f));
227
+ if (existFiles.length) {
228
+ const picked = existFiles[Math.floor(Math.random() * existFiles.length)];
229
+ return [
230
+ lines.trimEnd(),
231
+ import_koishi.h.image((0, import_url.pathToFileURL)(picked).href)
232
+ ];
233
+ }
234
+ return lines.trimEnd();
235
+ } catch (err) {
236
+ if (err.constructor.name === "MapleStoryApiError") return "查询失败,请检查角色名(仅限TMS)";
237
+ return "查询失败,请稍后再试或联系开发者(布丁@2482457432 )";
238
+ }
239
+ });
240
+ }
241
+ __name(apply, "apply");
242
+ // Annotate the CommonJS export names for ESM import in node:
243
+ 0 && (module.exports = {
244
+ Config,
245
+ apply,
246
+ name
247
+ });
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "koishi-plugin-msbao",
3
+ "description": "Pudding's plugin",
4
+ "version": "0.0.10",
5
+ "contributors": [
6
+ "MilkteaDoll <https://github.com/MilkteaDoll>"
7
+ ],
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "git+https://github.com/MilkteaDoll/msbao.git"
11
+ },
12
+ "main": "lib/index.js",
13
+ "typings": "lib/index.d.ts",
14
+ "files": [
15
+ "lib",
16
+ "dist",
17
+ "LICENSE",
18
+ "*.png"
19
+ ],
20
+ "license": "MIT",
21
+ "keywords": [
22
+ "chatbot",
23
+ "plugin"
24
+ ],
25
+ "peerDependencies": {
26
+ "koishi": "^4.18.7"
27
+ },
28
+ "dependencies": {
29
+ "maplestory-openapi": "^3.4.1"
30
+ }
31
+ }
package/readme.md ADDED
@@ -0,0 +1,50 @@
1
+ # msbao
2
+
3
+ <!-- [![npm](https://img.shields.io/npm/v/koishi-plugin-msbao?style=flat-square)](https://www.npmjs.com/package/koishi-plugin-msbao) -->
4
+ <p align="center">
5
+
6
+ <img src="https://visitor-badge.laobi.icu/badge?page_id=MilkteaDoll.MilkteaDoll" alt="visitors"/>
7
+ <a href="https://www.npmjs.com/package/koishi-plugin-msbao"><img src="https://img.shields.io/npm/v/koishi-plugin-msbao?style=flat-square"></a>
8
+ </p>
9
+
10
+
11
+
12
+ # 主要功能为“%查岗”与“%查询”指令
13
+ - 指令“%查询 ID”查询TMS角色信息
14
+ - 指令“%查岗 ID”查看角色最近7天经验变化
15
+ ---
16
+ # whitelistMode
17
+ - 是否开启白名单模式(开启后仅白名单群生效)
18
+ ## whitelist
19
+ - 允许回复的群号(私聊不限制,私聊全会回复)
20
+ - ---
21
+ # admins
22
+ - 管理员QQ号 (不一定得是群管理,此功能暂时无效)
23
+ - ---
24
+ # URL.Lists
25
+ ### 自定义查询指令
26
+ - ### URL.lists[*].name
27
+ - 触发指令
28
+ - ### URL.lists[*].websites
29
+ - 执行回复的内容
30
+ - ### URL.lists[*].useGlobalwlist
31
+ - 是否套用全局白名单
32
+ - ### URL.lists[*].selfWhitelst
33
+ - 若不套用全局白名单则使用此处的独立白名单
34
+
35
+ *例如触发指令填写“%百度”,在执行回复内填写'www.baidu.com'*
36
+ *则如果群内有人发送“%百度”时机器人自动回复网站*
37
+ # Key.keywrods
38
+ ---
39
+ - 通过正则表达式检测群内聊天是否包含某词执行回复自定义内容
40
+ # apiKey
41
+ - Nexon-API 密钥
42
+ - ---
43
+ # ms.queryInterval
44
+ - 查岗时使用的sleep延迟,避免短时间大量查询导致崩溃(
45
+ - ---
46
+ # ms.images
47
+ - 查岗时最后附带上的图片,如填写多个则随机一张进行发送
48
+ - 图片放在lib目录下
49
+ - ---
50
+ *随便写的,能用就行*