koishi-plugin-chatluna-character 0.0.41 → 0.0.43

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 CHANGED
@@ -10,6 +10,7 @@ export interface Config extends ChatLunaPlugin.Config {
10
10
  model: string;
11
11
  maxMessages: number;
12
12
  messageInterval: number;
13
+ messageProbability: number;
13
14
  maxTokens: number;
14
15
  applyGroup: string[];
15
16
  modelOverride: {
@@ -26,6 +27,8 @@ export interface Config extends ChatLunaPlugin.Config {
26
27
  muteTime: number;
27
28
  disableChatLuna: boolean;
28
29
  whiteListDisableChatLuna: string[];
30
+ splitVoice: boolean;
31
+ isAt: boolean;
29
32
  }
30
33
  export declare const Config: Schema<Config>;
31
34
  export declare const name = "chatluna-character";
package/lib/index.js CHANGED
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.name = exports.Config = exports.inject = exports.apply = void 0;
3
+ exports.name = exports.Config = exports.inject = void 0;
4
+ exports.apply = apply;
4
5
  /* eslint-disable max-len */
5
6
  const koishi_1 = require("koishi");
6
7
  const plugin_1 = require("./plugin");
@@ -17,7 +18,8 @@ function apply(ctx, config) {
17
18
  await (0, plugin_1.plugins)(ctx, config);
18
19
  },
19
20
  inject: {
20
- required: exports.inject.required.concat('chatluna', 'chatluna_character')
21
+ required: exports.inject.required.concat('chatluna_character'),
22
+ optional: exports.inject.optional
21
23
  },
22
24
  name: 'chatluna_entry_point'
23
25
  }, config);
@@ -41,10 +43,9 @@ function apply(ctx, config) {
41
43
  disposables.forEach((disposable) => disposable());
42
44
  });
43
45
  }
44
- exports.apply = apply;
45
46
  exports.inject = {
46
47
  required: ['chatluna', 'cache'],
47
- optional: ['chatluna_character']
48
+ optional: ['chatluna_character', 'vits']
48
49
  };
