koishi-plugin-starfx-bot 0.7.0 → 0.9.0
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/lib/index.d.ts +27 -34
- package/lib/index.js +200 -142
- package/lib/utils.d.ts +131 -0
- package/package.json +1 -1
- package/readme.md +69 -18
package/lib/index.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { Context,
|
|
1
|
+
import { Context, Logger, Schema } from 'koishi';
|
|
2
2
|
export declare const name = "starfx-bot";
|
|
3
3
|
export declare let baseDir: string;
|
|
4
4
|
export declare let assetsDir: string;
|
|
@@ -8,68 +8,61 @@ export interface Config {
|
|
|
8
8
|
openLock: boolean;
|
|
9
9
|
openSold: boolean;
|
|
10
10
|
bangdreamBorder: boolean;
|
|
11
|
+
record: boolean;
|
|
12
|
+
roll: boolean;
|
|
11
13
|
atNotSay: boolean;
|
|
12
|
-
atNotSayOther: boolean;
|
|
13
14
|
atNotSayProperty: number;
|
|
15
|
+
atNotSayOther: boolean;
|
|
14
16
|
atNotSayOtherProperty: number;
|
|
15
|
-
|
|
17
|
+
iLoveYou: boolean;
|
|
18
|
+
replyBot: string;
|
|
16
19
|
openRepeat: boolean;
|
|
17
20
|
minRepeatTimes: number;
|
|
18
21
|
repeatPossibility: number;
|
|
19
|
-
|
|
20
|
-
iLoveYou: boolean;
|
|
22
|
+
featureControl: string;
|
|
21
23
|
}
|
|
22
24
|
export declare const Config: Schema<Schemastery.ObjectS<{
|
|
23
25
|
openLock: Schema<boolean, boolean>;
|
|
24
26
|
openSold: Schema<boolean, boolean>;
|
|
25
27
|
bangdreamBorder: Schema<boolean, boolean>;
|
|
28
|
+
}> | Schemastery.ObjectS<{
|
|
26
29
|
record: Schema<boolean, boolean>;
|
|
30
|
+
}> | Schemastery.ObjectS<{
|
|
31
|
+
roll: Schema<boolean, boolean>;
|
|
32
|
+
}> | Schemastery.ObjectS<{
|
|
27
33
|
atNotSay: Schema<boolean, boolean>;
|
|
28
34
|
atNotSayProperty: Schema<number, number>;
|
|
29
35
|
atNotSayOther: Schema<boolean, boolean>;
|
|
30
36
|
atNotSayOtherProperty: Schema<number, number>;
|
|
37
|
+
iLoveYou: Schema<boolean, boolean>;
|
|
38
|
+
replyBot: Schema<"关闭" | "无需at" | "必须at", "关闭" | "无需at" | "必须at">;
|
|
39
|
+
}> | Schemastery.ObjectS<{
|
|
31
40
|
openRepeat: Schema<boolean, boolean>;
|
|
32
41
|
minRepeatTimes: Schema<number, number>;
|
|
33
42
|
repeatPossibility: Schema<number, number>;
|
|
34
|
-
|
|
35
|
-
|
|
43
|
+
}> | Schemastery.ObjectS<{
|
|
44
|
+
featureControl: Schema<string, string>;
|
|
36
45
|
}>, {
|
|
37
46
|
openLock: boolean;
|
|
38
47
|
openSold: boolean;
|
|
39
48
|
bangdreamBorder: boolean;
|
|
49
|
+
} & import("cosmokit").Dict & {
|
|
40
50
|
record: boolean;
|
|
51
|
+
} & {
|
|
52
|
+
roll: boolean;
|
|
53
|
+
} & {
|
|
41
54
|
atNotSay: boolean;
|
|
42
55
|
atNotSayProperty: number;
|
|
43
56
|
atNotSayOther: boolean;
|
|
44
57
|
atNotSayOtherProperty: number;
|
|
58
|
+
iLoveYou: boolean;
|
|
59
|
+
replyBot: "关闭" | "无需at" | "必须at";
|
|
60
|
+
} & {
|
|
45
61
|
openRepeat: boolean;
|
|
46
62
|
minRepeatTimes: number;
|
|
47
63
|
repeatPossibility: number;
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
} & import("cosmokit").Dict>;
|
|
51
|
-
export declare const usage = "<h5>StarFreedomX\u7684\u81EA\u7528\u63D2\u4EF6 \u653E\u4E86\u4E00\u4E9B\u5C0F\u529F\u80FD</h5>\n ";
|
|
52
|
-
export declare function apply(ctx: Context, cfg: Config): void;
|
|
53
|
-
export declare function addRecord(ctx: Context, gid: string, avatarUrl: string): Promise<string>;
|
|
54
|
-
export declare function getRecord(gid: string): Promise<string>;
|
|
55
|
-
export declare function getImageSrc(session: Session, param: string, option?: {
|
|
56
|
-
number?: boolean;
|
|
57
|
-
img?: boolean;
|
|
58
|
-
at?: boolean;
|
|
59
|
-
noParam?: boolean;
|
|
60
|
-
quote?: boolean;
|
|
61
|
-
}): Promise<string>;
|
|
62
|
-
export declare function handleBanGDreamConfig(ctx: Context, options: any): Promise<{
|
|
63
|
-
color: string;
|
|
64
|
-
band: string;
|
|
65
|
-
starType: string;
|
|
66
|
-
starNum: number;
|
|
67
|
-
border: string;
|
|
64
|
+
} & {
|
|
65
|
+
featureControl: string;
|
|
68
66
|
}>;
|
|
69
|
-
export declare
|
|
70
|
-
|
|
71
|
-
* "卖掉了"绘图函数
|
|
72
|
-
* @param ctx
|
|
73
|
-
* @param baseImage
|
|
74
|
-
*/
|
|
75
|
-
export declare function drawSold(ctx: Context, baseImage: string): Promise<"发生错误" | "输入无效" | h>;
|
|
67
|
+
export declare const usage = "<h2>StarFreedomX\u7684\u81EA\u7528\u63D2\u4EF6 \u653E\u4E86\u4E00\u4E9B\u5C0F\u529F\u80FD</h2>\n ";
|
|
68
|
+
export declare function apply(ctx: Context, cfg: Config): void;
|
package/lib/index.js
CHANGED
|
@@ -33,153 +33,42 @@ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: tru
|
|
|
33
33
|
// src/locales/zh-CN.yml
|
|
34
34
|
var require_zh_CN = __commonJS({
|
|
35
35
|
"src/locales/zh-CN.yml"(exports2, module2) {
|
|
36
|
-
module2.exports = { commands:
|
|
36
|
+
module2.exports = { commands: { roll: { messages: { noParam: "请输入要随机的对象" } } }, middleware: { messages: { atNotReply: "艾特我又不说话", atNotReplyOther: "艾特他又不说话", notBot: "我才不是机器人!", loveMessage: "我喜欢你", iLoveU: "我也喜欢你" } } };
|
|
37
37
|
}
|
|
38
38
|
});
|
|
39
39
|
|
|
40
40
|
// src/index.ts
|
|
41
41
|
var src_exports = {};
|
|
42
42
|
__export(src_exports, {
|
|
43
|
-
Config: () =>
|
|
44
|
-
addRecord: () => addRecord,
|
|
43
|
+
Config: () => Config2,
|
|
45
44
|
apply: () => apply,
|
|
46
45
|
assetsDir: () => assetsDir,
|
|
47
46
|
baseDir: () => baseDir,
|
|
48
|
-
drawLock: () => drawLock,
|
|
49
|
-
drawSold: () => drawSold,
|
|
50
|
-
getImageSrc: () => getImageSrc,
|
|
51
|
-
getRecord: () => getRecord,
|
|
52
|
-
handleBanGDreamConfig: () => handleBanGDreamConfig,
|
|
53
47
|
name: () => name,
|
|
54
48
|
repeatContextMap: () => repeatContextMap,
|
|
55
49
|
starfxLogger: () => starfxLogger,
|
|
56
50
|
usage: () => usage
|
|
57
51
|
});
|
|
58
52
|
module.exports = __toCommonJS(src_exports);
|
|
53
|
+
var import_koishi2 = require("koishi");
|
|
54
|
+
var fs2 = __toESM(require("fs"));
|
|
55
|
+
|
|
56
|
+
// src/utils.ts
|
|
59
57
|
var import_koishi = require("koishi");
|
|
58
|
+
var import_fs = __toESM(require("fs"));
|
|
59
|
+
var import_node_path = __toESM(require("node:path"));
|
|
60
60
|
var import_sharp = __toESM(require("sharp"));
|
|
61
|
-
var fs = __toESM(require("fs"));
|
|
62
61
|
var import_jimp = require("jimp");
|
|
63
|
-
var import_node_path = __toESM(require("node:path"));
|
|
64
|
-
var name = "starfx-bot";
|
|
65
|
-
var baseDir;
|
|
66
|
-
var assetsDir;
|
|
67
|
-
var starfxLogger = new import_koishi.Logger("starfx-bot");
|
|
68
|
-
var repeatContextMap = /* @__PURE__ */ new Map();
|
|
69
|
-
var Config = import_koishi.Schema.intersect([
|
|
70
|
-
import_koishi.Schema.object({
|
|
71
|
-
openLock: import_koishi.Schema.boolean().default(true).description("开启明日方舟封印功能"),
|
|
72
|
-
openSold: import_koishi.Schema.boolean().default(true).description('开启闲鱼"卖掉了"功能'),
|
|
73
|
-
bangdreamBorder: import_koishi.Schema.boolean().default(true).description("开启BanG Dream!边框功能"),
|
|
74
|
-
record: import_koishi.Schema.boolean().default(true).description("开启群语录功能"),
|
|
75
|
-
atNotSay: import_koishi.Schema.boolean().default(true).description("开启‘艾特我又不说话’功能"),
|
|
76
|
-
atNotSayProperty: import_koishi.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.5).description("'艾特我又不说话'回复概率"),
|
|
77
|
-
atNotSayOther: import_koishi.Schema.boolean().default(true).description("开启‘艾特他又不说话’功能"),
|
|
78
|
-
atNotSayOtherProperty: import_koishi.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.5).description("'艾特他又不说话'回复概率"),
|
|
79
|
-
openRepeat: import_koishi.Schema.boolean().default(true).description("开启复读功能"),
|
|
80
|
-
minRepeatTimes: import_koishi.Schema.number().default(2).description("最少重复次数"),
|
|
81
|
-
repeatPossibility: import_koishi.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.3).description("复读发生概率"),
|
|
82
|
-
iLoveYou: import_koishi.Schema.boolean().default(true).description("开启‘我喜欢你’功能"),
|
|
83
|
-
replyBot: import_koishi.Schema.union(["关闭", "无需at", "必须at"]).default("无需at").description("回复‘我才不是机器人!’功能")
|
|
84
|
-
})
|
|
85
|
-
]);
|
|
86
|
-
var usage = `<h5>StarFreedomX的自用插件 放了一些小功能</h5>
|
|
87
|
-
`;
|
|
88
|
-
function apply(ctx, cfg) {
|
|
89
|
-
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
90
|
-
baseDir = ctx.baseDir;
|
|
91
|
-
assetsDir = `${ctx.baseDir}/data/starfx-bot/assets`;
|
|
92
|
-
initAssets();
|
|
93
|
-
if (cfg.openLock) {
|
|
94
|
-
ctx.command("封印 [param]").action(async ({ session }, param) => {
|
|
95
|
-
return await drawLock(ctx, await getImageSrc(session, param));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
98
|
-
if (cfg.openSold) {
|
|
99
|
-
ctx.command("卖掉了 [param]").action(async ({ session }, param) => {
|
|
100
|
-
return await drawSold(ctx, await getImageSrc(session, param));
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
if (cfg.bangdreamBorder) {
|
|
104
|
-
ctx.command("bangdreamborder [param]").alias("bdbd").option("starNum", "-n <starNum: number>").option("color", "-c <color: string>").option("train", "-t <train: string>").option("band", "-b <band: string>").action(async ({ session, options }, param) => {
|
|
105
|
-
session.send("图片处理中请稍等...");
|
|
106
|
-
console.log(param);
|
|
107
|
-
const drawConfig = await handleBanGDreamConfig(ctx, options);
|
|
108
|
-
const imageBase64 = await drawBanGDream(await getImageSrc(session, param), drawConfig);
|
|
109
|
-
return import_koishi.h.image(imageBase64);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
if (cfg.record) {
|
|
113
|
-
ctx.command("投稿 [param]").action(async ({ session }, param) => {
|
|
114
|
-
const imageSrc = await getImageSrc(
|
|
115
|
-
session,
|
|
116
|
-
param,
|
|
117
|
-
{
|
|
118
|
-
img: true,
|
|
119
|
-
at: false,
|
|
120
|
-
quote: true,
|
|
121
|
-
noParam: false,
|
|
122
|
-
number: false
|
|
123
|
-
}
|
|
124
|
-
);
|
|
125
|
-
if (!imageSrc) {
|
|
126
|
-
return "请发送带图片的指令消息或引用图片消息进行投稿";
|
|
127
|
-
}
|
|
128
|
-
return await addRecord(ctx, session.gid.replace(":", "_"), imageSrc);
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
ctx.middleware(async (session, next) => {
|
|
132
|
-
const elements = session.elements;
|
|
133
|
-
if (cfg.openRepeat) {
|
|
134
|
-
const gid = session.gid;
|
|
135
|
-
const content = session.content;
|
|
136
|
-
const groupMap = repeatContextMap.get(gid);
|
|
137
|
-
if (!groupMap || groupMap[0] !== content) {
|
|
138
|
-
repeatContextMap.set(gid, [content, 1]);
|
|
139
|
-
} else {
|
|
140
|
-
if (groupMap[1] !== -1 && ++groupMap[1] >= cfg.minRepeatTimes && import_koishi.Random.bool(cfg.repeatPossibility)) {
|
|
141
|
-
groupMap[1] = -1;
|
|
142
|
-
await session.send(content);
|
|
143
|
-
}
|
|
144
|
-
}
|
|
145
|
-
}
|
|
146
|
-
await atNotSayReply(cfg, session, elements);
|
|
147
|
-
await replyBot(cfg, session, elements);
|
|
148
|
-
await iLoveYou(cfg, session, elements);
|
|
149
|
-
if (cfg.record && session.content?.startsWith("语录")) {
|
|
150
|
-
const filepath = await getRecord(session.gid.replace(":", "_"));
|
|
151
|
-
starfxLogger.info(`send record: ${filepath}`);
|
|
152
|
-
if (!filepath) return "暂无语录呢";
|
|
153
|
-
return import_koishi.h.image(filepath);
|
|
154
|
-
}
|
|
155
|
-
return next();
|
|
156
|
-
});
|
|
157
|
-
function initAssets() {
|
|
158
|
-
const fromUrl = `${__dirname}/../assets`;
|
|
159
|
-
assetsDir = `${ctx.baseDir}/data/starfx-bot/assets`;
|
|
160
|
-
if (!fs.existsSync(fromUrl)) return;
|
|
161
|
-
if (!fs.readdirSync(fromUrl)?.length) return;
|
|
162
|
-
if (!fs.existsSync(assetsDir)) {
|
|
163
|
-
fs.mkdirSync(assetsDir, { recursive: true });
|
|
164
|
-
}
|
|
165
|
-
fs.cpSync(fromUrl, assetsDir, { recursive: true, force: true });
|
|
166
|
-
if (process.env.NODE_ENV !== "development") {
|
|
167
|
-
fs.rmSync(fromUrl, { recursive: true });
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
__name(initAssets, "initAssets");
|
|
171
|
-
}
|
|
172
|
-
__name(apply, "apply");
|
|
173
62
|
async function addRecord(ctx, gid, avatarUrl) {
|
|
174
63
|
const recordDir = `${assetsDir}/record/${gid}`;
|
|
175
64
|
const avatarBuffer = await ctx.http.get(avatarUrl, { responseType: "arraybuffer" });
|
|
176
65
|
saveImage(avatarBuffer, recordDir);
|
|
177
|
-
return "
|
|
66
|
+
return "投稿收到啦";
|
|
178
67
|
}
|
|
179
68
|
__name(addRecord, "addRecord");
|
|
180
69
|
async function getRecord(gid) {
|
|
181
70
|
const recordDir = `${assetsDir}/record/${gid}`;
|
|
182
|
-
const files =
|
|
71
|
+
const files = import_fs.default.readdirSync(recordDir);
|
|
183
72
|
return files?.length ? import_node_path.default.join(recordDir, import_koishi.Random.pick(files)) : null;
|
|
184
73
|
}
|
|
185
74
|
__name(getRecord, "getRecord");
|
|
@@ -191,9 +80,9 @@ function getTodayPrefix() {
|
|
|
191
80
|
return `${yyyy}${mm}${dd}`;
|
|
192
81
|
}
|
|
193
82
|
__name(getTodayPrefix, "getTodayPrefix");
|
|
194
|
-
function getNextSequenceNumber(directory, prefix) {
|
|
195
|
-
const files =
|
|
196
|
-
const regex = new RegExp(`^${prefix}-(\\d+)
|
|
83
|
+
function getNextSequenceNumber(directory, prefix, suffix = "jpg") {
|
|
84
|
+
const files = import_fs.default.existsSync(directory) ? import_fs.default.readdirSync(directory) : [];
|
|
85
|
+
const regex = new RegExp(`^${prefix}-(\\d+)\\.${suffix}$`);
|
|
197
86
|
let maxNum = 0;
|
|
198
87
|
files.forEach((file) => {
|
|
199
88
|
const match = file.match(regex);
|
|
@@ -207,16 +96,18 @@ function getNextSequenceNumber(directory, prefix) {
|
|
|
207
96
|
return maxNum + 1;
|
|
208
97
|
}
|
|
209
98
|
__name(getNextSequenceNumber, "getNextSequenceNumber");
|
|
210
|
-
function saveImage(arrayBuffer, directory) {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
99
|
+
function saveImage(arrayBuffer, directory, filename) {
|
|
100
|
+
if (!filename) {
|
|
101
|
+
const prefix = getTodayPrefix();
|
|
102
|
+
const seq = getNextSequenceNumber(directory, prefix);
|
|
103
|
+
filename = `${prefix}-${seq}.jpg`;
|
|
104
|
+
}
|
|
214
105
|
const filepath = import_node_path.default.join(directory, filename);
|
|
215
|
-
if (!
|
|
216
|
-
|
|
106
|
+
if (!import_fs.default.existsSync(directory)) {
|
|
107
|
+
import_fs.default.mkdirSync(directory, { recursive: true });
|
|
217
108
|
}
|
|
218
109
|
const buffer = Buffer.from(arrayBuffer);
|
|
219
|
-
|
|
110
|
+
import_fs.default.writeFileSync(filepath, buffer);
|
|
220
111
|
console.log(`已保存图片:${filepath}`);
|
|
221
112
|
}
|
|
222
113
|
__name(saveImage, "saveImage");
|
|
@@ -250,7 +141,7 @@ async function getImageSrc(session, param, option) {
|
|
|
250
141
|
return "";
|
|
251
142
|
}
|
|
252
143
|
__name(getImageSrc, "getImageSrc");
|
|
253
|
-
async function handleBanGDreamConfig(
|
|
144
|
+
async function handleBanGDreamConfig(options) {
|
|
254
145
|
const types = {
|
|
255
146
|
cool: ["cool", "blue", "蓝", "蓝色"],
|
|
256
147
|
powerful: ["powerful", "red", "红", "红色"],
|
|
@@ -278,7 +169,7 @@ async function handleBanGDreamConfig(ctx, options) {
|
|
|
278
169
|
starNum: 0,
|
|
279
170
|
border: ""
|
|
280
171
|
};
|
|
281
|
-
if (options
|
|
172
|
+
if (options?.color) {
|
|
282
173
|
for (const [type, aliases] of Object.entries(types)) {
|
|
283
174
|
if (aliases.includes(options.color.toLowerCase())) {
|
|
284
175
|
drawConfig.color = type;
|
|
@@ -286,7 +177,7 @@ async function handleBanGDreamConfig(ctx, options) {
|
|
|
286
177
|
}
|
|
287
178
|
}
|
|
288
179
|
}
|
|
289
|
-
if (options
|
|
180
|
+
if (options?.band) {
|
|
290
181
|
for (const [band, aliases] of Object.entries(bands)) {
|
|
291
182
|
if (aliases.includes(options.band.toLowerCase())) {
|
|
292
183
|
drawConfig.band = band;
|
|
@@ -294,9 +185,9 @@ async function handleBanGDreamConfig(ctx, options) {
|
|
|
294
185
|
}
|
|
295
186
|
}
|
|
296
187
|
}
|
|
297
|
-
const starNum = options
|
|
188
|
+
const starNum = options?.starNum ? parseInt(options.starNum) : 0;
|
|
298
189
|
drawConfig.starNum = starNum > 0 && starNum < 10 ? starNum : 0;
|
|
299
|
-
if (options
|
|
190
|
+
if (options?.train) {
|
|
300
191
|
for (const [train, aliases] of Object.entries(trains)) {
|
|
301
192
|
if (aliases.includes(options.train.toLowerCase())) {
|
|
302
193
|
drawConfig.starType = train;
|
|
@@ -459,18 +350,185 @@ async function drawBanGDream(avatar, inputOptions) {
|
|
|
459
350
|
return `data:image/png;base64,${(await image.getBuffer("image/jpeg")).toString("base64")}`;
|
|
460
351
|
}
|
|
461
352
|
__name(drawBanGDream, "drawBanGDream");
|
|
353
|
+
function parseJsonControl(text) {
|
|
354
|
+
try {
|
|
355
|
+
return JSON.parse(text);
|
|
356
|
+
} catch (e) {
|
|
357
|
+
starfxLogger.warn("[功能控制] JSON 解析失败");
|
|
358
|
+
return null;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
__name(parseJsonControl, "parseJsonControl");
|
|
362
|
+
function detectControl(controlJson, guildId, funName) {
|
|
363
|
+
const rule = controlJson?.[funName];
|
|
364
|
+
if (!rule || rule.whitelist === void 0 || !Array.isArray(rule.groups)) {
|
|
365
|
+
return true;
|
|
366
|
+
}
|
|
367
|
+
const inList = rule.groups.includes(Number(guildId));
|
|
368
|
+
return rule.whitelist ? inList : !inList;
|
|
369
|
+
}
|
|
370
|
+
__name(detectControl, "detectControl");
|
|
371
|
+
|
|
372
|
+
// src/index.ts
|
|
373
|
+
var name = "starfx-bot";
|
|
374
|
+
var baseDir;
|
|
375
|
+
var assetsDir;
|
|
376
|
+
var starfxLogger = new import_koishi2.Logger("starfx-bot");
|
|
377
|
+
var repeatContextMap = /* @__PURE__ */ new Map();
|
|
378
|
+
var Config2 = import_koishi2.Schema.intersect([
|
|
379
|
+
import_koishi2.Schema.object({
|
|
380
|
+
openLock: import_koishi2.Schema.boolean().default(true).description("开启明日方舟封印功能"),
|
|
381
|
+
openSold: import_koishi2.Schema.boolean().default(true).description('开启闲鱼"卖掉了"功能'),
|
|
382
|
+
bangdreamBorder: import_koishi2.Schema.boolean().default(true).description("开启BanG Dream!边框功能")
|
|
383
|
+
}).description("绘图功能"),
|
|
384
|
+
import_koishi2.Schema.object({
|
|
385
|
+
record: import_koishi2.Schema.boolean().default(true).description("开启群语录功能")
|
|
386
|
+
//saveArchive: Schema.boolean().default(false).description('开启入典功能').hidden(),
|
|
387
|
+
}).description("语录记录功能"),
|
|
388
|
+
import_koishi2.Schema.object({
|
|
389
|
+
roll: import_koishi2.Schema.boolean().default(false).description("开启roll随机数功能").hidden()
|
|
390
|
+
}).description("指令小功能"),
|
|
391
|
+
import_koishi2.Schema.object({
|
|
392
|
+
atNotSay: import_koishi2.Schema.boolean().default(true).description("开启‘艾特我又不说话’功能"),
|
|
393
|
+
atNotSayProperty: import_koishi2.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.5).description("'艾特我又不说话'回复概率"),
|
|
394
|
+
atNotSayOther: import_koishi2.Schema.boolean().default(true).description("开启‘艾特他又不说话’功能"),
|
|
395
|
+
atNotSayOtherProperty: import_koishi2.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.5).description("'艾特他又不说话'回复概率"),
|
|
396
|
+
iLoveYou: import_koishi2.Schema.boolean().default(true).description("开启‘我喜欢你’功能"),
|
|
397
|
+
replyBot: import_koishi2.Schema.union(["关闭", "无需at", "必须at"]).default("无需at").description("回复‘我才不是机器人!’功能")
|
|
398
|
+
}).description("特定回应功能"),
|
|
399
|
+
import_koishi2.Schema.object({
|
|
400
|
+
openRepeat: import_koishi2.Schema.boolean().default(true).description("开启复读功能"),
|
|
401
|
+
minRepeatTimes: import_koishi2.Schema.number().default(2).description("最少重复次数"),
|
|
402
|
+
repeatPossibility: import_koishi2.Schema.number().role("slider").min(0).max(1).step(0.01).default(0.3).description("复读发生概率")
|
|
403
|
+
}).description("复读功能"),
|
|
404
|
+
import_koishi2.Schema.object({
|
|
405
|
+
featureControl: import_koishi2.Schema.string().role("textarea", { rows: [15] }).default("{\n\n}").description(`黑/白名单配置,语法为JSON格式(可以不缩进),<br>
|
|
406
|
+
可配置功能键及语法详见 [项目地址](https://github.com/StarFreedomX/starfx-bot)或[npm发布页](https://www.npmjs.com/package/koishi-plugin-bangdream-ccg)`)
|
|
407
|
+
}).description("高级配置")
|
|
408
|
+
]);
|
|
409
|
+
var usage = `<h2>StarFreedomX的自用插件 放了一些小功能</h2>
|
|
410
|
+
`;
|
|
411
|
+
function apply(ctx, cfg) {
|
|
412
|
+
ctx.i18n.define("zh-CN", require_zh_CN());
|
|
413
|
+
baseDir = ctx.baseDir;
|
|
414
|
+
assetsDir = `${ctx.baseDir}/data/starfx-bot/assets`;
|
|
415
|
+
initAssets();
|
|
416
|
+
const controlJson = parseJsonControl(cfg.featureControl);
|
|
417
|
+
if (cfg.openLock) {
|
|
418
|
+
ctx.command("封印 [param]").action(async ({ session }, param) => {
|
|
419
|
+
if (detectControl(controlJson, session.guildId, "lock"))
|
|
420
|
+
return await drawLock(ctx, await getImageSrc(session, param));
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
if (cfg.openSold) {
|
|
424
|
+
ctx.command("卖掉了 [param]").action(async ({ session }, param) => {
|
|
425
|
+
if (detectControl(controlJson, session.guildId, "sold"))
|
|
426
|
+
return await drawSold(ctx, await getImageSrc(session, param));
|
|
427
|
+
});
|
|
428
|
+
}
|
|
429
|
+
if (cfg.roll) {
|
|
430
|
+
ctx.command("roll <param:text>").action(async ({ session }, param) => {
|
|
431
|
+
if (detectControl(controlJson, session.guildId, "roll")) {
|
|
432
|
+
if (!param) return session.text(".noParam");
|
|
433
|
+
if (param.endsWith("的概率")) {
|
|
434
|
+
}
|
|
435
|
+
const parts = param.split(/(?:\s+|还是)+/).filter(Boolean);
|
|
436
|
+
if (parts.length > 1) {
|
|
437
|
+
} else {
|
|
438
|
+
const items = param.split("r");
|
|
439
|
+
if (items.length === 2) {
|
|
440
|
+
const num = parseInt(items[0]);
|
|
441
|
+
const noodles = parseInt(items[1]);
|
|
442
|
+
}
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
}
|
|
447
|
+
if (cfg.bangdreamBorder) {
|
|
448
|
+
ctx.command("bdbd [param]").option("starNum", "-n <starNum: number>").option("color", "-c <color: string>").option("train", "-t <train: string>").option("band", "-b <band: string>").action(async ({ session, options }, param) => {
|
|
449
|
+
if (detectControl(controlJson, session.guildId, "bdbd")) {
|
|
450
|
+
const p = session.send("图片处理中请稍等...");
|
|
451
|
+
console.log(param);
|
|
452
|
+
const drawConfig = await handleBanGDreamConfig(options);
|
|
453
|
+
const imageBase64 = await drawBanGDream(await getImageSrc(session, param), drawConfig);
|
|
454
|
+
await p;
|
|
455
|
+
return import_koishi2.h.image(imageBase64);
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
}
|
|
459
|
+
if (cfg.record) {
|
|
460
|
+
ctx.command("投稿 [param]").action(async ({ session }, param) => {
|
|
461
|
+
if (detectControl(controlJson, session.guildId, "record")) {
|
|
462
|
+
const imageSrc = await getImageSrc(
|
|
463
|
+
session,
|
|
464
|
+
param,
|
|
465
|
+
{
|
|
466
|
+
img: true,
|
|
467
|
+
at: false,
|
|
468
|
+
quote: true,
|
|
469
|
+
noParam: false,
|
|
470
|
+
number: false
|
|
471
|
+
}
|
|
472
|
+
);
|
|
473
|
+
if (!imageSrc) {
|
|
474
|
+
return "请发送带图片的指令消息或引用图片消息进行投稿";
|
|
475
|
+
}
|
|
476
|
+
return await addRecord(ctx, session.gid.replace(":", "_"), imageSrc);
|
|
477
|
+
}
|
|
478
|
+
});
|
|
479
|
+
ctx.command("语录").action(async ({ session }) => {
|
|
480
|
+
if (detectControl(controlJson, session.guildId, "record")) {
|
|
481
|
+
const filepath = await getRecord(session.gid.replace(":", "_"));
|
|
482
|
+
starfxLogger.info(`send record: ${filepath}`);
|
|
483
|
+
if (!filepath) return "暂无语录呢";
|
|
484
|
+
return import_koishi2.h.image(filepath);
|
|
485
|
+
}
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
ctx.middleware(async (session, next) => {
|
|
489
|
+
const elements = session.elements;
|
|
490
|
+
if (cfg.openRepeat && detectControl(controlJson, session.guildId, "repeat")) {
|
|
491
|
+
const content = session.content;
|
|
492
|
+
const ctxArr = repeatContextMap.get(session.gid);
|
|
493
|
+
if (!ctxArr || ctxArr[0] !== content) {
|
|
494
|
+
repeatContextMap.set(session.gid, [content, 1]);
|
|
495
|
+
} else {
|
|
496
|
+
if (ctxArr[1] !== -1 && ++ctxArr[1] >= cfg.minRepeatTimes && import_koishi2.Random.bool(cfg.repeatPossibility)) {
|
|
497
|
+
ctxArr[1] = -1;
|
|
498
|
+
await session.send(content);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
}
|
|
502
|
+
if (detectControl(controlJson, session.guildId, "atNotSay"))
|
|
503
|
+
await atNotSayReply(cfg, session, elements);
|
|
504
|
+
if (detectControl(controlJson, session.guildId, "replyBot"))
|
|
505
|
+
await replyBot(cfg, session, elements);
|
|
506
|
+
if (detectControl(controlJson, session.guildId, "iLoveYou"))
|
|
507
|
+
await iLoveYou(cfg, session, elements);
|
|
508
|
+
return next();
|
|
509
|
+
});
|
|
510
|
+
function initAssets() {
|
|
511
|
+
const fromUrl = `${__dirname}/../assets`;
|
|
512
|
+
assetsDir = `${ctx.baseDir}/data/starfx-bot/assets`;
|
|
513
|
+
if (!fs2.existsSync(fromUrl)) return;
|
|
514
|
+
if (!fs2.readdirSync(fromUrl)?.length) return;
|
|
515
|
+
if (!fs2.existsSync(assetsDir)) {
|
|
516
|
+
fs2.mkdirSync(assetsDir, { recursive: true });
|
|
517
|
+
}
|
|
518
|
+
fs2.cpSync(fromUrl, assetsDir, { recursive: true, force: true });
|
|
519
|
+
if (process.env.NODE_ENV !== "development") {
|
|
520
|
+
fs2.rmSync(fromUrl, { recursive: true });
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
__name(initAssets, "initAssets");
|
|
524
|
+
}
|
|
525
|
+
__name(apply, "apply");
|
|
462
526
|
// Annotate the CommonJS export names for ESM import in node:
|
|
463
527
|
0 && (module.exports = {
|
|
464
528
|
Config,
|
|
465
|
-
addRecord,
|
|
466
529
|
apply,
|
|
467
530
|
assetsDir,
|
|
468
531
|
baseDir,
|
|
469
|
-
drawLock,
|
|
470
|
-
drawSold,
|
|
471
|
-
getImageSrc,
|
|
472
|
-
getRecord,
|
|
473
|
-
handleBanGDreamConfig,
|
|
474
532
|
name,
|
|
475
533
|
repeatContextMap,
|
|
476
534
|
starfxLogger,
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
import { Context, h, Session } from "koishi";
|
|
2
|
+
import sharp from "sharp";
|
|
3
|
+
import { Config } from "./index";
|
|
4
|
+
interface FeatureControl {
|
|
5
|
+
[feature: string]: {
|
|
6
|
+
whitelist: boolean;
|
|
7
|
+
groups: number[];
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/**
|
|
11
|
+
* 添加投稿
|
|
12
|
+
* @param ctx Context
|
|
13
|
+
* @param gid 当前群组的gid,注意需要把:替换为_等其它字符
|
|
14
|
+
* @param avatarUrl 图片的Url网络地址
|
|
15
|
+
*/
|
|
16
|
+
export declare function addRecord(ctx: Context, gid: string, avatarUrl: string): Promise<string>;
|
|
17
|
+
/**
|
|
18
|
+
* 从当前群组的语录中随机获取一张,同样需要把gid的:替换为_
|
|
19
|
+
* @param gid
|
|
20
|
+
* @return 图片的文件路径
|
|
21
|
+
*/
|
|
22
|
+
export declare function getRecord(gid: string): Promise<string>;
|
|
23
|
+
/**
|
|
24
|
+
* 返回当前日期,格式为yyyyMMdd
|
|
25
|
+
* @return 日期
|
|
26
|
+
*/
|
|
27
|
+
export declare function getTodayPrefix(): string;
|
|
28
|
+
/**
|
|
29
|
+
* 获取当前目录下特定`前缀+{数字}.后缀名`的{数字}的最大值+1
|
|
30
|
+
* @param directory 检索的目录
|
|
31
|
+
* @param prefix 前缀
|
|
32
|
+
* @param suffix 后缀(可选,默认jpg)
|
|
33
|
+
* @return 当前被使用的序列最大值+1
|
|
34
|
+
*/
|
|
35
|
+
export declare function getNextSequenceNumber(directory: string, prefix: string, suffix?: string): number;
|
|
36
|
+
/**
|
|
37
|
+
* 保存图片到本地
|
|
38
|
+
* @param arrayBuffer 传入的buffer,注意是arraybuffer
|
|
39
|
+
* @param directory 保存的目录
|
|
40
|
+
* @param filename 文件名 默认为yyyyMMdd-{num}.jpg
|
|
41
|
+
*/
|
|
42
|
+
export declare function saveImage(arrayBuffer: ArrayBuffer, directory: string, filename?: string): void;
|
|
43
|
+
/**
|
|
44
|
+
* 通过session、param判断获取图片的url
|
|
45
|
+
* @param session 当前会话的Session
|
|
46
|
+
* @param param 当前指令传入的param
|
|
47
|
+
* @param option 获取选项
|
|
48
|
+
*
|
|
49
|
+
* @param option.number 通过发送消息param的数字转换为qq头像
|
|
50
|
+
* @param option.img 是否检测发送消息中携带的字符串
|
|
51
|
+
* @param option.at 是否检测发送消息所at对象返回qq头像
|
|
52
|
+
* @param option.noParam 是否在没有参数时使用自身头像
|
|
53
|
+
* @param option.quote 是否包括引用的消息
|
|
54
|
+
* @return 图片url
|
|
55
|
+
*/
|
|
56
|
+
export declare function getImageSrc(session: Session, param: string, option?: {
|
|
57
|
+
number?: boolean;
|
|
58
|
+
img?: boolean;
|
|
59
|
+
at?: boolean;
|
|
60
|
+
noParam?: boolean;
|
|
61
|
+
quote?: boolean;
|
|
62
|
+
}): Promise<string>;
|
|
63
|
+
/**
|
|
64
|
+
* 将options的参数转换为drawBanGDreamBorder的配置
|
|
65
|
+
* @param options
|
|
66
|
+
* @return 适用于边框绘制的参数
|
|
67
|
+
*/
|
|
68
|
+
export declare function handleBanGDreamConfig(options: any): Promise<{
|
|
69
|
+
color: string;
|
|
70
|
+
band: string;
|
|
71
|
+
starType: string;
|
|
72
|
+
starNum: number;
|
|
73
|
+
border: string;
|
|
74
|
+
}>;
|
|
75
|
+
/**
|
|
76
|
+
* "封印"绘图功能
|
|
77
|
+
* @param ctx Context
|
|
78
|
+
* @param baseImage 被封印的图片url
|
|
79
|
+
* @return 画完的图片 h对象
|
|
80
|
+
*/
|
|
81
|
+
export declare function drawLock(ctx: Context, baseImage: string): Promise<"发生错误" | "输入无效" | h>;
|
|
82
|
+
/**
|
|
83
|
+
* "卖掉了"绘图函数
|
|
84
|
+
* @param ctx
|
|
85
|
+
* @param baseImage
|
|
86
|
+
* @return 画完的图片 h对象
|
|
87
|
+
*/
|
|
88
|
+
export declare function drawSold(ctx: Context, baseImage: string): Promise<"发生错误" | "输入无效" | h>;
|
|
89
|
+
/**
|
|
90
|
+
* 从url下载图片并返回sharp对象
|
|
91
|
+
* @param ctx Context
|
|
92
|
+
* @param url 要下载的图片url
|
|
93
|
+
* @return sharp对象或数字 -2代表url为空 -1代表下载异常
|
|
94
|
+
*/
|
|
95
|
+
export declare function getImageFromUrl(ctx: Context, url: string): Promise<-2 | -1 | sharp.Sharp>;
|
|
96
|
+
/**
|
|
97
|
+
* at不说话功能实现
|
|
98
|
+
* @param cfg Koishi插件配置
|
|
99
|
+
* @param session 当前会话Session对象
|
|
100
|
+
* @param elements 当前消息elements
|
|
101
|
+
*/
|
|
102
|
+
export declare function atNotSayReply(cfg: Config, session: Session, elements: h[]): Promise<void>;
|
|
103
|
+
/**
|
|
104
|
+
* “我才不是机器人”回复功能
|
|
105
|
+
* @param cfg Koishi插件配置
|
|
106
|
+
* @param session 当前会话Session
|
|
107
|
+
* @param elements 当前消息elements
|
|
108
|
+
*/
|
|
109
|
+
export declare function replyBot(cfg: Config, session: Session, elements: h[]): Promise<void>;
|
|
110
|
+
/**
|
|
111
|
+
* 回复“我喜欢你”功能
|
|
112
|
+
* @param cfg Koishi插件配置
|
|
113
|
+
* @param session 当前会话Session
|
|
114
|
+
* @param elements 当前消息elements
|
|
115
|
+
*/
|
|
116
|
+
export declare function iLoveYou(cfg: Config, session: Session, elements: h[]): Promise<void>;
|
|
117
|
+
/**
|
|
118
|
+
* BanG Dream!边框绘制功能
|
|
119
|
+
* @param avatar 要绘制的底图
|
|
120
|
+
* @param inputOptions 输入参数,接受color band starType starNum border
|
|
121
|
+
*/
|
|
122
|
+
export declare function drawBanGDream(avatar: string, inputOptions?: {
|
|
123
|
+
color: string;
|
|
124
|
+
band: string;
|
|
125
|
+
starType: string;
|
|
126
|
+
starNum: number;
|
|
127
|
+
border: string;
|
|
128
|
+
}): Promise<string>;
|
|
129
|
+
export declare function parseJsonControl(text: string): FeatureControl | null;
|
|
130
|
+
export declare function detectControl(controlJson: FeatureControl, guildId: string, funName: string): boolean;
|
|
131
|
+
export {};
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -4,28 +4,79 @@
|
|
|
4
4
|
|
|
5
5
|
StarFreedomX机器人的小功能,自用
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
## 已加入的功能列表:
|
|
8
8
|
|
|
9
9
|
* 明日方舟封印功能
|
|
10
10
|
* 闲鱼“卖掉了”功能
|
|
11
11
|
* 艾特我/他又不说话
|
|
12
12
|
* 我才不是机器人!
|
|
13
13
|
* 我也喜欢你!
|
|
14
|
+
* BanG Dream!边框绘制
|
|
15
|
+
* 群语录
|
|
16
|
+
* 复读
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
18
|
+
## List to Do
|
|
19
|
+
|
|
20
|
+
* 为每种功能添加单独的群聊控制开关
|
|
21
|
+
* 修改BanG Dream!边框的绘制为使用sharp,加快速度
|
|
22
|
+
* 增加入典功能
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
## 🔧 功能权限控制(可选)
|
|
26
|
+
|
|
27
|
+
本插件支持为各个功能设置 **群聊白名单 / 黑名单**,用于控制不同功能在指定群聊中是否启用。
|
|
28
|
+
|
|
29
|
+
配置格式为 JSON
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
- `whitelist: true`:启用白名单模式,仅允许列出的群使用该功能
|
|
33
|
+
- `whitelist: false`:启用黑名单模式,禁止列出的群使用该功能
|
|
34
|
+
- `groups`:群号数组,必须为数字
|
|
35
|
+
|
|
36
|
+
若未配置某功能项,则默认所有群均可使用。
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
### 📌 可配置功能键一览
|
|
41
|
+
|
|
42
|
+
| 键名 | 功能说明 |
|
|
43
|
+
|------------|----------------------------------------|
|
|
44
|
+
| `lock` | 明日方舟封印功能(对应 `openLock`) |
|
|
45
|
+
| `sold` | 闲鱼“卖掉了”功能(对应 `openSold`) |
|
|
46
|
+
| `repeat` | 群复读功能(对应 `openRepeat`) |
|
|
47
|
+
| `record` | 群语录功能(对应 `投稿、语录`) |
|
|
48
|
+
| `atNotSay` | “艾特我/他又不说话”系列功能 |
|
|
49
|
+
| `replyBot` | “我才不是机器人!”系列功能 |
|
|
50
|
+
| `iLoveYou` | “我也喜欢你”系列功能 |
|
|
51
|
+
| `bdbd` | BanG Dream! 边框功能(对应 `bangdreamBorder`) |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
### 🧪 示例:仅允许特定群使用 `roll`,禁止某群使用 `sold`
|
|
56
|
+
|
|
57
|
+
```json
|
|
58
|
+
{
|
|
59
|
+
"roll": {
|
|
60
|
+
"whitelist": true,
|
|
61
|
+
"groups": [123456789,114514191]
|
|
62
|
+
},
|
|
63
|
+
"sold": {
|
|
64
|
+
"whitelist": false,
|
|
65
|
+
"groups": [987654321]
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## 更新日志
|
|
71
|
+
| 版本 | 更新日志 |
|
|
72
|
+
|---------|-------------------------------|
|
|
73
|
+
| `0.0.1` | 加入封印功能 |
|
|
74
|
+
| `0.1.0` | 加入"卖掉了"功能 |
|
|
75
|
+
| `0.2.0` | 加入"艾特我又不说话"回复功能 |
|
|
76
|
+
| `0.3.0` | 加入"我喜欢你"和"我才不是机器人!"回复功能 |
|
|
77
|
+
| `0.4.0` | 更改处理库为sharp |
|
|
78
|
+
| `0.5.0` | 加入BanG Dream!边框功能 |
|
|
79
|
+
| `0.6.0` | 加入群语录功能,修复一处bangdream绘制的bug |
|
|
80
|
+
| `0.7.0` | 加入复读功能 |
|
|
81
|
+
| `0.8.0` | 将各个工具函数封装在utils.ts中,更改语录的触发方式 |
|
|
82
|
+
| `0.9.0` | 添加白名单/黑名单模式,对每个功能做过滤 |
|