koishi-plugin-chatluna-character 0.0.45 → 0.0.47

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.cjs ADDED
@@ -0,0 +1,1179 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
8
+ var __glob = (map) => (path4) => {
9
+ var fn = map[path4];
10
+ if (fn)
11
+ return fn();
12
+ throw new Error("Module not found in bundle: " + path4);
13
+ };
14
+ var __esm = (fn, res) => function __init() {
15
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
16
+ };
17
+ var __export = (target, all) => {
18
+ for (var name2 in all)
19
+ __defProp(target, name2, { get: all[name2], enumerable: true });
20
+ };
21
+ var __copyProps = (to, from, except, desc) => {
22
+ if (from && typeof from === "object" || typeof from === "function") {
23
+ for (let key of __getOwnPropNames(from))
24
+ if (!__hasOwnProp.call(to, key) && key !== except)
25
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
26
+ }
27
+ return to;
28
+ };
29
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
30
+ // If the importer is in node compatibility mode or this is not an ESM
31
+ // file that has been converted to a CommonJS file using a Babel-
32
+ // compatible transform (i.e. "__esModule" has not been set), then set
33
+ // "default" to the CommonJS "module.exports" for node compatibility.
34
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
35
+ mod
36
+ ));
37
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
38
+
39
+ // src/plugins/chat.ts
40
+ var chat_exports = {};
41
+ __export(chat_exports, {
42
+ apply: () => apply
43
+ });
44
+ async function apply(ctx, config) {
45
+ const service = ctx.chatluna_character;
46
+ const preset = service.preset;
47
+ const stickerService = service.stickerService;
48
+ logger = service.logger;
49
+ const modelPool = {};
50
+ const [platform, modelName] = (0, import_count_tokens.parseRawModelName)(config.model);
51
+ await ctx.chatluna.awaitLoadPlatform(platform);
52
+ const globalModel = await ctx.chatluna.createChatModel(
53
+ platform,
54
+ modelName
55
+ );
56
+ logger.info("global model loaded %c", config.model);
57
+ if (config.modelOverride?.length > 0) {
58
+ for (const override of config.modelOverride) {
59
+ modelPool[override.groupId] = (async () => {
60
+ const [platform2, modelName2] = (0, import_count_tokens.parseRawModelName)(override.model);
61
+ await ctx.chatluna.awaitLoadPlatform(platform2);
62
+ const loadedModel = await ctx.chatluna.createChatModel(
63
+ platform2,
64
+ modelName2
65
+ );
66
+ logger.info(
67
+ "override model loaded %c for group %c",
68
+ override.model,
69
+ override.groupId
70
+ );
71
+ modelPool[override.groupId] = Promise.resolve(loadedModel);
72
+ return loadedModel;
73
+ })();
74
+ }
75
+ }
76
+ const globalPreset = await preset.getPreset(config.defaultPreset);
77
+ const presetPool = {};
78
+ service.collect(async (session, messages) => {
79
+ const guildId = session.event.guild?.id ?? session.guildId;
80
+ const model = await (modelPool[guildId] ?? Promise.resolve(globalModel));
81
+ const currentGuildConfig = config.configs[guildId];
82
+ let copyOfConfig = Object.assign({}, config);
83
+ let currentPreset = globalPreset;
84
+ if (currentGuildConfig != null) {
85
+ copyOfConfig = Object.assign({}, copyOfConfig, currentGuildConfig);
86
+ currentPreset = presetPool[guildId] ?? (() => {
87
+ const template = preset.getPresetForCache(
88
+ currentGuildConfig.preset
89
+ );
90
+ presetPool[guildId] = template;
91
+ return template;
92
+ })();
93
+ }
94
+ const [recentMessage, lastMessage] = await formatMessage(
95
+ messages,
96
+ copyOfConfig,
97
+ model,
98
+ currentPreset.system.template,
99
+ currentPreset.system.template
100
+ );
101
+ const temp = await service.getTemp(session);
102
+ const formattedSystemPrompt = await currentPreset.system.format({
103
+ time: (/* @__PURE__ */ new Date()).toLocaleString()
104
+ });
105
+ logger.debug("messages_new: " + JSON.stringify(recentMessage));
106
+ logger.debug("messages_last: " + JSON.stringify(lastMessage));
107
+ const humanMessage = new import_messages.HumanMessage(
108
+ await currentPreset.input.format({
109
+ history_new: recentMessage,
110
+ history_last: lastMessage,
111
+ time: (/* @__PURE__ */ new Date()).toLocaleString()
112
+ })
113
+ );
114
+ const completionMessages = await formatCompletionMessages(
115
+ [new import_messages.SystemMessage(formattedSystemPrompt)].concat(
116
+ temp.completionMessages
117
+ ),
118
+ humanMessage,
119
+ copyOfConfig,
120
+ model
121
+ );
122
+ logger.debug(
123
+ "completion message: " + JSON.stringify(completionMessages.map((it) => it.content))
124
+ );
125
+ let responseMessage;
126
+ let parsedResponse;
127
+ let isError = false;
128
+ for (let i = 0; i < 3; i++) {
129
+ try {
130
+ responseMessage = await model.invoke(completionMessages);
131
+ logger.debug("model response: " + responseMessage.content);
132
+ parsedResponse = parseResponse(
133
+ responseMessage.content,
134
+ copyOfConfig.isAt
135
+ );
136
+ break;
137
+ } catch (e) {
138
+ logger.error(e);
139
+ await (0, import_koishi.sleep)(2e3);
140
+ if (i === 2) {
141
+ isError = true;
142
+ }
143
+ }
144
+ }
145
+ if (isError) {
146
+ return;
147
+ }
148
+ if (parsedResponse.elements.length < 1) {
149
+ service.mute(session, copyOfConfig.muteTime);
150
+ return;
151
+ }
152
+ temp.completionMessages.push(humanMessage, responseMessage);
153
+ if (temp.completionMessages.length > 8) {
154
+ while (temp.completionMessages.length <= 4) {
155
+ temp.completionMessages.shift();
156
+ }
157
+ }
158
+ const random = new import_koishi.Random();
159
+ for (const elements of parsedResponse.elements) {
160
+ const text = elements.map((element) => element.attrs.content ?? "").join("");
161
+ let maxTime = text.length * copyOfConfig.typingTime + 100;
162
+ if (parsedResponse.messageType === "voice" && isEmoticonStatement(text)) {
163
+ continue;
164
+ }
165
+ if (config.splitVoice !== true && parsedResponse.messageType === "voice") {
166
+ maxTime = parsedResponse.rawMessage.length * copyOfConfig.typingTime + 100;
167
+ await (0, import_koishi.sleep)(random.int(maxTime / 4, maxTime / 2));
168
+ await session.send(
169
+ await ctx.vits.say({ input: parsedResponse.rawMessage })
170
+ );
171
+ break;
172
+ }
173
+ try {
174
+ await (0, import_koishi.sleep)(random.int(maxTime / 2, maxTime));
175
+ switch (parsedResponse.messageType) {
176
+ case "text":
177
+ await session.send(elements);
178
+ break;
179
+ case "voice":
180
+ await session.send(
181
+ await ctx.vits.say({
182
+ input: text
183
+ })
184
+ );
185
+ break;
186
+ default:
187
+ await session.send(elements);
188
+ break;
189
+ }
190
+ } catch (e) {
191
+ logger.error(e);
192
+ await session.send(elements);
193
+ }
194
+ }
195
+ const sticker = await stickerService.randomStick();
196
+ if (sticker) {
197
+ await (0, import_koishi.sleep)(random.int(500, 2e3));
198
+ await session.send(sticker);
199
+ }
200
+ service.mute(session, copyOfConfig.coolDownTime * 1e3);
201
+ await service.broadcastOnBot(session, parsedResponse.elements.flat());
202
+ });
203
+ }
204
+ function isEmoticonStatement(text) {
205
+ const regex = /^[\p{P}\p{S}\p{Z}\p{M}\p{N}\p{L}\s]*\p{So}[\p{P}\p{S}\p{Z}\p{M}\p{N}\p{L}\s]*$/u;
206
+ return regex.test(text);
207
+ }
208
+ function parseResponse(response, useAt = true) {
209
+ let rawMessage;
210
+ let parsedMessage = "";
211
+ let messageType = "text";
212
+ try {
213
+ rawMessage = response.match(/<message>\s*(.*?)\s*<\/message>/)?.[1];
214
+ if (rawMessage == null) {
215
+ logger.debug("failed to parse response: " + response);
216
+ rawMessage = response.substring(
217
+ response.indexOf("{"),
218
+ response.lastIndexOf("}") + 1
219
+ );
220
+ }
221
+ const tempJson = JSON.parse(rawMessage);
222
+ rawMessage = tempJson.content;
223
+ messageType = tempJson.type;
224
+ if (typeof rawMessage !== "string") {
225
+ throw new Error("Failed to parse response: " + response);
226
+ }
227
+ } catch (e) {
228
+ logger.error(e);
229
+ throw new Error("Failed to parse response: " + response);
230
+ }
231
+ const resultElements = [];
232
+ const currentElements = [];
233
+ const atMatch = matchAt(rawMessage);
234
+ if (atMatch.length > 0) {
235
+ let lastAtIndex = 0;
236
+ for (const at of atMatch) {
237
+ const before = rawMessage.substring(lastAtIndex, at.start);
238
+ if (before.length > 0) {
239
+ parsedMessage += before;
240
+ currentElements.push(import_koishi.h.text(before));
241
+ }
242
+ if (useAt) {
243
+ currentElements.push(import_koishi.h.at(at.at));
244
+ }
245
+ lastAtIndex = at.end;
246
+ }
247
+ const after = rawMessage.substring(lastAtIndex);
248
+ if (after.length > 0) {
249
+ parsedMessage += after;
250
+ currentElements.push(import_koishi.h.text(after));
251
+ }
252
+ } else {
253
+ parsedMessage = rawMessage;
254
+ currentElements.push(import_koishi.h.text(rawMessage));
255
+ }
256
+ for (let currentElement of currentElements) {
257
+ if (currentElement.type === "text") {
258
+ const text = currentElement.attrs.content;
259
+ const matchArray = splitSentence(text).filter((x) => x.length > 0);
260
+ for (const match of matchArray) {
261
+ currentElement = import_koishi.h.text(match);
262
+ resultElements.push([currentElement]);
263
+ }
264
+ } else {
265
+ resultElements.push([currentElement]);
266
+ }
267
+ }
268
+ if (resultElements[0]?.[0]?.type === "at" && resultElements.length > 1) {
269
+ resultElements[1].unshift(import_koishi.h.text(" "));
270
+ resultElements[1].unshift(resultElements[0][0]);
271
+ resultElements.shift();
272
+ }
273
+ return {
274
+ elements: resultElements,
275
+ rawMessage: parsedMessage,
276
+ messageType
277
+ };
278
+ }
279
+ function splitSentence(text) {
280
+ const result = [];
281
+ const lines = text.split("\n").filter((line) => line.trim().length > 0).join(" ");
282
+ const state = {
283
+ bracket: 0,
284
+ text: 0
285
+ };
286
+ let current = "";
287
+ const punctuations = [
288
+ ",",
289
+ "。",
290
+ "?",
291
+ "!",
292
+ ";",
293
+ ":",
294
+ ",",
295
+ "?",
296
+ "!",
297
+ ";",
298
+ ":",
299
+ "、",
300
+ "~",
301
+ "—",
302
+ "\r"
303
+ ];
304
+ const retainPunctuations = ["?", "!", "?", "!", "~"];
305
+ const mustPunctuations = ["。", "?", "!", "?", "!", ":", ":"];
306
+ const brackets = ["【", "】", "《", "》", "(", ")", "(", ")"];
307
+ for (let index = 0; index < lines.length; index++) {
308
+ const char = lines[index];
309
+ const nextChar = lines?.[index + 1];
310
+ const indexOfBrackets = brackets.indexOf(char);
311
+ if (indexOfBrackets > -1) {
312
+ state.bracket += indexOfBrackets % 2 === 0 ? 1 : -1;
313
+ }
314
+ if (indexOfBrackets > -1 && state.bracket === 0 && state.text > 0) {
315
+ current += char;
316
+ result.push(current);
317
+ state.text = 0;
318
+ current = "";
319
+ continue;
320
+ } else if (indexOfBrackets % 2 === 0 && state.bracket === 1) {
321
+ result.push(current);
322
+ state.text = 0;
323
+ current = char;
324
+ continue;
325
+ } else if (state.bracket > 0) {
326
+ current += char;
327
+ state.text++;
328
+ continue;
329
+ }
330
+ if (!punctuations.includes(char)) {
331
+ current += char;
332
+ continue;
333
+ }
334
+ if (retainPunctuations.includes(char)) {
335
+ current += char;
336
+ }
337
+ if (retainPunctuations.indexOf(nextChar) % 2 === 0 && retainPunctuations.indexOf(char) % 2 === 1) {
338
+ index += 1;
339
+ }
340
+ if (current.length < 1) {
341
+ continue;
342
+ }
343
+ if (current.length > 2 || mustPunctuations.includes(char)) {
344
+ result.push(current.trimStart().trimEnd());
345
+ current = "";
346
+ } else if (!retainPunctuations.includes(char)) {
347
+ current += char;
348
+ }
349
+ }
350
+ if (current.length > 0) {
351
+ result.push(current.trimStart().trimEnd());
352
+ }
353
+ return result.filter((item) => punctuations.indexOf(item) === -1);
354
+ }
355
+ function matchAt(str) {
356
+ const atRegex = /([((]).*-(\d+)-<at>([))])/g;
357
+ return [...str.matchAll(atRegex)].map((item) => {
358
+ return {
359
+ at: item[2],
360
+ start: item.index,
361
+ end: item.index + item[0].length
362
+ };
363
+ });
364
+ }
365
+ async function formatCompletionMessages(messages, humanMessage, config, model) {
366
+ const maxTokens = config.maxTokens - 600;
367
+ const systemMessage = messages.shift();
368
+ let currentTokens = 0;
369
+ currentTokens += await model.getNumTokens(systemMessage.content);
370
+ currentTokens += await model.getNumTokens(humanMessage.content);
371
+ const result = [];
372
+ result.unshift(humanMessage);
373
+ for (let index = messages.length - 1; index >= 0; index--) {
374
+ const message = messages[index];
375
+ const messageTokens = await model.getNumTokens(
376
+ message.content
377
+ );
378
+ if (currentTokens + messageTokens > maxTokens) {
379
+ break;
380
+ }
381
+ currentTokens += messageTokens;
382
+ result.unshift(message);
383
+ }
384
+ logger.debug(`maxTokens: ${maxTokens}, currentTokens: ${currentTokens}`);
385
+ result.unshift(systemMessage);
386
+ return result;
387
+ }
388
+ async function formatMessage(messages, config, model, systemPrompt, historyPrompt) {
389
+ const maxTokens = config.maxTokens - 300;
390
+ let currentTokens = 0;
391
+ currentTokens += await model.getNumTokens(systemPrompt);
392
+ currentTokens += await model.getNumTokens(historyPrompt);
393
+ const calculatedMessages = [];
394
+ const random = new import_koishi.Random();
395
+ for (let i = messages.length - 1; i >= 0; i--) {
396
+ const message = messages[i];
397
+ const voiceProbability = random.int(1, 10) > 5 ? "voice" : "text";
398
+ const jsonMessage = `{"name":"${message.name}","id":"${message.id}","content":${JSON.stringify(
399
+ message.content
400
+ )}","type":"${voiceProbability}"}`;
401
+ const jsonMessageToken = await model.getNumTokens(jsonMessage);
402
+ if (currentTokens + jsonMessageToken > maxTokens - 4) {
403
+ break;
404
+ }
405
+ currentTokens += jsonMessageToken;
406
+ calculatedMessages.unshift(jsonMessage);
407
+ }
408
+ const lastMessage = calculatedMessages.pop();
409
+ if (lastMessage === void 0) {
410
+ throw new Error(
411
+ "lastMessage is undefined, please set the max token to be bigger"
412
+ );
413
+ }
414
+ return [calculatedMessages, lastMessage];
415
+ }
416
+ var import_messages, import_koishi, import_count_tokens, logger;
417
+ var init_chat = __esm({
418
+ "src/plugins/chat.ts"() {
419
+ import_messages = require("@langchain/core/messages");
420
+ import_koishi = require("koishi");
421
+ import_count_tokens = require("koishi-plugin-chatluna/llm-core/utils/count_tokens");
422
+ __name(apply, "apply");
423
+ __name(isEmoticonStatement, "isEmoticonStatement");
424
+ __name(parseResponse, "parseResponse");
425
+ __name(splitSentence, "splitSentence");
426
+ __name(matchAt, "matchAt");
427
+ __name(formatCompletionMessages, "formatCompletionMessages");
428
+ __name(formatMessage, "formatMessage");
429
+ }
430
+ });
431
+
432
+ // src/plugins/filter.ts
433
+ var filter_exports = {};
434
+ __export(filter_exports, {
435
+ apply: () => apply2,
436
+ groupInfos: () => groupInfos
437
+ });
438
+ async function apply2(ctx, config) {
439
+ const maxMessages = config.messageInterval;
440
+ const service = ctx.chatluna_character;
441
+ const preset = service.preset;
442
+ const logger2 = service.logger;
443
+ const globalPreset = await preset.getPreset(config.defaultPreset);
444
+ const presetPool = {};
445
+ service.addFilter((session, message) => {
446
+ const guildId = session.guildId;
447
+ const info = groupInfos[guildId] || {
448
+ messageCount: 0,
449
+ messageSendProbability: 1
450
+ };
451
+ const currentGuildConfig = config.configs[guildId];
452
+ let copyOfConfig = Object.assign({}, config);
453
+ let currentPreset = globalPreset;
454
+ if (currentGuildConfig != null) {
455
+ copyOfConfig = Object.assign({}, copyOfConfig, currentGuildConfig);
456
+ currentPreset = presetPool[guildId] ?? (() => {
457
+ const template = preset.getPresetForCache(
458
+ currentGuildConfig.preset
459
+ );
460
+ presetPool[guildId] = template;
461
+ return template;
462
+ })();
463
+ }
464
+ let { messageCount, messageSendProbability } = info;
465
+ logger2.debug(
466
+ `messageCount: ${messageCount}, messageSendProbability: ${messageSendProbability}. content: ${JSON.stringify(
467
+ message
468
+ )}`
469
+ );
470
+ if (copyOfConfig.disableChatLuna && copyOfConfig.whiteListDisableChatLuna.includes(guildId)) {
471
+ const selfId = session.bot.userId ?? session.bot.selfId ?? "0";
472
+ const guildMessages = ctx.chatluna_character.getMessages(guildId);
473
+ let maxRecentMessage = 0;
474
+ if (guildMessages == null || guildMessages.length === 0) {
475
+ maxRecentMessage = 6;
476
+ }
477
+ while (maxRecentMessage < 5) {
478
+ const currentMessage = guildMessages[guildMessages?.length - 1 - maxRecentMessage];
479
+ if (currentMessage == null) {
480
+ return false;
481
+ }
482
+ if (currentMessage.id === selfId) {
483
+ break;
484
+ }
485
+ maxRecentMessage++;
486
+ }
487
+ }
488
+ if (copyOfConfig.isForceMute && session.stripped.appel && currentPreset.mute_keyword?.length > 0) {
489
+ const needMute = currentPreset.mute_keyword.some(
490
+ (value) => message.content.includes(value)
491
+ );
492
+ if (needMute) {
493
+ logger2.debug(`mute content: ${message.content}`);
494
+ service.mute(session, config.muteTime);
495
+ }
496
+ }
497
+ const isMute = service.isMute(session);
498
+ if ((messageCount > maxMessages || messageSendProbability > 1 || session.stripped.appel || config.isNickname && currentPreset.nick_name.some(
499
+ (value) => message.content.startsWith(value)
500
+ )) && !isMute) {
501
+ info.messageCount = 0;
502
+ info.messageSendProbability = 1;
503
+ groupInfos[session.guildId] = info;
504
+ return true;
505
+ }
506
+ if (Math.random() > messageSendProbability && !isMute) {
507
+ info.messageCount = 0;
508
+ info.messageSendProbability = 1;
509
+ groupInfos[session.guildId] = info;
510
+ return true;
511
+ }
512
+ messageCount++;
513
+ messageSendProbability -= 1 / maxMessages * copyOfConfig.messageProbability;
514
+ info.messageCount = messageCount;
515
+ info.messageSendProbability = messageSendProbability;
516
+ groupInfos[session.guildId] = info;
517
+ return false;
518
+ });
519
+ }
520
+ var groupInfos;
521
+ var init_filter = __esm({
522
+ "src/plugins/filter.ts"() {
523
+ groupInfos = {};
524
+ __name(apply2, "apply");
525
+ }
526
+ });
527
+
528
+ // src/plugins/commands.ts
529
+ var commands_exports = {};
530
+ __export(commands_exports, {
531
+ apply: () => apply3
532
+ });
533
+ function apply3(ctx, config) {
534
+ ctx.command("chatluna.character", "角色扮演相关命令");
535
+ ctx.command(
536
+ "chatluna.character.clear [group]",
537
+ "清除群组的聊天记录"
538
+ ).action(async ({ session }, group) => {
539
+ if (!session.isDirect) {
540
+ return;
541
+ }
542
+ const groupId = group ?? session.guildId;
543
+ if (!groupId) {
544
+ return "请检查你是否提供了群组 id";
545
+ }
546
+ const groupInfo = groupInfos[groupId];
547
+ if (!groupInfo) {
548
+ return "没有找到群组信息";
549
+ }
550
+ groupInfo.messageCount = 0;
551
+ groupInfo.messageSendProbability = 0;
552
+ ctx.chatluna_character.clear(groupId);
553
+ return `已清除群组 ${groupId} 的聊天记录`;
554
+ });
555
+ }
556
+ var init_commands = __esm({
557
+ "src/plugins/commands.ts"() {
558
+ init_filter();
559
+ __name(apply3, "apply");
560
+ }
561
+ });
562
+
563
+ // src/plugins/config.ts
564
+ var config_exports = {};
565
+ __export(config_exports, {
566
+ apply: () => apply4
567
+ });
568
+ async function apply4(ctx, config) {
569
+ ctx.on("chatluna/model-added", async (service) => {
570
+ ctx.schema.set("model", import_koishi2.Schema.union(await getModelNames(service)));
571
+ });
572
+ ctx.on("chatluna/model-removed", async (service) => {
573
+ ctx.schema.set("model", import_koishi2.Schema.union(await getModelNames(service)));
574
+ });
575
+ ctx.schema.set(
576
+ "model",
577
+ import_koishi2.Schema.union(await getModelNames(ctx.chatluna.platform))
578
+ );
579
+ }
580
+ async function getModelNames(service) {
581
+ return service.getAllModels(import_types.ModelType.llm).map((m) => import_koishi2.Schema.const(m));
582
+ }
583
+ var import_koishi2, import_types;
584
+ var init_config = __esm({
585
+ "src/plugins/config.ts"() {
586
+ import_koishi2 = require("koishi");
587
+ import_types = require("koishi-plugin-chatluna/llm-core/platform/types");
588
+ __name(apply4, "apply");
589
+ __name(getModelNames, "getModelNames");
590
+ }
591
+ });
592
+
593
+ // src/plugins/interception.ts
594
+ var interception_exports = {};
595
+ __export(interception_exports, {
596
+ apply: () => apply5
597
+ });
598
+ function apply5(ctx, config) {
599
+ ctx.on("chatluna/before-check-sender", async (session) => {
600
+ const guildId = session.guildId;
601
+ if (!config.applyGroup.includes(guildId) || session.isDirect || !session.stripped.appel) {
602
+ return false;
603
+ }
604
+ if (config.disableChatLuna && config.whiteListDisableChatLuna?.includes(guildId)) {
605
+ const selfId = session.bot.userId ?? session.bot.selfId ?? "0";
606
+ const guildMessages = ctx.chatluna_character.getMessages(guildId);
607
+ if (guildMessages == null || guildMessages.length === 0) {
608
+ return true;
609
+ }
610
+ let maxRecentMessage = 0;
611
+ while (maxRecentMessage < 3) {
612
+ const currentMessage = guildMessages[guildMessages?.length - 1 - maxRecentMessage];
613
+ if (currentMessage == null) {
614
+ return false;
615
+ }
616
+ if (currentMessage.id === selfId) {
617
+ return true;
618
+ }
619
+ maxRecentMessage++;
620
+ }
621
+ }
622
+ return config.disableChatLuna;
623
+ });
624
+ }
625
+ var init_interception = __esm({
626
+ "src/plugins/interception.ts"() {
627
+ __name(apply5, "apply");
628
+ }
629
+ });
630
+
631
+ // src/index.ts
632
+ var src_exports = {};
633
+ __export(src_exports, {
634
+ Config: () => Config,
635
+ apply: () => apply6,
636
+ inject: () => inject,
637
+ name: () => name
638
+ });
639
+ module.exports = __toCommonJS(src_exports);
640
+ var import_koishi6 = require("koishi");
641
+
642
+ // src/plugin.ts
643
+ var import_promises = __toESM(require("fs/promises"));
644
+ var import_path = __toESM(require("path"));
645
+
646
+ // import("./plugins/**/*.ts") in src/plugin.ts
647
+ var globImport_plugins_ts = __glob({
648
+ "./plugins/chat.ts": () => Promise.resolve().then(() => (init_chat(), chat_exports)),
649
+ "./plugins/commands.ts": () => Promise.resolve().then(() => (init_commands(), commands_exports)),
650
+ "./plugins/config.ts": () => Promise.resolve().then(() => (init_config(), config_exports)),
651
+ "./plugins/filter.ts": () => Promise.resolve().then(() => (init_filter(), filter_exports)),
652
+ "./plugins/interception.ts": () => Promise.resolve().then(() => (init_interception(), interception_exports))
653
+ });
654
+
655
+ // src/plugin.ts
656
+ async function plugins(ctx, config) {
657
+ const list = await import_promises.default.readdir(import_path.default.join(__dirname, "plugins"));
658
+ for (let file of list) {
659
+ if (file.endsWith(".d.ts")) {
660
+ file = file.slice(0, -5);
661
+ }
662
+ const command = await globImport_plugins_ts(`./plugins/${file}.ts`);
663
+ if (command.apply) {
664
+ await command.apply(ctx, config);
665
+ }
666
+ }
667
+ }
668
+ __name(plugins, "plugins");
669
+
670
+ // src/service/message.ts
671
+ var import_events = __toESM(require("events"));
672
+ var import_koishi5 = require("koishi");
673
+ var import_logger = require("koishi-plugin-chatluna/utils/logger");
674
+
675
+ // src/preset.ts
676
+ var import_prompts = require("@langchain/core/prompts");
677
+ var import_promises2 = __toESM(require("fs/promises"));
678
+ var import_js_yaml = require("js-yaml");
679
+ var import_koishi3 = require("koishi");
680
+ var import_error = require("koishi-plugin-chatluna/utils/error");
681
+ var import_path2 = __toESM(require("path"));
682
+ var Preset = class {
683
+ constructor(ctx) {
684
+ this.ctx = ctx;
685
+ }
686
+ static {
687
+ __name(this, "Preset");
688
+ }
689
+ _presets = [];
690
+ async loadAllPreset() {
691
+ await this._checkPresetDir();
692
+ const presetDir = this.resolvePresetDir();
693
+ const files = await import_promises2.default.readdir(presetDir);
694
+ this._presets.length = 0;
695
+ for (const file of files) {
696
+ const extension = import_path2.default.extname(file);
697
+ if (extension !== ".yml") {
698
+ continue;
699
+ }
700
+ const rawText = await import_promises2.default.readFile(
701
+ import_path2.default.join(presetDir, file),
702
+ "utf-8"
703
+ );
704
+ const preset = loadPreset(rawText);
705
+ preset.path = import_path2.default.join(presetDir, file);
706
+ this._presets.push(preset);
707
+ }
708
+ this.ctx.schema.set(
709
+ "character-preset",
710
+ import_koishi3.Schema.union(
711
+ this._presets.map((preset) => preset.name).concat("无").map((name2) => import_koishi3.Schema.const(name2))
712
+ )
713
+ );
714
+ }
715
+ async getPreset(triggerKeyword, loadForDisk = true, throwError = true) {
716
+ if (loadForDisk) {
717
+ await this.loadAllPreset();
718
+ }
719
+ return this.getPresetForCache(triggerKeyword, throwError);
720
+ }
721
+ getPresetForCache(triggerKeyword, throwError = true) {
722
+ const preset = this._presets.find(
723
+ (preset2) => preset2.name === triggerKeyword
724
+ );
725
+ if (preset) {
726
+ return preset;
727
+ }
728
+ if (throwError) {
729
+ throw new import_error.ChatLunaError(
730
+ import_error.ChatLunaErrorCode.PREST_NOT_FOUND,
731
+ new Error(`No preset found for keyword ${triggerKeyword}`)
732
+ );
733
+ }
734
+ return void 0;
735
+ }
736
+ async getDefaultPreset() {
737
+ if (this._presets.length === 0) {
738
+ await this.loadAllPreset();
739
+ }
740
+ const preset = this._presets.find((preset2) => preset2.name === "默认");
741
+ if (preset) {
742
+ return preset;
743
+ } else {
744
+ await this._copyDefaultPresets();
745
+ return this.getDefaultPreset();
746
+ }
747
+ }
748
+ async getAllPreset() {
749
+ await this.loadAllPreset();
750
+ return this._presets.map((preset) => preset.name);
751
+ }
752
+ async resetDefaultPreset() {
753
+ await this._copyDefaultPresets();
754
+ }
755
+ resolvePresetDir() {
756
+ return import_path2.default.resolve(this.ctx.baseDir, "data/chathub/character/presets");
757
+ }
758
+ async _checkPresetDir() {
759
+ const presetDir = import_path2.default.join(this.resolvePresetDir());
760
+ try {
761
+ await import_promises2.default.access(presetDir);
762
+ } catch (err) {
763
+ if (err.code === "ENOENT") {
764
+ await import_promises2.default.mkdir(presetDir, { recursive: true });
765
+ await this._copyDefaultPresets();
766
+ } else {
767
+ throw err;
768
+ }
769
+ }
770
+ }
771
+ async _copyDefaultPresets() {
772
+ const currentPresetDir = import_path2.default.join(this.resolvePresetDir());
773
+ const defaultPresetDir = import_path2.default.join(__dirname, "../resources/presets");
774
+ const files = await import_promises2.default.readdir(defaultPresetDir);
775
+ for (const file of files) {
776
+ const filePath = import_path2.default.join(defaultPresetDir, file);
777
+ const fileStat = await import_promises2.default.stat(filePath);
778
+ if (fileStat.isFile()) {
779
+ await import_promises2.default.mkdir(currentPresetDir, { recursive: true });
780
+ this.ctx.chatluna_character.logger.debug(
781
+ `copy preset file ${filePath} to ${currentPresetDir}`
782
+ );
783
+ await import_promises2.default.copyFile(filePath, import_path2.default.join(currentPresetDir, file));
784
+ }
785
+ }
786
+ }
787
+ };
788
+ function loadPreset(text) {
789
+ const rawPreset = (0, import_js_yaml.load)(text);
790
+ return {
791
+ name: rawPreset.name,
792
+ nick_name: rawPreset.nick_name,
793
+ input: import_prompts.PromptTemplate.fromTemplate(rawPreset.input),
794
+ system: import_prompts.PromptTemplate.fromTemplate(rawPreset.system),
795
+ mute_keyword: rawPreset.mute_keyword ?? []
796
+ };
797
+ }
798
+ __name(loadPreset, "loadPreset");
799
+
800
+ // src/service/sticker.ts
801
+ var import_koishi4 = require("koishi");
802
+ var import_path3 = __toESM(require("path"));
803
+ var import_promises3 = __toESM(require("fs/promises"));
804
+ var StickerService = class {
805
+ constructor(_ctx, _config) {
806
+ this._ctx = _ctx;
807
+ this._config = _config;
808
+ }
809
+ static {
810
+ __name(this, "StickerService");
811
+ }
812
+ _stickers;
813
+ async init() {
814
+ const sickerDir = import_path3.default.resolve(
815
+ this._ctx.baseDir,
816
+ "data/chathub/character/sticker"
817
+ );
818
+ try {
819
+ await import_promises3.default.access(sickerDir);
820
+ } catch (error) {
821
+ await import_promises3.default.mkdir(sickerDir, { recursive: true });
822
+ await import_promises3.default.cp(
823
+ import_path3.default.resolve(__dirname, "../../resources/sticker"),
824
+ sickerDir,
825
+ {
826
+ recursive: true
827
+ }
828
+ );
829
+ }
830
+ const files = await import_promises3.default.readdir(sickerDir);
831
+ this._stickers = files.map((file) => import_path3.default.resolve(sickerDir, file));
832
+ }
833
+ async randomStick() {
834
+ const random = Math.random();
835
+ if (random >= this._config.sendStickerProbability) {
836
+ return void 0;
837
+ }
838
+ const index = Math.floor(Math.random() * this._stickers.length);
839
+ const sticker = this._stickers[index];
840
+ if (!sticker) {
841
+ return void 0;
842
+ }
843
+ this._ctx.root.chatluna_character.logger.debug(
844
+ `send sticker: ${sticker}`
845
+ );
846
+ return import_koishi4.h.image(await (0, import_promises3.readFile)(sticker), `image/${getFileType(sticker)}`);
847
+ }
848
+ };
849
+ function getFileType(path4) {
850
+ const type = path4.split(".").pop().toLocaleLowerCase();
851
+ if (type === "jpg") {
852
+ return "jpeg";
853
+ }
854
+ return type;
855
+ }
856
+ __name(getFileType, "getFileType");
857
+
858
+ // src/service/message.ts
859
+ var MessageCollector = class extends import_koishi5.Service {
860
+ constructor(ctx, _config) {
861
+ super(ctx, "chatluna_character");
862
+ this.ctx = ctx;
863
+ this._config = _config;
864
+ this.stickerService = new StickerService(ctx, _config);
865
+ this.logger = (0, import_logger.createLogger)(ctx, "chatluna-character");
866
+ this.preset = new Preset(ctx);
867
+ }
868
+ static {
869
+ __name(this, "MessageCollector");
870
+ }
871
+ _messages = {};
872
+ _eventEmitter = new import_events.default();
873
+ _filters = [];
874
+ _groupLocks = {};
875
+ _groupTemp = {};
876
+ stickerService;
877
+ preset;
878
+ addFilter(filter) {
879
+ this._filters.push(filter);
880
+ }
881
+ mute(session, time) {
882
+ const lock = this._getGroupLocks(session.guildId);
883
+ let mute = lock.mute ?? 0;
884
+ if (mute < (/* @__PURE__ */ new Date()).getTime()) {
885
+ mute = (/* @__PURE__ */ new Date()).getTime() + time;
886
+ } else {
887
+ mute = mute + time;
888
+ }
889
+ lock.mute = mute;
890
+ }
891
+ collect(func) {
892
+ this._eventEmitter.on("collect", func);
893
+ }
894
+ getMessages(groupId) {
895
+ return this._messages[groupId];
896
+ }
897
+ isMute(session) {
898
+ const lock = this._getGroupLocks(session.guildId);
899
+ return lock.mute > (/* @__PURE__ */ new Date()).getTime();
900
+ }
901
+ async updateTemp(session, temp) {
902
+ await this._lock(session);
903
+ const groupId = session.guildId;
904
+ this._groupTemp[groupId] = temp;
905
+ await this._unlock(session);
906
+ }
907
+ async getTemp(session) {
908
+ await this._lock(session);
909
+ const groupId = session.guildId;
910
+ const temp = this._groupTemp[groupId] ?? {
911
+ completionMessages: []
912
+ };
913
+ this._groupTemp[groupId] = temp;
914
+ await this._unlock(session);
915
+ return temp;
916
+ }
917
+ _getGroupLocks(groupId) {
918
+ if (!this._groupLocks[groupId]) {
919
+ this._groupLocks[groupId] = {
920
+ lock: false,
921
+ mute: 0
922
+ };
923
+ }
924
+ return this._groupLocks[groupId];
925
+ }
926
+ _lock(session) {
927
+ const groupLock = this._getGroupLocks(session.guildId);
928
+ return new Promise((resolve) => {
929
+ const interval = setInterval(() => {
930
+ if (!groupLock.lock) {
931
+ groupLock.lock = true;
932
+ clearInterval(interval);
933
+ resolve();
934
+ }
935
+ }, 100);
936
+ });
937
+ }
938
+ _unlock(session) {
939
+ const groupLock = this._getGroupLocks(session.guildId);
940
+ return new Promise((resolve) => {
941
+ const interval = setInterval(() => {
942
+ if (groupLock.lock) {
943
+ groupLock.lock = false;
944
+ clearInterval(interval);
945
+ resolve();
946
+ }
947
+ }, 100);
948
+ });
949
+ }
950
+ clear(groupId) {
951
+ if (groupId) {
952
+ this._messages[groupId] = [];
953
+ } else {
954
+ this._messages = {};
955
+ }
956
+ this._groupTemp[groupId] = {
957
+ completionMessages: []
958
+ };
959
+ }
960
+ async broadcastOnBot(session, elements) {
961
+ if (session.isDirect) {
962
+ return;
963
+ }
964
+ await this._lock(session);
965
+ const groupId = session.guildId;
966
+ const maxMessageSize = this._config.maxMessages;
967
+ const groupArray = this._messages[groupId] ? this._messages[groupId] : [];
968
+ const content = mapElementToString(session, session.content, elements);
969
+ if (content.length < 1) {
970
+ await this._unlock(session);
971
+ return;
972
+ }
973
+ const message = {
974
+ content,
975
+ name: session.bot.user.name,
976
+ id: session.bot.userId ?? session.bot.selfId ?? "0"
977
+ };
978
+ groupArray.push(message);
979
+ if (groupArray.length > maxMessageSize) {
980
+ while (groupArray.length > maxMessageSize) {
981
+ groupArray.shift();
982
+ }
983
+ }
984
+ this._messages[groupId] = groupArray;
985
+ await this._unlock(session);
986
+ }
987
+ async broadcast(session) {
988
+ if (session.isDirect) {
989
+ return;
990
+ }
991
+ await this._lock(session);
992
+ const groupId = session.guildId;
993
+ const maxMessageSize = this._config.maxMessages;
994
+ const groupArray = this._messages[groupId] ? this._messages[groupId] : [];
995
+ const elements = session.elements ? session.elements : [import_koishi5.h.text(session.content)];
996
+ const content = mapElementToString(session, session.content, elements);
997
+ if (content.length < 1) {
998
+ await this._unlock(session);
999
+ return;
1000
+ }
1001
+ const message = {
1002
+ content,
1003
+ name: session.author.name,
1004
+ id: session.author.id,
1005
+ quote: session.quote ? {
1006
+ content: mapElementToString(
1007
+ session,
1008
+ session.quote.content,
1009
+ // TODO: remove as
1010
+ session.quote.elements
1011
+ ),
1012
+ name: session.quote.user.name,
1013
+ id: session.quote.user.id
1014
+ } : void 0
1015
+ };
1016
+ groupArray.push(message);
1017
+ if (groupArray.length > maxMessageSize) {
1018
+ while (groupArray.length > maxMessageSize) {
1019
+ groupArray.shift();
1020
+ }
1021
+ }
1022
+ this._messages[groupId] = groupArray;
1023
+ if (this._filters.some((func) => func(session, message)) && !this.isMute(session)) {
1024
+ this._eventEmitter.emit("collect", session, groupArray);
1025
+ await this._unlock(session);
1026
+ return true;
1027
+ } else {
1028
+ await this._unlock(session);
1029
+ return this.isMute(session);
1030
+ }
1031
+ }
1032
+ };
1033
+ function mapElementToString(session, content, elements) {
1034
+ const filteredBuffer = [];
1035
+ if (content.trimEnd().length < 1) {
1036
+ return "";
1037
+ }
1038
+ for (const element of elements) {
1039
+ if (element.type === "text") {
1040
+ const content2 = element.attrs.content;
1041
+ if (content2?.trimEnd()?.length > 0) {
1042
+ filteredBuffer.push(content2);
1043
+ }
1044
+ } else if (element.type === "at") {
1045
+ let name2 = element.attrs?.name;
1046
+ if (element.attrs.id === session.bot.selfId) {
1047
+ name2 = name2 ?? session.bot.user.name ?? "0";
1048
+ }
1049
+ if (name2 == null) {
1050
+ name2 = element.attrs.id ?? "0";
1051
+ }
1052
+ filteredBuffer.push(`(${name2}-${element.attrs.id}-<at>)`);
1053
+ }
1054
+ }
1055
+ return filteredBuffer.join("");
1056
+ }
1057
+ __name(mapElementToString, "mapElementToString");
1058
+
1059
+ // src/index.ts
1060
+ function apply6(ctx, config) {
1061
+ const disposables = [];
1062
+ ctx.on("ready", async () => {
1063
+ ctx.plugin(MessageCollector, config);
1064
+ });
1065
+ ctx.plugin(
1066
+ {
1067
+ apply: async (ctx2, config2) => {
1068
+ await ctx2.chatluna_character.stickerService.init();
1069
+ await ctx2.chatluna_character.preset.loadAllPreset();
1070
+ await plugins(ctx2, config2);
1071
+ },
1072
+ inject: Object.assign({}, inject, {
1073
+ chatluna_character: {
1074
+ required: true
1075
+ }
1076
+ }),
1077
+ name: "chatluna_character_entry_point"
1078
+ },
1079
+ config
1080
+ );
1081
+ disposables.push(
1082
+ ctx.middleware(async (session, next) => {
1083
+ if (!ctx.chatluna_character) {
1084
+ return next();
1085
+ }
1086
+ if (ctx.bots[session.uid]) {
1087
+ return next();
1088
+ }
1089
+ const guildId = session.guildId;
1090
+ if (!config.applyGroup.includes(guildId)) {
1091
+ return next();
1092
+ }
1093
+ if (!await ctx.chatluna_character.broadcast(session)) {
1094
+ return next();
1095
+ }
1096
+ })
1097
+ );
1098
+ ctx.on("dispose", () => {
1099
+ disposables.forEach((disposable) => disposable());
1100
+ });
1101
+ }
1102
+ __name(apply6, "apply");
1103
+ var inject = {
1104
+ chatluna: {
1105
+ required: true
1106
+ },
1107
+ cache: {
1108
+ required: true
1109
+ },
1110
+ chatluna_character: {
1111
+ required: false
1112
+ },
1113
+ vits: {
1114
+ required: false
1115
+ }
1116
+ };
1117
+ var Config = import_koishi6.Schema.intersect([
1118
+ import_koishi6.Schema.object({
1119
+ applyGroup: import_koishi6.Schema.array(import_koishi6.Schema.string()).description("应用到的群组"),
1120
+ maxMessages: import_koishi6.Schema.number().description("存储在内存里的最大消息数量").default(10).min(3).role("slider").max(100),
1121
+ disableChatLuna: import_koishi6.Schema.boolean().default(true).description("在使用此插件的群聊里,是否禁用 ChatLuna 主功能"),
1122
+ whiteListDisableChatLuna: import_koishi6.Schema.array(import_koishi6.Schema.string()).description(
1123
+ "在使用此插件时,不禁用 ChatLuna 主功能的群聊列表"
1124
+ )
1125
+ }).description("基础配置"),
1126
+ import_koishi6.Schema.object({
1127
+ model: import_koishi6.Schema.dynamic("model").description("使用的模型"),
1128
+ modelOverride: import_koishi6.Schema.array(
1129
+ import_koishi6.Schema.object({
1130
+ groupId: import_koishi6.Schema.string().required().description("群组 ID"),
1131
+ model: import_koishi6.Schema.dynamic("model").description("模型")
1132
+ })
1133
+ ).description("针对某个群的模型设置,会覆盖上面的配置"),
1134
+ maxTokens: import_koishi6.Schema.number().default(4e3).min(1024).max(16e3).description("使用聊天的最大 token 数")
1135
+ }).description("模型配置"),
1136
+ import_koishi6.Schema.object({
1137
+ isNickname: import_koishi6.Schema.boolean().description("允许 bot 配置中的昵称引发回复").default(true),
1138
+ isForceMute: import_koishi6.Schema.boolean().description(
1139
+ "是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)"
1140
+ ).default(true),
1141
+ isAt: import_koishi6.Schema.boolean().description("是否允许 bot 艾特他人").default(true),
1142
+ splitVoice: import_koishi6.Schema.boolean().description("是否分段发送语言").default(false),
1143
+ messageInterval: import_koishi6.Schema.number().default(14).min(0).role("slider").max(100).description("随机发送消息的间隔"),
1144
+ messageProbability: import_koishi6.Schema.number().default(0.1).min(0).max(4).role("slider").step(1e-5).description("发送消息的叠加概率(线性增长)"),
1145
+ coolDownTime: import_koishi6.Schema.number().default(10).min(1).max(60 * 24).description("冷却发言时间(秒)"),
1146
+ typingTime: import_koishi6.Schema.number().default(440).min(100).role("slider").max(1500).description("模拟打字时的间隔(毫秒)"),
1147
+ muteTime: import_koishi6.Schema.number().default(1e3 * 60).min(1e3).max(1e3 * 60 * 10 * 10).description("闭嘴时的禁言时间(毫秒)"),
1148
+ sendStickerProbability: import_koishi6.Schema.number().default(0.6).min(0).max(1).role("slider").step(0.01).description("发送表情的概率"),
1149
+ defaultPreset: import_koishi6.Schema.dynamic("character-preset").description("使用的伪装预设").default("旧梦旧念")
1150
+ }).description("对话设置"),
1151
+ import_koishi6.Schema.object({
1152
+ configs: import_koishi6.Schema.dict(
1153
+ import_koishi6.Schema.object({
1154
+ maxTokens: import_koishi6.Schema.number().default(4e3).min(1024).max(2e4).description("使用聊天的最大 token 数"),
1155
+ isAt: import_koishi6.Schema.boolean().description("是否启用@").default(true),
1156
+ splitVoice: import_koishi6.Schema.boolean().description("是否分段发送语言").default(false),
1157
+ isNickname: import_koishi6.Schema.boolean().description("允许 bot 配置中的昵称引发回复").default(true),
1158
+ isForceMute: import_koishi6.Schema.boolean().description(
1159
+ "是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)"
1160
+ ).default(true),
1161
+ messageInterval: import_koishi6.Schema.number().default(10).min(0).role("slider").max(50).description("随机发送消息的间隔"),
1162
+ messageProbability: import_koishi6.Schema.number().default(0.1).min(0).max(1).role("slider").step(1e-5).description("发送消息的叠加概率(线性增长)"),
1163
+ coolDownTime: import_koishi6.Schema.number().default(10).min(1).max(60 * 24 * 24).description("冷却发言时间(秒)"),
1164
+ typingTime: import_koishi6.Schema.number().default(440).min(100).role("slider").max(1700).description("模拟打字时的间隔(毫秒)"),
1165
+ muteTime: import_koishi6.Schema.number().default(1e3 * 60).min(1e3).max(1e3 * 60 * 10 * 10).description("闭嘴时的禁言时间(毫秒)"),
1166
+ sendStickerProbability: import_koishi6.Schema.number().default(0.2).min(0).max(1).role("slider").step(0.01).description("发送表情的概率"),
1167
+ preset: import_koishi6.Schema.dynamic("character-preset").description("使用的伪装预设").default("旧梦旧念")
1168
+ })
1169
+ ).role("table").description("分群配置,会覆盖上面的默认配置(键填写群号)")
1170
+ }).description("分群配置")
1171
+ ]);
1172
+ var name = "chatluna-character";
1173
+ // Annotate the CommonJS export names for ESM import in node:
1174
+ 0 && (module.exports = {
1175
+ Config,
1176
+ apply,
1177
+ inject,
1178
+ name
1179
+ });