mioku-plugin-impact 2.0.1 → 2.0.3
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/README.md +9 -11
- package/db.ts +79 -15
- package/handlers/dajiao.ts +8 -10
- package/handlers/open-module.ts +1 -10
- package/handlers/pk.ts +11 -11
- package/handlers/query-injection.ts +1 -1
- package/handlers/queryjj.ts +5 -5
- package/handlers/rank.ts +8 -5
- package/handlers/suo.ts +10 -12
- package/handlers/yinpa.ts +24 -21
- package/image.ts +9 -3
- package/index.ts +95 -24
- package/package.json +2 -97
- package/types.ts +1 -1
- package/utils.ts +33 -15
- package/router.ts +0 -94
package/README.md
CHANGED
|
@@ -20,22 +20,20 @@
|
|
|
20
20
|
npx mioku install plugin impact
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
启用后请先在群里让管理员/群主/主人发送
|
|
23
|
+
启用后请先在群里让管理员/群主/主人发送 `.开启淫趴`(也支持 `.开启银趴`、`.开始银趴`),关闭则用 `.关闭银趴` / `.禁止淫趴` 等。
|
|
24
24
|
|
|
25
25
|
## 命令一览
|
|
26
26
|
|
|
27
27
|
| 命令 | 说明 |
|
|
28
28
|
| --- | --- |
|
|
29
|
-
|
|
|
30
|
-
|
|
|
31
|
-
|
|
|
32
|
-
|
|
|
33
|
-
|
|
|
34
|
-
|
|
|
35
|
-
|
|
|
36
|
-
|
|
|
37
|
-
|
|
38
|
-
> 帮助信息已经接入 mioku 自带的 `mioku-plugin-help`,群里使用对应的帮助命令即可看到上述命令列表,不再单独提供 `淫趴介绍`。
|
|
29
|
+
| `.开启淫趴` / `.关闭银趴` 等 | 在本群启用/关闭淫趴功能(仅管理/群主/主人) |
|
|
30
|
+
| `.打胶` / `.开导` | 给自己增加随机长度(默认 5 分钟 CD) |
|
|
31
|
+
| `.嗦牛子 [@某人]` | 嗦自己或被 @ 群友的牛子(默认 5 分钟 CD) |
|
|
32
|
+
| `.pk @某人` / `.对决 @某人` | 牛子对决,胜者抢走败者的部分长度(默认 1 分钟 CD) |
|
|
33
|
+
| `.查询 [@某人]` | 查询自己或目标的牛子长度 |
|
|
34
|
+
| `.jj排行榜` / `.jj排名` / `.jj榜单` / `.jjrank` | 输出 Top5 与倒数 5 名图表,并附上自己的排名 |
|
|
35
|
+
| `.日群友` / `.透群友 [@某人]` / `.透群主` / `.透管理` | 随机或定向选取一位幸运儿进行注入(默认 1 小时 CD,主人无 CD) |
|
|
36
|
+
| `.注入查询 [历史/全部] [@某人]` | 查询当日或全部历史被注入量;带 `历史` / `全部` 时附走势图 |
|
|
39
37
|
|
|
40
38
|
## 数据存储
|
|
41
39
|
|
package/db.ts
CHANGED
|
@@ -10,17 +10,17 @@ import { getToday, nowSeconds } from "./utils";
|
|
|
10
10
|
export const DEFAULT_JJ_LENGTH = 10.0;
|
|
11
11
|
|
|
12
12
|
export interface ImpactDatabase {
|
|
13
|
-
isUserInTable(userId:
|
|
14
|
-
addNewUser(userId:
|
|
15
|
-
updateActivity(userId:
|
|
16
|
-
getJjLength(userId:
|
|
13
|
+
isUserInTable(userId: string): boolean;
|
|
14
|
+
addNewUser(userId: string): Promise<void>;
|
|
15
|
+
updateActivity(userId: string): Promise<void>;
|
|
16
|
+
getJjLength(userId: string): number;
|
|
17
17
|
/** 累加长度(可负)。先确保用户存在再调用。 */
|
|
18
|
-
addJjLength(userId:
|
|
19
|
-
isGroupAllowed(groupId:
|
|
20
|
-
setGroupAllow(groupId:
|
|
21
|
-
insertEjaculation(userId:
|
|
22
|
-
getEjaculationData(userId:
|
|
23
|
-
getTodayEjaculationVolume(userId:
|
|
18
|
+
addJjLength(userId: string, delta: number): Promise<void>;
|
|
19
|
+
isGroupAllowed(groupId: string): boolean;
|
|
20
|
+
setGroupAllow(groupId: string, allow: boolean): Promise<void>;
|
|
21
|
+
insertEjaculation(userId: string, volume: number): Promise<void>;
|
|
22
|
+
getEjaculationData(userId: string): EjaculationRecord[];
|
|
23
|
+
getTodayEjaculationVolume(userId: string): number;
|
|
24
24
|
/** 一天没活跃的用户随机扣 0–1cm(仅对 jj_length > 1 的用户生效) */
|
|
25
25
|
punishInactiveUsers(): Promise<number>;
|
|
26
26
|
/** 全表按 jjLength 倒序排列 */
|
|
@@ -29,11 +29,73 @@ export interface ImpactDatabase {
|
|
|
29
29
|
}
|
|
30
30
|
|
|
31
31
|
interface UserRow {
|
|
32
|
-
user_id:
|
|
32
|
+
user_id: string;
|
|
33
33
|
jj_length: number;
|
|
34
34
|
last_masturbation_time: number;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
/**
|
|
38
|
+
* 旧库的 users.user_id / groups.group_id 是 INTEGER PRIMARY KEY(rowid 别名),
|
|
39
|
+
* 写入 openid 会抛 SQLiteError: datatype mismatch,这里重建成 TEXT 并保留原数据。
|
|
40
|
+
*/
|
|
41
|
+
function widenLegacyIdColumns(db: Database): void {
|
|
42
|
+
const rebuild = (
|
|
43
|
+
table: string,
|
|
44
|
+
idColumn: string,
|
|
45
|
+
createSql: string,
|
|
46
|
+
columns: string,
|
|
47
|
+
): void => {
|
|
48
|
+
const info = db
|
|
49
|
+
.query(`PRAGMA table_info(${table})`)
|
|
50
|
+
.all() as Array<{ name?: string; type?: string }>;
|
|
51
|
+
const column = info.find((item) => item.name === idColumn);
|
|
52
|
+
if (!column || String(column.type ?? "").toUpperCase() === "TEXT") return;
|
|
53
|
+
|
|
54
|
+
db.exec("BEGIN");
|
|
55
|
+
try {
|
|
56
|
+
db.exec(`ALTER TABLE ${table} RENAME TO ${table}_legacy`);
|
|
57
|
+
db.exec(createSql);
|
|
58
|
+
db.exec(
|
|
59
|
+
`INSERT INTO ${table} (${columns})
|
|
60
|
+
SELECT ${columns
|
|
61
|
+
.split(",")
|
|
62
|
+
.map((name) =>
|
|
63
|
+
name.trim() === idColumn
|
|
64
|
+
? `CAST(${idColumn} AS TEXT)`
|
|
65
|
+
: name.trim(),
|
|
66
|
+
)
|
|
67
|
+
.join(", ")}
|
|
68
|
+
FROM ${table}_legacy`,
|
|
69
|
+
);
|
|
70
|
+
db.exec(`DROP TABLE ${table}_legacy`);
|
|
71
|
+
db.exec("COMMIT");
|
|
72
|
+
} catch (err) {
|
|
73
|
+
db.exec("ROLLBACK");
|
|
74
|
+
throw err;
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
rebuild(
|
|
79
|
+
"users",
|
|
80
|
+
"user_id",
|
|
81
|
+
`CREATE TABLE users (
|
|
82
|
+
user_id TEXT PRIMARY KEY,
|
|
83
|
+
jj_length REAL NOT NULL,
|
|
84
|
+
last_masturbation_time INTEGER NOT NULL
|
|
85
|
+
)`,
|
|
86
|
+
"user_id, jj_length, last_masturbation_time",
|
|
87
|
+
);
|
|
88
|
+
rebuild(
|
|
89
|
+
"groups",
|
|
90
|
+
"group_id",
|
|
91
|
+
`CREATE TABLE groups (
|
|
92
|
+
group_id TEXT PRIMARY KEY,
|
|
93
|
+
allow INTEGER NOT NULL DEFAULT 0
|
|
94
|
+
)`,
|
|
95
|
+
"group_id, allow",
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
|
|
37
99
|
export async function initImpactDatabase(): Promise<ImpactDatabase> {
|
|
38
100
|
const dir = ensureDataDir("impact");
|
|
39
101
|
const dbPath = path.join(dir, "impact.db");
|
|
@@ -43,19 +105,19 @@ export async function initImpactDatabase(): Promise<ImpactDatabase> {
|
|
|
43
105
|
|
|
44
106
|
db.exec(`
|
|
45
107
|
CREATE TABLE IF NOT EXISTS users (
|
|
46
|
-
user_id
|
|
108
|
+
user_id TEXT PRIMARY KEY,
|
|
47
109
|
jj_length REAL NOT NULL,
|
|
48
110
|
last_masturbation_time INTEGER NOT NULL
|
|
49
111
|
);
|
|
50
112
|
|
|
51
113
|
CREATE TABLE IF NOT EXISTS groups (
|
|
52
|
-
group_id
|
|
114
|
+
group_id TEXT PRIMARY KEY,
|
|
53
115
|
allow INTEGER NOT NULL DEFAULT 0
|
|
54
116
|
);
|
|
55
117
|
|
|
56
118
|
CREATE TABLE IF NOT EXISTS ejaculations (
|
|
57
119
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
58
|
-
user_id
|
|
120
|
+
user_id TEXT NOT NULL,
|
|
59
121
|
date TEXT NOT NULL,
|
|
60
122
|
volume REAL NOT NULL,
|
|
61
123
|
UNIQUE(user_id, date)
|
|
@@ -63,6 +125,8 @@ export async function initImpactDatabase(): Promise<ImpactDatabase> {
|
|
|
63
125
|
CREATE INDEX IF NOT EXISTS idx_ejaculations_user ON ejaculations(user_id, date);
|
|
64
126
|
`);
|
|
65
127
|
|
|
128
|
+
widenLegacyIdColumns(db);
|
|
129
|
+
|
|
66
130
|
const stmts = {
|
|
67
131
|
insertUser: db.prepare(`
|
|
68
132
|
INSERT INTO users (user_id, jj_length, last_masturbation_time)
|
|
@@ -214,7 +278,7 @@ export async function initImpactDatabase(): Promise<ImpactDatabase> {
|
|
|
214
278
|
getRanking() {
|
|
215
279
|
const rows = stmts.selectAllUsers.all({}) as UserRow[];
|
|
216
280
|
return rows
|
|
217
|
-
.map((row) => ({ userId: row.user_id, jjLength: row.jj_length }))
|
|
281
|
+
.map((row) => ({ userId: String(row.user_id), jjLength: row.jj_length }))
|
|
218
282
|
.sort((a, b) => b.jjLength - a.jjLength);
|
|
219
283
|
},
|
|
220
284
|
|
package/handlers/dajiao.ts
CHANGED
|
@@ -4,12 +4,12 @@ import type { HandlerContext } from "./types";
|
|
|
4
4
|
|
|
5
5
|
export async function handleDajiao(h: HandlerContext): Promise<void> {
|
|
6
6
|
const { ctx, db, cd, config, event } = h;
|
|
7
|
-
const uid = String(event.user_id);
|
|
7
|
+
const uid = String(event.user_id ?? "").trim();
|
|
8
8
|
const cdState = checkCooldown(cd.dj, uid, config.djCd);
|
|
9
9
|
if (!cdState.ok) {
|
|
10
10
|
await event.reply(
|
|
11
11
|
[
|
|
12
|
-
ctx.segment.at(
|
|
12
|
+
ctx.segment.at(uid),
|
|
13
13
|
ctx.segment.text(
|
|
14
14
|
`你已经打不动了喵, 请等待${roundTo(cdState.remaining, 3)}秒后再打喵`,
|
|
15
15
|
),
|
|
@@ -21,25 +21,23 @@ export async function handleDajiao(h: HandlerContext): Promise<void> {
|
|
|
21
21
|
|
|
22
22
|
markCooldown(cd.dj, uid);
|
|
23
23
|
|
|
24
|
-
if (db.isUserInTable(
|
|
24
|
+
if (db.isUserInTable(uid)) {
|
|
25
25
|
const delta = getRandomNum();
|
|
26
|
-
await db.addJjLength(
|
|
26
|
+
await db.addJjLength(uid, delta);
|
|
27
27
|
await event.reply(
|
|
28
28
|
[
|
|
29
|
-
ctx.segment.at(
|
|
29
|
+
ctx.segment.at(uid),
|
|
30
30
|
ctx.segment.text(
|
|
31
|
-
`打胶结束喵, 你的${pickJj()}很满意喵, 长了${delta}cm喵, 目前长度为${db.getJjLength(
|
|
32
|
-
Number(uid),
|
|
33
|
-
)}cm喵`,
|
|
31
|
+
`打胶结束喵, 你的${pickJj()}很满意喵, 长了${delta}cm喵, 目前长度为${db.getJjLength(uid)}cm喵`,
|
|
34
32
|
),
|
|
35
33
|
],
|
|
36
34
|
false,
|
|
37
35
|
);
|
|
38
36
|
} else {
|
|
39
|
-
await db.addNewUser(
|
|
37
|
+
await db.addNewUser(uid);
|
|
40
38
|
await event.reply(
|
|
41
39
|
[
|
|
42
|
-
ctx.segment.at(
|
|
40
|
+
ctx.segment.at(uid),
|
|
43
41
|
ctx.segment.text(
|
|
44
42
|
`你还没有创建${pickJj()}, 咱帮你创建了喵, 目前长度是10cm喵`,
|
|
45
43
|
),
|
package/handlers/open-module.ts
CHANGED
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { isOwner } from "mioku";
|
|
2
|
-
import { isGroupAdmin } from "../utils";
|
|
3
1
|
import type { HandlerContext } from "./types";
|
|
4
2
|
|
|
5
3
|
export async function handleOpenModule(
|
|
@@ -7,14 +5,7 @@ export async function handleOpenModule(
|
|
|
7
5
|
enable: boolean,
|
|
8
6
|
): Promise<void> {
|
|
9
7
|
const { ctx, db, event } = h;
|
|
10
|
-
|
|
11
|
-
await event.reply(
|
|
12
|
-
[ctx.segment.text("权限不足喵, 仅管理员/群主/主人可以开关淫趴")],
|
|
13
|
-
false,
|
|
14
|
-
);
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
await db.setGroupAllow(Number(event.group_id), enable);
|
|
8
|
+
await db.setGroupAllow(String(event.group_id ?? "").trim(), enable);
|
|
18
9
|
await event.reply(
|
|
19
10
|
[ctx.segment.text(enable ? "功能已开启喵" : "功能已禁用喵")],
|
|
20
11
|
false,
|
package/handlers/pk.ts
CHANGED
|
@@ -4,12 +4,12 @@ import type { HandlerContext } from "./types";
|
|
|
4
4
|
|
|
5
5
|
export async function handlePk(h: HandlerContext): Promise<void> {
|
|
6
6
|
const { ctx, db, cd, config, event } = h;
|
|
7
|
-
const uid = String(event.user_id);
|
|
7
|
+
const uid = String(event.user_id ?? "").trim();
|
|
8
8
|
const cdState = checkCooldown(cd.pk, uid, config.pkCd);
|
|
9
9
|
if (!cdState.ok) {
|
|
10
10
|
await event.reply(
|
|
11
11
|
[
|
|
12
|
-
ctx.segment.at(
|
|
12
|
+
ctx.segment.at(uid),
|
|
13
13
|
ctx.segment.text(
|
|
14
14
|
`你已经pk不动了喵, 请等待${roundTo(cdState.remaining, 3)}秒后再pk喵`,
|
|
15
15
|
),
|
|
@@ -24,10 +24,10 @@ export async function handlePk(h: HandlerContext): Promise<void> {
|
|
|
24
24
|
// pk 必须带 @,无 @ 时不响应
|
|
25
25
|
return;
|
|
26
26
|
}
|
|
27
|
-
if (at ===
|
|
27
|
+
if (at === uid) {
|
|
28
28
|
await event.reply(
|
|
29
29
|
[
|
|
30
|
-
ctx.segment.at(
|
|
30
|
+
ctx.segment.at(uid),
|
|
31
31
|
ctx.segment.text("你不能pk自己喵"),
|
|
32
32
|
],
|
|
33
33
|
false,
|
|
@@ -37,15 +37,15 @@ export async function handlePk(h: HandlerContext): Promise<void> {
|
|
|
37
37
|
|
|
38
38
|
markCooldown(cd.pk, uid);
|
|
39
39
|
|
|
40
|
-
const meIn = db.isUserInTable(
|
|
40
|
+
const meIn = db.isUserInTable(uid);
|
|
41
41
|
const otherIn = db.isUserInTable(at);
|
|
42
42
|
if (!meIn || !otherIn) {
|
|
43
|
-
if (!meIn) await db.addNewUser(
|
|
43
|
+
if (!meIn) await db.addNewUser(uid);
|
|
44
44
|
if (!otherIn) await db.addNewUser(at);
|
|
45
45
|
clearCooldown(cd.pk, uid);
|
|
46
46
|
await event.reply(
|
|
47
47
|
[
|
|
48
|
-
ctx.segment.at(
|
|
48
|
+
ctx.segment.at(uid),
|
|
49
49
|
ctx.segment.text(
|
|
50
50
|
`你或对面还没有创建${pickJj()}喵, 咱全帮你创建了喵, 你们的${pickJj()}长度都是10cm喵`,
|
|
51
51
|
),
|
|
@@ -58,11 +58,11 @@ export async function handlePk(h: HandlerContext): Promise<void> {
|
|
|
58
58
|
const win = Math.random() > 0.5;
|
|
59
59
|
const delta = getRandomNum();
|
|
60
60
|
if (win) {
|
|
61
|
-
await db.addJjLength(
|
|
61
|
+
await db.addJjLength(uid, delta / 2);
|
|
62
62
|
await db.addJjLength(at, -delta);
|
|
63
63
|
await event.reply(
|
|
64
64
|
[
|
|
65
|
-
ctx.segment.at(
|
|
65
|
+
ctx.segment.at(uid),
|
|
66
66
|
ctx.segment.text(
|
|
67
67
|
`对决胜利喵, 你的${pickJj()}增加了${roundTo(
|
|
68
68
|
delta / 2,
|
|
@@ -73,11 +73,11 @@ export async function handlePk(h: HandlerContext): Promise<void> {
|
|
|
73
73
|
false,
|
|
74
74
|
);
|
|
75
75
|
} else {
|
|
76
|
-
await db.addJjLength(
|
|
76
|
+
await db.addJjLength(uid, -delta);
|
|
77
77
|
await db.addJjLength(at, delta / 2);
|
|
78
78
|
await event.reply(
|
|
79
79
|
[
|
|
80
|
-
ctx.segment.at(
|
|
80
|
+
ctx.segment.at(uid),
|
|
81
81
|
ctx.segment.text(
|
|
82
82
|
`对决失败喵, 在对面牛子的阴影笼罩下你的${pickJj()}减小了${delta}cm喵, 对面增加了${roundTo(
|
|
83
83
|
delta / 2,
|
|
@@ -8,7 +8,7 @@ export async function handleQueryInjection(
|
|
|
8
8
|
): Promise<void> {
|
|
9
9
|
const { ctx, db, screenshot, event } = h;
|
|
10
10
|
const at = getAtUserId(event.message);
|
|
11
|
-
const target = at ??
|
|
11
|
+
const target = at ?? String(event.user_id ?? "").trim();
|
|
12
12
|
// 有 @ 用"该用户",无 @ 用"您"
|
|
13
13
|
const replay1 = at != null ? "该用户" : "您";
|
|
14
14
|
|
package/handlers/queryjj.ts
CHANGED
|
@@ -3,14 +3,14 @@ import type { HandlerContext } from "./types";
|
|
|
3
3
|
|
|
4
4
|
export async function handleQueryJj(h: HandlerContext): Promise<void> {
|
|
5
5
|
const { ctx, db, event } = h;
|
|
6
|
-
const uid =
|
|
6
|
+
const uid = String(event.user_id ?? "").trim();
|
|
7
7
|
const at = getAtUserId(event.message);
|
|
8
8
|
|
|
9
9
|
if (at == null) {
|
|
10
10
|
if (db.isUserInTable(uid)) {
|
|
11
11
|
await event.reply(
|
|
12
12
|
[
|
|
13
|
-
ctx.segment.at(
|
|
13
|
+
ctx.segment.at(uid),
|
|
14
14
|
ctx.segment.text(`你的${pickJj()}目前长度为${db.getJjLength(uid)}cm喵`),
|
|
15
15
|
],
|
|
16
16
|
false,
|
|
@@ -19,7 +19,7 @@ export async function handleQueryJj(h: HandlerContext): Promise<void> {
|
|
|
19
19
|
await db.addNewUser(uid);
|
|
20
20
|
await event.reply(
|
|
21
21
|
[
|
|
22
|
-
ctx.segment.at(
|
|
22
|
+
ctx.segment.at(uid),
|
|
23
23
|
ctx.segment.text(
|
|
24
24
|
`你还没有创建${pickJj()}喵, 咱帮你创建了喵, 目前长度是10cm喵`,
|
|
25
25
|
),
|
|
@@ -33,7 +33,7 @@ export async function handleQueryJj(h: HandlerContext): Promise<void> {
|
|
|
33
33
|
if (db.isUserInTable(at)) {
|
|
34
34
|
await event.reply(
|
|
35
35
|
[
|
|
36
|
-
ctx.segment.at(
|
|
36
|
+
ctx.segment.at(uid),
|
|
37
37
|
ctx.segment.text(`TA的${pickJj()}目前长度为${db.getJjLength(at)}cm喵`),
|
|
38
38
|
],
|
|
39
39
|
false,
|
|
@@ -42,7 +42,7 @@ export async function handleQueryJj(h: HandlerContext): Promise<void> {
|
|
|
42
42
|
await db.addNewUser(at);
|
|
43
43
|
await event.reply(
|
|
44
44
|
[
|
|
45
|
-
ctx.segment.at(
|
|
45
|
+
ctx.segment.at(uid),
|
|
46
46
|
ctx.segment.text(
|
|
47
47
|
`TA还没有创建${pickJj()}喵, 咱帮他创建了喵, 目前长度是10cm喵`,
|
|
48
48
|
),
|
package/handlers/rank.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { renderRankChart, type RankRow } from "../image";
|
|
2
|
-
import { getStrangerNickname, pickJj } from "../utils";
|
|
2
|
+
import { getStrangerNickname, pickJj, resolveAvatarUrl } from "../utils";
|
|
3
3
|
import type { HandlerContext } from "./types";
|
|
4
4
|
|
|
5
5
|
export async function handleRank(h: HandlerContext): Promise<void> {
|
|
@@ -21,12 +21,14 @@ export async function handleRank(h: HandlerContext): Promise<void> {
|
|
|
21
21
|
const top5 = ranking.slice(0, 5);
|
|
22
22
|
const last5 = ranking.slice(-5);
|
|
23
23
|
|
|
24
|
-
const myIndex = ranking.findIndex(
|
|
24
|
+
const myIndex = ranking.findIndex(
|
|
25
|
+
(r) => r.userId === String(event.user_id ?? "").trim(),
|
|
26
|
+
);
|
|
25
27
|
if (myIndex < 0) {
|
|
26
|
-
await db.addNewUser(
|
|
28
|
+
await db.addNewUser(String(event.user_id ?? "").trim());
|
|
27
29
|
await event.reply(
|
|
28
30
|
[
|
|
29
|
-
ctx.segment.at(String(event.user_id)),
|
|
31
|
+
ctx.segment.at(String(event.user_id ?? "").trim()),
|
|
30
32
|
ctx.segment.text(
|
|
31
33
|
`你还没有创建${pickJj()}看不到rank喵, 咱帮你创建了喵, 目前长度是10cm喵`,
|
|
32
34
|
),
|
|
@@ -40,6 +42,7 @@ export async function handleRank(h: HandlerContext): Promise<void> {
|
|
|
40
42
|
[...top5, ...last5].map(async (r) => ({
|
|
41
43
|
name: await getStrangerNickname(event.bot, r.userId),
|
|
42
44
|
userId: r.userId,
|
|
45
|
+
avatar: resolveAvatarUrl(event, r.userId),
|
|
43
46
|
jjLength: r.jjLength,
|
|
44
47
|
})),
|
|
45
48
|
);
|
|
@@ -48,7 +51,7 @@ export async function handleRank(h: HandlerContext): Promise<void> {
|
|
|
48
51
|
|
|
49
52
|
await event.reply(
|
|
50
53
|
[
|
|
51
|
-
ctx.segment.at(String(event.user_id)),
|
|
54
|
+
ctx.segment.at(String(event.user_id ?? "").trim()),
|
|
52
55
|
ctx.segment.image(imagePath),
|
|
53
56
|
ctx.segment.text(`你的排名为${myIndex + 1}喵`),
|
|
54
57
|
],
|
package/handlers/suo.ts
CHANGED
|
@@ -4,12 +4,12 @@ import type { HandlerContext } from "./types";
|
|
|
4
4
|
|
|
5
5
|
export async function handleSuo(h: HandlerContext): Promise<void> {
|
|
6
6
|
const { ctx, db, cd, config, event } = h;
|
|
7
|
-
const uid = String(event.user_id);
|
|
7
|
+
const uid = String(event.user_id ?? "").trim();
|
|
8
8
|
const cdState = checkCooldown(cd.suo, uid, config.suoCd);
|
|
9
9
|
if (!cdState.ok) {
|
|
10
10
|
await event.reply(
|
|
11
11
|
[
|
|
12
|
-
ctx.segment.at(
|
|
12
|
+
ctx.segment.at(uid),
|
|
13
13
|
ctx.segment.text(
|
|
14
14
|
`你已经嗦不动了喵, 请等待${roundTo(cdState.remaining, 3)}秒后再嗦喵`,
|
|
15
15
|
),
|
|
@@ -22,26 +22,24 @@ export async function handleSuo(h: HandlerContext): Promise<void> {
|
|
|
22
22
|
|
|
23
23
|
const at = getAtUserId(event.message);
|
|
24
24
|
if (at == null) {
|
|
25
|
-
if (db.isUserInTable(
|
|
25
|
+
if (db.isUserInTable(uid)) {
|
|
26
26
|
const delta = getRandomNum();
|
|
27
|
-
await db.addJjLength(
|
|
27
|
+
await db.addJjLength(uid, delta);
|
|
28
28
|
await event.reply(
|
|
29
29
|
[
|
|
30
|
-
ctx.segment.at(
|
|
30
|
+
ctx.segment.at(uid),
|
|
31
31
|
ctx.segment.text(
|
|
32
|
-
`你的${pickJj()}很满意喵, 嗦长了${delta}cm喵, 目前长度为${db.getJjLength(
|
|
33
|
-
Number(uid),
|
|
34
|
-
)}cm喵`,
|
|
32
|
+
`你的${pickJj()}很满意喵, 嗦长了${delta}cm喵, 目前长度为${db.getJjLength(uid)}cm喵`,
|
|
35
33
|
),
|
|
36
34
|
],
|
|
37
35
|
false,
|
|
38
36
|
);
|
|
39
37
|
} else {
|
|
40
|
-
await db.addNewUser(
|
|
38
|
+
await db.addNewUser(uid);
|
|
41
39
|
clearCooldown(cd.suo, uid);
|
|
42
40
|
await event.reply(
|
|
43
41
|
[
|
|
44
|
-
ctx.segment.at(
|
|
42
|
+
ctx.segment.at(uid),
|
|
45
43
|
ctx.segment.text(
|
|
46
44
|
`你还没有创建${pickJj()}喵, 咱帮你创建了喵, 目前长度是10cm喵`,
|
|
47
45
|
),
|
|
@@ -57,7 +55,7 @@ export async function handleSuo(h: HandlerContext): Promise<void> {
|
|
|
57
55
|
await db.addJjLength(at, delta);
|
|
58
56
|
await event.reply(
|
|
59
57
|
[
|
|
60
|
-
ctx.segment.at(
|
|
58
|
+
ctx.segment.at(uid),
|
|
61
59
|
ctx.segment.text(
|
|
62
60
|
`对方的${pickJj()}很满意喵, 嗦长了${delta}cm喵, 目前长度为${db.getJjLength(
|
|
63
61
|
at,
|
|
@@ -71,7 +69,7 @@ export async function handleSuo(h: HandlerContext): Promise<void> {
|
|
|
71
69
|
clearCooldown(cd.suo, uid);
|
|
72
70
|
await event.reply(
|
|
73
71
|
[
|
|
74
|
-
ctx.segment.at(
|
|
72
|
+
ctx.segment.at(uid),
|
|
75
73
|
ctx.segment.text(
|
|
76
74
|
`TA还没有创建${pickJj()}喵, 咱帮TA创建了喵, 目前长度是10cm喵`,
|
|
77
75
|
),
|
package/handlers/yinpa.ts
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { MiokuContext } from "mioku";
|
|
2
2
|
import { checkCooldown, clearCooldown, markCooldown } from "../state";
|
|
3
3
|
import {
|
|
4
4
|
getAtUserId,
|
|
5
|
-
getAvatarUrl,
|
|
6
5
|
getSenderName,
|
|
6
|
+
resolveAvatarUrl,
|
|
7
7
|
roundTo,
|
|
8
8
|
} from "../utils";
|
|
9
9
|
import type { HandlerContext } from "./types";
|
|
10
10
|
|
|
11
11
|
interface PrepMember {
|
|
12
|
-
user_id:
|
|
12
|
+
user_id: string;
|
|
13
13
|
role: "owner" | "admin" | "member";
|
|
14
14
|
card?: string;
|
|
15
15
|
nickname?: string;
|
|
@@ -27,15 +27,15 @@ export async function handleYinpa(
|
|
|
27
27
|
subject: "owner" | "admin" | "member",
|
|
28
28
|
): Promise<void> {
|
|
29
29
|
const { ctx, db, cd, config, event } = h;
|
|
30
|
-
const
|
|
31
|
-
const
|
|
30
|
+
const miokuCtx = ctx as MiokuContext;
|
|
31
|
+
const uid = String(event.user_id ?? "").trim();
|
|
32
32
|
|
|
33
|
-
const cdState = checkCooldown(cd.fuck,
|
|
33
|
+
const cdState = checkCooldown(cd.fuck, uid, config.fuckCd);
|
|
34
34
|
// 主人不受 CD 限制
|
|
35
|
-
if (!cdState.ok && !
|
|
35
|
+
if (!cdState.ok && !miokuCtx.isMaster?.(event)) {
|
|
36
36
|
await event.reply(
|
|
37
37
|
[
|
|
38
|
-
ctx.segment.at(
|
|
38
|
+
ctx.segment.at(uid),
|
|
39
39
|
ctx.segment.text(
|
|
40
40
|
`你已经榨不出来任何东西了, 请先休息${roundTo(cdState.remaining, 3)}秒`,
|
|
41
41
|
),
|
|
@@ -44,7 +44,7 @@ export async function handleYinpa(
|
|
|
44
44
|
);
|
|
45
45
|
return;
|
|
46
46
|
}
|
|
47
|
-
markCooldown(cd.fuck,
|
|
47
|
+
markCooldown(cd.fuck, uid);
|
|
48
48
|
|
|
49
49
|
const reqUserCard = getSenderName(event);
|
|
50
50
|
let prepList: PrepMember[];
|
|
@@ -53,19 +53,19 @@ export async function handleYinpa(
|
|
|
53
53
|
if (!bot) throw new Error("bot not found");
|
|
54
54
|
const members = await bot.getGroupMembers(String(event.group_id));
|
|
55
55
|
prepList = members.map((m: import("mioku").MemberInfo) => ({
|
|
56
|
-
user_id:
|
|
56
|
+
user_id: String(m.user_id ?? "").trim(),
|
|
57
57
|
role: (m.role as PrepMember["role"]) ?? "member",
|
|
58
58
|
card: m.card,
|
|
59
59
|
nickname: m.nickname,
|
|
60
60
|
}));
|
|
61
61
|
} catch (err) {
|
|
62
|
-
clearCooldown(cd.fuck,
|
|
62
|
+
clearCooldown(cd.fuck, uid);
|
|
63
63
|
ctx.logger.error(`impact: 获取群成员列表失败: ${err}`);
|
|
64
64
|
await event.reply([ctx.segment.text("喵喵喵? 拿不到群成员列表了!")], false);
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
|
|
68
|
-
let lucky:
|
|
68
|
+
let lucky: string | null;
|
|
69
69
|
if (subject === "owner") {
|
|
70
70
|
lucky = await pickOwner(prepList, uid, reqUserCard, ctx, event);
|
|
71
71
|
} else if (subject === "admin") {
|
|
@@ -75,7 +75,7 @@ export async function handleYinpa(
|
|
|
75
75
|
}
|
|
76
76
|
if (lucky == null) {
|
|
77
77
|
// pickXxx 内部已 finish 并清掉 CD
|
|
78
|
-
clearCooldown(cd.fuck,
|
|
78
|
+
clearCooldown(cd.fuck, uid);
|
|
79
79
|
return;
|
|
80
80
|
}
|
|
81
81
|
|
|
@@ -95,13 +95,14 @@ export async function handleYinpa(
|
|
|
95
95
|
"群友";
|
|
96
96
|
const todayTotal = db.getTodayEjaculationVolume(lucky);
|
|
97
97
|
const seconds = 1 + Math.floor(Math.random() * 20);
|
|
98
|
+
const avatarUrl = resolveAvatarUrl(event, lucky);
|
|
98
99
|
|
|
99
100
|
await event.reply(
|
|
100
101
|
[
|
|
101
102
|
ctx.segment.text(
|
|
102
103
|
`好欸!${reqUserCard}(${uid})用时${seconds}秒 \n给 ${luckyCard}(${lucky}) 注入了${volume}毫升的脱氧核糖核酸, 当日总注入量为:${todayTotal}毫升\n`,
|
|
103
104
|
),
|
|
104
|
-
ctx.segment.image(
|
|
105
|
+
...(avatarUrl ? [ctx.segment.image(avatarUrl)] : []),
|
|
105
106
|
],
|
|
106
107
|
false,
|
|
107
108
|
);
|
|
@@ -109,11 +110,11 @@ export async function handleYinpa(
|
|
|
109
110
|
|
|
110
111
|
async function pickOwner(
|
|
111
112
|
prepList: PrepMember[],
|
|
112
|
-
uid:
|
|
113
|
+
uid: string,
|
|
113
114
|
reqUserCard: string,
|
|
114
115
|
ctx: any,
|
|
115
116
|
event: any,
|
|
116
|
-
): Promise<
|
|
117
|
+
): Promise<string | null> {
|
|
117
118
|
// 找不到群主时回退到自己
|
|
118
119
|
const owner = prepList.find((p) => p.role === "owner");
|
|
119
120
|
const lucky = owner?.user_id ?? uid;
|
|
@@ -130,12 +131,14 @@ async function pickOwner(
|
|
|
130
131
|
|
|
131
132
|
async function pickAdmin(
|
|
132
133
|
prepList: PrepMember[],
|
|
133
|
-
uid:
|
|
134
|
+
uid: string,
|
|
134
135
|
reqUserCard: string,
|
|
135
136
|
ctx: any,
|
|
136
137
|
event: any,
|
|
137
|
-
): Promise<
|
|
138
|
-
let admins = prepList
|
|
138
|
+
): Promise<string | null> {
|
|
139
|
+
let admins = prepList
|
|
140
|
+
.filter((p) => p.role === "admin")
|
|
141
|
+
.map((p) => p.user_id);
|
|
139
142
|
if (admins.includes(uid)) {
|
|
140
143
|
// 自己是管理的话移除自己
|
|
141
144
|
admins = admins.filter((id) => id !== uid);
|
|
@@ -154,11 +157,11 @@ async function pickAdmin(
|
|
|
154
157
|
|
|
155
158
|
async function pickMember(
|
|
156
159
|
prepList: PrepMember[],
|
|
157
|
-
uid:
|
|
160
|
+
uid: string,
|
|
158
161
|
reqUserCard: string,
|
|
159
162
|
ctx: any,
|
|
160
163
|
event: any,
|
|
161
|
-
): Promise<
|
|
164
|
+
): Promise<string | null> {
|
|
162
165
|
const at = getAtUserId(event.message);
|
|
163
166
|
if (at != null) {
|
|
164
167
|
// 有 @ 就直接指定目标,不播报抽取提示
|
package/image.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import type { ScreenshotService } from "mioku";
|
|
2
|
-
import { escapeHtml
|
|
2
|
+
import { escapeHtml } from "./utils";
|
|
3
3
|
|
|
4
4
|
export interface RankRow {
|
|
5
5
|
name: string;
|
|
6
|
-
userId:
|
|
6
|
+
userId: string;
|
|
7
|
+
avatar?: string;
|
|
7
8
|
jjLength: number;
|
|
8
9
|
}
|
|
9
10
|
|
|
@@ -67,9 +68,14 @@ export async function renderRankChart(
|
|
|
67
68
|
const center = trackLeft + trackWidth / 2;
|
|
68
69
|
const barLeft = isPositive ? center : center - barWidth;
|
|
69
70
|
const labelText = `${row.jjLength.toFixed(2)} cm`;
|
|
71
|
+
const avatar = String(row.avatar ?? "").trim();
|
|
70
72
|
return `
|
|
71
73
|
<div class="row" style="top:${headerHeight + i * rowHeight}px;">
|
|
72
|
-
|
|
74
|
+
${
|
|
75
|
+
avatar
|
|
76
|
+
? `<img class="avatar" src="${escapeHtml(avatar)}" />`
|
|
77
|
+
: ""
|
|
78
|
+
}
|
|
73
79
|
<div class="name">${escapeHtml(truncate(row.name, 9))}</div>
|
|
74
80
|
<div class="track" style="left:${trackLeft}px; width:${trackWidth}px;">
|
|
75
81
|
<div class="axis"></div>
|
package/index.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { definePlugin, type MiokuContext } from "mioku";
|
|
1
|
+
import { definePlugin, type CommandDefinition, type MiokuContext } from "mioku";
|
|
2
2
|
import {
|
|
3
3
|
cloneConfig,
|
|
4
4
|
DEFAULT_CONFIG,
|
|
@@ -16,14 +16,21 @@ import { handleRank } from "./handlers/rank";
|
|
|
16
16
|
import { handleSuo } from "./handlers/suo";
|
|
17
17
|
import { handleYinpa } from "./handlers/yinpa";
|
|
18
18
|
import type { HandlerContext } from "./handlers/types";
|
|
19
|
-
import { parseImpactCommand } from "./router";
|
|
20
19
|
import { createCooldownState } from "./state";
|
|
21
20
|
import { NOT_ALLOW_TEXT, isGroupEvent } from "./utils";
|
|
22
21
|
|
|
22
|
+
type HandlerType =
|
|
23
|
+
| "pk"
|
|
24
|
+
| "dajiao"
|
|
25
|
+
| "suo"
|
|
26
|
+
| "queryjj"
|
|
27
|
+
| "rank"
|
|
28
|
+
| "yinpa"
|
|
29
|
+
| "open_module"
|
|
30
|
+
| "query_injection";
|
|
31
|
+
|
|
23
32
|
const impactPlugin = definePlugin({
|
|
24
33
|
name: "impact",
|
|
25
|
-
version: "1.0.0",
|
|
26
|
-
description: "让群友们眼前一黑的淫趴插件(牛子比拼),移植自 nonebot_plugin_impact",
|
|
27
34
|
|
|
28
35
|
async setup(ctx: MiokuContext) {
|
|
29
36
|
ctx.logger.info("impact 插件正在初始化...");
|
|
@@ -74,33 +81,22 @@ const impactPlugin = definePlugin({
|
|
|
74
81
|
});
|
|
75
82
|
}
|
|
76
83
|
|
|
77
|
-
|
|
84
|
+
const run = (type: HandlerType) => async (event: any, extra?: unknown) => {
|
|
78
85
|
if (!isGroupEvent(event)) return;
|
|
79
|
-
const text = ctx.text(event);
|
|
80
|
-
if (!text) return;
|
|
81
|
-
|
|
82
|
-
const cmd = parseImpactCommand(text);
|
|
83
|
-
if (cmd.type === "none") return;
|
|
84
|
-
|
|
85
86
|
const h: HandlerContext = { ctx, db, cd, config, screenshot, event };
|
|
86
|
-
|
|
87
87
|
try {
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
await handleOpenModule(h, cmd.enable);
|
|
88
|
+
if (type === "open_module") {
|
|
89
|
+
await handleOpenModule(h, extra as boolean);
|
|
91
90
|
return;
|
|
92
91
|
}
|
|
93
|
-
|
|
94
|
-
// 其余命令需要群开启了银趴
|
|
95
|
-
if (!db.isGroupAllowed(Number(event.group_id))) {
|
|
92
|
+
if (!db.isGroupAllowed(String(event.group_id ?? "").trim())) {
|
|
96
93
|
await event.reply(
|
|
97
|
-
[ctx.segment.at(String(event.user_id)), ctx.segment.text(NOT_ALLOW_TEXT)],
|
|
94
|
+
[ctx.segment.at(String(event.user_id ?? "").trim()), ctx.segment.text(NOT_ALLOW_TEXT)],
|
|
98
95
|
false,
|
|
99
96
|
);
|
|
100
97
|
return;
|
|
101
98
|
}
|
|
102
|
-
|
|
103
|
-
switch (cmd.type) {
|
|
99
|
+
switch (type) {
|
|
104
100
|
case "pk":
|
|
105
101
|
return await handlePk(h);
|
|
106
102
|
case "dajiao":
|
|
@@ -112,12 +108,12 @@ const impactPlugin = definePlugin({
|
|
|
112
108
|
case "rank":
|
|
113
109
|
return await handleRank(h);
|
|
114
110
|
case "yinpa":
|
|
115
|
-
return await handleYinpa(h,
|
|
111
|
+
return await handleYinpa(h, extra as "owner" | "admin" | "member");
|
|
116
112
|
case "query_injection":
|
|
117
|
-
return await handleQueryInjection(h,
|
|
113
|
+
return await handleQueryInjection(h, extra as "today" | "history");
|
|
118
114
|
}
|
|
119
115
|
} catch (error) {
|
|
120
|
-
ctx.logger.error(`impact 命令 ${
|
|
116
|
+
ctx.logger.error(`impact 命令 ${type} 执行失败: ${error}`);
|
|
121
117
|
try {
|
|
122
118
|
await event.reply(
|
|
123
119
|
[ctx.segment.text(`淫趴插件出错了喵: ${String(error)}`)],
|
|
@@ -127,6 +123,81 @@ const impactPlugin = definePlugin({
|
|
|
127
123
|
// 忽略二次失败
|
|
128
124
|
}
|
|
129
125
|
}
|
|
126
|
+
};
|
|
127
|
+
|
|
128
|
+
// 裸触发设计:开启淫趴 / .开启淫趴 都可用
|
|
129
|
+
const cmd = (command: CommandDefinition) =>
|
|
130
|
+
ctx.command({ ...command, prefixes: false });
|
|
131
|
+
|
|
132
|
+
cmd({
|
|
133
|
+
id: "开启淫趴",
|
|
134
|
+
name: "开启淫趴",
|
|
135
|
+
match: /^(?:开始银趴|关闭银趴|开启淫趴|禁止淫趴|开启银趴|禁止银趴)\s*$/,
|
|
136
|
+
permission: "admin",
|
|
137
|
+
description: "在本群开启或关闭淫趴功能",
|
|
138
|
+
usage: ".开启淫趴 / .关闭银趴",
|
|
139
|
+
handler: ({ event, match }) =>
|
|
140
|
+
run("open_module")(event, match![0].startsWith("开启") || match![0].startsWith("开始")),
|
|
141
|
+
});
|
|
142
|
+
cmd({
|
|
143
|
+
id: "日透",
|
|
144
|
+
name: "日群友",
|
|
145
|
+
match: /^(日群友|透群友|日群主|透群主|日管理|透管理)(?:\s|$|@)/,
|
|
146
|
+
description: "随机或定向选取一位幸运儿进行注入,按命令决定身份",
|
|
147
|
+
usage: ".日群友 / .透群友 @某人 / .透群主 / .透管理",
|
|
148
|
+
handler: ({ event, match }) => {
|
|
149
|
+
const word = match![1];
|
|
150
|
+
const subject = word.includes("群主") ? "owner" : word.includes("管理") ? "admin" : "member";
|
|
151
|
+
return run("yinpa")(event, subject);
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
cmd({
|
|
155
|
+
id: "pk",
|
|
156
|
+
name: "pk",
|
|
157
|
+
match: /^(?:pk|PK|对决)(?:\s|@|$)/,
|
|
158
|
+
description: "和 @ 的群友进行牛子对决,胜者抢走败者的随机长度",
|
|
159
|
+
usage: ".pk @某人",
|
|
160
|
+
handler: ({ event }) => run("pk")(event),
|
|
161
|
+
});
|
|
162
|
+
cmd({
|
|
163
|
+
id: "打胶",
|
|
164
|
+
name: "打胶",
|
|
165
|
+
match: /^(?:打胶|开导)\s*$/,
|
|
166
|
+
description: "给自己的牛子增加一段随机长度",
|
|
167
|
+
handler: ({ event }) => run("dajiao")(event),
|
|
168
|
+
});
|
|
169
|
+
cmd({
|
|
170
|
+
id: "嗦牛子",
|
|
171
|
+
name: "嗦牛子",
|
|
172
|
+
match: /^嗦牛子(?:\s|@|$)/,
|
|
173
|
+
description: "嗦自己或被 @ 的群友的牛子,增加随机长度",
|
|
174
|
+
usage: ".嗦牛子 / .嗦牛子 @某人",
|
|
175
|
+
handler: ({ event }) => run("suo")(event),
|
|
176
|
+
});
|
|
177
|
+
cmd({
|
|
178
|
+
id: "查询",
|
|
179
|
+
name: "查询",
|
|
180
|
+
match: /^查询(?:\s|@|$)/,
|
|
181
|
+
description: "查询自己或 @ 的群友的牛子长度",
|
|
182
|
+
usage: ".查询 / .查询 @某人",
|
|
183
|
+
handler: ({ event }) => run("queryjj")(event),
|
|
184
|
+
});
|
|
185
|
+
cmd({
|
|
186
|
+
id: "注入查询",
|
|
187
|
+
name: "注入查询",
|
|
188
|
+
match: /^(?:注入查询|摄入查询|射入查询)(.*)$/,
|
|
189
|
+
description: "查询当日或全部历史被注入量,加 历史/全部 看走势图",
|
|
190
|
+
usage: ".注入查询 / .注入查询 历史 / .注入查询 @某人 全部",
|
|
191
|
+
handler: ({ event, match }) =>
|
|
192
|
+
run("query_injection")(event, /历史|全部/.test(match![1] || "") ? "history" : "today"),
|
|
193
|
+
});
|
|
194
|
+
cmd({
|
|
195
|
+
id: "jj排行榜",
|
|
196
|
+
name: "jj排行榜",
|
|
197
|
+
match: /^(?:jj|JJ)(?:排行榜|排名|榜单|rank|RANK)(?:\s|$)/,
|
|
198
|
+
description: "查看牛子长度前五与倒数五名的图表",
|
|
199
|
+
usage: ".jj排行榜 / .jjrank",
|
|
200
|
+
handler: ({ event }) => run("rank")(event),
|
|
130
201
|
});
|
|
131
202
|
|
|
132
203
|
ctx.logger.info("impact 插件初始化完成");
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-impact",
|
|
3
|
-
"version": "2.0.
|
|
3
|
+
"version": "2.0.3",
|
|
4
4
|
"description": "让群友们眼前一黑的淫趴插件",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -16,102 +16,7 @@
|
|
|
16
16
|
"screenshot",
|
|
17
17
|
"help",
|
|
18
18
|
"config"
|
|
19
|
-
]
|
|
20
|
-
"accessHooks": [
|
|
21
|
-
{
|
|
22
|
-
"id": "开启淫趴",
|
|
23
|
-
"match": "/^(?:开启|禁止|开始|关闭)(?:淫趴|银趴)\\s*$/",
|
|
24
|
-
"description": "群内开启或关闭淫趴功能"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"id": "日透",
|
|
28
|
-
"match": "/^(?:日群友|透群友|日群主|透群主|日管理|透管理)(?:\\s|$|@)/",
|
|
29
|
-
"description": "日 / 透 系列命令"
|
|
30
|
-
},
|
|
31
|
-
{
|
|
32
|
-
"id": "打胶",
|
|
33
|
-
"match": "/^(?:打胶|开导)\\s*$/",
|
|
34
|
-
"description": "打胶 / 开导"
|
|
35
|
-
},
|
|
36
|
-
{
|
|
37
|
-
"id": "嗦牛子",
|
|
38
|
-
"match": "/^嗦牛子(?:\\s|$|@)/",
|
|
39
|
-
"description": "嗦自己或被 @ 群友的牛子"
|
|
40
|
-
},
|
|
41
|
-
{
|
|
42
|
-
"id": "pk",
|
|
43
|
-
"match": "/^(?:pk|PK|对决)(?:\\s|@|$)/",
|
|
44
|
-
"description": "牛子对决"
|
|
45
|
-
},
|
|
46
|
-
{
|
|
47
|
-
"id": "查询",
|
|
48
|
-
"match": "/^查询(?:\\s|@|$)/",
|
|
49
|
-
"description": "查询自己或被 @ 群友的牛子长度"
|
|
50
|
-
},
|
|
51
|
-
{
|
|
52
|
-
"id": "注入查询",
|
|
53
|
-
"match": "/^(?:注入查询|摄入查询|射入查询)/",
|
|
54
|
-
"description": "查询当日或历史被注入量"
|
|
55
|
-
},
|
|
56
|
-
{
|
|
57
|
-
"id": "jj排行榜",
|
|
58
|
-
"match": "/^(?:jj|JJ)(?:排行榜|排名|榜单|rank|RANK)(?:\\s|$)/",
|
|
59
|
-
"description": "牛子长度排行榜"
|
|
60
|
-
}
|
|
61
|
-
],
|
|
62
|
-
"help": {
|
|
63
|
-
"title": "淫趴 ",
|
|
64
|
-
"description": "让群友们眼前一黑的牛子比拼插件",
|
|
65
|
-
"commands": [
|
|
66
|
-
{
|
|
67
|
-
"cmd": "开启淫趴 / 禁止淫趴",
|
|
68
|
-
"desc": "在本群开启或关闭淫趴功能(管理员/群主/主人可用)",
|
|
69
|
-
"usage": "开启淫趴 / 关闭银趴",
|
|
70
|
-
"role": "admin"
|
|
71
|
-
},
|
|
72
|
-
{
|
|
73
|
-
"cmd": "打胶 / 开导",
|
|
74
|
-
"desc": "给自己的牛子增加一段随机长度",
|
|
75
|
-
"role": "member"
|
|
76
|
-
},
|
|
77
|
-
{
|
|
78
|
-
"cmd": "嗦牛子",
|
|
79
|
-
"desc": "嗦自己或被 @ 的群友的牛子,增加随机长度",
|
|
80
|
-
"usage": "嗦牛子 / 嗦牛子 @某人",
|
|
81
|
-
"role": "member"
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
"cmd": "pk / 对决",
|
|
85
|
-
"desc": "和 @ 的群友进行牛子对决,胜者抢走败者的随机长度",
|
|
86
|
-
"usage": "pk @某人",
|
|
87
|
-
"role": "member"
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
"cmd": "查询",
|
|
91
|
-
"desc": "查询自己或 @ 的群友的牛子长度",
|
|
92
|
-
"usage": "查询 / 查询 @某人",
|
|
93
|
-
"role": "member"
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
"cmd": "jj排行榜",
|
|
97
|
-
"desc": "查看牛子长度前五与倒数五名的图表",
|
|
98
|
-
"usage": "jj排行榜 / jjrank",
|
|
99
|
-
"role": "member"
|
|
100
|
-
},
|
|
101
|
-
{
|
|
102
|
-
"cmd": "日群友 / 透群友 / 透群主 / 透管理",
|
|
103
|
-
"desc": "随机或定向选取一位幸运儿进行注入,按命令决定身份",
|
|
104
|
-
"usage": "日群友 / 透群友 @某人 / 透群主 / 透管理",
|
|
105
|
-
"role": "member"
|
|
106
|
-
},
|
|
107
|
-
{
|
|
108
|
-
"cmd": "注入查询",
|
|
109
|
-
"desc": "查询当日或全部历史被注入量,加 历史/全部 看走势图",
|
|
110
|
-
"usage": "注入查询 / 注入查询 历史 / 注入查询 @某人 全部",
|
|
111
|
-
"role": "member"
|
|
112
|
-
}
|
|
113
|
-
]
|
|
114
|
-
}
|
|
19
|
+
]
|
|
115
20
|
},
|
|
116
21
|
"peerDependencies": {
|
|
117
22
|
"mioku": "^1.0.0"
|
package/types.ts
CHANGED
package/utils.ts
CHANGED
|
@@ -35,17 +35,18 @@ export function roundTo(value: number, digits: number): number {
|
|
|
35
35
|
}
|
|
36
36
|
|
|
37
37
|
/**
|
|
38
|
-
* 提取消息里第一个 @
|
|
39
|
-
*
|
|
38
|
+
* 提取消息里第一个 @ 的目标 id,忽略 @全体。
|
|
39
|
+
* 兼容 qq-official 的 target 段。找不到返回 undefined。
|
|
40
40
|
*/
|
|
41
|
-
export function getAtUserId(message: any):
|
|
41
|
+
export function getAtUserId(message: any): string | undefined {
|
|
42
42
|
if (!Array.isArray(message)) return undefined;
|
|
43
43
|
for (const seg of message) {
|
|
44
44
|
if (seg?.type !== "at") continue;
|
|
45
|
-
const raw = seg?.qq ?? seg?.data?.
|
|
46
|
-
if (raw == null
|
|
47
|
-
const
|
|
48
|
-
if (
|
|
45
|
+
const raw = seg?.data?.qq ?? seg?.qq ?? seg?.data?.target ?? seg?.target;
|
|
46
|
+
if (raw == null) continue;
|
|
47
|
+
const id = String(raw).trim();
|
|
48
|
+
if (!id || id === "all") continue;
|
|
49
|
+
return id;
|
|
49
50
|
}
|
|
50
51
|
return undefined;
|
|
51
52
|
}
|
|
@@ -54,11 +55,6 @@ export function isGroupEvent(event: any): boolean {
|
|
|
54
55
|
return event?.message_type === "group" && event?.group_id != null;
|
|
55
56
|
}
|
|
56
57
|
|
|
57
|
-
export function isGroupAdmin(event: any): boolean {
|
|
58
|
-
const role = event?.sender?.role;
|
|
59
|
-
return role === "admin" || role === "owner";
|
|
60
|
-
}
|
|
61
|
-
|
|
62
58
|
export function getSenderName(event: any): string {
|
|
63
59
|
const card = String(event?.sender?.card || "").trim();
|
|
64
60
|
if (card) return card;
|
|
@@ -69,7 +65,7 @@ export function getSenderName(event: any): string {
|
|
|
69
65
|
|
|
70
66
|
export async function getStrangerNickname(
|
|
71
67
|
bot: import("mioku").Bot | undefined,
|
|
72
|
-
userId:
|
|
68
|
+
userId: string,
|
|
73
69
|
): Promise<string> {
|
|
74
70
|
try {
|
|
75
71
|
const info = bot ? await bot.getFriendInfo(userId) : undefined;
|
|
@@ -81,8 +77,30 @@ export async function getStrangerNickname(
|
|
|
81
77
|
return String(userId);
|
|
82
78
|
}
|
|
83
79
|
|
|
84
|
-
|
|
85
|
-
|
|
80
|
+
const QQ_AVATAR_ADAPTERS = new Set(["onebotv11", "icqq"]);
|
|
81
|
+
const QQ_ID_RE = /^\d{4,12}$/;
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* 生成头像地址。仅在 QQ 系适配器且 id 是 QQ 号时才回退到 qlogo,
|
|
85
|
+
* 其他平台(如 qq-official 的 openid)没有通用头像接口,返回空串由调用方降级。
|
|
86
|
+
*/
|
|
87
|
+
export function getAvatarUrl(
|
|
88
|
+
userId: string,
|
|
89
|
+
options?: { avatar?: unknown; adapter?: unknown },
|
|
90
|
+
): string {
|
|
91
|
+
const direct = String(options?.avatar ?? "").trim();
|
|
92
|
+
if (direct) return direct;
|
|
93
|
+
const id = String(userId ?? "").trim();
|
|
94
|
+
const adapter = String(options?.adapter ?? "").trim().toLowerCase();
|
|
95
|
+
if (adapter && !QQ_AVATAR_ADAPTERS.has(adapter)) return "";
|
|
96
|
+
return QQ_ID_RE.test(id) ? `https://q1.qlogo.cn/g?b=qq&nk=${id}&s=640` : "";
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export function resolveAvatarUrl(event: any, userId: string): string {
|
|
100
|
+
const id = String(userId ?? "").trim();
|
|
101
|
+
const isSelf = id !== "" && id === String(event?.user_id ?? "").trim();
|
|
102
|
+
const avatar = isSelf ? event?.sender?.avatar ?? event?.avatar : undefined;
|
|
103
|
+
return getAvatarUrl(id, { avatar, adapter: event?.bot?.adapter });
|
|
86
104
|
}
|
|
87
105
|
|
|
88
106
|
export function escapeHtml(value: string): string {
|
package/router.ts
DELETED
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
export type ImpactCommand =
|
|
2
|
-
| { type: "pk" }
|
|
3
|
-
| { type: "dajiao" }
|
|
4
|
-
| { type: "suo" }
|
|
5
|
-
| { type: "queryjj" }
|
|
6
|
-
| { type: "rank" }
|
|
7
|
-
| { type: "yinpa"; subject: "member" | "owner" | "admin" }
|
|
8
|
-
| { type: "open_module"; enable: boolean }
|
|
9
|
-
| { type: "query_injection"; mode: "today" | "history" }
|
|
10
|
-
| { type: "none" };
|
|
11
|
-
|
|
12
|
-
const YINPA_RE =
|
|
13
|
-
/^(?:日群友|透群友|日群主|透群主|日管理|透管理)(?:\s|$|@)/u;
|
|
14
|
-
|
|
15
|
-
const OPEN_MODULE_RE =
|
|
16
|
-
/^(?:开始银趴|关闭银趴|开启淫趴|禁止淫趴|开启银趴|禁止银趴)\s*$/u;
|
|
17
|
-
|
|
18
|
-
const RANK_HEADS = new Set([
|
|
19
|
-
"jj排行榜",
|
|
20
|
-
"jj排名",
|
|
21
|
-
"jj榜单",
|
|
22
|
-
"jjrank",
|
|
23
|
-
"JJ排行榜",
|
|
24
|
-
"JJ排名",
|
|
25
|
-
"JJ榜单",
|
|
26
|
-
"JJrank",
|
|
27
|
-
"JJRANK",
|
|
28
|
-
]);
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* 把消息文本解析成 impact 命令。
|
|
32
|
-
* 仅看消息开头(去掉前导空白),命中失败返回 { type: "none" }。
|
|
33
|
-
*/
|
|
34
|
-
export function parseImpactCommand(text: string): ImpactCommand {
|
|
35
|
-
const head = text.replace(/^\s+/, "");
|
|
36
|
-
if (!head) return { type: "none" };
|
|
37
|
-
|
|
38
|
-
// 银趴开关:完整匹配
|
|
39
|
-
const open = head.match(OPEN_MODULE_RE);
|
|
40
|
-
if (open) {
|
|
41
|
-
const word = open[0];
|
|
42
|
-
const enable = word.startsWith("开启") || word.startsWith("开始");
|
|
43
|
-
return { type: "open_module", enable };
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
// 透/日 系列
|
|
47
|
-
const yinpa = head.match(YINPA_RE);
|
|
48
|
-
if (yinpa) {
|
|
49
|
-
const word = yinpa[0];
|
|
50
|
-
const subject: "owner" | "admin" | "member" = word.includes("群主")
|
|
51
|
-
? "owner"
|
|
52
|
-
: word.includes("管理")
|
|
53
|
-
? "admin"
|
|
54
|
-
: "member";
|
|
55
|
-
return { type: "yinpa", subject };
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// pk / 对决:要求形如 "pk @某人" 或 "pk"。具体的 at 校验放到 handler。
|
|
59
|
-
if (/^(pk|PK|对决)(\s|@|$)/.test(head)) {
|
|
60
|
-
return { type: "pk" };
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// 打胶 / 开导:完整匹配
|
|
64
|
-
if (/^(打胶|开导)\s*$/.test(head)) {
|
|
65
|
-
return { type: "dajiao" };
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
// 嗦牛子
|
|
69
|
-
if (/^嗦牛子(\s|@|$)/.test(head)) {
|
|
70
|
-
return { type: "suo" };
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
// 注入查询 / 摄入查询 / 射入查询
|
|
74
|
-
const inj = head.match(/^(?:注入查询|摄入查询|射入查询)(.*)$/u);
|
|
75
|
-
if (inj) {
|
|
76
|
-
const rest = inj[1] || "";
|
|
77
|
-
const mode = /历史|全部/.test(rest) ? "history" : "today";
|
|
78
|
-
return { type: "query_injection", mode };
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
// 排行榜
|
|
82
|
-
for (const word of RANK_HEADS) {
|
|
83
|
-
if (head === word || head.startsWith(`${word} `) || head.startsWith(`${word}\n`)) {
|
|
84
|
-
return { type: "rank" };
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
// 只匹配 "查询" + 空白/at/结束,避免误吞太多文本
|
|
89
|
-
if (/^查询(\s|@|$)/.test(head)) {
|
|
90
|
-
return { type: "queryjj" };
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
return { type: "none" };
|
|
94
|
-
}
|