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.
@@ -0,0 +1,4 @@
1
+ import { Context } from 'koishi';
2
+ import { CCBConfig } from '../config';
3
+ import { CcbState } from '../utils';
4
+ export declare function applyCcbCommand(ctx: Context, config: CCBConfig, state: CcbState): void;
@@ -0,0 +1,3 @@
1
+ import { Context } from 'koishi';
2
+ import { CcbState } from '../utils';
3
+ export declare function applyCharmCommand(ctx: Context, state: CcbState): void;
@@ -0,0 +1,4 @@
1
+ import { Context } from 'koishi';
2
+ import { CCBConfig } from '../config';
3
+ import { CcbState } from '../utils';
4
+ export declare function applyCommands(ctx: Context, config: CCBConfig, state: CcbState): void;
@@ -0,0 +1,3 @@
1
+ import { Context } from 'koishi';
2
+ import { CcbState } from '../utils';
3
+ export declare function applyInfoCommand(ctx: Context, state: CcbState): void;
@@ -0,0 +1,3 @@
1
+ import { Context } from 'koishi';
2
+ import { CcbState } from '../utils';
3
+ export declare function applyRankCommands(ctx: Context, state: CcbState): void;
@@ -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, Schema } from 'koishi';
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 interface CheatConfig {
5
- userId: string;
6
- ywWindow: number;
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
- function apply(ctx, config) {
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
- const CLEANUP_INTERVAL = 10 * 60 * 1e3;
123
- const cleanupTimer = setInterval(() => {
124
- const now = Date.now() / 1e3;
125
- for (const userId in banList) {
126
- if (banList[userId] < now) delete banList[userId];
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] || actionTimes[userId].length === 0) {
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 cacheNow = Date.now();
134
- for (const [key, value] of nicknameCache) {
135
- if (cacheNow - value.timestamp > CACHE_DURATION) {
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
- }, CLEANUP_INTERVAL);
140
- ctx.on("dispose", () => {
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
- __name(getAvatar, "getAvatar");
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
- __name(getUserNickname, "getUserNickname");
196
- function checkGroupCommand(session) {
212
+ checkGroupCommand(session) {
197
213
  if (!session.guildId) {
198
214
  return "此命令只能在群聊中使用。";
199
215
  }
200
216
  return null;
201
217
  }
202
- __name(checkGroupCommand, "checkGroupCommand");
203
- async function findTargetUser(session, input) {
218
+ async findTargetUser(session, input) {
204
219
  if (!input) return null;
205
- const atMatch = input.match(/<at\s[^>]*id="([^"]+)"/);
206
- if (atMatch) return atMatch[1];
207
- const unionMatch = input.match(/^[^:]+:(.+)$/);
208
- if (unionMatch) {
209
- return unionMatch[1];
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
- const clean = /* @__PURE__ */ __name((s) => s.replace(/\s/g, "").toLowerCase(), "clean");
218
- const targetName = clean(input);
219
- let found = members.find((m) => {
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
- return clean(nick) === targetName;
222
- });
223
- if (!found) {
224
- found = members.find((m) => {
225
- const nick = m.nick || m.user?.name || m.name || "";
226
- return clean(nick).includes(targetName);
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
- if (found) return found.user?.id;
256
+ const finalMatch = exactMatchId || partialMatchId;
257
+ if (finalMatch) return finalMatch;
230
258
  } catch (e) {
231
259
  }
232
260
  return null;
233
261
  }
234
- __name(findTargetUser, "findTargetUser");
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
- __name(validateTargetUser, "validateTargetUser");
255
- async function updateCCBRecord(session, groupId, targetUserId, duration, V, nickname, crit, pic) {
256
- const [record] = await ctx.database.get("ccb_record", { groupId, userId: targetUserId });
257
- if (!record) {
258
- return await createNewCCBRecord(session, groupId, targetUserId, duration, V, nickname, crit, pic);
259
- }
260
- const senderId = session.userId;
261
- const newNum = (record.num || 0) + 1;
262
- const newVol = parseFloat(((record.vol || 0) + V).toFixed(2));
263
- let ccb_by = record.ccb_by || {};
264
- ccb_by = JSON.parse(JSON.stringify(ccb_by));
265
- if (senderId in ccb_by) {
266
- const current = ccb_by[senderId];
267
- ccb_by[senderId] = {
268
- count: (current?.count || 0) + 1,
269
- first: current?.first || false,
270
- max: current?.max || false
271
- };
272
- } else {
273
- ccb_by[senderId] = { count: 1, first: false, max: false };
274
- }
275
- let prev_max = record.max || 0;
276
- if (prev_max === 0 && (record.num || 0) > 0) {
277
- prev_max = parseFloat(((record.vol || 0) / (record.num || 0)).toFixed(2));
278
- }
279
- let newMax = prev_max;
280
- if (V > prev_max) {
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
- __name(updateCCBRecord, "updateCCBRecord");
302
- async function createNewCCBRecord(session, groupId, targetUserId, duration, V, nickname, crit, pic) {
303
- const newRecord = {
304
- groupId,
305
- userId: targetUserId,
306
- num: 1,
307
- vol: V,
308
- max: V,
309
- ccb_by: { [session.userId]: { count: 1, first: true, max: true } }
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
- await ctx.database.upsert("ccb_record", [newRecord]);
312
- const resultMessage = crit ? `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了 💥 暴击!${V.toFixed(2)}ml的生命因子` : `你和${nickname}发生了${duration}min长的ccb行为,向ta注入了${V.toFixed(2)}ml的生命因子`;
313
- const message = [
314
- resultMessage,
315
- import_koishi.segment.image(pic),
316
- "这是ta的初体验。"
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
- __name(createNewCCBRecord, "createNewCCBRecord");
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
- if (typeof optionVal === "string" && optionVal.trim()) {
353
- return `无法找到用户「${optionVal}」,请检查输入是否正确。`;
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
- const now2 = Date.now();
356
- const [userSetting] = await ctx.database.get("ccb_setting", { userId: senderId });
357
- const lastToggle = userSetting?.lastToggleTime || 0;
358
- const cooldownResult = checkCooldown(lastToggle);
359
- if (cooldownResult) return cooldownResult;
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: now2,
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: now2
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-beta.1",
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
@@ -8,4 +8,4 @@ Koishi插件,与群友发生ccb行为。(移植自astrbot_plugin_ccb_plus)
8
8
 
9
9
  以后会进行小幅修改
10
10
 
11
- > 使用 Qwen3-Coder & Gemini-3-Pro-Preview 协助完成
11
+ > 使用 Qwen3-Coder & Gemini-3.1-Pro-Preview 协助完成