koishi-plugin-ccb-plus 0.2.8-beta.1 → 0.2.8
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/commands/ccb.d.ts +4 -0
- package/lib/commands/charm.d.ts +3 -0
- package/lib/commands/index.d.ts +4 -0
- package/lib/commands/info.d.ts +3 -0
- package/lib/commands/rank.d.ts +3 -0
- package/lib/config.d.ts +21 -0
- package/lib/core.d.ts +3 -0
- package/lib/index.d.ts +5 -47
- package/lib/index.js +237 -174
- package/lib/model.d.ts +28 -0
- package/lib/utils.d.ts +19 -0
- package/package.json +3 -3
- package/readme.md +1 -1
package/lib/config.d.ts
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { Schema } from 'koishi';
|
|
2
|
+
export interface CheatConfig {
|
|
3
|
+
userId: string;
|
|
4
|
+
ywWindow: number;
|
|
5
|
+
ywThreshold: number;
|
|
6
|
+
ywProbability: number;
|
|
7
|
+
critProb: number;
|
|
8
|
+
ywBanDuration: number;
|
|
9
|
+
}
|
|
10
|
+
export interface CCBConfig {
|
|
11
|
+
ywWindow: number;
|
|
12
|
+
ywThreshold: number;
|
|
13
|
+
ywBanDuration: number;
|
|
14
|
+
ywProbability: number;
|
|
15
|
+
whiteList: string[];
|
|
16
|
+
selfCcb: boolean;
|
|
17
|
+
critProb: number;
|
|
18
|
+
toggleCooldown: number;
|
|
19
|
+
cheatList: CheatConfig[];
|
|
20
|
+
}
|
|
21
|
+
export declare const Config: Schema<CCBConfig>;
|
package/lib/core.d.ts
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
import { Context, Session } from 'koishi';
|
|
2
|
+
export declare function createNewCCBRecord(ctx: Context, session: Session, groupId: string, targetUserId: string, duration: number, V: number, nickname: string, crit: boolean, pic: string): Promise<string>;
|
|
3
|
+
export declare function updateCCBRecord(ctx: Context, session: Session, groupId: string, targetUserId: string, duration: number, V: number, nickname: string, crit: boolean, pic: string): Promise<string>;
|
package/lib/index.d.ts
CHANGED
|
@@ -1,50 +1,8 @@
|
|
|
1
|
-
import { Context
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
import { Config, CCBConfig } from './config';
|
|
2
3
|
export declare const name = "ccb-plus";
|
|
3
4
|
export declare const inject: string[];
|
|
4
|
-
export
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
ywThreshold: number;
|
|
8
|
-
ywProbability: number;
|
|
9
|
-
critProb: number;
|
|
10
|
-
ywBanDuration: number;
|
|
11
|
-
}
|
|
12
|
-
export interface CCBConfig {
|
|
13
|
-
ywWindow: number;
|
|
14
|
-
ywThreshold: number;
|
|
15
|
-
ywBanDuration: number;
|
|
16
|
-
ywProbability: number;
|
|
17
|
-
whiteList: string[];
|
|
18
|
-
selfCcb: boolean;
|
|
19
|
-
critProb: number;
|
|
20
|
-
toggleCooldown: number;
|
|
21
|
-
cheatList: CheatConfig[];
|
|
22
|
-
}
|
|
23
|
-
export interface CCBRecord {
|
|
24
|
-
groupId: string;
|
|
25
|
-
userId: string;
|
|
26
|
-
num: number;
|
|
27
|
-
vol: number;
|
|
28
|
-
max: number;
|
|
29
|
-
ccb_by: {
|
|
30
|
-
[actorId: string]: {
|
|
31
|
-
count: number;
|
|
32
|
-
first: boolean;
|
|
33
|
-
max: boolean;
|
|
34
|
-
};
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
export interface CCBUserSetting {
|
|
38
|
-
userId: string;
|
|
39
|
-
optOut: boolean;
|
|
40
|
-
lastToggleTime: number;
|
|
41
|
-
overrides: Record<string, boolean>;
|
|
42
|
-
}
|
|
43
|
-
declare module 'koishi' {
|
|
44
|
-
interface Tables {
|
|
45
|
-
ccb_record: CCBRecord;
|
|
46
|
-
ccb_setting: CCBUserSetting;
|
|
47
|
-
}
|
|
48
|
-
}
|
|
49
|
-
export declare const Config: Schema<CCBConfig>;
|
|
5
|
+
export { Config };
|
|
6
|
+
export * from './config';
|
|
7
|
+
export * from './model';
|
|
50
8
|
export declare function apply(ctx: Context, config: CCBConfig): void;
|
package/lib/index.js
CHANGED
|
@@ -32,15 +32,14 @@ var src_exports = {};
|
|
|
32
32
|
__export(src_exports, {
|
|
33
33
|
Config: () => Config,
|
|
34
34
|
apply: () => apply,
|
|
35
|
+
applyDatabase: () => applyDatabase,
|
|
35
36
|
inject: () => inject,
|
|
36
37
|
name: () => name
|
|
37
38
|
});
|
|
38
39
|
module.exports = __toCommonJS(src_exports);
|
|
40
|
+
|
|
41
|
+
// src/config.ts
|
|
39
42
|
var import_koishi = require("koishi");
|
|
40
|
-
var import_fs = require("fs");
|
|
41
|
-
var path = __toESM(require("path"));
|
|
42
|
-
var name = "ccb-plus";
|
|
43
|
-
var inject = ["database"];
|
|
44
43
|
var Config = import_koishi.Schema.object({
|
|
45
44
|
ywWindow: import_koishi.Schema.number().default(60).description("全局触发冷却的窗口时间(秒)"),
|
|
46
45
|
ywThreshold: import_koishi.Schema.number().default(5).description("全局窗口时间内最大ccb数"),
|
|
@@ -59,7 +58,11 @@ var Config = import_koishi.Schema.object({
|
|
|
59
58
|
ywBanDuration: import_koishi.Schema.number().default(60).description("特权冷却时长(秒)")
|
|
60
59
|
})).role("table").description("开挂名单(优先级高于全局设置)")
|
|
61
60
|
});
|
|
62
|
-
|
|
61
|
+
|
|
62
|
+
// src/model.ts
|
|
63
|
+
var import_fs = require("fs");
|
|
64
|
+
var path = __toESM(require("path"));
|
|
65
|
+
function applyDatabase(ctx) {
|
|
63
66
|
ctx.model.extend("ccb_record", {
|
|
64
67
|
groupId: "string",
|
|
65
68
|
userId: "string",
|
|
@@ -79,11 +82,6 @@ function apply(ctx, config) {
|
|
|
79
82
|
}, {
|
|
80
83
|
primary: "userId"
|
|
81
84
|
});
|
|
82
|
-
const actionTimes = {};
|
|
83
|
-
const banList = {};
|
|
84
|
-
const nicknameCache = /* @__PURE__ */ new Map();
|
|
85
|
-
const MAX_CACHE_SIZE = 2e3;
|
|
86
|
-
const CACHE_DURATION = 5 * 60 * 1e3;
|
|
87
85
|
ctx.on("ready", async () => {
|
|
88
86
|
const DATA_FILE = path.join(ctx.baseDir, "data", "ccb.json");
|
|
89
87
|
try {
|
|
@@ -119,47 +117,66 @@ function apply(ctx, config) {
|
|
|
119
117
|
}
|
|
120
118
|
}
|
|
121
119
|
});
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
}
|
|
121
|
+
__name(applyDatabase, "applyDatabase");
|
|
122
|
+
|
|
123
|
+
// src/utils.ts
|
|
124
|
+
var import_koishi2 = require("koishi");
|
|
125
|
+
var CcbState = class _CcbState {
|
|
126
|
+
static {
|
|
127
|
+
__name(this, "CcbState");
|
|
128
|
+
}
|
|
129
|
+
actionTimes = {};
|
|
130
|
+
banList = {};
|
|
131
|
+
nicknameCache = /* @__PURE__ */ new Map();
|
|
132
|
+
static MAX_CACHE_SIZE = 2e3;
|
|
133
|
+
static CACHE_DURATION = 5 * 60 * 1e3;
|
|
134
|
+
cleanupTimer;
|
|
135
|
+
constructor(ctx) {
|
|
136
|
+
const CLEANUP_INTERVAL = 10 * 60 * 1e3;
|
|
137
|
+
this.cleanupTimer = setInterval(() => this.cleanup(), CLEANUP_INTERVAL);
|
|
138
|
+
ctx.on("dispose", () => {
|
|
139
|
+
clearInterval(this.cleanupTimer);
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
cleanup() {
|
|
143
|
+
const nowMs = Date.now();
|
|
144
|
+
const nowSec = nowMs / 1e3;
|
|
145
|
+
for (const userId in this.banList) {
|
|
146
|
+
if (this.banList[userId] < nowSec) {
|
|
147
|
+
delete this.banList[userId];
|
|
148
|
+
}
|
|
127
149
|
}
|
|
128
|
-
for (const userId in actionTimes) {
|
|
129
|
-
if (!actionTimes[userId]
|
|
130
|
-
delete actionTimes[userId];
|
|
150
|
+
for (const userId in this.actionTimes) {
|
|
151
|
+
if (!this.actionTimes[userId]?.length) {
|
|
152
|
+
delete this.actionTimes[userId];
|
|
131
153
|
}
|
|
132
154
|
}
|
|
133
|
-
const
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
nicknameCache.delete(key);
|
|
155
|
+
for (const [key, value] of this.nicknameCache) {
|
|
156
|
+
if (nowMs - value.timestamp > _CcbState.CACHE_DURATION) {
|
|
157
|
+
this.nicknameCache.delete(key);
|
|
137
158
|
}
|
|
138
159
|
}
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
clearInterval(cleanupTimer);
|
|
142
|
-
});
|
|
143
|
-
function getAvatar(userId) {
|
|
160
|
+
}
|
|
161
|
+
getAvatar(userId) {
|
|
144
162
|
return `https://q4.qlogo.cn/headimg_dl?dst_uin=${userId}&spec=640`;
|
|
145
163
|
}
|
|
146
|
-
|
|
147
|
-
async function getUserNickname(session, userId) {
|
|
164
|
+
async getUserNickname(session, userId) {
|
|
148
165
|
const cacheKey = `${session.guildId}:${userId}`;
|
|
149
|
-
const cached = nicknameCache.get(cacheKey);
|
|
166
|
+
const cached = this.nicknameCache.get(cacheKey);
|
|
150
167
|
const now = Date.now();
|
|
151
|
-
if (cached && now - cached.timestamp < CACHE_DURATION) {
|
|
168
|
+
if (cached && now - cached.timestamp < _CcbState.CACHE_DURATION) {
|
|
152
169
|
return cached.name;
|
|
153
170
|
}
|
|
154
171
|
const setAndReturnName = /* @__PURE__ */ __name((name2) => {
|
|
155
172
|
if (name2 && name2 !== userId) {
|
|
156
173
|
const actualName = name2.trim();
|
|
157
174
|
if (actualName) {
|
|
158
|
-
if (nicknameCache.size >= MAX_CACHE_SIZE) {
|
|
159
|
-
const oldestKey = nicknameCache.keys().next().value;
|
|
160
|
-
if (oldestKey) nicknameCache.delete(oldestKey);
|
|
175
|
+
if (this.nicknameCache.size >= _CcbState.MAX_CACHE_SIZE) {
|
|
176
|
+
const oldestKey = this.nicknameCache.keys().next().value;
|
|
177
|
+
if (oldestKey) this.nicknameCache.delete(oldestKey);
|
|
161
178
|
}
|
|
162
|
-
nicknameCache.set(cacheKey, { name: actualName, timestamp: now });
|
|
179
|
+
this.nicknameCache.set(cacheKey, { name: actualName, timestamp: now });
|
|
163
180
|
return actualName;
|
|
164
181
|
}
|
|
165
182
|
}
|
|
@@ -189,52 +206,62 @@ function apply(ctx, config) {
|
|
|
189
206
|
} catch (nestedError) {
|
|
190
207
|
}
|
|
191
208
|
const friendlyName = `用户${userId}`;
|
|
192
|
-
nicknameCache.set(cacheKey, { name: friendlyName, timestamp: now });
|
|
209
|
+
this.nicknameCache.set(cacheKey, { name: friendlyName, timestamp: now });
|
|
193
210
|
return friendlyName;
|
|
194
211
|
}
|
|
195
|
-
|
|
196
|
-
function checkGroupCommand(session) {
|
|
212
|
+
checkGroupCommand(session) {
|
|
197
213
|
if (!session.guildId) {
|
|
198
214
|
return "此命令只能在群聊中使用。";
|
|
199
215
|
}
|
|
200
216
|
return null;
|
|
201
217
|
}
|
|
202
|
-
|
|
203
|
-
async function findTargetUser(session, input) {
|
|
218
|
+
async findTargetUser(session, input) {
|
|
204
219
|
if (!input) return null;
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
220
|
+
try {
|
|
221
|
+
const elements = import_koishi2.h.parse(input);
|
|
222
|
+
const atEl = elements.find((el) => el.type === "at");
|
|
223
|
+
if (atEl?.attrs?.id) {
|
|
224
|
+
return String(atEl.attrs.id);
|
|
225
|
+
}
|
|
226
|
+
} catch (e) {
|
|
227
|
+
const atMatch = input.match(/<at\s+(?:.*?\s+)?id=(["'])(.*?)\1/i);
|
|
228
|
+
if (atMatch) return atMatch[2];
|
|
229
|
+
}
|
|
230
|
+
const colonIndex = input.indexOf(":");
|
|
231
|
+
if (colonIndex > 0 && colonIndex < input.length - 1) {
|
|
232
|
+
return input.slice(colonIndex + 1);
|
|
210
233
|
}
|
|
211
234
|
if (/^\d+$/.test(input)) {
|
|
212
235
|
return input;
|
|
213
236
|
}
|
|
214
237
|
try {
|
|
215
238
|
const list = await session.bot.getGuildMemberList(session.guildId);
|
|
216
|
-
const members = list?.data
|
|
217
|
-
|
|
218
|
-
const targetName =
|
|
219
|
-
let
|
|
239
|
+
const members = list?.data;
|
|
240
|
+
if (!members?.length) return null;
|
|
241
|
+
const targetName = input.replace(/\s/g, "").toLowerCase();
|
|
242
|
+
let exactMatchId;
|
|
243
|
+
let partialMatchId;
|
|
244
|
+
for (const m of members) {
|
|
220
245
|
const nick = m.nick || m.user?.name || m.name || "";
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
246
|
+
if (!nick) continue;
|
|
247
|
+
const cleanNick = nick.replace(/\s/g, "").toLowerCase();
|
|
248
|
+
if (cleanNick === targetName) {
|
|
249
|
+
exactMatchId = m.user?.id;
|
|
250
|
+
break;
|
|
251
|
+
}
|
|
252
|
+
if (!partialMatchId && cleanNick.includes(targetName)) {
|
|
253
|
+
partialMatchId = m.user?.id;
|
|
254
|
+
}
|
|
228
255
|
}
|
|
229
|
-
|
|
256
|
+
const finalMatch = exactMatchId || partialMatchId;
|
|
257
|
+
if (finalMatch) return finalMatch;
|
|
230
258
|
} catch (e) {
|
|
231
259
|
}
|
|
232
260
|
return null;
|
|
233
261
|
}
|
|
234
|
-
|
|
235
|
-
async function validateTargetUser(session, target) {
|
|
262
|
+
async validateTargetUser(session, target) {
|
|
236
263
|
if (target) {
|
|
237
|
-
const foundId = await findTargetUser(session, target);
|
|
264
|
+
const foundId = await this.findTargetUser(session, target);
|
|
238
265
|
if (foundId) {
|
|
239
266
|
try {
|
|
240
267
|
const member = await session.bot.getGuildMember(session.guildId, foundId);
|
|
@@ -251,75 +278,81 @@ function apply(ctx, config) {
|
|
|
251
278
|
}
|
|
252
279
|
return session.userId;
|
|
253
280
|
}
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
newMax = V;
|
|
282
|
-
for (const k in ccb_by) {
|
|
283
|
-
if (ccb_by[k]) ccb_by[k].max = false;
|
|
284
|
-
}
|
|
285
|
-
if (ccb_by[senderId]) ccb_by[senderId].max = true;
|
|
286
|
-
}
|
|
287
|
-
await ctx.database.set("ccb_record", { groupId, userId: targetUserId }, {
|
|
288
|
-
num: newNum,
|
|
289
|
-
vol: newVol,
|
|
290
|
-
max: newMax,
|
|
291
|
-
ccb_by
|
|
292
|
-
});
|
|
293
|
-
const resultMessage = crit ? `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了 💥 暴击!${V.toFixed(2)}ml的生命因子` : `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了${V.toFixed(2)}ml的生命因子`;
|
|
294
|
-
const message = [
|
|
295
|
-
resultMessage,
|
|
296
|
-
import_koishi.segment.image(pic),
|
|
297
|
-
`这是ta的第${newNum}次。`
|
|
298
|
-
].join("\n");
|
|
299
|
-
return message;
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
// src/core.ts
|
|
284
|
+
var import_koishi3 = require("koishi");
|
|
285
|
+
async function createNewCCBRecord(ctx, session, groupId, targetUserId, duration, V, nickname, crit, pic) {
|
|
286
|
+
const newRecord = {
|
|
287
|
+
groupId,
|
|
288
|
+
userId: targetUserId,
|
|
289
|
+
num: 1,
|
|
290
|
+
vol: V,
|
|
291
|
+
max: V,
|
|
292
|
+
ccb_by: { [session.userId]: { count: 1, first: true, max: true } }
|
|
293
|
+
};
|
|
294
|
+
await ctx.database.upsert("ccb_record", [newRecord]);
|
|
295
|
+
const resultMessage = crit ? `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了 💥 暴击!${V.toFixed(2)}ml的生命因子` : `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了${V.toFixed(2)}ml的生命因子`;
|
|
296
|
+
const message = [
|
|
297
|
+
resultMessage,
|
|
298
|
+
import_koishi3.segment.image(pic),
|
|
299
|
+
"这是ta的初体验。"
|
|
300
|
+
].join("\n");
|
|
301
|
+
return message;
|
|
302
|
+
}
|
|
303
|
+
__name(createNewCCBRecord, "createNewCCBRecord");
|
|
304
|
+
async function updateCCBRecord(ctx, session, groupId, targetUserId, duration, V, nickname, crit, pic) {
|
|
305
|
+
const [record] = await ctx.database.get("ccb_record", { groupId, userId: targetUserId });
|
|
306
|
+
if (!record) {
|
|
307
|
+
return await createNewCCBRecord(ctx, session, groupId, targetUserId, duration, V, nickname, crit, pic);
|
|
300
308
|
}
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
309
|
+
const senderId = session.userId;
|
|
310
|
+
const newNum = (record.num || 0) + 1;
|
|
311
|
+
const newVol = parseFloat(((record.vol || 0) + V).toFixed(2));
|
|
312
|
+
let ccb_by = record.ccb_by || {};
|
|
313
|
+
ccb_by = JSON.parse(JSON.stringify(ccb_by));
|
|
314
|
+
if (senderId in ccb_by) {
|
|
315
|
+
const current = ccb_by[senderId];
|
|
316
|
+
ccb_by[senderId] = {
|
|
317
|
+
count: (current?.count || 0) + 1,
|
|
318
|
+
first: current?.first || false,
|
|
319
|
+
max: current?.max || false
|
|
310
320
|
};
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
].join("\n");
|
|
318
|
-
return message;
|
|
321
|
+
} else {
|
|
322
|
+
ccb_by[senderId] = { count: 1, first: false, max: false };
|
|
323
|
+
}
|
|
324
|
+
let prev_max = record.max || 0;
|
|
325
|
+
if (prev_max === 0 && (record.num || 0) > 0) {
|
|
326
|
+
prev_max = parseFloat(((record.vol || 0) / (record.num || 0)).toFixed(2));
|
|
319
327
|
}
|
|
320
|
-
|
|
328
|
+
let newMax = prev_max;
|
|
329
|
+
if (V > prev_max) {
|
|
330
|
+
newMax = V;
|
|
331
|
+
for (const k in ccb_by) {
|
|
332
|
+
if (ccb_by[k]) ccb_by[k].max = false;
|
|
333
|
+
}
|
|
334
|
+
if (ccb_by[senderId]) ccb_by[senderId].max = true;
|
|
335
|
+
}
|
|
336
|
+
await ctx.database.set("ccb_record", { groupId, userId: targetUserId }, {
|
|
337
|
+
num: newNum,
|
|
338
|
+
vol: newVol,
|
|
339
|
+
max: newMax,
|
|
340
|
+
ccb_by
|
|
341
|
+
});
|
|
342
|
+
const resultMessage = crit ? `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了 💥 暴击!${V.toFixed(2)}ml的生命因子` : `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了${V.toFixed(2)}ml的生命因子`;
|
|
343
|
+
const message = [
|
|
344
|
+
resultMessage,
|
|
345
|
+
import_koishi3.segment.image(pic),
|
|
346
|
+
`这是ta的第${newNum}次。`
|
|
347
|
+
].join("\n");
|
|
348
|
+
return message;
|
|
349
|
+
}
|
|
350
|
+
__name(updateCCBRecord, "updateCCBRecord");
|
|
351
|
+
|
|
352
|
+
// src/commands/ccb.ts
|
|
353
|
+
function applyCcbCommand(ctx, config, state) {
|
|
321
354
|
ctx.command("ccb [target:user]", "给群友注入生命因子").option("off", "--off [user:string] 将自己加入白名单(禁止被人ccb),可指定用户").option("on", "--on [user:string] 将自己移出白名单(允许被人ccb),可指定用户").action(async ({ session, options }, target) => {
|
|
322
|
-
const checkResult = checkGroupCommand(session);
|
|
355
|
+
const checkResult = state.checkGroupCommand(session);
|
|
323
356
|
if (checkResult) return checkResult;
|
|
324
357
|
const senderId = session.userId;
|
|
325
358
|
const checkCooldown = /* @__PURE__ */ __name((lastToggle) => {
|
|
@@ -340,7 +373,7 @@ function apply(ctx, config) {
|
|
|
340
373
|
const optionVal = isOff ? options.off : options.on;
|
|
341
374
|
let targetUserStr = null;
|
|
342
375
|
if (typeof optionVal === "string" && optionVal.trim()) {
|
|
343
|
-
targetUserStr = await findTargetUser(session, optionVal.trim());
|
|
376
|
+
targetUserStr = await state.findTargetUser(session, optionVal.trim());
|
|
344
377
|
}
|
|
345
378
|
if (!targetUserStr) {
|
|
346
379
|
const atEl = session.elements?.find((el) => el.type === "at");
|
|
@@ -348,47 +381,42 @@ function apply(ctx, config) {
|
|
|
348
381
|
targetUserStr = String(atEl.attrs.id);
|
|
349
382
|
}
|
|
350
383
|
}
|
|
351
|
-
if (!targetUserStr) {
|
|
352
|
-
|
|
353
|
-
|
|
384
|
+
if (!targetUserStr && typeof optionVal === "string" && optionVal.trim()) {
|
|
385
|
+
return `无法找到用户「${optionVal}」,请检查输入是否正确。`;
|
|
386
|
+
}
|
|
387
|
+
if (targetUserStr) {
|
|
388
|
+
try {
|
|
389
|
+
const memberInfo = await session.bot.getGuildMember(session.guildId, targetUserStr);
|
|
390
|
+
if (!memberInfo) return "无法找到指定用户,请检查输入是否正确。";
|
|
391
|
+
} catch (error) {
|
|
392
|
+
return "无法找到指定用户,请检查输入是否正确。";
|
|
354
393
|
}
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
394
|
+
}
|
|
395
|
+
const [userSetting] = await ctx.database.get("ccb_setting", { userId: senderId });
|
|
396
|
+
const lastToggle = userSetting?.lastToggleTime || 0;
|
|
397
|
+
const cooldownResult = checkCooldown(lastToggle);
|
|
398
|
+
if (cooldownResult) return cooldownResult;
|
|
399
|
+
const nowMs = Date.now();
|
|
400
|
+
if (!targetUserStr) {
|
|
360
401
|
const newOptOut = !!isOff;
|
|
361
402
|
await ctx.database.upsert("ccb_setting", [{
|
|
362
403
|
userId: senderId,
|
|
363
404
|
optOut: newOptOut,
|
|
364
|
-
lastToggleTime:
|
|
405
|
+
lastToggleTime: nowMs,
|
|
365
406
|
overrides: userSetting?.overrides || {}
|
|
366
407
|
}]);
|
|
367
408
|
return newOptOut ? "已开启全局保护模式,阻止你被ccb。" : "已关闭全局保护模式,允许你被ccb。";
|
|
368
409
|
} else {
|
|
369
|
-
try {
|
|
370
|
-
const memberInfo = await session.bot.getGuildMember(session.guildId, targetUserStr);
|
|
371
|
-
if (!memberInfo) {
|
|
372
|
-
return "无法找到指定用户,请检查输入是否正确。";
|
|
373
|
-
}
|
|
374
|
-
} catch (error) {
|
|
375
|
-
return "无法找到指定用户,请检查输入是否正确。";
|
|
376
|
-
}
|
|
377
410
|
const targetId = targetUserStr;
|
|
378
|
-
const now2 = Date.now();
|
|
379
|
-
const [userSetting] = await ctx.database.get("ccb_setting", { userId: senderId });
|
|
380
|
-
const lastToggle = userSetting?.lastToggleTime || 0;
|
|
381
|
-
const cooldownResult = checkCooldown(lastToggle);
|
|
382
|
-
if (cooldownResult) return cooldownResult;
|
|
383
411
|
const overrides = userSetting?.overrides || {};
|
|
384
412
|
overrides[targetId] = !isOff;
|
|
385
413
|
await ctx.database.upsert("ccb_setting", [{
|
|
386
414
|
userId: senderId,
|
|
387
415
|
overrides,
|
|
388
416
|
optOut: userSetting?.optOut ?? false,
|
|
389
|
-
lastToggleTime:
|
|
417
|
+
lastToggleTime: nowMs
|
|
390
418
|
}]);
|
|
391
|
-
const targetNick = await getUserNickname(session, targetId).catch(() => targetId) || targetId;
|
|
419
|
+
const targetNick = await state.getUserNickname(session, targetId).catch(() => targetId) || targetId;
|
|
392
420
|
return isOff ? `已禁止用户 ${targetNick} 对你ccb。` : `已允许用户 ${targetNick} 对你ccb。`;
|
|
393
421
|
}
|
|
394
422
|
}
|
|
@@ -406,45 +434,45 @@ function apply(ctx, config) {
|
|
|
406
434
|
ywProbability: cheatSetting ? cheatSetting.ywProbability : config.ywProbability,
|
|
407
435
|
critProb: cheatSetting ? cheatSetting.critProb : config.critProb
|
|
408
436
|
};
|
|
409
|
-
const banEnd = banList[actorId] || 0;
|
|
437
|
+
const banEnd = state.banList[actorId] || 0;
|
|
410
438
|
if (now < banEnd) {
|
|
411
439
|
const remain = Math.floor(banEnd - now);
|
|
412
440
|
const m = Math.floor(remain / 60);
|
|
413
441
|
const s = remain % 60;
|
|
414
442
|
return `嘻嘻,你已经一滴不剩了,填充还剩 ${m}分${s}秒`;
|
|
415
443
|
}
|
|
416
|
-
const times = actionTimes[actorId] = actionTimes[actorId] || [];
|
|
444
|
+
const times = state.actionTimes[actorId] = state.actionTimes[actorId] || [];
|
|
417
445
|
const cutoff = now - currentConfig.ywWindow;
|
|
418
446
|
while (times.length > 0 && times[0] < cutoff) {
|
|
419
447
|
times.shift();
|
|
420
448
|
}
|
|
421
449
|
times.push(now);
|
|
422
450
|
if (times.length > currentConfig.ywThreshold) {
|
|
423
|
-
banList[actorId] = now + currentConfig.ywBanDuration;
|
|
424
|
-
actionTimes[actorId] = [];
|
|
451
|
+
state.banList[actorId] = now + currentConfig.ywBanDuration;
|
|
452
|
+
state.actionTimes[actorId] = [];
|
|
425
453
|
return "冲得出来吗你就冲,再冲就给你折了";
|
|
426
454
|
}
|
|
427
|
-
let targetUserId = await validateTargetUser(session, target);
|
|
455
|
+
let targetUserId = await state.validateTargetUser(session, target);
|
|
428
456
|
if (targetUserId.startsWith("无法找到")) {
|
|
429
457
|
return targetUserId;
|
|
430
458
|
}
|
|
431
459
|
if (config.whiteList.includes(targetUserId)) {
|
|
432
|
-
const nickname = await getUserNickname(session, targetUserId) || targetUserId;
|
|
460
|
+
const nickname = await state.getUserNickname(session, targetUserId) || targetUserId;
|
|
433
461
|
return `${nickname} 拒绝了和你ccb。`;
|
|
434
462
|
}
|
|
435
463
|
if (senderSetting?.overrides?.[targetUserId] === false) {
|
|
436
|
-
const nickname = await getUserNickname(session, targetUserId) || targetUserId;
|
|
464
|
+
const nickname = await state.getUserNickname(session, targetUserId) || targetUserId;
|
|
437
465
|
return `你已禁止与 ${nickname} 进行ccb。`;
|
|
438
466
|
}
|
|
439
467
|
const [targetSetting] = await ctx.database.get("ccb_setting", { userId: targetUserId });
|
|
440
468
|
if (targetSetting) {
|
|
441
469
|
const overrides = targetSetting.overrides || {};
|
|
442
470
|
if (overrides[actorId] === false) {
|
|
443
|
-
const nickname = await getUserNickname(session, targetUserId) || targetUserId;
|
|
471
|
+
const nickname = await state.getUserNickname(session, targetUserId) || targetUserId;
|
|
444
472
|
return `${nickname} 拒绝了和你ccb`;
|
|
445
473
|
}
|
|
446
474
|
if (overrides[actorId] !== true && targetSetting.optOut) {
|
|
447
|
-
const nickname = await getUserNickname(session, targetUserId) || targetUserId;
|
|
475
|
+
const nickname = await state.getUserNickname(session, targetUserId) || targetUserId;
|
|
448
476
|
return `${nickname} 拒绝了和你ccb`;
|
|
449
477
|
}
|
|
450
478
|
}
|
|
@@ -459,26 +487,31 @@ function apply(ctx, config) {
|
|
|
459
487
|
V = parseFloat((V * 2).toFixed(2));
|
|
460
488
|
crit = true;
|
|
461
489
|
}
|
|
462
|
-
const pic = getAvatar(targetUserId);
|
|
490
|
+
const pic = state.getAvatar(targetUserId);
|
|
463
491
|
let message;
|
|
464
492
|
try {
|
|
465
|
-
const nickname = await getUserNickname(session, targetUserId);
|
|
466
|
-
message = await updateCCBRecord(session, session.guildId, targetUserId, duration, V, nickname, crit, pic);
|
|
493
|
+
const nickname = await state.getUserNickname(session, targetUserId);
|
|
494
|
+
message = await updateCCBRecord(ctx, session, session.guildId, targetUserId, duration, V, nickname, crit, pic);
|
|
467
495
|
} catch (e) {
|
|
468
496
|
console.error(`报错: ${e}`);
|
|
469
497
|
return "对方拒绝了和你ccb";
|
|
470
498
|
}
|
|
471
499
|
if (Math.random() < currentConfig.ywProbability) {
|
|
472
|
-
banList[actorId] = now + currentConfig.ywBanDuration;
|
|
500
|
+
state.banList[actorId] = now + currentConfig.ywBanDuration;
|
|
473
501
|
await session.send(message);
|
|
474
502
|
return "💥你炸膛了!不能ccb了(悲)";
|
|
475
503
|
}
|
|
476
504
|
return message;
|
|
477
505
|
});
|
|
506
|
+
}
|
|
507
|
+
__name(applyCcbCommand, "applyCcbCommand");
|
|
508
|
+
|
|
509
|
+
// src/commands/rank.ts
|
|
510
|
+
function applyRankCommands(ctx, state) {
|
|
478
511
|
async function buildRanking(session, title, data, formatLine) {
|
|
479
512
|
const nicknameMap = /* @__PURE__ */ new Map();
|
|
480
513
|
await Promise.all(data.map(async (r) => {
|
|
481
|
-
nicknameMap.set(r.userId, await getUserNickname(session, r.userId));
|
|
514
|
+
nicknameMap.set(r.userId, await state.getUserNickname(session, r.userId));
|
|
482
515
|
}));
|
|
483
516
|
let msg = `${title}
|
|
484
517
|
`;
|
|
@@ -490,7 +523,7 @@ function apply(ctx, config) {
|
|
|
490
523
|
}
|
|
491
524
|
__name(buildRanking, "buildRanking");
|
|
492
525
|
ctx.command("ccbtop", "按次数排行").action(async ({ session }) => {
|
|
493
|
-
const checkResult = checkGroupCommand(session);
|
|
526
|
+
const checkResult = state.checkGroupCommand(session);
|
|
494
527
|
if (checkResult) return checkResult;
|
|
495
528
|
const groupData = await ctx.database.get("ccb_record", { groupId: session.guildId });
|
|
496
529
|
if (!groupData.length) return "当前群暂无ccb记录。";
|
|
@@ -504,7 +537,7 @@ function apply(ctx, config) {
|
|
|
504
537
|
);
|
|
505
538
|
});
|
|
506
539
|
ctx.command("ccbvol", "按注入量排行").action(async ({ session }) => {
|
|
507
|
-
const checkResult = checkGroupCommand(session);
|
|
540
|
+
const checkResult = state.checkGroupCommand(session);
|
|
508
541
|
if (checkResult) return checkResult;
|
|
509
542
|
const groupData = await ctx.database.get("ccb_record", { groupId: session.guildId });
|
|
510
543
|
if (!groupData.length) return "当前群暂无ccb记录。";
|
|
@@ -518,7 +551,7 @@ function apply(ctx, config) {
|
|
|
518
551
|
);
|
|
519
552
|
});
|
|
520
553
|
ctx.command("ccbmax", "按max值排行并输出产生者").action(async ({ session }) => {
|
|
521
|
-
const checkResult = checkGroupCommand(session);
|
|
554
|
+
const checkResult = state.checkGroupCommand(session);
|
|
522
555
|
if (checkResult) return checkResult;
|
|
523
556
|
const groupData = await ctx.database.get("ccb_record", { groupId: session.guildId });
|
|
524
557
|
if (!groupData.length) return "当前群暂无ccb记录。";
|
|
@@ -558,7 +591,7 @@ function apply(ctx, config) {
|
|
|
558
591
|
const uniqueUserIds = [...new Set(userIds)];
|
|
559
592
|
const nicknameMap = /* @__PURE__ */ new Map();
|
|
560
593
|
await Promise.all(uniqueUserIds.map(async (uid) => {
|
|
561
|
-
nicknameMap.set(uid, await getUserNickname(session, uid));
|
|
594
|
+
nicknameMap.set(uid, await state.getUserNickname(session, uid));
|
|
562
595
|
}));
|
|
563
596
|
let msg = "单次最大注入排行榜 TOP5:\n";
|
|
564
597
|
for (let i = 0; i < entries.length; i++) {
|
|
@@ -571,10 +604,15 @@ function apply(ctx, config) {
|
|
|
571
604
|
}
|
|
572
605
|
return msg.trim();
|
|
573
606
|
});
|
|
607
|
+
}
|
|
608
|
+
__name(applyRankCommands, "applyRankCommands");
|
|
609
|
+
|
|
610
|
+
// src/commands/info.ts
|
|
611
|
+
function applyInfoCommand(ctx, state) {
|
|
574
612
|
ctx.command("ccbinfo [target:user]", "查询某人ccb信息").action(async ({ session }, target) => {
|
|
575
|
-
const checkResult = checkGroupCommand(session);
|
|
613
|
+
const checkResult = state.checkGroupCommand(session);
|
|
576
614
|
if (checkResult) return checkResult;
|
|
577
|
-
let targetUserId = await validateTargetUser(session, target);
|
|
615
|
+
let targetUserId = await state.validateTargetUser(session, target);
|
|
578
616
|
if (targetUserId.startsWith("无法找到")) {
|
|
579
617
|
return targetUserId;
|
|
580
618
|
}
|
|
@@ -606,8 +644,8 @@ function apply(ctx, config) {
|
|
|
606
644
|
}
|
|
607
645
|
}
|
|
608
646
|
}
|
|
609
|
-
const target_nick = await getUserNickname(session, targetUserId);
|
|
610
|
-
const first_nick = first_actor ? await getUserNickname(session, first_actor) : "未知";
|
|
647
|
+
const target_nick = await state.getUserNickname(session, targetUserId);
|
|
648
|
+
const first_nick = first_actor ? await state.getUserNickname(session, first_actor) : "未知";
|
|
611
649
|
const msg = [
|
|
612
650
|
`【${target_nick} 】`,
|
|
613
651
|
`• 开拓者:${first_nick}`,
|
|
@@ -618,8 +656,13 @@ function apply(ctx, config) {
|
|
|
618
656
|
].join("\n");
|
|
619
657
|
return msg;
|
|
620
658
|
});
|
|
659
|
+
}
|
|
660
|
+
__name(applyInfoCommand, "applyInfoCommand");
|
|
661
|
+
|
|
662
|
+
// src/commands/charm.ts
|
|
663
|
+
function applyCharmCommand(ctx, state) {
|
|
621
664
|
ctx.command("ccbcharm", "魅力榜 - 计算群中最受欢迎的群友").action(async ({ session }) => {
|
|
622
|
-
const checkResult = checkGroupCommand(session);
|
|
665
|
+
const checkResult = state.checkGroupCommand(session);
|
|
623
666
|
if (checkResult) return checkResult;
|
|
624
667
|
const w_num = 1;
|
|
625
668
|
const w_vol = 0.1;
|
|
@@ -640,7 +683,7 @@ function apply(ctx, config) {
|
|
|
640
683
|
}).sort((a, b) => b.val - a.val).slice(0, 5);
|
|
641
684
|
const nicknameMap = /* @__PURE__ */ new Map();
|
|
642
685
|
await Promise.all(ranking.map(async (r) => {
|
|
643
|
-
nicknameMap.set(r.userId, await getUserNickname(session, r.userId));
|
|
686
|
+
nicknameMap.set(r.userId, await state.getUserNickname(session, r.userId));
|
|
644
687
|
}));
|
|
645
688
|
let msg = "💎 魅力榜 TOP5 💎\n";
|
|
646
689
|
for (let i = 0; i < ranking.length; i++) {
|
|
@@ -652,11 +695,31 @@ function apply(ctx, config) {
|
|
|
652
695
|
return msg.trim();
|
|
653
696
|
});
|
|
654
697
|
}
|
|
698
|
+
__name(applyCharmCommand, "applyCharmCommand");
|
|
699
|
+
|
|
700
|
+
// src/commands/index.ts
|
|
701
|
+
function applyCommands(ctx, config, state) {
|
|
702
|
+
applyCcbCommand(ctx, config, state);
|
|
703
|
+
applyRankCommands(ctx, state);
|
|
704
|
+
applyInfoCommand(ctx, state);
|
|
705
|
+
applyCharmCommand(ctx, state);
|
|
706
|
+
}
|
|
707
|
+
__name(applyCommands, "applyCommands");
|
|
708
|
+
|
|
709
|
+
// src/index.ts
|
|
710
|
+
var name = "ccb-plus";
|
|
711
|
+
var inject = ["database"];
|
|
712
|
+
function apply(ctx, config) {
|
|
713
|
+
applyDatabase(ctx);
|
|
714
|
+
const state = new CcbState(ctx);
|
|
715
|
+
applyCommands(ctx, config, state);
|
|
716
|
+
}
|
|
655
717
|
__name(apply, "apply");
|
|
656
718
|
// Annotate the CommonJS export names for ESM import in node:
|
|
657
719
|
0 && (module.exports = {
|
|
658
720
|
Config,
|
|
659
721
|
apply,
|
|
722
|
+
applyDatabase,
|
|
660
723
|
inject,
|
|
661
724
|
name
|
|
662
725
|
});
|
package/lib/model.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { Context } from 'koishi';
|
|
2
|
+
export interface CCBRecord {
|
|
3
|
+
groupId: string;
|
|
4
|
+
userId: string;
|
|
5
|
+
num: number;
|
|
6
|
+
vol: number;
|
|
7
|
+
max: number;
|
|
8
|
+
ccb_by: {
|
|
9
|
+
[actorId: string]: {
|
|
10
|
+
count: number;
|
|
11
|
+
first: boolean;
|
|
12
|
+
max: boolean;
|
|
13
|
+
};
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
export interface CCBUserSetting {
|
|
17
|
+
userId: string;
|
|
18
|
+
optOut: boolean;
|
|
19
|
+
lastToggleTime: number;
|
|
20
|
+
overrides: Record<string, boolean>;
|
|
21
|
+
}
|
|
22
|
+
declare module 'koishi' {
|
|
23
|
+
interface Tables {
|
|
24
|
+
ccb_record: CCBRecord;
|
|
25
|
+
ccb_setting: CCBUserSetting;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
export declare function applyDatabase(ctx: Context): void;
|
package/lib/utils.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { Context, Session } from 'koishi';
|
|
2
|
+
export declare class CcbState {
|
|
3
|
+
actionTimes: Record<string, number[]>;
|
|
4
|
+
banList: Record<string, number>;
|
|
5
|
+
nicknameCache: Map<string, {
|
|
6
|
+
name: string;
|
|
7
|
+
timestamp: number;
|
|
8
|
+
}>;
|
|
9
|
+
private static MAX_CACHE_SIZE;
|
|
10
|
+
private static CACHE_DURATION;
|
|
11
|
+
private cleanupTimer;
|
|
12
|
+
constructor(ctx: Context);
|
|
13
|
+
private cleanup;
|
|
14
|
+
getAvatar(userId: string): string;
|
|
15
|
+
getUserNickname(session: Session, userId: string): Promise<string>;
|
|
16
|
+
checkGroupCommand(session: Session): string | null;
|
|
17
|
+
findTargetUser(session: Session, input: string): Promise<string | null>;
|
|
18
|
+
validateTargetUser(session: Session, target: string): Promise<string>;
|
|
19
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-ccb-plus",
|
|
3
|
-
"description": "Koishi插件,与群友发生ccb行为。(移植自astrbot_plugin_ccb_plus)",
|
|
4
|
-
"version": "0.2.8
|
|
3
|
+
"description": "Koishi 插件,与群友发生 ccb 行为。(移植自 astrbot_plugin_ccb_plus )",
|
|
4
|
+
"version": "0.2.8",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -20,4 +20,4 @@
|
|
|
20
20
|
"peerDependencies": {
|
|
21
21
|
"koishi": "^4.18.7"
|
|
22
22
|
}
|
|
23
|
-
}
|
|
23
|
+
}
|
package/readme.md
CHANGED