koishi-plugin-maple-dice-v2 0.0.1
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 +23 -0
- package/lib/index.js +607 -0
- package/package.json +18 -0
- package/readme.md +5 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { Context, Schema } from 'koishi';
|
|
2
|
+
export declare const name = "maple-dice-v2";
|
|
3
|
+
export declare const using: readonly ["database"];
|
|
4
|
+
declare module 'koishi' {
|
|
5
|
+
interface Tables {
|
|
6
|
+
'maple-dice-responses': DiceResponse;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
interface DiceResponse {
|
|
10
|
+
id: number;
|
|
11
|
+
level: string;
|
|
12
|
+
content: string;
|
|
13
|
+
author: string;
|
|
14
|
+
group: string;
|
|
15
|
+
created: Date;
|
|
16
|
+
}
|
|
17
|
+
export interface Config {
|
|
18
|
+
defaultSkill: number;
|
|
19
|
+
replyMode: 'global' | 'group' | 'personal';
|
|
20
|
+
}
|
|
21
|
+
export declare const Config: Schema<Config>;
|
|
22
|
+
export declare function apply(ctx: Context, config: Config): void;
|
|
23
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,607 @@
|
|
|
1
|
+
import { Schema } from 'koishi';
|
|
2
|
+
export const name = 'maple-dice-v2';
|
|
3
|
+
export const using = ['database'];
|
|
4
|
+
// 骰子解析类
|
|
5
|
+
class DiceRoller {
|
|
6
|
+
static parseDice(expression) {
|
|
7
|
+
// 如果表达式为空,默认为1d100
|
|
8
|
+
if (!expression) {
|
|
9
|
+
expression = '1d100';
|
|
10
|
+
}
|
|
11
|
+
// 清理表达式
|
|
12
|
+
expression = expression.replace(/\s+/g, '').toLowerCase();
|
|
13
|
+
// 匹配 xdy 后面可选的运算符和数字,支持 + - * /
|
|
14
|
+
const diceRegex = /^(\d+)?d(\d+)(?:([+\-*/])(\d+))?$/;
|
|
15
|
+
const match = expression.match(diceRegex);
|
|
16
|
+
if (!match) {
|
|
17
|
+
throw new Error('无效的掷骰表达式。正确格式: r xdy[+-*/]z');
|
|
18
|
+
}
|
|
19
|
+
const count = match[1] ? parseInt(match[1]) : 1;
|
|
20
|
+
const sides = parseInt(match[2]);
|
|
21
|
+
const operator = match[3] || '';
|
|
22
|
+
const modifier = match[4] ? parseInt(match[4]) : 0;
|
|
23
|
+
if (count < 1 || sides < 1) {
|
|
24
|
+
throw new Error('骰子数量和面数必须为正整数');
|
|
25
|
+
}
|
|
26
|
+
if (count > 100) {
|
|
27
|
+
throw new Error('一次最多只能掷 100 个骰子');
|
|
28
|
+
}
|
|
29
|
+
const rolls = [];
|
|
30
|
+
for (let i = 0; i < count; i++) {
|
|
31
|
+
rolls.push(Math.floor(Math.random() * sides) + 1);
|
|
32
|
+
}
|
|
33
|
+
const diceTotal = rolls.reduce((sum, roll) => sum + roll, 0);
|
|
34
|
+
let total;
|
|
35
|
+
switch (operator) {
|
|
36
|
+
case '+':
|
|
37
|
+
total = diceTotal + modifier;
|
|
38
|
+
break;
|
|
39
|
+
case '-':
|
|
40
|
+
total = diceTotal - modifier;
|
|
41
|
+
break;
|
|
42
|
+
case '*':
|
|
43
|
+
total = diceTotal * modifier;
|
|
44
|
+
break;
|
|
45
|
+
case '/':
|
|
46
|
+
if (modifier === 0) {
|
|
47
|
+
throw new Error('除数不能为0');
|
|
48
|
+
}
|
|
49
|
+
total = Math.floor(diceTotal / modifier); // 向下取整
|
|
50
|
+
break;
|
|
51
|
+
default:
|
|
52
|
+
total = diceTotal;
|
|
53
|
+
}
|
|
54
|
+
let expr = `${count}d${sides}`;
|
|
55
|
+
if (operator) {
|
|
56
|
+
expr += `${operator}${modifier}`;
|
|
57
|
+
}
|
|
58
|
+
return {
|
|
59
|
+
total,
|
|
60
|
+
details: rolls,
|
|
61
|
+
expression: expr,
|
|
62
|
+
operator,
|
|
63
|
+
modifier,
|
|
64
|
+
count,
|
|
65
|
+
sides
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
// 技能检定
|
|
69
|
+
static skillCheck(skillValue) {
|
|
70
|
+
const diceValue = Math.floor(Math.random() * 100) + 1;
|
|
71
|
+
// 判断成功等级
|
|
72
|
+
let successLevel = '';
|
|
73
|
+
if (diceValue === 1) {
|
|
74
|
+
successLevel = 'greatSuccess'; // 大成功
|
|
75
|
+
}
|
|
76
|
+
else if (diceValue === 100) {
|
|
77
|
+
successLevel = 'greatFail'; // 大失败
|
|
78
|
+
}
|
|
79
|
+
else if (diceValue <= Math.floor(skillValue / 5)) {
|
|
80
|
+
successLevel = 'extremeSuccess'; // 极难成功
|
|
81
|
+
}
|
|
82
|
+
else if (diceValue <= Math.floor(skillValue / 2)) {
|
|
83
|
+
successLevel = 'hardSuccess'; // 困难成功
|
|
84
|
+
}
|
|
85
|
+
else if (diceValue <= skillValue) {
|
|
86
|
+
successLevel = 'success'; // 成功
|
|
87
|
+
}
|
|
88
|
+
else {
|
|
89
|
+
successLevel = 'fail'; // 失败
|
|
90
|
+
}
|
|
91
|
+
return {
|
|
92
|
+
diceValue,
|
|
93
|
+
skillValue,
|
|
94
|
+
successLevel
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
// 奖励骰/惩罚骰检定
|
|
98
|
+
static bonusPenaltyCheck(tensCount, skillValue, isReward) {
|
|
99
|
+
// 生成十位数的备选结果
|
|
100
|
+
const tens = [];
|
|
101
|
+
for (let i = 0; i < tensCount; i++) {
|
|
102
|
+
tens.push(Math.floor(Math.random() * 10)); // 0-9
|
|
103
|
+
}
|
|
104
|
+
// 生成个位数
|
|
105
|
+
const unit = Math.floor(Math.random() * 10); // 0-9
|
|
106
|
+
// 选择十位数:奖励骰取最小,惩罚骰取最大
|
|
107
|
+
let selectedTen;
|
|
108
|
+
if (isReward) {
|
|
109
|
+
selectedTen = Math.min(...tens);
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
selectedTen = Math.max(...tens);
|
|
113
|
+
}
|
|
114
|
+
// 组合成最终骰值 (0-99, 0表示100)
|
|
115
|
+
let diceValue = selectedTen * 10 + unit;
|
|
116
|
+
if (diceValue === 0) {
|
|
117
|
+
diceValue = 100;
|
|
118
|
+
}
|
|
119
|
+
// 判断成功等级
|
|
120
|
+
let successLevel = '';
|
|
121
|
+
if (diceValue === 1) {
|
|
122
|
+
successLevel = 'greatSuccess'; // 大成功
|
|
123
|
+
}
|
|
124
|
+
else if (diceValue === 100) {
|
|
125
|
+
successLevel = 'greatFail'; // 大失败
|
|
126
|
+
}
|
|
127
|
+
else if (diceValue <= Math.floor(skillValue / 5)) {
|
|
128
|
+
successLevel = 'extremeSuccess'; // 极难成功
|
|
129
|
+
}
|
|
130
|
+
else if (diceValue <= Math.floor(skillValue / 2)) {
|
|
131
|
+
successLevel = 'hardSuccess'; // 困难成功
|
|
132
|
+
}
|
|
133
|
+
else if (diceValue <= skillValue) {
|
|
134
|
+
successLevel = 'success'; // 成功
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
137
|
+
successLevel = 'fail'; // 失败
|
|
138
|
+
}
|
|
139
|
+
return {
|
|
140
|
+
diceValue,
|
|
141
|
+
skillValue,
|
|
142
|
+
successLevel,
|
|
143
|
+
tens,
|
|
144
|
+
unit
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
// 获取成功等级的描述
|
|
148
|
+
static getSuccessLevelText(level) {
|
|
149
|
+
const texts = {
|
|
150
|
+
'greatSuccess': '大成功',
|
|
151
|
+
'greatFail': '大失败',
|
|
152
|
+
'extremeSuccess': '极难成功',
|
|
153
|
+
'hardSuccess': '困难成功',
|
|
154
|
+
'success': '成功',
|
|
155
|
+
'fail': '失败'
|
|
156
|
+
};
|
|
157
|
+
return texts[level] || level;
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
// 配置定义
|
|
161
|
+
export const Config = Schema.object({
|
|
162
|
+
defaultSkill: Schema.number()
|
|
163
|
+
.default(50)
|
|
164
|
+
.min(1)
|
|
165
|
+
.max(100)
|
|
166
|
+
.description('默认技能值(当ra指令未指定时使用)'),
|
|
167
|
+
replyMode: Schema.union([
|
|
168
|
+
Schema.const('global').description('全局: 调用所有自定义回复'),
|
|
169
|
+
Schema.const('group').description('群组: 仅调用当前群组内添加的所有自定义回复(私聊时,仅调用本人私聊添加的所有自定义回复)'),
|
|
170
|
+
Schema.const('personal').description('个人: 仅调用由本人添加的所有自定义回复')
|
|
171
|
+
])
|
|
172
|
+
.default('global')
|
|
173
|
+
.description('回复模式'),
|
|
174
|
+
});
|
|
175
|
+
// 等级名称到内部键的映射
|
|
176
|
+
const levelNameToKey = {
|
|
177
|
+
'大成功': 'greatSuccess',
|
|
178
|
+
'大失败': 'greatFail',
|
|
179
|
+
'极难成功': 'extremeSuccess',
|
|
180
|
+
'困难成功': 'hardSuccess',
|
|
181
|
+
'成功': 'success',
|
|
182
|
+
'失败': 'fail'
|
|
183
|
+
};
|
|
184
|
+
const keyToLevelName = {
|
|
185
|
+
'greatSuccess': '大成功',
|
|
186
|
+
'greatFail': '大失败',
|
|
187
|
+
'extremeSuccess': '极难成功',
|
|
188
|
+
'hardSuccess': '困难成功',
|
|
189
|
+
'success': '成功',
|
|
190
|
+
'fail': '失败'
|
|
191
|
+
};
|
|
192
|
+
// 根据回复模式和会话信息筛选回复
|
|
193
|
+
function filterResponsesByMode(responses, session, replyMode, levelName) {
|
|
194
|
+
// 如果没有任何回复,返回空数组
|
|
195
|
+
if (responses.length === 0) {
|
|
196
|
+
return [];
|
|
197
|
+
}
|
|
198
|
+
// 如果是私聊环境
|
|
199
|
+
const isPrivate = !session.guildId;
|
|
200
|
+
const userId = session.userId;
|
|
201
|
+
const groupId = isPrivate ? '私聊' : session.guildId;
|
|
202
|
+
// 根据回复模式筛选
|
|
203
|
+
switch (replyMode) {
|
|
204
|
+
case 'global':
|
|
205
|
+
// 全局模式:返回所有该等级的自定义回复
|
|
206
|
+
return responses.filter(r => r.level === levelName);
|
|
207
|
+
case 'group':
|
|
208
|
+
// 群组模式:返回当前群组(或私聊)内添加的所有自定义回复
|
|
209
|
+
return responses.filter(r => r.level === levelName &&
|
|
210
|
+
r.group === groupId);
|
|
211
|
+
case 'personal':
|
|
212
|
+
// 个人模式:返回由本人添加的所有自定义回复
|
|
213
|
+
return responses.filter(r => r.level === levelName &&
|
|
214
|
+
r.author === userId);
|
|
215
|
+
default:
|
|
216
|
+
// 默认返回全局模式
|
|
217
|
+
return responses.filter(r => r.level === levelName);
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
// 获取随机回复 - 从数据库读取
|
|
221
|
+
async function getRandomResponse(ctx, session, config, successLevel, diceValue, skillValue) {
|
|
222
|
+
// 将内部等级转换为显示名称
|
|
223
|
+
const levelName = keyToLevelName[successLevel] || '成功';
|
|
224
|
+
// 从数据库获取所有回复
|
|
225
|
+
const allResponses = await ctx.database.get('maple-dice-responses', {});
|
|
226
|
+
// 根据回复模式筛选回复
|
|
227
|
+
const filteredResponses = filterResponsesByMode(allResponses, session, config.replyMode, levelName);
|
|
228
|
+
let replies = [];
|
|
229
|
+
if (filteredResponses.length === 0) {
|
|
230
|
+
// 如果没有符合条件的自定义回复,使用默认回复
|
|
231
|
+
const defaultResponses = {
|
|
232
|
+
'大成功': ['大成功!骰值:{result}'],
|
|
233
|
+
'大失败': ['大失败!骰值:{result}'],
|
|
234
|
+
'极难成功': ['极难成功!骰值:{result}'],
|
|
235
|
+
'困难成功': ['困难成功!骰值:{result}'],
|
|
236
|
+
'成功': ['成功!骰值:{result}'],
|
|
237
|
+
'失败': ['失败!骰值:{result}'],
|
|
238
|
+
};
|
|
239
|
+
replies = defaultResponses[levelName] || ['{level}!骰值:{result}'];
|
|
240
|
+
}
|
|
241
|
+
else {
|
|
242
|
+
replies = filteredResponses.map(r => r.content);
|
|
243
|
+
}
|
|
244
|
+
const randomResponse = replies[Math.floor(Math.random() * replies.length)];
|
|
245
|
+
// 替换变量
|
|
246
|
+
const levelText = DiceRoller.getSuccessLevelText(successLevel);
|
|
247
|
+
return randomResponse
|
|
248
|
+
.replace(/{result}/g, diceValue.toString())
|
|
249
|
+
.replace(/{skill}/g, skillValue.toString())
|
|
250
|
+
.replace(/{level}/g, levelText);
|
|
251
|
+
}
|
|
252
|
+
// 主插件函数
|
|
253
|
+
export function apply(ctx, config) {
|
|
254
|
+
console.log('maple-dice-v2 插件加载中...');
|
|
255
|
+
// 扩展数据库表,添加group字段
|
|
256
|
+
ctx.model.extend('maple-dice-responses', {
|
|
257
|
+
id: 'unsigned',
|
|
258
|
+
level: 'string',
|
|
259
|
+
content: 'text',
|
|
260
|
+
author: 'string',
|
|
261
|
+
group: 'string', // 添加群字段
|
|
262
|
+
created: 'timestamp',
|
|
263
|
+
}, {
|
|
264
|
+
primary: 'id',
|
|
265
|
+
autoInc: true,
|
|
266
|
+
});
|
|
267
|
+
// r 指令 - 修改为可选参数,支持加减乘除
|
|
268
|
+
ctx.command('r [expression:string]', '基础掷骰(默认为1d100)')
|
|
269
|
+
.alias('roll')
|
|
270
|
+
.alias('掷骰')
|
|
271
|
+
.example('r 掷1个100面骰(默认)')
|
|
272
|
+
.example('r d6 掷1个6面骰')
|
|
273
|
+
.example('r 3d6 掷3个6面骰')
|
|
274
|
+
.example('r 2d20+5 掷2个20面骰并加5')
|
|
275
|
+
.example('r 3d10-2 掷3个10面骰并减2')
|
|
276
|
+
.example('r 2d6*3 掷2个6面骰并乘以3')
|
|
277
|
+
.example('r 4d10/2 掷4个10面骰并除以2')
|
|
278
|
+
.action(async ({ session }, expression) => {
|
|
279
|
+
try {
|
|
280
|
+
const result = DiceRoller.parseDice(expression);
|
|
281
|
+
let output = `r ${result.expression} = `;
|
|
282
|
+
// 根据运算符决定输出格式
|
|
283
|
+
if (!result.operator) {
|
|
284
|
+
// 没有运算符
|
|
285
|
+
if (result.count === 1) {
|
|
286
|
+
output += `${result.total}`;
|
|
287
|
+
}
|
|
288
|
+
else {
|
|
289
|
+
output += `${result.details.join('+')} = ${result.total}`;
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
else {
|
|
293
|
+
// 有运算符
|
|
294
|
+
if (result.count === 1) {
|
|
295
|
+
// 只有一个骰子
|
|
296
|
+
output += `${result.details[0]}${result.operator}${result.modifier} = ${result.total}`;
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// 多个骰子,用括号括起细节
|
|
300
|
+
// 对于加减法:r 2d3-1 = (1+2)-1 = 2
|
|
301
|
+
output += `(${result.details.join('+')})${result.operator}${result.modifier} = ${result.total}`;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
return output;
|
|
305
|
+
}
|
|
306
|
+
catch (error) {
|
|
307
|
+
return `错误: ${error.message}`;
|
|
308
|
+
}
|
|
309
|
+
});
|
|
310
|
+
// ra 指令
|
|
311
|
+
ctx.command('ra [skill:number]', '技能检定')
|
|
312
|
+
.alias('检定')
|
|
313
|
+
.action(async ({ session }, skill) => {
|
|
314
|
+
const skillValue = skill || config.defaultSkill;
|
|
315
|
+
if (skillValue < 1 || skillValue > 100) {
|
|
316
|
+
return '技能值必须在1-100之间';
|
|
317
|
+
}
|
|
318
|
+
const result = DiceRoller.skillCheck(skillValue);
|
|
319
|
+
const response = await getRandomResponse(ctx, session, config, result.successLevel, result.diceValue, skillValue);
|
|
320
|
+
const levelText = DiceRoller.getSuccessLevelText(result.successLevel);
|
|
321
|
+
return `ra ${skillValue}=${result.diceValue}/${skillValue} ${levelText}\n${response}`;
|
|
322
|
+
});
|
|
323
|
+
// rb 指令 - 奖励骰
|
|
324
|
+
ctx.command('rb <input:string>', '奖励骰检定')
|
|
325
|
+
.example('rb 3/70 进行3个奖励骰的70点技能检定')
|
|
326
|
+
.example('rb 2 进行2个奖励骰,使用默认技能值')
|
|
327
|
+
.action(async ({ session }, input) => {
|
|
328
|
+
try {
|
|
329
|
+
// 解析输入格式: "x/y" 或 "x"
|
|
330
|
+
const parts = input.split('/');
|
|
331
|
+
const tensCount = parseInt(parts[0]);
|
|
332
|
+
if (isNaN(tensCount) || tensCount < 1) {
|
|
333
|
+
return '奖励骰数量必须是正整数';
|
|
334
|
+
}
|
|
335
|
+
let skillValue = config.defaultSkill;
|
|
336
|
+
if (parts.length > 1) {
|
|
337
|
+
skillValue = parseInt(parts[1]);
|
|
338
|
+
if (isNaN(skillValue) || skillValue < 1 || skillValue > 100) {
|
|
339
|
+
return '技能值必须在1-100之间';
|
|
340
|
+
}
|
|
341
|
+
}
|
|
342
|
+
const result = DiceRoller.bonusPenaltyCheck(tensCount, skillValue, true);
|
|
343
|
+
const response = await getRandomResponse(ctx, session, config, result.successLevel, result.diceValue, skillValue);
|
|
344
|
+
const levelText = DiceRoller.getSuccessLevelText(result.successLevel);
|
|
345
|
+
// 格式化十位数显示
|
|
346
|
+
const tensDisplay = `[${result.tens.join(',')}]`;
|
|
347
|
+
const unitDisplay = `[${result.unit}]`;
|
|
348
|
+
return `rb ${tensCount}/${skillValue}=${tensDisplay}+${unitDisplay}=${result.diceValue}/${skillValue} ${levelText}\n${response}`;
|
|
349
|
+
}
|
|
350
|
+
catch (error) {
|
|
351
|
+
return `错误: ${error.message}`;
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
// rp 指令 - 惩罚骰
|
|
355
|
+
ctx.command('rp <input:string>', '惩罚骰检定')
|
|
356
|
+
.example('rp 3/70 进行3个惩罚骰的70点技能检定')
|
|
357
|
+
.example('rp 2 进行2个惩罚骰,使用默认技能值')
|
|
358
|
+
.action(async ({ session }, input) => {
|
|
359
|
+
try {
|
|
360
|
+
// 解析输入格式: "x/y" 或 "x"
|
|
361
|
+
const parts = input.split('/');
|
|
362
|
+
const tensCount = parseInt(parts[0]);
|
|
363
|
+
if (isNaN(tensCount) || tensCount < 1) {
|
|
364
|
+
return '惩罚骰数量必须是正整数';
|
|
365
|
+
}
|
|
366
|
+
let skillValue = config.defaultSkill;
|
|
367
|
+
if (parts.length > 1) {
|
|
368
|
+
skillValue = parseInt(parts[1]);
|
|
369
|
+
if (isNaN(skillValue) || skillValue < 1 || skillValue > 100) {
|
|
370
|
+
return '技能值必须在1-100之间';
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
const result = DiceRoller.bonusPenaltyCheck(tensCount, skillValue, false);
|
|
374
|
+
const response = await getRandomResponse(ctx, session, config, result.successLevel, result.diceValue, skillValue);
|
|
375
|
+
const levelText = DiceRoller.getSuccessLevelText(result.successLevel);
|
|
376
|
+
// 格式化十位数显示
|
|
377
|
+
const tensDisplay = `[${result.tens.join(',')}]`;
|
|
378
|
+
const unitDisplay = `[${result.unit}]`;
|
|
379
|
+
return `rp ${tensCount}/${skillValue}=${tensDisplay}+${unitDisplay}=${result.diceValue}/${skillValue} ${levelText}\n${response}`;
|
|
380
|
+
}
|
|
381
|
+
catch (error) {
|
|
382
|
+
return `错误: ${error.message}`;
|
|
383
|
+
}
|
|
384
|
+
});
|
|
385
|
+
// 添加自定义回复指令
|
|
386
|
+
ctx.command('添加自定义回复 <level:string> <text:string>', '添加自定义回复(用&分隔多条回复)')
|
|
387
|
+
.alias('添加回复')
|
|
388
|
+
.example('添加自定义回复 大成功 太棒了!大成功!骰值:{result}&奇迹般的大成功!骰值:{result}')
|
|
389
|
+
.action(async ({ session }, level, text) => {
|
|
390
|
+
// 检查等级是否有效
|
|
391
|
+
const validLevels = ['大成功', '极难成功', '困难成功', '成功', '失败', '大失败'];
|
|
392
|
+
if (!validLevels.includes(level)) {
|
|
393
|
+
return `无效的成功等级。可用等级:${validLevels.join('、')}`;
|
|
394
|
+
}
|
|
395
|
+
// 检查用户ID是否存在
|
|
396
|
+
if (!session?.userId) {
|
|
397
|
+
return '无法获取用户信息,请确保在正确的环境下使用此指令';
|
|
398
|
+
}
|
|
399
|
+
// 获取群组信息
|
|
400
|
+
const groupId = session.guildId ? session.guildId : '私聊';
|
|
401
|
+
// 按&符号分割回复文本,并过滤空项
|
|
402
|
+
const replies = text.split('&')
|
|
403
|
+
.map(item => item.trim())
|
|
404
|
+
.filter(item => item.length > 0);
|
|
405
|
+
if (replies.length === 0) {
|
|
406
|
+
return '未提供有效的回复文本';
|
|
407
|
+
}
|
|
408
|
+
// 批量添加回复,包含作者和群组信息
|
|
409
|
+
const addedResponses = [];
|
|
410
|
+
for (const reply of replies) {
|
|
411
|
+
const result = await ctx.database.create('maple-dice-responses', {
|
|
412
|
+
level,
|
|
413
|
+
content: reply,
|
|
414
|
+
author: session.userId,
|
|
415
|
+
group: groupId,
|
|
416
|
+
created: new Date(),
|
|
417
|
+
});
|
|
418
|
+
addedResponses.push(result);
|
|
419
|
+
}
|
|
420
|
+
return `成功为【${level}】添加了 ${replies.length} 条自定义回复(群组: ${groupId})\n新增内容:\n${addedResponses.map((r, i) => `${i + 1}. [ID:${r.id}] ${r.content}`).join('\n')}`;
|
|
421
|
+
});
|
|
422
|
+
// 查看自定义回复指令 - 添加选项
|
|
423
|
+
ctx.command('查看自定义回复 [options]', '查看自定义回复')
|
|
424
|
+
.alias('查看回复')
|
|
425
|
+
.option('mode', '-m 查看由本人添加的所有自定义回复')
|
|
426
|
+
.option('group', '-g 查看当前群组内添加的所有自定义回复(私聊时,仅调用本人私聊添加的所有自定义回复)')
|
|
427
|
+
.option('all', '-a 查看所有自定义回复')
|
|
428
|
+
.example('查看自定义回复 -g 查看当前群组内的自定义回复(默认)')
|
|
429
|
+
.example('查看自定义回复 -m 查看由本人添加的自定义回复')
|
|
430
|
+
.example('查看自定义回复 -a 查看所有自定义回复')
|
|
431
|
+
.action(async ({ session, options }, level) => {
|
|
432
|
+
// 确定查询模式
|
|
433
|
+
let mode = 'group'; // 默认模式为群组模式
|
|
434
|
+
if (options?.all) {
|
|
435
|
+
mode = 'all';
|
|
436
|
+
}
|
|
437
|
+
else if (options?.mode) {
|
|
438
|
+
mode = 'personal';
|
|
439
|
+
}
|
|
440
|
+
else if (options?.group) {
|
|
441
|
+
mode = 'group';
|
|
442
|
+
}
|
|
443
|
+
// 获取当前会话信息
|
|
444
|
+
const isPrivate = !session?.guildId;
|
|
445
|
+
const userId = session?.userId;
|
|
446
|
+
const groupId = isPrivate ? '私聊' : session?.guildId;
|
|
447
|
+
// 构建查询条件
|
|
448
|
+
const query = {};
|
|
449
|
+
if (level) {
|
|
450
|
+
const validLevels = ['大成功', '极难成功', '困难成功', '成功', '失败', '大失败'];
|
|
451
|
+
if (!validLevels.includes(level)) {
|
|
452
|
+
return `无效的成功等级。可用等级:${validLevels.join('、')}`;
|
|
453
|
+
}
|
|
454
|
+
query.level = level;
|
|
455
|
+
}
|
|
456
|
+
// 根据模式添加额外查询条件
|
|
457
|
+
switch (mode) {
|
|
458
|
+
case 'personal':
|
|
459
|
+
if (!userId) {
|
|
460
|
+
return '无法获取用户信息,请确保在正确的环境下使用此指令';
|
|
461
|
+
}
|
|
462
|
+
query.author = userId;
|
|
463
|
+
break;
|
|
464
|
+
case 'group':
|
|
465
|
+
if (!groupId) {
|
|
466
|
+
return '无法获取群组信息,请确保在正确的环境下使用此指令';
|
|
467
|
+
}
|
|
468
|
+
query.group = groupId;
|
|
469
|
+
break;
|
|
470
|
+
case 'all':
|
|
471
|
+
// 不添加额外的查询条件
|
|
472
|
+
break;
|
|
473
|
+
}
|
|
474
|
+
// 从数据库获取回复
|
|
475
|
+
const responses = await ctx.database.get('maple-dice-responses', query);
|
|
476
|
+
if (responses.length === 0) {
|
|
477
|
+
const modeText = {
|
|
478
|
+
'all': '所有',
|
|
479
|
+
'group': `当前${isPrivate ? '私聊' : '群组'}`,
|
|
480
|
+
'personal': '本人'
|
|
481
|
+
}[mode];
|
|
482
|
+
const levelText = level ? `【${level}】` : '';
|
|
483
|
+
return `${modeText}${levelText}暂无自定义回复,请使用【添加自定义回复】指令添加`;
|
|
484
|
+
}
|
|
485
|
+
// 按等级分组并按ID排序
|
|
486
|
+
const grouped = {};
|
|
487
|
+
for (const response of responses) {
|
|
488
|
+
if (!grouped[response.level]) {
|
|
489
|
+
grouped[response.level] = [];
|
|
490
|
+
}
|
|
491
|
+
grouped[response.level].push(response);
|
|
492
|
+
}
|
|
493
|
+
// 每个等级内按ID排序
|
|
494
|
+
for (const levelName in grouped) {
|
|
495
|
+
grouped[levelName].sort((a, b) => a.id - b.id);
|
|
496
|
+
}
|
|
497
|
+
// 构建输出,显示作者信息和可修改标记
|
|
498
|
+
if (level) {
|
|
499
|
+
// 只显示指定等级
|
|
500
|
+
const levelResponses = grouped[level] || [];
|
|
501
|
+
const modeText = {
|
|
502
|
+
'all': '所有',
|
|
503
|
+
'group': `当前${isPrivate ? '私聊' : '群组'}`,
|
|
504
|
+
'personal': '本人'
|
|
505
|
+
}[mode];
|
|
506
|
+
let output = `${modeText}【${level}】(${levelResponses.length}条):\n`;
|
|
507
|
+
output += levelResponses.map((r, index) => {
|
|
508
|
+
const isOwner = userId && r.author === userId;
|
|
509
|
+
return `${index + 1}. [ID:${r.id}] ${r.content} ${isOwner ? '(可修改)' : ''}`;
|
|
510
|
+
}).join('\n');
|
|
511
|
+
return output;
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
// 显示所有等级
|
|
515
|
+
const modeText = {
|
|
516
|
+
'all': '所有',
|
|
517
|
+
'group': `当前${isPrivate ? '私聊' : '群组'}`,
|
|
518
|
+
'personal': '本人'
|
|
519
|
+
}[mode];
|
|
520
|
+
let output = `${modeText}自定义回复:\n\n`;
|
|
521
|
+
const levelNames = ['大成功', '极难成功', '困难成功', '成功', '失败', '大失败'];
|
|
522
|
+
for (const levelName of levelNames) {
|
|
523
|
+
const levelResponses = grouped[levelName] || [];
|
|
524
|
+
if (levelResponses.length > 0) {
|
|
525
|
+
output += `【${levelName}】(${levelResponses.length}条):\n`;
|
|
526
|
+
output += levelResponses.map((r, index) => {
|
|
527
|
+
const isOwner = userId && r.author === userId;
|
|
528
|
+
return `${index + 1}. [ID:${r.id}] ${r.content} ${isOwner ? '(可修改)' : ''}`;
|
|
529
|
+
}).join('\n');
|
|
530
|
+
output += '\n\n';
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
return output.trim();
|
|
534
|
+
}
|
|
535
|
+
});
|
|
536
|
+
// 删除自定义回复指令 - 检查作者权限
|
|
537
|
+
ctx.command('删除自定义回复 <id:number>', '删除自定义回复')
|
|
538
|
+
.alias('删除回复')
|
|
539
|
+
.example('删除自定义回复 1 删除ID为1的回复')
|
|
540
|
+
.action(async ({ session }, id) => {
|
|
541
|
+
// 检查用户ID是否存在
|
|
542
|
+
if (!session?.userId) {
|
|
543
|
+
return '无法获取用户信息,请确保在正确的环境下使用此指令';
|
|
544
|
+
}
|
|
545
|
+
// 检查ID是否存在
|
|
546
|
+
const existing = await ctx.database.get('maple-dice-responses', { id });
|
|
547
|
+
if (existing.length === 0) {
|
|
548
|
+
return `未找到ID为${id}的回复`;
|
|
549
|
+
}
|
|
550
|
+
// 检查权限:只能删除自己添加的回复
|
|
551
|
+
const response = existing[0];
|
|
552
|
+
if (response.author !== session.userId) {
|
|
553
|
+
return `无法删除此回复,您只能删除自己添加的回复。\n输入【查看自定义回复 -m】查看由自己添加的回复`;
|
|
554
|
+
}
|
|
555
|
+
// 获取要删除的回复内容
|
|
556
|
+
const contentToDelete = response.content;
|
|
557
|
+
const level = response.level;
|
|
558
|
+
// 删除回复
|
|
559
|
+
await ctx.database.remove('maple-dice-responses', { id });
|
|
560
|
+
return `已删除【${level}】的回复(ID: ${id}):\n${contentToDelete}`;
|
|
561
|
+
});
|
|
562
|
+
// 修改自定义回复指令 - 检查作者权限
|
|
563
|
+
ctx.command('修改自定义回复 <id:number> <text:string>', '修改自定义回复')
|
|
564
|
+
.alias('修改回复')
|
|
565
|
+
.example('修改自定义回复 1 新的回复内容')
|
|
566
|
+
.action(async ({ session }, id, text) => {
|
|
567
|
+
// 检查用户ID是否存在
|
|
568
|
+
if (!session?.userId) {
|
|
569
|
+
return '无法获取用户信息,请确保在正确的环境下使用此指令';
|
|
570
|
+
}
|
|
571
|
+
// 检查ID是否存在
|
|
572
|
+
const existing = await ctx.database.get('maple-dice-responses', { id });
|
|
573
|
+
if (existing.length === 0) {
|
|
574
|
+
return `未找到ID为${id}的回复`;
|
|
575
|
+
}
|
|
576
|
+
// 检查权限:只能修改自己添加的回复
|
|
577
|
+
const response = existing[0];
|
|
578
|
+
if (response.author !== session.userId) {
|
|
579
|
+
return `无法修改此回复,您只能修改自己添加的回复。\n输入【查看自定义回复 -m】查看由自己添加的回复`;
|
|
580
|
+
}
|
|
581
|
+
// 检查新内容是否为空
|
|
582
|
+
if (!text.trim()) {
|
|
583
|
+
return '新回复内容不能为空';
|
|
584
|
+
}
|
|
585
|
+
// 获取旧内容和等级
|
|
586
|
+
const oldContent = response.content;
|
|
587
|
+
const level = response.level;
|
|
588
|
+
// 修改回复
|
|
589
|
+
await ctx.database.set('maple-dice-responses', { id }, {
|
|
590
|
+
content: text.trim(),
|
|
591
|
+
created: new Date(), // 更新修改时间
|
|
592
|
+
});
|
|
593
|
+
return `已修改【${level}】的回复(ID: ${id}):\n原内容: ${oldContent}\n新内容: ${text.trim()}`;
|
|
594
|
+
});
|
|
595
|
+
// 清空所有自定义回复指令 - 不进行作者限制
|
|
596
|
+
ctx.command('清空自定义回复', '清空所有自定义回复')
|
|
597
|
+
.alias('清空回复')
|
|
598
|
+
.action(async () => {
|
|
599
|
+
const count = await ctx.database.remove('maple-dice-responses', {});
|
|
600
|
+
return `已清空所有 ${count} 条自定义回复`;
|
|
601
|
+
});
|
|
602
|
+
ctx.on('ready', () => {
|
|
603
|
+
console.log('maple-dice-v2 插件已加载完成');
|
|
604
|
+
ctx.logger('maple-dice-v2').info(`默认技能值: ${config.defaultSkill}`);
|
|
605
|
+
ctx.logger('maple-dice-v2').info(`回复模式: ${config.replyMode}`);
|
|
606
|
+
});
|
|
607
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-maple-dice-v2",
|
|
3
|
+
"description": "-",
|
|
4
|
+
"version": "0.0.1",
|
|
5
|
+
"main": "lib/index.js",
|
|
6
|
+
"typings": "lib/index.d.ts",
|
|
7
|
+
"files": [
|
|
8
|
+
"lib",
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"license": "MIT",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"maple"
|
|
14
|
+
],
|
|
15
|
+
"peerDependencies": {
|
|
16
|
+
"koishi": "^4.18.7"
|
|
17
|
+
}
|
|
18
|
+
}
|