49
50
  exports.Config = koishi_1.Schema.intersect([
50
51
  koishi_1.Schema.object({
@@ -52,7 +53,7 @@ exports.Config = koishi_1.Schema.intersect([
52
53
  maxMessages: koishi_1.Schema.number()
53
54
  .description('存储在内存里的最大消息数量')
54
55
  .default(10)
55
- .min(7)
56
+ .min(3)
56
57
  .role('slider')
57
58
  .max(100),
58
59
  disableChatLuna: koishi_1.Schema.boolean()
@@ -67,7 +68,7 @@ exports.Config = koishi_1.Schema.intersect([
67
68
  model: koishi_1.Schema.dynamic('model').description('模型')
68
69
  })).description('针对某个群的模型设置,会覆盖上面的配置'),
69
70
  maxTokens: koishi_1.Schema.number()
70
- .default(2048)
71
+ .default(4000)
71
72
  .min(1024)
72
73
  .max(16000)
73
74
  .description('使用聊天的最大 token 数')
@@ -79,12 +80,25 @@ exports.Config = koishi_1.Schema.intersect([
79
80
  isForceMute: koishi_1.Schema.boolean()
80
81
  .description('是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)')
81
82
  .default(true),
83
+ isAt: koishi_1.Schema.boolean()
84
+ .description('是否允许 bot 艾特他人')
85
+ .default(true),
86
+ splitVoice: koishi_1.Schema.boolean()
87
+ .description('是否分段发送语言')
88
+ .default(false),
82
89
  messageInterval: koishi_1.Schema.number()
83
90
  .default(14)
84
91
  .min(0)
85
92
  .role('slider')
86
- .max(50)
93
+ .max(100)
87
94
  .description('随机发送消息的间隔'),
95
+ messageProbability: koishi_1.Schema.number()
96
+ .default(0.1)
97
+ .min(0)
98
+ .max(4)
99
+ .role('slider')
100
+ .step(0.00001)
101
+ .description('发送消息的叠加概率(线性增长)'),
88
102
  coolDownTime: koishi_1.Schema.number()
89
103
  .default(10)
90
104
  .min(1)
@@ -115,10 +129,14 @@ exports.Config = koishi_1.Schema.intersect([
115
129
  koishi_1.Schema.object({
116
130
  configs: koishi_1.Schema.dict(koishi_1.Schema.object({
117
131
  maxTokens: koishi_1.Schema.number()
118
- .default(2048)
132
+ .default(4000)
119
133
  .min(1024)
120
- .max(16000)
134
+ .max(20000)
121
135
  .description('使用聊天的最大 token 数'),
136
+ isAt: koishi_1.Schema.boolean().description('是否启用@').default(true),
137
+ splitVoice: koishi_1.Schema.boolean()
138
+ .description('是否分段发送语言')
139
+ .default(false),
122
140
  isNickname: koishi_1.Schema.boolean()
123
141
  .description('允许 bot 配置中的昵称引发回复')
124
142
  .default(true),
@@ -126,21 +144,28 @@ exports.Config = koishi_1.Schema.intersect([
126
144
  .description('是否启用强制禁言(当聊天涉及到关键词时则会禁言,关键词需要在预设文件里配置)')
127
145
  .default(true),
128
146
  messageInterval: koishi_1.Schema.number()
129
- .default(14)
147
+ .default(10)
130
148
  .min(0)
131
149
  .role('slider')
132
150
  .max(50)
133
151
  .description('随机发送消息的间隔'),
152
+ messageProbability: koishi_1.Schema.number()
153
+ .default(0.1)
154
+ .min(0)
155
+ .max(1)
156
+ .role('slider')
157
+ .step(0.00001)
158
+ .description('发送消息的叠加概率(线性增长)'),
134
159
  coolDownTime: koishi_1.Schema.number()
135
160
  .default(10)
136
161
  .min(1)
137
- .max(60 * 24)
162
+ .max(60 * 24 * 24)
138
163
  .description('冷却发言时间(秒)'),
139
164
  typingTime: koishi_1.Schema.number()
140
165
  .default(440)
141
166
  .min(100)
142
167
  .role('slider')
143
- .max(1500)
168
+ .max(1700)
144
169
  .description('模拟打字时的间隔(毫秒)'),
145
170
  muteTime: koishi_1.Schema.number()
146
171
  .default(1000 * 60)
@@ -148,7 +173,7 @@ exports.Config = koishi_1.Schema.intersect([
148
173
  .max(1000 * 60 * 10 * 10)
149
174
  .description('闭嘴时的禁言时间(毫秒)'),
150
175
  sendStickerProbability: koishi_1.Schema.number()
151
- .default(0.6)
176
+ .default(0.2)
152
177
  .min(0)
153
178
  .max(1)
154
179
  .role('slider')
@@ -4,7 +4,6 @@ exports.apply = void 0;
4
4
  const messages_1 = require("@langchain/core/messages");
5
5
  const koishi_1 = require("koishi");
6
6
  const count_tokens_1 = require("koishi-plugin-chatluna/lib/llm-core/utils/count_tokens");
7
- let logger;
8
7
  async function apply(ctx, config) {
9
8
  const service = ctx.chatluna_character;
10
9
  const preset = service.preset;
@@ -62,70 +61,106 @@ async function apply(ctx, config) {
62
61
  logger.debug('completion message: ' +
63
62
  JSON.stringify(completionMessages.map((it) => it.content)));
64
63
  let responseMessage;
65
- let response;
64
+ let parsedResponse;
66
65
  let isError = false;
67
- for (let i = 0; i < 2; i++) {
66
+ for (let i = 0; i < 3; i++) {
68
67
  try {
69
68
  responseMessage = await model.invoke(completionMessages);
70
69
  logger.debug('model response: ' + responseMessage.content);
71
- response = parseResponse(responseMessage.content);
70
+ parsedResponse = parseResponse(responseMessage.content, copyOfConfig.isAt);
72
71
  break;
73
72
  }
74
73
  catch (e) {
75
74
  logger.error(e);
76
- await (0, koishi_1.sleep)(5000);
77
- if (i === 1) {
75
+ await (0, koishi_1.sleep)(2000);
76
+ if (i === 2) {
78
77
  isError = true;
79
78
  }
80
- continue;
81
79
  }
82
80
  }
83
81
  if (isError) {
84
82
  return;
85
83
  }
86
- if (response.length < 1) {
84
+ if (parsedResponse.elements.length < 1) {
87
85
  service.mute(session, copyOfConfig.muteTime);
88
86
  return;
89
87
  }
90
88
  temp.completionMessages.push(humanMessage, responseMessage);
91
- if (temp.completionMessages.length > 10) {
92
- while (temp.completionMessages.length <= 10) {
89
+ if (temp.completionMessages.length > 6) {
90
+ while (temp.completionMessages.length <= 3) {
93
91
  temp.completionMessages.shift();
94
92
  }
95
93
  }
96
94
  const random = new koishi_1.Random();
97
- for (const elements of response) {
95
+ for (const elements of parsedResponse.elements) {
98
96
  const text = elements
99
97
  .map((element) => element.attrs.content ?? '')
100
98
  .join('');
101
- const maxTime = text.length * copyOfConfig.typingTime + 100;
102
- await (0, koishi_1.sleep)(random.int(maxTime / 2, maxTime));
103
- session.send(elements);
99
+ let maxTime = text.length * copyOfConfig.typingTime + 100;
100
+ if (parsedResponse.messageType === 'voice' &&
101
+ isEmoticonStatement(text)) {
102
+ continue;
103
+ }
104
+ if (config.splitVoice !== true) {
105
+ maxTime =
106
+ parsedResponse.rawMessage.length * copyOfConfig.typingTime +
107
+ 100;
108
+ await (0, koishi_1.sleep)(random.int(maxTime / 4, maxTime / 2));
109
+ await session.send(await ctx.vits.say({ input: text }));
110
+ break;
111
+ }
112
+ try {
113
+ await (0, koishi_1.sleep)(random.int(maxTime / 2, maxTime));
114
+ switch (parsedResponse.messageType) {
115
+ case 'text':
116
+ await session.send(elements);
117
+ break;
118
+ case 'voice':
119
+ await (0, koishi_1.sleep)(random.int(maxTime / 4, maxTime / 2));
120
+ await session.send(await ctx.vits.say({
121
+ input: text
122
+ }));
123
+ break;
124
+ }
125
+ }
126
+ catch (e) {
127
+ logger.error(e);
128
+ // fallback to text
129
+ await session.send(elements);
130
+ }
104
131
  }
105
132
  const sticker = await stickerService.randomStick();
106
133
  if (sticker) {
107
134
  await (0, koishi_1.sleep)(random.int(500, 2000));
108
- session.send(sticker);
135
+ await session.send(sticker);
109
136
  }
110
137
  service.mute(session, copyOfConfig.coolDownTime * 1000);
111
- service.broadcastOnBot(session, response.flat());
138
+ await service.broadcastOnBot(session, parsedResponse.elements.flat());
112
139
  });
113
140
  }
114
141
  exports.apply = apply;
115
- function parseResponse(response) {
116
- let message;
142
+ function isEmoticonStatement(text) {
143
+ 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;
144
+ return regex.test(text);
145
+ }
146
+ function parseResponse(response, useAt = true) {
147
+ let rawMessage;
148
+ let parsedMessage = '';
149
+ let messageType = 'text';
117
150
  try {
118
151
  // match json object
119
152
  // best match <message>content</message>
120
- message = response.match(/<message>\s*(.*?)\s*<\/message>/)?.[1];
121
- if (message == null) {
153
+ rawMessage = response.match(/<message>\s*(.*?)\s*<\/message>/)?.[1];
154
+ if (rawMessage == null) {
122
155
  logger.debug('failed to parse response: ' + response);
123
- // try find the first "{" and the last "}", sub it and as a json
156
+ // try to find the first "{" and the last "}", sub it and as a json
124
157
  // good luck.
125
- message = response.substring(response.indexOf('{'), response.lastIndexOf('}') + 1);
158
+ rawMessage = response.substring(response.indexOf('{'), response.lastIndexOf('}') + 1);
126
159
  }
127
- message = JSON.parse(message).content;
128
- if (typeof message !== 'string') {
160
+ const tempJson = JSON.parse(rawMessage);
161
+ rawMessage = tempJson.content;
162
+ messageType = tempJson.type;
163
+ if (typeof rawMessage !== 'string') {
129
164
  throw new Error('Failed to parse response: ' + response);
130
165
  }
131
166
  }
@@ -136,24 +171,29 @@ function parseResponse(response) {
136
171
  const resultElements = [];
137
172
  const currentElements = [];
138
173
  // match [at:name:id] -> id
139
- const atMatch = matchAt(message);
174
+ const atMatch = matchAt(rawMessage);
140
175
  if (atMatch.length > 0) {
141
176
  let lastAtIndex = 0;
142
177
  for (const at of atMatch) {
143
- const before = message.substring(lastAtIndex, at.start);
178
+ const before = rawMessage.substring(lastAtIndex, at.start);
144
179
  if (before.length > 0) {
180
+ parsedMessage += before;
145
181
  currentElements.push(koishi_1.h.text(before));
146
182
  }
147
- currentElements.push(koishi_1.h.at(at.at));
183
+ if (useAt) {
184
+ currentElements.push(koishi_1.h.at(at.at));
185
+ }
148
186
  lastAtIndex = at.end;
149
187
  }
150
- const after = message.substring(lastAtIndex);
188
+ const after = rawMessage.substring(lastAtIndex);
151
189
  if (after.length > 0) {
190
+ parsedMessage += after;
152
191
  currentElements.push(koishi_1.h.text(after));
153
192
  }
154
193
  }
155
194
  else {
156
- currentElements.push(koishi_1.h.text(message));
195
+ parsedMessage = rawMessage;
196
+ currentElements.push(koishi_1.h.text(rawMessage));
157
197
  }
158
198
  for (let currentElement of currentElements) {
159
199
  if (currentElement.type === 'text') {
@@ -173,7 +213,11 @@ function parseResponse(response) {
173
213
  resultElements[1].unshift(resultElements[0][0]);
174
214
  resultElements.shift();
175
215
  }
176
- return resultElements;
216
+ return {
217
+ elements: resultElements,
218
+ rawMessage: parsedMessage,
219
+ messageType
220
+ };
177
221
  }
178
222
  function splitSentence(text) {
179
223
  const result = [];
@@ -204,7 +248,7 @@ function splitSentence(text) {
204
248
  '\r'
205
249
  ];
206
250
  const retainPunctuations = ['?', '!', '?', '!', '~'];
207
- const mustPunctuations = ['。', '?', '!', '?', '!', ':', ':', '~'];
251
+ const mustPunctuations = ['。', '?', '!', '?', '!', ':', ':'];
208
252
  const brackets = ['【', '】', '《', '》', '(', ')', '(', ')'];
209
253
  for (let index = 0; index < lines.length; index++) {
210
254
  const char = lines[index];
@@ -261,7 +305,7 @@ function splitSentence(text) {
261
305
  function matchAt(str) {
262
306
  // (旧梦旧念:3510003509:<at>)
263
307
  // /\(.*\-(\d+)\-<at>\)/g
264
- const atRegex = /(\(|().*\-(\d+)\-<at>(\)|))/g;
308
+ const atRegex = /([((]).*-(\d+)-<at>([))])/g;
265
309
  return [...str.matchAll(atRegex)].map((item) => {
266
310
  return {
267
311
  at: item[2],
@@ -299,7 +343,7 @@ async function formatMessage(messages, config, model, systemPrompt, historyPromp
299
343
  const calculatedMessages = [];
300
344
  for (let i = messages.length - 1; i >= 0; i--) {
301
345
  const message = messages[i];
302
- const jsonMessage = `{"name":"${message.name}","id":"${message.id}","content":${JSON.stringify(message.content)}"}`;
346
+ const jsonMessage = `{"name":"${message.name}","id":"${message.id}","content":${JSON.stringify(message.content)}","type":"text"}`;
303
347
  const jsonMessageToken = await model.getNumTokens(jsonMessage);
304
348
  if (currentTokens + jsonMessageToken > maxTokens - 4) {
305
349
  break;
@@ -308,5 +352,9 @@ async function formatMessage(messages, config, model, systemPrompt, historyPromp
308
352
  calculatedMessages.unshift(jsonMessage);
309
353
  }
310
354
  const lastMessage = calculatedMessages.pop();
355
+ if (lastMessage === undefined) {
356
+ throw new Error('lastMessage is undefined, please set the max token to be bigger');
357
+ }
311
358
  return [calculatedMessages, lastMessage];
312
359
  }
360
+ let logger;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apply = void 0;
3
+ exports.apply = apply;
4
4
  const filter_1 = require("./filter");
5
5
  function apply(ctx, config) {
6
6
  ctx.command('chatluna.character', '角色扮演相关命令');
@@ -22,4 +22,3 @@ function apply(ctx, config) {
22
22
  return `已清除群组 ${groupId} 的聊天记录`;
23
23
  });
24
24
  }
25
- exports.apply = apply;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apply = void 0;
3
+ exports.apply = apply;
4
4
  const koishi_1 = require("koishi");
5
5
  const types_1 = require("koishi-plugin-chatluna/lib/llm-core/platform/types");
6
6
  async function apply(ctx, config) {
@@ -12,7 +12,6 @@ async function apply(ctx, config) {
12
12
  });
13
13
  ctx.schema.set('model', koishi_1.Schema.union(await getModelNames(ctx.chatluna.platform)));
14
14
  }
15
- exports.apply = apply;
16
15
  async function getModelNames(service) {
17
16
  return service.getAllModels(types_1.ModelType.llm).map((m) => koishi_1.Schema.const(m));
18
17
  }
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apply = exports.groupInfos = void 0;
3
+ exports.groupInfos = void 0;
4
+ exports.apply = apply;
4
5
  exports.groupInfos = {};
5
6
  async function apply(ctx, config) {
6
7
  const maxMessages = config.messageInterval;
@@ -82,11 +83,11 @@ async function apply(ctx, config) {
82
83
  return true;
83
84
  }
84
85
  messageCount++;
85
- messageSendProbability -= (1 / maxMessages) * 0.145;
86
+ messageSendProbability -=
87
+ (1 / maxMessages) * copyOfConfig.messageProbability;
86
88
  info.messageCount = messageCount;
87
89
  info.messageSendProbability = messageSendProbability;
88
90
  exports.groupInfos[session.guildId] = info;
89
91
  return false;
90
92
  });
91
93
  }
92
- exports.apply = apply;
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.apply = void 0;
3
+ exports.apply = apply;
4
4
  function apply(ctx, config) {
5
5
  ctx.on('chatluna/before-check-sender', async (session) => {
6
6
  const guildId = session.guildId;
@@ -33,4 +33,3 @@ function apply(ctx, config) {
33
33
  return config.disableChatLuna;
34
34
  });
35
35
  }
36
- exports.apply = apply;
package/lib/preset.js CHANGED
@@ -3,7 +3,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
- exports.loadPreset = exports.Preset = void 0;
6
+ exports.Preset = void 0;
7
+ exports.loadPreset = loadPreset;
7
8
  const prompts_1 = require("@langchain/core/prompts");
8
9
  const promises_1 = __importDefault(require("fs/promises"));
9
10
  const js_yaml_1 = require("js-yaml");
@@ -121,4 +122,3 @@ function loadPreset(text) {
121
122
  mute_keyword: rawPreset.mute_keyword ?? []
122
123
  };
123
124
  }
124
- exports.loadPreset = loadPreset;
package/lib/types.d.ts CHANGED
@@ -9,10 +9,6 @@ export interface Message {
9
9
  export interface GroupTemp {
10
10
  completionMessages: BaseMessage[];
11
11
  }
12
- export interface ModelResponse {
13
- think: string;
14
- reply: string;
15
- }
16
12
  export interface GuildConfig {
17
13
  preset: string;
18
14
  messageInterval: number;
@@ -21,7 +17,10 @@ export interface GuildConfig {
21
17
  isForceMute: boolean;
22
18
  sendStickerProbability: number;
23
19
  coolDownTime: number;
20
+ splitVoice: boolean;
21
+ isAt: boolean;
24
22
  typingTime: number;
23
+ messageProbability: number;
25
24
  muteTime: number;
26
25
  }
27
26
  export interface PresetTemplate {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "koishi-plugin-chatluna-character",
3
3
  "description": "Let the large language model play a role, disguise as a group friend",
4
- "version": "0.0.41",
4
+ "version": "0.0.43",
5
5
  "main": "lib/index.js",
6
6
  "typings": "lib/index.d.ts",
7
7
  "files": [
@@ -31,14 +31,14 @@
31
31
  },
32
32
  "homepage": "https://github.com/ChatLunaLab/chatluna-character#readme",
33
33
  "dependencies": {
34
- "@langchain/core": "^0.1.43",
34
+ "@langchain/core": "^0.1.48",
35
35
  "js-yaml": "^4.1.0"
36
36
  },
37
37
  "devDependencies": {
38
38
  "@typescript-eslint/eslint-plugin": "^6.21.1-alpha.1",
39
39
  "@typescript-eslint/parser": "^6.21.1-alpha.1",
40
40
  "atsc": "^1.2.2",
41
- "esbuild": "^0.20.1",
41
+ "esbuild": "^0.20.2",
42
42
  "esbuild-register": "^3.5.0",
43
43
  "eslint": "^8.57.0",
44
44
  "eslint-config-prettier": "^9.1.0",
@@ -47,10 +47,11 @@
47
47
  "eslint-plugin-n": "^16.6.2",
48
48
  "eslint-plugin-prettier": "^5.1.3",
49
49
  "eslint-plugin-promise": "^6.1.1",
50
+ "@initencounter/vits": "^0.0.3",
50
51
  "koishi": "^4.17.2"
51
52
  },
52
53
  "peerDependencies": {
53
- "koishi": "^4.16.7",
54
+ "koishi": "^4.17.0",
54
55
  "koishi-plugin-chatluna": "^1.0.0-beta.42"
55
56
  },
56
57
  "engines": {
@@ -26,9 +26,9 @@ input: |
26
26
  think content here
27
27
  </think>
28
28
 
29
- // 不要冒充任何人,以自己身份回复,回复需 20 字内
29
+ // 不要冒充任何人,以自己身份回复,回复需 20 字内, 消息类型可选 text 或者 voice
30
30
  <message>
31
- {{"name":"旧梦旧念","id":"0","content":"reply content here"}}
31
+ {{"name":"旧梦旧念","id":"0","content":"reply content here","type:"text or voice"}}
32
32
  </message>
33
33
 
34
34
  system: |
@@ -58,7 +58,7 @@ system: |
58
58
  4.你需要使用中文回复,当然他人用日语或者让你用日语回复你也用日语回复
59
59
  5.你回复的长度应该与其他人发信息的长度差不多,只能在20字内,简短,口语化
60
60
  6.你不会主动发出话题,更不会发起这种比如有什么事吗,你好吗,你在干嘛等
61
- 7.你喜欢深度参与聊天话题,而不是附和他人的消息
61
+ 7.你偶尔会选择以语音回复群友的消息
62
62
  8.对于现实事件,如邀请去看电影,你需要尽可能的拒绝
63
63
  9.你不使用任何第一人称的视角去发出话题,你只在回答问题,你更多的是使用附和手法
64
64
  }}
@@ -66,19 +66,22 @@ system: |
66
66
 
67
67
  回复规则(请务必记住回复规则的格式){{
68
68
 
69
- 历史聊天格式:{{"name":"name","id":"id","content":"content"}}
69
+ 历史聊天格式:{{"name":"name","id":"id","content":"content","type":"text"}}
70
70
 
71
71
  名为旧梦旧念的消息是你之前思考过的回复。
72
72
  如出现 (name-id-<at>),就是在艾特某人。
73
73
 
74
74
  普通回复:
75
- {{"name":"旧梦旧念","id":"0","content":"回复内容"}}
75
+ {{"name":"旧梦旧念","id":"0","content":"回复内容","type":"text"}}
76
76
 
77
77
  如你需要 at 回复的话,加入 (name-id-<at>) 到回复内容,可加入多个 at:
78
- {{"name":"旧梦旧念","id":"0","content":"(name-id-<at>)回复内容(name-id-<at>)"}}
78
+ {{"name":"旧梦旧念","id":"0","content":"(name-id-<at>)回复内容(name-id-<at>)","type":"text"}}
79
+
80
+ 如需以语音回复,可使用下面的格式:
81
+ {{"name":"旧梦旧念","id":"0","content":"","type":"voice"}}
79
82
 
80
83
  如你认为聊的话题你不理解,不想参与,等让你认为无需回复,直接返回空字符:
81
- {{"name":"旧梦旧念","id":"0","content":""}}
84
+ {{"name":"旧梦旧念","id":"0","content":"","type":"text"}}
82
85
  }}
83
86
 
84
87
  mute_keyword: