mioku-plugin-impact 2.0.2 → 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 -1
- 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 +21 -19
- package/image.ts +9 -3
- package/index.ts +10 -9
- package/package.json +1 -1
- package/types.ts +1 -1
- package/utils.ts +33 -10
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
|
@@ -5,7 +5,7 @@ export async function handleOpenModule(
|
|
|
5
5
|
enable: boolean,
|
|
6
6
|
): Promise<void> {
|
|
7
7
|
const { ctx, db, event } = h;
|
|
8
|
-
await db.setGroupAllow(
|
|
8
|
+
await db.setGroupAllow(String(event.group_id ?? "").trim(), enable);
|
|
9
9
|
await event.reply(
|
|
10
10
|
[ctx.segment.text(enable ? "功能已开启喵" : "功能已禁用喵")],
|
|
11
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
|
@@ -2,14 +2,14 @@ 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;
|
|
@@ -28,15 +28,14 @@ export async function handleYinpa(
|
|
|
28
28
|
): Promise<void> {
|
|
29
29
|
const { ctx, db, cd, config, event } = h;
|
|
30
30
|
const miokuCtx = ctx as MiokuContext;
|
|
31
|
-
const uid =
|
|
32
|
-
const uidKey = String(uid);
|
|
31
|
+
const uid = String(event.user_id ?? "").trim();
|
|
33
32
|
|
|
34
|
-
const cdState = checkCooldown(cd.fuck,
|
|
33
|
+
const cdState = checkCooldown(cd.fuck, uid, config.fuckCd);
|
|
35
34
|
// 主人不受 CD 限制
|
|
36
35
|
if (!cdState.ok && !miokuCtx.isMaster?.(event)) {
|
|
37
36
|
await event.reply(
|
|
38
37
|
[
|
|
39
|
-
ctx.segment.at(
|
|
38
|
+
ctx.segment.at(uid),
|
|
40
39
|
ctx.segment.text(
|
|
41
40
|
`你已经榨不出来任何东西了, 请先休息${roundTo(cdState.remaining, 3)}秒`,
|
|
42
41
|
),
|
|
@@ -45,7 +44,7 @@ export async function handleYinpa(
|
|
|
45
44
|
);
|
|
46
45
|
return;
|
|
47
46
|
}
|
|
48
|
-
markCooldown(cd.fuck,
|
|
47
|
+
markCooldown(cd.fuck, uid);
|
|
49
48
|
|
|
50
49
|
const reqUserCard = getSenderName(event);
|
|
51
50
|
let prepList: PrepMember[];
|
|
@@ -54,19 +53,19 @@ export async function handleYinpa(
|
|
|
54
53
|
if (!bot) throw new Error("bot not found");
|
|
55
54
|
const members = await bot.getGroupMembers(String(event.group_id));
|
|
56
55
|
prepList = members.map((m: import("mioku").MemberInfo) => ({
|
|
57
|
-
user_id:
|
|
56
|
+
user_id: String(m.user_id ?? "").trim(),
|
|
58
57
|
role: (m.role as PrepMember["role"]) ?? "member",
|
|
59
58
|
card: m.card,
|
|
60
59
|
nickname: m.nickname,
|
|
61
60
|
}));
|
|
62
61
|
} catch (err) {
|
|
63
|
-
clearCooldown(cd.fuck,
|
|
62
|
+
clearCooldown(cd.fuck, uid);
|
|
64
63
|
ctx.logger.error(`impact: 获取群成员列表失败: ${err}`);
|
|
65
64
|
await event.reply([ctx.segment.text("喵喵喵? 拿不到群成员列表了!")], false);
|
|
66
65
|
return;
|
|
67
66
|
}
|
|
68
67
|
|
|
69
|
-
let lucky:
|
|
68
|
+
let lucky: string | null;
|
|
70
69
|
if (subject === "owner") {
|
|
71
70
|
lucky = await pickOwner(prepList, uid, reqUserCard, ctx, event);
|
|
72
71
|
} else if (subject === "admin") {
|
|
@@ -76,7 +75,7 @@ export async function handleYinpa(
|
|
|
76
75
|
}
|
|
77
76
|
if (lucky == null) {
|
|
78
77
|
// pickXxx 内部已 finish 并清掉 CD
|
|
79
|
-
clearCooldown(cd.fuck,
|
|
78
|
+
clearCooldown(cd.fuck, uid);
|
|
80
79
|
return;
|
|
81
80
|
}
|
|
82
81
|
|
|
@@ -96,13 +95,14 @@ export async function handleYinpa(
|
|
|
96
95
|
"群友";
|
|
97
96
|
const todayTotal = db.getTodayEjaculationVolume(lucky);
|
|
98
97
|
const seconds = 1 + Math.floor(Math.random() * 20);
|
|
98
|
+
const avatarUrl = resolveAvatarUrl(event, lucky);
|
|
99
99
|
|
|
100
100
|
await event.reply(
|
|
101
101
|
[
|
|
102
102
|
ctx.segment.text(
|
|
103
103
|
`好欸!${reqUserCard}(${uid})用时${seconds}秒 \n给 ${luckyCard}(${lucky}) 注入了${volume}毫升的脱氧核糖核酸, 当日总注入量为:${todayTotal}毫升\n`,
|
|
104
104
|
),
|
|
105
|
-
ctx.segment.image(
|
|
105
|
+
...(avatarUrl ? [ctx.segment.image(avatarUrl)] : []),
|
|
106
106
|
],
|
|
107
107
|
false,
|
|
108
108
|
);
|
|
@@ -110,11 +110,11 @@ export async function handleYinpa(
|
|
|
110
110
|
|
|
111
111
|
async function pickOwner(
|
|
112
112
|
prepList: PrepMember[],
|
|
113
|
-
uid:
|
|
113
|
+
uid: string,
|
|
114
114
|
reqUserCard: string,
|
|
115
115
|
ctx: any,
|
|
116
116
|
event: any,
|
|
117
|
-
): Promise<
|
|
117
|
+
): Promise<string | null> {
|
|
118
118
|
// 找不到群主时回退到自己
|
|
119
119
|
const owner = prepList.find((p) => p.role === "owner");
|
|
120
120
|
const lucky = owner?.user_id ?? uid;
|
|
@@ -131,12 +131,14 @@ async function pickOwner(
|
|
|
131
131
|
|
|
132
132
|
async function pickAdmin(
|
|
133
133
|
prepList: PrepMember[],
|
|
134
|
-
uid:
|
|
134
|
+
uid: string,
|
|
135
135
|
reqUserCard: string,
|
|
136
136
|
ctx: any,
|
|
137
137
|
event: any,
|
|
138
|
-
): Promise<
|
|
139
|
-
let admins = prepList
|
|
138
|
+
): Promise<string | null> {
|
|
139
|
+
let admins = prepList
|
|
140
|
+
.filter((p) => p.role === "admin")
|
|
141
|
+
.map((p) => p.user_id);
|
|
140
142
|
if (admins.includes(uid)) {
|
|
141
143
|
// 自己是管理的话移除自己
|
|
142
144
|
admins = admins.filter((id) => id !== uid);
|
|
@@ -155,11 +157,11 @@ async function pickAdmin(
|
|
|
155
157
|
|
|
156
158
|
async function pickMember(
|
|
157
159
|
prepList: PrepMember[],
|
|
158
|
-
uid:
|
|
160
|
+
uid: string,
|
|
159
161
|
reqUserCard: string,
|
|
160
162
|
ctx: any,
|
|
161
163
|
event: any,
|
|
162
|
-
): Promise<
|
|
164
|
+
): Promise<string | null> {
|
|
163
165
|
const at = getAtUserId(event.message);
|
|
164
166
|
if (at != null) {
|
|
165
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
|
@@ -89,9 +89,9 @@ const impactPlugin = definePlugin({
|
|
|
89
89
|
await handleOpenModule(h, extra as boolean);
|
|
90
90
|
return;
|
|
91
91
|
}
|
|
92
|
-
if (!db.isGroupAllowed(
|
|
92
|
+
if (!db.isGroupAllowed(String(event.group_id ?? "").trim())) {
|
|
93
93
|
await event.reply(
|
|
94
|
-
[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)],
|
|
95
95
|
false,
|
|
96
96
|
);
|
|
97
97
|
return;
|
|
@@ -125,6 +125,7 @@ const impactPlugin = definePlugin({
|
|
|
125
125
|
}
|
|
126
126
|
};
|
|
127
127
|
|
|
128
|
+
// 裸触发设计:开启淫趴 / .开启淫趴 都可用
|
|
128
129
|
const cmd = (command: CommandDefinition) =>
|
|
129
130
|
ctx.command({ ...command, prefixes: false });
|
|
130
131
|
|
|
@@ -134,7 +135,7 @@ const impactPlugin = definePlugin({
|
|
|
134
135
|
match: /^(?:开始银趴|关闭银趴|开启淫趴|禁止淫趴|开启银趴|禁止银趴)\s*$/,
|
|
135
136
|
permission: "admin",
|
|
136
137
|
description: "在本群开启或关闭淫趴功能",
|
|
137
|
-
usage: "
|
|
138
|
+
usage: ".开启淫趴 / .关闭银趴",
|
|
138
139
|
handler: ({ event, match }) =>
|
|
139
140
|
run("open_module")(event, match![0].startsWith("开启") || match![0].startsWith("开始")),
|
|
140
141
|
});
|
|
@@ -143,7 +144,7 @@ const impactPlugin = definePlugin({
|
|
|
143
144
|
name: "日群友",
|
|
144
145
|
match: /^(日群友|透群友|日群主|透群主|日管理|透管理)(?:\s|$|@)/,
|
|
145
146
|
description: "随机或定向选取一位幸运儿进行注入,按命令决定身份",
|
|
146
|
-
usage: "
|
|
147
|
+
usage: ".日群友 / .透群友 @某人 / .透群主 / .透管理",
|
|
147
148
|
handler: ({ event, match }) => {
|
|
148
149
|
const word = match![1];
|
|
149
150
|
const subject = word.includes("群主") ? "owner" : word.includes("管理") ? "admin" : "member";
|
|
@@ -155,7 +156,7 @@ const impactPlugin = definePlugin({
|
|
|
155
156
|
name: "pk",
|
|
156
157
|
match: /^(?:pk|PK|对决)(?:\s|@|$)/,
|
|
157
158
|
description: "和 @ 的群友进行牛子对决,胜者抢走败者的随机长度",
|
|
158
|
-
usage: "pk @某人",
|
|
159
|
+
usage: ".pk @某人",
|
|
159
160
|
handler: ({ event }) => run("pk")(event),
|
|
160
161
|
});
|
|
161
162
|
cmd({
|
|
@@ -170,7 +171,7 @@ const impactPlugin = definePlugin({
|
|
|
170
171
|
name: "嗦牛子",
|
|
171
172
|
match: /^嗦牛子(?:\s|@|$)/,
|
|
172
173
|
description: "嗦自己或被 @ 的群友的牛子,增加随机长度",
|
|
173
|
-
usage: "
|
|
174
|
+
usage: ".嗦牛子 / .嗦牛子 @某人",
|
|
174
175
|
handler: ({ event }) => run("suo")(event),
|
|
175
176
|
});
|
|
176
177
|
cmd({
|
|
@@ -178,7 +179,7 @@ const impactPlugin = definePlugin({
|
|
|
178
179
|
name: "查询",
|
|
179
180
|
match: /^查询(?:\s|@|$)/,
|
|
180
181
|
description: "查询自己或 @ 的群友的牛子长度",
|
|
181
|
-
usage: "
|
|
182
|
+
usage: ".查询 / .查询 @某人",
|
|
182
183
|
handler: ({ event }) => run("queryjj")(event),
|
|
183
184
|
});
|
|
184
185
|
cmd({
|
|
@@ -186,7 +187,7 @@ const impactPlugin = definePlugin({
|
|
|
186
187
|
name: "注入查询",
|
|
187
188
|
match: /^(?:注入查询|摄入查询|射入查询)(.*)$/,
|
|
188
189
|
description: "查询当日或全部历史被注入量,加 历史/全部 看走势图",
|
|
189
|
-
usage: "
|
|
190
|
+
usage: ".注入查询 / .注入查询 历史 / .注入查询 @某人 全部",
|
|
190
191
|
handler: ({ event, match }) =>
|
|
191
192
|
run("query_injection")(event, /历史|全部/.test(match![1] || "") ? "history" : "today"),
|
|
192
193
|
});
|
|
@@ -195,7 +196,7 @@ const impactPlugin = definePlugin({
|
|
|
195
196
|
name: "jj排行榜",
|
|
196
197
|
match: /^(?:jj|JJ)(?:排行榜|排名|榜单|rank|RANK)(?:\s|$)/,
|
|
197
198
|
description: "查看牛子长度前五与倒数五名的图表",
|
|
198
|
-
usage: "jj排行榜 / jjrank",
|
|
199
|
+
usage: ".jj排行榜 / .jjrank",
|
|
199
200
|
handler: ({ event }) => run("rank")(event),
|
|
200
201
|
});
|
|
201
202
|
|
package/package.json
CHANGED
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
|
}
|
|
@@ -64,7 +65,7 @@ export function getSenderName(event: any): string {
|
|
|
64
65
|
|
|
65
66
|
export async function getStrangerNickname(
|
|
66
67
|
bot: import("mioku").Bot | undefined,
|
|
67
|
-
userId:
|
|
68
|
+
userId: string,
|
|
68
69
|
): Promise<string> {
|
|
69
70
|
try {
|
|
70
71
|
const info = bot ? await bot.getFriendInfo(userId) : undefined;
|
|
@@ -76,8 +77,30 @@ export async function getStrangerNickname(
|
|
|
76
77
|
return String(userId);
|
|
77
78
|
}
|
|
78
79
|
|
|
79
|
-
|
|
80
|
-
|
|
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 });
|
|
81
104
|
}
|
|
82
105
|
|
|
83
106
|
export function escapeHtml(value: string): string {
|