koishi-plugin-brick-qq 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 +47 -0
- package/lib/index.js +469 -0
- package/package.json +23 -0
- package/readme.md +96 -0
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Context, Schema } from "koishi";
|
|
2
|
+
export declare const name = "brick";
|
|
3
|
+
export declare const usage = "windbullet\u5927\u4F6C\u7684brick\u63D2\u4EF6\u4F18\u5316\u7248\uFF0C\u6DFB\u52A0\u4E86\u4E00\u4E9B\u8FB9\u754C\u5224\u65AD\u548C\u66B4\u529B\u62CD\u4EBA\u529F\u80FD\u3002\n\u70E7\u5236\u7816\u5757\uFF0C\u7136\u540E\u62CD\u6655\u7FA4\u53CB\uFF01\n\u5982\u679C\u673A\u5668\u4EBA\u6CA1\u6709\u7981\u8A00\u7684\u6743\u9650\uFF0C\u5C06\u6539\u4E3A\u505C\u6B62\u54CD\u5E94\u88AB\u62CD\u6655\u7684\u7528\u6237\u76F8\u540C\u65F6\u95F4\uFF01\n\u5F00\u59CB\u70E7\u5236\u4E4B\u540E\uFF0C\u7FA4\u5185\u5176\u4ED6\u7FA4\u53CB\u53D1\u9001\u4E00\u5B9A\u6570\u91CF\u7684\u6D88\u606F\u5C31\u80FD\u5B8C\u6210\u70E7\u5236\uFF01\n\u70E7\u51FA\u6765\u7684\u7816\u5934\u4E0D\u80FD\u8DE8\u7FA4\u7528\u54E6\uFF01 ";
|
|
4
|
+
export interface Brick {
|
|
5
|
+
id: number;
|
|
6
|
+
userId: string;
|
|
7
|
+
guildId: string;
|
|
8
|
+
brick: number;
|
|
9
|
+
lastSlap: number;
|
|
10
|
+
checkingDay: string;
|
|
11
|
+
}
|
|
12
|
+
export interface BrickCheckinStats {
|
|
13
|
+
id: number;
|
|
14
|
+
userId: string;
|
|
15
|
+
totalCheckins: number;
|
|
16
|
+
streakDays: number;
|
|
17
|
+
lastCheckinDay: string;
|
|
18
|
+
}
|
|
19
|
+
declare module "koishi" {
|
|
20
|
+
interface Channel {
|
|
21
|
+
brickEnabled: boolean;
|
|
22
|
+
}
|
|
23
|
+
interface Tables {
|
|
24
|
+
brick: Brick;
|
|
25
|
+
brick_checkin_stats: BrickCheckinStats;
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
interface SpecialUser {
|
|
29
|
+
[userId: string]: number;
|
|
30
|
+
}
|
|
31
|
+
export interface Config {
|
|
32
|
+
maxBrick: number;
|
|
33
|
+
cost: number;
|
|
34
|
+
cooldown: number;
|
|
35
|
+
minMuteTime: number;
|
|
36
|
+
maxMuteTime: number;
|
|
37
|
+
ignoreMuted: boolean;
|
|
38
|
+
reverse: number;
|
|
39
|
+
specialUser: SpecialUser;
|
|
40
|
+
checking: boolean;
|
|
41
|
+
minGain?: number;
|
|
42
|
+
maxGain?: number;
|
|
43
|
+
}
|
|
44
|
+
export declare const Config: Schema<Config>;
|
|
45
|
+
export declare const inject: string[];
|
|
46
|
+
export declare function apply(ctx: Context, config: Config): Promise<void>;
|
|
47
|
+
export {};
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,469 @@
|
|
|
1
|
+
var __defProp = Object.defineProperty;
|
|
2
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
6
|
+
var __export = (target, all) => {
|
|
7
|
+
for (var name2 in all)
|
|
8
|
+
__defProp(target, name2, { get: all[name2], enumerable: true });
|
|
9
|
+
};
|
|
10
|
+
var __copyProps = (to, from, except, desc) => {
|
|
11
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
12
|
+
for (let key of __getOwnPropNames(from))
|
|
13
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
14
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
15
|
+
}
|
|
16
|
+
return to;
|
|
17
|
+
};
|
|
18
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
19
|
+
|
|
20
|
+
// src/index.ts
|
|
21
|
+
var src_exports = {};
|
|
22
|
+
__export(src_exports, {
|
|
23
|
+
Config: () => Config,
|
|
24
|
+
apply: () => apply,
|
|
25
|
+
inject: () => inject,
|
|
26
|
+
name: () => name,
|
|
27
|
+
usage: () => usage
|
|
28
|
+
});
|
|
29
|
+
module.exports = __toCommonJS(src_exports);
|
|
30
|
+
var import_koishi = require("koishi");
|
|
31
|
+
var import_adapter_qq = require("@satorijs/adapter-qq");
|
|
32
|
+
var name = "brick";
|
|
33
|
+
var usage = `windbullet大佬的brick插件优化版,添加了一些边界判断和暴力拍人功能。
|
|
34
|
+
烧制砖块,然后拍晕群友!
|
|
35
|
+
如果机器人没有禁言的权限,将改为停止响应被拍晕的用户相同时间!
|
|
36
|
+
开始烧制之后,群内其他群友发送一定数量的消息就能完成烧制!
|
|
37
|
+
烧出来的砖头不能跨群用哦! `;
|
|
38
|
+
var Config = import_koishi.Schema.intersect([
|
|
39
|
+
import_koishi.Schema.object({
|
|
40
|
+
maxBrick: import_koishi.Schema.number().default(1).min(1).description("砖块最多持有量"),
|
|
41
|
+
cost: import_koishi.Schema.number().required().min(1).description("多少条消息能烧好一块砖"),
|
|
42
|
+
cooldown: import_koishi.Schema.number().default(60).min(0).description("拍砖冷却时间(秒)"),
|
|
43
|
+
minMuteTime: import_koishi.Schema.number().default(10).min(1).description("最小禁言时间(秒)"),
|
|
44
|
+
maxMuteTime: import_koishi.Schema.number().default(120).min(1).description("最大禁言时间(秒)"),
|
|
45
|
+
ignoreMuted: import_koishi.Schema.boolean().default(false).description("是否不使用内置计时器判断用户是否被禁言(开启时,用户被拍晕后未被禁言也能正常使用bot以及再被拍晕)"),
|
|
46
|
+
reverse: import_koishi.Schema.number().default(10).min(0).max(100).description("反被拍晕的默认概率(%)"),
|
|
47
|
+
specialUser: import_koishi.Schema.dict(Number).role("table").description("键为用户ID,值为被拍时的反击概率(%)<br/>不设置则使用默认概率")
|
|
48
|
+
}),
|
|
49
|
+
import_koishi.Schema.object({
|
|
50
|
+
checking: import_koishi.Schema.boolean().default(false).description("是否开启每日签到(获取随机数量的砖头)")
|
|
51
|
+
}),
|
|
52
|
+
import_koishi.Schema.union([
|
|
53
|
+
import_koishi.Schema.object({
|
|
54
|
+
checking: import_koishi.Schema.const(true).required(),
|
|
55
|
+
minGain: import_koishi.Schema.number().required().min(0).description("最小获取数量(不可为负数)"),
|
|
56
|
+
maxGain: import_koishi.Schema.number().required().min(0).description("最大获取数量")
|
|
57
|
+
}),
|
|
58
|
+
import_koishi.Schema.object({
|
|
59
|
+
checking: import_koishi.Schema.const(false)
|
|
60
|
+
})
|
|
61
|
+
])
|
|
62
|
+
]);
|
|
63
|
+
var inject = ["database"];
|
|
64
|
+
async function apply(ctx, config) {
|
|
65
|
+
ctx.model.extend("channel", {
|
|
66
|
+
brickEnabled: { type: "boolean", initial: true }
|
|
67
|
+
});
|
|
68
|
+
ctx.model.extend(
|
|
69
|
+
"brick",
|
|
70
|
+
{
|
|
71
|
+
id: "unsigned",
|
|
72
|
+
userId: "string",
|
|
73
|
+
guildId: "string",
|
|
74
|
+
brick: "integer",
|
|
75
|
+
lastSlap: "unsigned",
|
|
76
|
+
checkingDay: "string"
|
|
77
|
+
},
|
|
78
|
+
{ primary: "id", autoInc: true, unique: [["userId", "guildId"]] }
|
|
79
|
+
);
|
|
80
|
+
ctx.model.extend(
|
|
81
|
+
"brick_checkin_stats",
|
|
82
|
+
{
|
|
83
|
+
id: "unsigned",
|
|
84
|
+
userId: "string",
|
|
85
|
+
totalCheckins: "unsigned",
|
|
86
|
+
streakDays: "unsigned",
|
|
87
|
+
lastCheckinDay: "string"
|
|
88
|
+
},
|
|
89
|
+
{ primary: "id", autoInc: true, unique: [["userId"]] }
|
|
90
|
+
);
|
|
91
|
+
const users = {};
|
|
92
|
+
function tryCleanupUser(key) {
|
|
93
|
+
const status = users[key];
|
|
94
|
+
if (status && !status.burning && !status.muted) {
|
|
95
|
+
delete users[key];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
__name(tryCleanupUser, "tryCleanupUser");
|
|
99
|
+
function getBrickChannel(session) {
|
|
100
|
+
return session.channel;
|
|
101
|
+
}
|
|
102
|
+
__name(getBrickChannel, "getBrickChannel");
|
|
103
|
+
function ensureBrickEnabled(session) {
|
|
104
|
+
if (getBrickChannel(session).brickEnabled === false) {
|
|
105
|
+
return "本群未开启砖头功能";
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
__name(ensureBrickEnabled, "ensureBrickEnabled");
|
|
109
|
+
ctx.guild().command("砖头");
|
|
110
|
+
ctx.guild().command("开启砖头", "开启本群砖头功能").channelFields(["brickEnabled"]).action(async ({ session }) => {
|
|
111
|
+
const channel = getBrickChannel(session);
|
|
112
|
+
if (channel.brickEnabled) {
|
|
113
|
+
return "本群已开启砖头功能";
|
|
114
|
+
}
|
|
115
|
+
channel.brickEnabled = true;
|
|
116
|
+
return "砖头功能开启成功";
|
|
117
|
+
});
|
|
118
|
+
ctx.guild().command("关闭砖头", "关闭本群砖头功能").channelFields(["brickEnabled"]).action(async ({ session }) => {
|
|
119
|
+
const channel = getBrickChannel(session);
|
|
120
|
+
if (channel.brickEnabled === false) {
|
|
121
|
+
return "本群已关闭砖头功能";
|
|
122
|
+
}
|
|
123
|
+
channel.brickEnabled = false;
|
|
124
|
+
return "砖头功能关闭成功";
|
|
125
|
+
});
|
|
126
|
+
ctx.guild().command("砖头.烧砖", "烧点砖头拍人").alias("烧砖").channelFields(["brickEnabled"]).action(async ({ session }) => {
|
|
127
|
+
const disabledMessage = ensureBrickEnabled(session);
|
|
128
|
+
if (disabledMessage) return disabledMessage;
|
|
129
|
+
const user = `${session.guildId}:${session.userId}`;
|
|
130
|
+
if (!users[user]) {
|
|
131
|
+
users[user] = { burning: false, muted: false };
|
|
132
|
+
}
|
|
133
|
+
const userData = await ctx.database.get("brick", {
|
|
134
|
+
userId: session.userId,
|
|
135
|
+
guildId: session.guildId
|
|
136
|
+
});
|
|
137
|
+
if (userData.length === 0) {
|
|
138
|
+
await ctx.database.create("brick", {
|
|
139
|
+
userId: session.userId,
|
|
140
|
+
guildId: session.guildId,
|
|
141
|
+
brick: 0
|
|
142
|
+
});
|
|
143
|
+
} else if (userData[0].brick >= config.maxBrick) {
|
|
144
|
+
return `你最多只能拥有${config.maxBrick}块砖`;
|
|
145
|
+
}
|
|
146
|
+
if (users[user]?.muted && !config.ignoreMuted) {
|
|
147
|
+
return "你还在晕着呢,不能烧砖哦";
|
|
148
|
+
}
|
|
149
|
+
if (users[user]?.burning) {
|
|
150
|
+
return `已经在烧砖了`;
|
|
151
|
+
}
|
|
152
|
+
users[user].burning = true;
|
|
153
|
+
await session.send(`现在开始烧砖啦,群友每发送${config.cost}条消息就烧好一块砖`);
|
|
154
|
+
let messageCount = 0;
|
|
155
|
+
const dispose = ctx.guild(session.guildId).middleware(async (session_in, next) => {
|
|
156
|
+
if (![session.selfId, session.userId].includes(session_in.userId)) {
|
|
157
|
+
messageCount += 1;
|
|
158
|
+
if (messageCount >= config.cost) {
|
|
159
|
+
dispose();
|
|
160
|
+
await ctx.database.upsert(
|
|
161
|
+
"brick",
|
|
162
|
+
(row) => [
|
|
163
|
+
{
|
|
164
|
+
userId: session.userId,
|
|
165
|
+
guildId: session.guildId,
|
|
166
|
+
brick: import_koishi.$.add(row.brick, 1)
|
|
167
|
+
}
|
|
168
|
+
],
|
|
169
|
+
["userId", "guildId"]
|
|
170
|
+
);
|
|
171
|
+
users[user].burning = false;
|
|
172
|
+
tryCleanupUser(user);
|
|
173
|
+
await session.qq.sendMessage(session.channelId, {
|
|
174
|
+
msg_type: import_adapter_qq.QQ.Message.Type.MARKDOWN,
|
|
175
|
+
msg_id: session.messageId,
|
|
176
|
+
markdown: {
|
|
177
|
+
content: `<qqbot-at-user id="${session.userId}" /> 砖已经烧好啦`
|
|
178
|
+
}
|
|
179
|
+
});
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
return next();
|
|
184
|
+
}, true);
|
|
185
|
+
});
|
|
186
|
+
ctx.guild().command("砖头.拍人 <user:user>", "拍晕(禁言)对方随机时间,有概率被反将一军", { checkArgCount: true }).alias("拍人").example("拍人 @koishi").channelFields(["brickEnabled"]).action(async ({ session }, user) => {
|
|
187
|
+
const disabledMessage = ensureBrickEnabled(session);
|
|
188
|
+
if (disabledMessage) return disabledMessage;
|
|
189
|
+
if (!user) return "你要拍谁呢?拍人 @用户";
|
|
190
|
+
const targetUserId = user.split(":")[1];
|
|
191
|
+
const userKey = `${session.guildId}:${session.userId}`;
|
|
192
|
+
const targetUserKey = `${session.guildId}:${targetUserId}`;
|
|
193
|
+
if (targetUserId === session.userId) {
|
|
194
|
+
return "不能拍自己哦";
|
|
195
|
+
}
|
|
196
|
+
if (!users[userKey]) {
|
|
197
|
+
users[userKey] = { burning: false, muted: false };
|
|
198
|
+
}
|
|
199
|
+
if (!users[targetUserKey]) {
|
|
200
|
+
users[targetUserKey] = { burning: false, muted: false };
|
|
201
|
+
}
|
|
202
|
+
const brickData = await ctx.database.get("brick", {
|
|
203
|
+
userId: session.userId,
|
|
204
|
+
guildId: session.guildId
|
|
205
|
+
});
|
|
206
|
+
if (brickData.length === 0 || brickData[0].brick <= 0) {
|
|
207
|
+
return "你在这个群还没有砖头,使用 砖头.烧砖 烧点砖头吧";
|
|
208
|
+
}
|
|
209
|
+
const lastSlap = brickData[0].lastSlap || 0;
|
|
210
|
+
const diff = Math.trunc(Date.now() / 1e3 - lastSlap);
|
|
211
|
+
if (lastSlap > 0 && diff < config.cooldown) {
|
|
212
|
+
return `${Math.abs(diff - config.cooldown)} 秒后才能再拍人哦`;
|
|
213
|
+
}
|
|
214
|
+
if (users[targetUserKey].muted && !config.ignoreMuted) {
|
|
215
|
+
return "他已经晕了...";
|
|
216
|
+
}
|
|
217
|
+
await ctx.database.upsert(
|
|
218
|
+
"brick",
|
|
219
|
+
(row) => [
|
|
220
|
+
{
|
|
221
|
+
userId: session.userId,
|
|
222
|
+
guildId: session.guildId,
|
|
223
|
+
brick: import_koishi.$.subtract(row.brick, 1),
|
|
224
|
+
// 使用 Math.trunc 确保存入整数,与 unsigned 类型一致
|
|
225
|
+
lastSlap: Math.trunc(Date.now() / 1e3)
|
|
226
|
+
}
|
|
227
|
+
],
|
|
228
|
+
["userId", "guildId"]
|
|
229
|
+
);
|
|
230
|
+
const muteTime = import_koishi.Random.int(config.minMuteTime, config.maxMuteTime + 1);
|
|
231
|
+
const muteTimeMs = muteTime * 1e3;
|
|
232
|
+
const probability = config.specialUser[targetUserId] !== void 0 ? config.specialUser[targetUserId] / 100 : config.reverse / 100;
|
|
233
|
+
if (import_koishi.Random.bool(probability)) {
|
|
234
|
+
await slap(session.userId);
|
|
235
|
+
session.qq.sendMessage(session.channelId, {
|
|
236
|
+
msg_type: import_adapter_qq.QQ.Message.Type.MARKDOWN,
|
|
237
|
+
msg_id: session.messageId,
|
|
238
|
+
markdown: {
|
|
239
|
+
content: `<qqbot-at-user id="${targetUserId}" />夺过你的砖头,把你拍晕了 ${muteTime} 秒`
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
return;
|
|
243
|
+
} else {
|
|
244
|
+
await slap(targetUserId);
|
|
245
|
+
session.qq.sendMessage(session.channelId, {
|
|
246
|
+
msg_type: import_adapter_qq.QQ.Message.Type.MARKDOWN,
|
|
247
|
+
msg_id: session.messageId,
|
|
248
|
+
markdown: {
|
|
249
|
+
content: `<qqbot-at-user id="${targetUserId}" /> 你被 <qqbot-at-user id="${session.userId}" /> 拍晕了 ${muteTime} 秒`
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
async function slap(slapedUserId) {
|
|
255
|
+
users[`${session.guildId}:${slapedUserId}`].muted = true;
|
|
256
|
+
try {
|
|
257
|
+
await session.bot.muteGuildMember(session.guildId, slapedUserId, muteTimeMs);
|
|
258
|
+
} catch {
|
|
259
|
+
}
|
|
260
|
+
if (!config.ignoreMuted) {
|
|
261
|
+
silent(slapedUserId, muteTimeMs);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
__name(slap, "slap");
|
|
265
|
+
function silent(slapedUserId, time) {
|
|
266
|
+
const dispose = ctx.guild(session.guildId).middleware((ses, next) => {
|
|
267
|
+
if (ses.userId !== slapedUserId) {
|
|
268
|
+
return next();
|
|
269
|
+
}
|
|
270
|
+
}, true);
|
|
271
|
+
ctx.setTimeout(() => {
|
|
272
|
+
dispose();
|
|
273
|
+
const key = `${session.guildId}:${slapedUserId}`;
|
|
274
|
+
if (users[key]) {
|
|
275
|
+
users[key].muted = false;
|
|
276
|
+
tryCleanupUser(key);
|
|
277
|
+
}
|
|
278
|
+
}, time);
|
|
279
|
+
}
|
|
280
|
+
__name(silent, "silent");
|
|
281
|
+
});
|
|
282
|
+
ctx.guild().command("砖头.暴力拍人 <user:user>", "消耗双倍砖头,100%拍晕对方", { checkArgCount: true }).alias("暴力拍人").example("暴力拍人 @刀豆").channelFields(["brickEnabled"]).action(async ({ session }, user) => {
|
|
283
|
+
const disabledMessage = ensureBrickEnabled(session);
|
|
284
|
+
if (disabledMessage) return disabledMessage;
|
|
285
|
+
if (!user) return "你要拍谁呢?拍人 @用户";
|
|
286
|
+
const targetUserId = user.split(":")[1];
|
|
287
|
+
const userKey = `${session.guildId}:${session.userId}`;
|
|
288
|
+
const targetUserKey = `${session.guildId}:${targetUserId}`;
|
|
289
|
+
if (targetUserId === session.userId) {
|
|
290
|
+
return "不能拍自己哦";
|
|
291
|
+
}
|
|
292
|
+
if (!users[userKey]) {
|
|
293
|
+
users[userKey] = { burning: false, muted: false };
|
|
294
|
+
}
|
|
295
|
+
if (!users[targetUserKey]) {
|
|
296
|
+
users[targetUserKey] = { burning: false, muted: false };
|
|
297
|
+
}
|
|
298
|
+
const brickData = await ctx.database.get("brick", {
|
|
299
|
+
userId: session.userId,
|
|
300
|
+
guildId: session.guildId
|
|
301
|
+
});
|
|
302
|
+
if (brickData.length === 0 || brickData[0].brick < 2) {
|
|
303
|
+
return "暴力拍人需要 2 块砖头,你的砖头不够哦";
|
|
304
|
+
}
|
|
305
|
+
const lastSlap = brickData[0].lastSlap || 0;
|
|
306
|
+
const diff = Math.trunc(Date.now() / 1e3 - lastSlap);
|
|
307
|
+
if (lastSlap > 0 && diff < config.cooldown) {
|
|
308
|
+
return `${Math.abs(diff - config.cooldown)} 秒后才能再拍人哦`;
|
|
309
|
+
}
|
|
310
|
+
if (users[targetUserKey].muted && !config.ignoreMuted) {
|
|
311
|
+
return "他已经晕了...";
|
|
312
|
+
}
|
|
313
|
+
await ctx.database.upsert(
|
|
314
|
+
"brick",
|
|
315
|
+
(row) => [
|
|
316
|
+
{
|
|
317
|
+
userId: session.userId,
|
|
318
|
+
guildId: session.guildId,
|
|
319
|
+
brick: import_koishi.$.subtract(row.brick, 2),
|
|
320
|
+
// 使用 Math.trunc 确保存入整数,与 unsigned 类型一致
|
|
321
|
+
lastSlap: Math.trunc(Date.now() / 1e3)
|
|
322
|
+
}
|
|
323
|
+
],
|
|
324
|
+
["userId", "guildId"]
|
|
325
|
+
);
|
|
326
|
+
const muteTime = import_koishi.Random.int(config.minMuteTime, config.maxMuteTime + 1) * 3;
|
|
327
|
+
const muteTimeMs = muteTime * 1e3;
|
|
328
|
+
users[targetUserKey].muted = true;
|
|
329
|
+
try {
|
|
330
|
+
await session.bot.muteGuildMember(session.guildId, targetUserId, muteTimeMs);
|
|
331
|
+
} catch {
|
|
332
|
+
}
|
|
333
|
+
if (!config.ignoreMuted) {
|
|
334
|
+
const dispose = ctx.guild(session.guildId).middleware((ses, next) => {
|
|
335
|
+
if (ses.userId !== targetUserId) {
|
|
336
|
+
return next();
|
|
337
|
+
}
|
|
338
|
+
}, true);
|
|
339
|
+
ctx.setTimeout(() => {
|
|
340
|
+
dispose();
|
|
341
|
+
if (users[targetUserKey]) {
|
|
342
|
+
users[targetUserKey].muted = false;
|
|
343
|
+
tryCleanupUser(targetUserKey);
|
|
344
|
+
}
|
|
345
|
+
}, muteTimeMs);
|
|
346
|
+
}
|
|
347
|
+
session.qq.sendMessage(session.guildId, {
|
|
348
|
+
msg_type: import_adapter_qq.QQ.Message.Type.MARKDOWN,
|
|
349
|
+
msg_id: session.messageId,
|
|
350
|
+
markdown: {
|
|
351
|
+
content: `<qqbot-at-user id="${targetUserId}" />你被 <qqbot-at-user id="${session.userId}" /> 暴力拍晕了 ${muteTime} 秒!`
|
|
352
|
+
}
|
|
353
|
+
});
|
|
354
|
+
return;
|
|
355
|
+
});
|
|
356
|
+
ctx.guild().platform("onebot").command("砖头.随机拍人", "随机拍晕(禁言)某个群友随机时间,有概率被反将一军").alias("随机拍人").channelFields(["brickEnabled"]).action(async ({ session }) => {
|
|
357
|
+
const disabledMessage = ensureBrickEnabled(session);
|
|
358
|
+
if (disabledMessage) return disabledMessage;
|
|
359
|
+
const guildMember = [];
|
|
360
|
+
for await (const member of session.bot.getGuildMemberIter(session.guildId)) {
|
|
361
|
+
if (member?.user.id && member.user.id !== session.selfId && member.user.id !== session.userId) {
|
|
362
|
+
guildMember.push(member.user.id);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
if (guildMember.length === 0) {
|
|
366
|
+
return "群里没有可以拍的人呢...";
|
|
367
|
+
}
|
|
368
|
+
await session.execute(`砖头.拍人 ${import_koishi.h.at(import_koishi.Random.pick(guildMember))}`);
|
|
369
|
+
});
|
|
370
|
+
ctx.guild().command("砖头.查看", "看看自己在这个群有多少砖头").alias("查看砖头").channelFields(["brickEnabled"]).action(async ({ session }) => {
|
|
371
|
+
const disabledMessage = ensureBrickEnabled(session);
|
|
372
|
+
if (disabledMessage) return disabledMessage;
|
|
373
|
+
const brickData = await ctx.database.get("brick", {
|
|
374
|
+
userId: session.userId,
|
|
375
|
+
guildId: session.guildId
|
|
376
|
+
});
|
|
377
|
+
if (brickData.length === 0 || brickData[0].brick === 0) {
|
|
378
|
+
return `你还没有砖头,使用 砖头.烧砖 烧点吧`;
|
|
379
|
+
} else {
|
|
380
|
+
return `你有 ${brickData[0].brick}/${config.maxBrick} 块砖头`;
|
|
381
|
+
}
|
|
382
|
+
});
|
|
383
|
+
if (config.checking) {
|
|
384
|
+
ctx.guild().command("砖头.签到").alias("砖头签到").action(async ({ session }) => {
|
|
385
|
+
const date = /* @__PURE__ */ new Date();
|
|
386
|
+
const today = `${date.getFullYear()}-${date.getMonth() + 1}-${date.getDate()}`;
|
|
387
|
+
let brick = import_koishi.Random.int(config.minGain, config.maxGain + 1);
|
|
388
|
+
const userData = await ctx.database.get("brick", {
|
|
389
|
+
userId: session.userId,
|
|
390
|
+
guildId: session.guildId
|
|
391
|
+
});
|
|
392
|
+
const currentGroupData = userData[0];
|
|
393
|
+
const statsData = await ctx.database.get("brick_checkin_stats", {
|
|
394
|
+
userId: session.userId
|
|
395
|
+
});
|
|
396
|
+
let totalCheckins = 0;
|
|
397
|
+
let streakDays = 0;
|
|
398
|
+
let lastCheckinDay = "";
|
|
399
|
+
let globalAlreadyCheckedInToday = false;
|
|
400
|
+
if (statsData.length > 0) {
|
|
401
|
+
totalCheckins = statsData[0].totalCheckins || 0;
|
|
402
|
+
streakDays = statsData[0].streakDays || 0;
|
|
403
|
+
lastCheckinDay = statsData[0].lastCheckinDay || "";
|
|
404
|
+
globalAlreadyCheckedInToday = lastCheckinDay === today;
|
|
405
|
+
}
|
|
406
|
+
if (!globalAlreadyCheckedInToday) {
|
|
407
|
+
const yesterday = new Date(date);
|
|
408
|
+
yesterday.setDate(date.getDate() - 1);
|
|
409
|
+
const yesterdayText = `${yesterday.getFullYear()}-${yesterday.getMonth() + 1}-${yesterday.getDate()}`;
|
|
410
|
+
totalCheckins += 1;
|
|
411
|
+
streakDays = lastCheckinDay === yesterdayText ? streakDays + 1 : 1;
|
|
412
|
+
lastCheckinDay = today;
|
|
413
|
+
await ctx.database.upsert(
|
|
414
|
+
"brick_checkin_stats",
|
|
415
|
+
[
|
|
416
|
+
{
|
|
417
|
+
userId: session.userId,
|
|
418
|
+
totalCheckins,
|
|
419
|
+
streakDays,
|
|
420
|
+
lastCheckinDay
|
|
421
|
+
}
|
|
422
|
+
],
|
|
423
|
+
["userId"]
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
if (currentGroupData?.checkingDay === today) {
|
|
427
|
+
return `你今天在本群已经签到过了,总签到 ${totalCheckins} 天,连续签到 ${streakDays} 天`;
|
|
428
|
+
}
|
|
429
|
+
const currentBrick = currentGroupData?.brick || 0;
|
|
430
|
+
if (currentBrick + brick > config.maxBrick) {
|
|
431
|
+
brick = config.maxBrick - currentBrick;
|
|
432
|
+
} else if (currentBrick + brick < 0) {
|
|
433
|
+
brick = -currentBrick;
|
|
434
|
+
}
|
|
435
|
+
await ctx.database.upsert(
|
|
436
|
+
"brick",
|
|
437
|
+
(row) => [
|
|
438
|
+
{
|
|
439
|
+
userId: session.userId,
|
|
440
|
+
guildId: session.guildId,
|
|
441
|
+
brick: import_koishi.$.add(row.brick, brick),
|
|
442
|
+
checkingDay: today
|
|
443
|
+
}
|
|
444
|
+
],
|
|
445
|
+
["userId", "guildId"]
|
|
446
|
+
);
|
|
447
|
+
const currentTotalBrick = currentBrick + brick;
|
|
448
|
+
if (globalAlreadyCheckedInToday) {
|
|
449
|
+
if (brick > 0) {
|
|
450
|
+
return `签到成功,你获得了 ${brick} 块砖头,现在有 ${currentTotalBrick}/${config.maxBrick} 块砖头;总签到 ${totalCheckins} 天,连续签到 ${streakDays} 天`;
|
|
451
|
+
}
|
|
452
|
+
return `签到成功,但本群砖头已满,未获得新砖头;当前有 ${currentTotalBrick}/${config.maxBrick} 块砖头,总签到 ${totalCheckins} 天,连续签到 ${streakDays} 天`;
|
|
453
|
+
}
|
|
454
|
+
if (brick > 0) {
|
|
455
|
+
return `签到成功,你获得了 ${brick} 块砖头,现在有 ${currentTotalBrick}/${config.maxBrick} 块砖头;总签到 ${totalCheckins} 天,连续签到 ${streakDays} 天`;
|
|
456
|
+
}
|
|
457
|
+
return `签到成功,但本群砖头已满,未获得新砖头;当前有 ${currentTotalBrick}/${config.maxBrick} 块砖头,总签到 ${totalCheckins} 天,连续签到 ${streakDays} 天`;
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
__name(apply, "apply");
|
|
462
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
463
|
+
0 && (module.exports = {
|
|
464
|
+
Config,
|
|
465
|
+
apply,
|
|
466
|
+
inject,
|
|
467
|
+
name,
|
|
468
|
+
usage
|
|
469
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "koishi-plugin-brick-qq",
|
|
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
|
+
"chatbot",
|
|
14
|
+
"koishi",
|
|
15
|
+
"plugin"
|
|
16
|
+
],
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@satorijs/adapter-qq": "^4.12.0"
|
|
19
|
+
},
|
|
20
|
+
"peerDependencies": {
|
|
21
|
+
"koishi": "4.18.11"
|
|
22
|
+
}
|
|
23
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# koishi-plugin-brick-qq
|
|
2
|
+
|
|
3
|
+
[](https://www.npmjs.com/package/koishi-plugin-brick-qq)
|
|
4
|
+
|
|
5
|
+
功夫再好,一砖撂倒。让群友帮你烧制砖头,再用砖头把人拍晕(禁言);普通拍人也可能遭到对方反击。
|
|
6
|
+
|
|
7
|
+
本插件基于原版 brick 插件优化,增加了群聊开关、暴力拍人、每日签到、持久化签到统计,以及禁言失败时的消息拦截降级。
|
|
8
|
+
|
|
9
|
+
## 使用要求
|
|
10
|
+
|
|
11
|
+
- Koishi 4.18
|
|
12
|
+
- 已启用 Koishi `database` 服务
|
|
13
|
+
- QQ 官方机器人适配器 `@satorijs/adapter-qq`
|
|
14
|
+
- Bot 需要群成员禁言权限;没有权限时,插件会在本地暂时停止响应被拍晕用户的消息
|
|
15
|
+
|
|
16
|
+
砖头数量按“用户 + 群聊”分别保存,不能跨群使用。
|
|
17
|
+
|
|
18
|
+
## 安装
|
|
19
|
+
|
|
20
|
+
在 Koishi 插件市场搜索 `brick-qq`,或通过包管理器安装:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
yarn add koishi-plugin-brick-qq
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
安装后请先启用数据库插件,再启用本插件,并至少配置 `cost`。
|
|
27
|
+
|
|
28
|
+
## 玩法
|
|
29
|
+
|
|
30
|
+
1. 发送 `烧砖` 开始烧制砖头。
|
|
31
|
+
2. 烧制期间,其他群友每发送 `cost` 条消息,即可烧好一块砖。
|
|
32
|
+
3. 发送 `拍人 @用户` 消耗一块砖,使对方随机禁言一段时间;对方有概率夺走砖头并反击。
|
|
33
|
+
4. 发送 `暴力拍人 @用户` 消耗两块砖,必定命中,并造成普通拍人的三倍禁言时间。
|
|
34
|
+
|
|
35
|
+
普通拍人与暴力拍人共用冷却时间。砖头在拍人判定前扣除,即使遭到反击也不会返还。
|
|
36
|
+
|
|
37
|
+
## 指令
|
|
38
|
+
|
|
39
|
+
| 指令 | 别名 | 说明 |
|
|
40
|
+
| --- | --- | --- |
|
|
41
|
+
| `砖头` | - | 查看砖头指令列表 |
|
|
42
|
+
| `砖头.烧砖` | `烧砖` | 开始烧制一块砖 |
|
|
43
|
+
| `砖头.拍人 @用户` | `拍人 @用户` | 消耗一块砖随机拍晕对方,有概率被反击 |
|
|
44
|
+
| `砖头.暴力拍人 @用户` | `暴力拍人 @用户` | 消耗两块砖必定拍晕对方,禁言时间为三倍 |
|
|
45
|
+
| `砖头.查看` | `查看砖头` | 查看自己在当前群持有的砖头 |
|
|
46
|
+
| `砖头.签到` | `砖头签到` | 每日签到并随机获得砖头,仅在开启签到功能后注册 |
|
|
47
|
+
| `开启砖头` | - | 开启当前群的砖头功能 |
|
|
48
|
+
| `关闭砖头` | - | 关闭当前群的砖头功能 |
|
|
49
|
+
|
|
50
|
+
源码中还保留了 `随机拍人` 指令,但该指令目前仅对 OneBot 平台注册,不适用于本插件面向的 QQ 官方机器人适配器。
|
|
51
|
+
|
|
52
|
+
## 配置项
|
|
53
|
+
|
|
54
|
+
| 配置项 | 类型 | 默认值 | 说明 |
|
|
55
|
+
| --- | --- | --- | --- |
|
|
56
|
+
| `maxBrick` | `number` | `1` | 每位用户在单个群内最多持有的砖头数量 |
|
|
57
|
+
| `cost` | `number` | 必填 | 烧好一块砖所需的其他群友消息数 |
|
|
58
|
+
| `cooldown` | `number` | `60` | 拍人冷却时间,单位为秒;设为 `0` 可关闭冷却 |
|
|
59
|
+
| `minMuteTime` | `number` | `10` | 普通拍人的最短禁言时间,单位为秒 |
|
|
60
|
+
| `maxMuteTime` | `number` | `120` | 普通拍人的最长禁言时间,单位为秒 |
|
|
61
|
+
| `ignoreMuted` | `boolean` | `false` | 是否关闭插件内置的拍晕状态限制与消息拦截,仅依赖平台禁言结果 |
|
|
62
|
+
| `reverse` | `number` | `10` | 普通拍人时对方反击的默认概率,范围为 `0`~`100` |
|
|
63
|
+
| `specialUser` | `Record<string, number>` | `{}` | 为指定 QQ 用户 ID 单独设置反击概率,值为 `0`~`100` |
|
|
64
|
+
| `checking` | `boolean` | `false` | 是否启用每日签到获取砖头 |
|
|
65
|
+
| `minGain` | `number` | - | 单次签到最少获得的砖头,仅在 `checking` 开启时必填 |
|
|
66
|
+
| `maxGain` | `number` | - | 单次签到最多获得的砖头,仅在 `checking` 开启时必填 |
|
|
67
|
+
|
|
68
|
+
建议保证 `maxMuteTime >= minMuteTime`、`maxGain >= minGain`。签到奖励不会让砖头数量超过 `maxBrick`。
|
|
69
|
+
|
|
70
|
+
`specialUser` 配置示例:
|
|
71
|
+
|
|
72
|
+
```yaml
|
|
73
|
+
specialUser:
|
|
74
|
+
"123456789": 50
|
|
75
|
+
"987654321": 100
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
上述配置表示用户 `123456789` 被普通拍人时有 50% 概率反击,用户 `987654321` 必定反击;未配置的用户使用 `reverse`。
|
|
79
|
+
|
|
80
|
+
## 签到说明
|
|
81
|
+
|
|
82
|
+
- 每位用户在每个群每天可以领取一次签到砖头。
|
|
83
|
+
- 总签到天数和连续签到天数按用户全局统计,不区分群聊。
|
|
84
|
+
- 同一天在其他群继续签到可以领取该群的砖头,但不会重复增加全局签到天数。
|
|
85
|
+
- 当当前群的砖头已经达到上限时,签到仍会成功,但不会获得新砖头。
|
|
86
|
+
|
|
87
|
+
## 注意事项
|
|
88
|
+
|
|
89
|
+
- 烧砖进度保存在内存中,插件重载或 Bot 重启后,尚未完成的烧砖进度会丢失。
|
|
90
|
+
- 默认情况下,平台禁言失败时插件会拦截被拍晕用户在当前群的消息,使其他插件暂时不响应该用户。
|
|
91
|
+
- 开启 `ignoreMuted` 后,插件不会使用上述降级机制,也不会阻止处于拍晕状态的用户继续使用 Bot;请确认 Bot 拥有有效的群禁言权限。
|
|
92
|
+
- QQ 原生 Markdown 消息是否能正常发送,取决于 QQ 官方机器人账号的消息能力和权限。
|
|
93
|
+
|
|
94
|
+
## 许可证
|
|
95
|
+
|
|
96
|
+
MIT
|