mioku-plugin-deerpipe 1.0.0 → 1.1.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/db.ts +141 -105
- package/image.ts +23 -4
- package/package.json +33 -1
package/db.ts
CHANGED
|
@@ -1,34 +1,22 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { ensureDataDir } from "mioku";
|
|
1
|
+
import { Database } from "bun:sqlite";
|
|
3
2
|
import * as path from "path";
|
|
3
|
+
import { ensureDataDir } from "mioku";
|
|
4
4
|
import type {
|
|
5
5
|
DeerCheckInResult,
|
|
6
6
|
DeerRankEntry,
|
|
7
7
|
DeerUser,
|
|
8
8
|
} from "./types";
|
|
9
9
|
|
|
10
|
-
interface
|
|
11
|
-
|
|
12
|
-
|
|
10
|
+
interface UserRow {
|
|
11
|
+
can_be_helped: number;
|
|
12
|
+
no_deer_until: number | null;
|
|
13
13
|
}
|
|
14
14
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
*/
|
|
19
|
-
interface DeerStore {
|
|
20
|
-
users: Record<string, UserRecord>;
|
|
21
|
-
records: Record<
|
|
22
|
-
string,
|
|
23
|
-
Record<string, Record<string, Record<string, number>>>
|
|
24
|
-
>;
|
|
15
|
+
interface RecordRow {
|
|
16
|
+
day: number;
|
|
17
|
+
count: number;
|
|
25
18
|
}
|
|
26
19
|
|
|
27
|
-
const DEFAULT_STORE: DeerStore = {
|
|
28
|
-
users: {},
|
|
29
|
-
records: {},
|
|
30
|
-
};
|
|
31
|
-
|
|
32
20
|
export interface DeerDatabase {
|
|
33
21
|
getOrCreateUser(scene: string, userId: number): DeerUser;
|
|
34
22
|
updateUser(user: DeerUser): Promise<void>;
|
|
@@ -53,6 +41,7 @@ export interface DeerDatabase {
|
|
|
53
41
|
limit: number,
|
|
54
42
|
): DeerRankEntry[];
|
|
55
43
|
cleanupOtherMonths(year: number, month: number): Promise<void>;
|
|
44
|
+
close(): void;
|
|
56
45
|
}
|
|
57
46
|
|
|
58
47
|
function userKey(scene: string, userId: number): string {
|
|
@@ -65,38 +54,74 @@ function monthKey(year: number, month: number): string {
|
|
|
65
54
|
|
|
66
55
|
export async function initDeerDatabase(): Promise<DeerDatabase> {
|
|
67
56
|
const dir = ensureDataDir("deerpipe");
|
|
68
|
-
const
|
|
69
|
-
const db =
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
57
|
+
const dbPath = path.join(dir, "deerpipe.db");
|
|
58
|
+
const db = new Database(dbPath);
|
|
59
|
+
|
|
60
|
+
db.exec("PRAGMA journal_mode = WAL");
|
|
61
|
+
|
|
62
|
+
db.exec(`
|
|
63
|
+
CREATE TABLE IF NOT EXISTS users (
|
|
64
|
+
key TEXT PRIMARY KEY,
|
|
65
|
+
can_be_helped INTEGER NOT NULL DEFAULT 1,
|
|
66
|
+
no_deer_until INTEGER
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
CREATE TABLE IF NOT EXISTS records (
|
|
70
|
+
scene TEXT NOT NULL,
|
|
71
|
+
month_key TEXT NOT NULL,
|
|
72
|
+
user_id INTEGER NOT NULL,
|
|
73
|
+
day INTEGER NOT NULL,
|
|
74
|
+
count INTEGER NOT NULL DEFAULT 0,
|
|
75
|
+
PRIMARY KEY (scene, month_key, user_id, day)
|
|
76
|
+
);
|
|
77
|
+
CREATE INDEX IF NOT EXISTS idx_records_scene_month
|
|
78
|
+
ON records(scene, month_key);
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
const stmts = {
|
|
82
|
+
getUser: db.prepare(
|
|
83
|
+
`SELECT can_be_helped, no_deer_until FROM users WHERE key = $key`,
|
|
84
|
+
),
|
|
85
|
+
insertUser: db.prepare(`
|
|
86
|
+
INSERT INTO users (key, can_be_helped, no_deer_until)
|
|
87
|
+
VALUES ($key, 1, NULL)
|
|
88
|
+
ON CONFLICT(key) DO NOTHING
|
|
89
|
+
`),
|
|
90
|
+
updateUser: db.prepare(`
|
|
91
|
+
INSERT INTO users (key, can_be_helped, no_deer_until)
|
|
92
|
+
VALUES ($key, $canBeHelped, $noDeerUntil)
|
|
93
|
+
ON CONFLICT(key) DO UPDATE SET
|
|
94
|
+
can_be_helped = $canBeHelped,
|
|
95
|
+
no_deer_until = $noDeerUntil
|
|
96
|
+
`),
|
|
97
|
+
getMonthRecords: db.prepare(`
|
|
98
|
+
SELECT day, count FROM records
|
|
99
|
+
WHERE scene = $scene AND month_key = $monthKey AND user_id = $userId
|
|
100
|
+
ORDER BY day ASC
|
|
101
|
+
`),
|
|
102
|
+
getDayRecord: db.prepare(`
|
|
103
|
+
SELECT count FROM records
|
|
104
|
+
WHERE scene = $scene AND month_key = $monthKey AND user_id = $userId AND day = $day
|
|
105
|
+
`),
|
|
106
|
+
insertRecord: db.prepare(`
|
|
107
|
+
INSERT INTO records (scene, month_key, user_id, day, count)
|
|
108
|
+
VALUES ($scene, $monthKey, $userId, $day, 1)
|
|
109
|
+
`),
|
|
110
|
+
incrementRecord: db.prepare(`
|
|
111
|
+
UPDATE records SET count = count + 1
|
|
112
|
+
WHERE scene = $scene AND month_key = $monthKey AND user_id = $userId AND day = $day
|
|
113
|
+
`),
|
|
114
|
+
getMonthRank: db.prepare(`
|
|
115
|
+
SELECT user_id, SUM(count) AS total FROM records
|
|
116
|
+
WHERE scene = $scene AND month_key = $monthKey
|
|
117
|
+
GROUP BY user_id
|
|
118
|
+
ORDER BY total DESC
|
|
119
|
+
LIMIT $limit
|
|
120
|
+
`),
|
|
121
|
+
deleteOtherMonths: db.prepare(
|
|
122
|
+
`DELETE FROM records WHERE month_key != $keepKey`,
|
|
123
|
+
),
|
|
124
|
+
};
|
|
100
125
|
|
|
101
126
|
function loadRecords(
|
|
102
127
|
scene: string,
|
|
@@ -104,30 +129,40 @@ export async function initDeerDatabase(): Promise<DeerDatabase> {
|
|
|
104
129
|
year: number,
|
|
105
130
|
month: number,
|
|
106
131
|
): Map<number, number> {
|
|
107
|
-
const
|
|
132
|
+
const rows = stmts.getMonthRecords.all({
|
|
133
|
+
$scene: scene,
|
|
134
|
+
$monthKey: monthKey(year, month),
|
|
135
|
+
$userId: userId,
|
|
136
|
+
}) as RecordRow[];
|
|
108
137
|
const map = new Map<number, number>();
|
|
109
|
-
for (const
|
|
110
|
-
map.set(
|
|
138
|
+
for (const row of rows) {
|
|
139
|
+
map.set(row.day, row.count);
|
|
111
140
|
}
|
|
112
141
|
return map;
|
|
113
142
|
}
|
|
114
143
|
|
|
115
144
|
return {
|
|
116
145
|
getOrCreateUser(scene, userId) {
|
|
117
|
-
const
|
|
146
|
+
const key = userKey(scene, userId);
|
|
147
|
+
let row = stmts.getUser.get({ $key: key }) as UserRow | null;
|
|
148
|
+
if (!row) {
|
|
149
|
+
stmts.insertUser.run({ $key: key });
|
|
150
|
+
row = { can_be_helped: 1, no_deer_until: null };
|
|
151
|
+
}
|
|
118
152
|
return {
|
|
119
153
|
scene,
|
|
120
154
|
userId,
|
|
121
|
-
canBeHelped: row.
|
|
122
|
-
noDeerUntil: row.
|
|
155
|
+
canBeHelped: Boolean(row.can_be_helped),
|
|
156
|
+
noDeerUntil: row.no_deer_until,
|
|
123
157
|
};
|
|
124
158
|
},
|
|
125
159
|
|
|
126
160
|
async updateUser(user) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
161
|
+
stmts.updateUser.run({
|
|
162
|
+
$key: userKey(user.scene, user.userId),
|
|
163
|
+
$canBeHelped: user.canBeHelped ? 1 : 0,
|
|
164
|
+
$noDeerUntil: user.noDeerUntil,
|
|
165
|
+
});
|
|
131
166
|
},
|
|
132
167
|
|
|
133
168
|
getRecords(scene, userId, year, month) {
|
|
@@ -135,25 +170,39 @@ export async function initDeerDatabase(): Promise<DeerDatabase> {
|
|
|
135
170
|
},
|
|
136
171
|
|
|
137
172
|
async checkIn(scene, userId, year, month, day, isPast) {
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
const
|
|
141
|
-
const
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
173
|
+
stmts.insertUser.run({ $key: userKey(scene, userId) });
|
|
174
|
+
|
|
175
|
+
const mKey = monthKey(year, month);
|
|
176
|
+
const existing = stmts.getDayRecord.get({
|
|
177
|
+
$scene: scene,
|
|
178
|
+
$monthKey: mKey,
|
|
179
|
+
$userId: userId,
|
|
180
|
+
$day: day,
|
|
181
|
+
}) as { count: number } | null;
|
|
182
|
+
|
|
183
|
+
if (existing && isPast) {
|
|
184
|
+
return {
|
|
185
|
+
ok: false,
|
|
186
|
+
records: loadRecords(scene, userId, year, month),
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (existing) {
|
|
191
|
+
stmts.incrementRecord.run({
|
|
192
|
+
$scene: scene,
|
|
193
|
+
$monthKey: mKey,
|
|
194
|
+
$userId: userId,
|
|
195
|
+
$day: day,
|
|
196
|
+
});
|
|
152
197
|
} else {
|
|
153
|
-
|
|
198
|
+
stmts.insertRecord.run({
|
|
199
|
+
$scene: scene,
|
|
200
|
+
$monthKey: mKey,
|
|
201
|
+
$userId: userId,
|
|
202
|
+
$day: day,
|
|
203
|
+
});
|
|
154
204
|
}
|
|
155
205
|
|
|
156
|
-
await db.write();
|
|
157
206
|
return {
|
|
158
207
|
ok: true,
|
|
159
208
|
records: loadRecords(scene, userId, year, month),
|
|
@@ -161,36 +210,23 @@ export async function initDeerDatabase(): Promise<DeerDatabase> {
|
|
|
161
210
|
},
|
|
162
211
|
|
|
163
212
|
getRank(scene, year, month, limit) {
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
174
|
-
totals.sort((a, b) => b.count - a.count);
|
|
175
|
-
return totals.slice(0, limit);
|
|
213
|
+
const rows = stmts.getMonthRank.all({
|
|
214
|
+
$scene: scene,
|
|
215
|
+
$monthKey: monthKey(year, month),
|
|
216
|
+
$limit: limit,
|
|
217
|
+
}) as Array<{ user_id: number; total: number }>;
|
|
218
|
+
return rows.map((row) => ({
|
|
219
|
+
userId: row.user_id,
|
|
220
|
+
count: row.total,
|
|
221
|
+
}));
|
|
176
222
|
},
|
|
177
223
|
|
|
178
224
|
async cleanupOtherMonths(year, month) {
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
delete sceneRecords[k];
|
|
185
|
-
dirty = true;
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
if (Object.keys(sceneRecords).length === 0) {
|
|
189
|
-
delete db.data.records[scene];
|
|
190
|
-
dirty = true;
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
if (dirty) await db.write();
|
|
225
|
+
stmts.deleteOtherMonths.run({ $keepKey: monthKey(year, month) });
|
|
226
|
+
},
|
|
227
|
+
|
|
228
|
+
close() {
|
|
229
|
+
db.close();
|
|
194
230
|
},
|
|
195
231
|
};
|
|
196
232
|
}
|
package/image.ts
CHANGED
|
@@ -65,17 +65,36 @@ interface AssetUris {
|
|
|
65
65
|
|
|
66
66
|
let assetsCache: AssetUris | null = null;
|
|
67
67
|
|
|
68
|
+
const TRANSPARENT_PNG =
|
|
69
|
+
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNgAAIAAAUAAen63NgAAAAASUVORK5CYII=";
|
|
70
|
+
|
|
68
71
|
async function loadAssets(): Promise<AssetUris> {
|
|
69
72
|
if (assetsCache) return assetsCache;
|
|
70
73
|
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
71
74
|
const assetsDir = path.join(here, "assets");
|
|
75
|
+
|
|
76
|
+
async function readOptional(name: string): Promise<Buffer | null> {
|
|
77
|
+
try {
|
|
78
|
+
return await fs.readFile(path.join(assetsDir, name));
|
|
79
|
+
} catch {
|
|
80
|
+
return null;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
72
84
|
const [avatar, check, deerpipe] = await Promise.all([
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
85
|
+
readOptional("akkarin@80x80.png"),
|
|
86
|
+
readOptional("check@96x100.png"),
|
|
87
|
+
readOptional("deerpipe@100x82.png"),
|
|
76
88
|
]);
|
|
89
|
+
if (!check || !deerpipe) {
|
|
90
|
+
throw new Error(
|
|
91
|
+
"deerpipe 必需素材缺失:check@96x100.png / deerpipe@100x82.png",
|
|
92
|
+
);
|
|
93
|
+
}
|
|
77
94
|
assetsCache = {
|
|
78
|
-
defaultAvatar:
|
|
95
|
+
defaultAvatar: avatar
|
|
96
|
+
? `data:image/png;base64,${avatar.toString("base64")}`
|
|
97
|
+
: TRANSPARENT_PNG,
|
|
79
98
|
check: `data:image/png;base64,${check.toString("base64")}`,
|
|
80
99
|
deerpipe: `data:image/png;base64,${deerpipe.toString("base64")}`,
|
|
81
100
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mioku-plugin-deerpipe",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.1.1",
|
|
4
4
|
"description": "🦌管签到插件,支持自🦌、帮🦌、补🦌、🦌历、🦌榜",
|
|
5
5
|
"main": "index.ts",
|
|
6
6
|
"type": "module",
|
|
@@ -16,6 +16,38 @@
|
|
|
16
16
|
"screenshot",
|
|
17
17
|
"help"
|
|
18
18
|
],
|
|
19
|
+
"accessHooks": [
|
|
20
|
+
{
|
|
21
|
+
"id": "🦌",
|
|
22
|
+
"match": "/^(?:🦌|鹿)(?:\\s|$|@)/",
|
|
23
|
+
"description": "自🦌或帮🦌"
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"id": "补🦌",
|
|
27
|
+
"match": "/^补(?:🦌|鹿)(?:\\s|\\d|$)/",
|
|
28
|
+
"description": "补签本月之前未签到的某天"
|
|
29
|
+
},
|
|
30
|
+
{
|
|
31
|
+
"id": "🦌历",
|
|
32
|
+
"match": "/^(?:🦌|鹿)历(?:\\s|$|@)/",
|
|
33
|
+
"description": "查看本月🦌签到日历"
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"id": "🦌榜",
|
|
37
|
+
"match": "/^(?:🦌|鹿)榜(?:\\s|$)/",
|
|
38
|
+
"description": "查看本月🦌签到排行榜"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "帮🦌",
|
|
42
|
+
"match": "/^帮(?:🦌|鹿)(?:\\s|$|@)/",
|
|
43
|
+
"description": "允许/禁止被别人帮🦌"
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"id": "禁🦌",
|
|
47
|
+
"match": "/^禁(?:🦌|鹿)(?:\\s|@|$)/",
|
|
48
|
+
"description": "禁止某人在一段时间内🦌"
|
|
49
|
+
}
|
|
50
|
+
],
|
|
19
51
|
"help": {
|
|
20
52
|
"title": "🦌管签到",
|
|
21
53
|
"description": "每月🦌管签到,含日历与排行榜",
|