koishi-plugin-ggcevo 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/index.d.ts +13 -0
- package/lib/index.js +109 -5
- package/package.json +1 -1
- package/readme.md +32 -1
package/lib/index.d.ts
CHANGED
|
@@ -12,6 +12,8 @@ export interface Config {
|
|
|
12
12
|
tencentDocsOpenId: string;
|
|
13
13
|
tencentDocsBanListFileId: string;
|
|
14
14
|
tencentDocsBanListSheetId: string;
|
|
15
|
+
tencentDocsAdminWelfareFileId: string;
|
|
16
|
+
tencentDocsAdminWelfareSheetId: string;
|
|
15
17
|
}
|
|
16
18
|
export declare const Config: Schema<Config>;
|
|
17
19
|
export declare const inject: {
|
|
@@ -42,6 +44,7 @@ declare module 'koishi' {
|
|
|
42
44
|
ggcevo_activity_claim_log: ActivityClaimLog;
|
|
43
45
|
ggcevo_docs_token: GgcEvoDocsToken;
|
|
44
46
|
ggcevo_ban_record: GgcEvoBanRecord;
|
|
47
|
+
ggcevo_admin_welfare: GgcEvoAdminWelfare;
|
|
45
48
|
}
|
|
46
49
|
}
|
|
47
50
|
export interface GgcEvoPlayer {
|
|
@@ -86,6 +89,16 @@ export interface GgcEvoBanRecord {
|
|
|
86
89
|
/** 最近一次同步时间 */
|
|
87
90
|
update_time: Date;
|
|
88
91
|
}
|
|
92
|
+
export interface GgcEvoAdminWelfare {
|
|
93
|
+
/** 自增ID, 对应文档行号(行号 = id + 1, 跳过表头) */
|
|
94
|
+
id: number;
|
|
95
|
+
/** A列: QQ号 */
|
|
96
|
+
qq: string;
|
|
97
|
+
/** B列: 游戏句柄 */
|
|
98
|
+
handle: string;
|
|
99
|
+
/** 最近一次同步时间 */
|
|
100
|
+
update_time: Date;
|
|
101
|
+
}
|
|
89
102
|
export interface Backpack {
|
|
90
103
|
id: number;
|
|
91
104
|
user_id: string;
|
package/lib/index.js
CHANGED
|
@@ -42,7 +42,9 @@ var Config = import_koishi.Schema.object({
|
|
|
42
42
|
tencentDocsAccessToken: import_koishi.Schema.string().description("腾讯文档 Access Token(用户级模式必填, 通过扫码授权获取)").role("secret").default(""),
|
|
43
43
|
tencentDocsOpenId: import_koishi.Schema.string().description("腾讯文档 Open ID(用户级模式必填, 与 Access Token 同时获取)").default(""),
|
|
44
44
|
tencentDocsBanListFileId: import_koishi.Schema.string().description("封禁记录在线表格的文件ID(短ID或完整ID)").default("DTVdYZVBDdFhEUkp6"),
|
|
45
|
-
tencentDocsBanListSheetId: import_koishi.Schema.string().description("封禁记录工作表ID(表格URL中tab参数)").default("BB08J2")
|
|
45
|
+
tencentDocsBanListSheetId: import_koishi.Schema.string().description("封禁记录工作表ID(表格URL中tab参数)").default("BB08J2"),
|
|
46
|
+
tencentDocsAdminWelfareFileId: import_koishi.Schema.string().description("管理员福利在线表格的文件ID(短ID或完整ID, A列QQ号/B列句柄)").default("DVGRhUUpXUVRJVVJs"),
|
|
47
|
+
tencentDocsAdminWelfareSheetId: import_koishi.Schema.string().description("管理员福利工作表ID(表格URL中tab参数)").default("BB08J2")
|
|
46
48
|
});
|
|
47
49
|
var inject = {
|
|
48
50
|
required: ["database"]
|
|
@@ -213,6 +215,15 @@ function apply(ctx, config) {
|
|
|
213
215
|
primary: "id",
|
|
214
216
|
autoInc: true
|
|
215
217
|
});
|
|
218
|
+
ctx.model.extend("ggcevo_admin_welfare", {
|
|
219
|
+
id: "unsigned",
|
|
220
|
+
qq: "string",
|
|
221
|
+
handle: "string",
|
|
222
|
+
update_time: "timestamp"
|
|
223
|
+
}, {
|
|
224
|
+
primary: "id",
|
|
225
|
+
autoInc: true
|
|
226
|
+
});
|
|
216
227
|
if (config.mapMonitorEnabled && config.mapMonitorGroups.length > 0 && config.mapMonitorMapIds.length > 0) {
|
|
217
228
|
ctx.setInterval(async () => {
|
|
218
229
|
try {
|
|
@@ -562,9 +573,13 @@ ${message}
|
|
|
562
573
|
}
|
|
563
574
|
lastAllowanceMonth = summary.last_allowance_month || 0;
|
|
564
575
|
}
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
576
|
+
const qq = String(session.userId || "");
|
|
577
|
+
const normalizedHandle = normalizeHandle(handle);
|
|
578
|
+
const welfareRecords = await ctx.database.get("ggcevo_admin_welfare", {});
|
|
579
|
+
const isWelfareMember = welfareRecords.some(
|
|
580
|
+
(r) => String(r.qq || "").trim() === qq.trim() && r.handle && normalizeHandle(r.handle) === normalizedHandle
|
|
581
|
+
);
|
|
582
|
+
if (isWelfareMember && lastAllowanceMonth !== currentMonth) {
|
|
568
583
|
monthlyAllowance = 50;
|
|
569
584
|
gugubReward += monthlyAllowance;
|
|
570
585
|
lastAllowanceMonth = currentMonth;
|
|
@@ -2045,7 +2060,7 @@ ${message}
|
|
|
2045
2060
|
if (method === "PUT") return ctx.http.put(url, form, { headers });
|
|
2046
2061
|
return ctx.http.post(url, form, { headers });
|
|
2047
2062
|
}, "docsApiRequest");
|
|
2048
|
-
ctx.command("
|
|
2063
|
+
ctx.command("腾讯文档/授权状态", "查看腾讯文档授权状态").action(async () => {
|
|
2049
2064
|
if (!isDocsConfigured()) return "❌ 腾讯文档功能未启用或配置不完整。";
|
|
2050
2065
|
const token = await getValidDocsToken();
|
|
2051
2066
|
if (!token) {
|
|
@@ -2075,6 +2090,13 @@ ${message}
|
|
|
2075
2090
|
}
|
|
2076
2091
|
if (v.link) return v.link.text || v.link.url || "";
|
|
2077
2092
|
if (v.location) return v.location.name || "";
|
|
2093
|
+
if (v.select) {
|
|
2094
|
+
const sel = v.select;
|
|
2095
|
+
const ids = Array.isArray(sel.value) ? sel.value.map(String) : [];
|
|
2096
|
+
const opts = Array.isArray(sel.options) ? sel.options : [];
|
|
2097
|
+
const texts = ids.map((id) => opts.find((o) => String(o.id) === String(id))?.text || "");
|
|
2098
|
+
return texts.filter(Boolean).join("、");
|
|
2099
|
+
}
|
|
2078
2100
|
return "";
|
|
2079
2101
|
}, "extractCellText");
|
|
2080
2102
|
const normalizeHandle = /* @__PURE__ */ __name((raw) => {
|
|
@@ -2157,6 +2179,78 @@ ${message}
|
|
|
2157
2179
|
);
|
|
2158
2180
|
return records.length;
|
|
2159
2181
|
}, "syncBanRecords");
|
|
2182
|
+
const fetchAdminWelfareFromDocs = /* @__PURE__ */ __name(async () => {
|
|
2183
|
+
const fileId = config.tencentDocsAdminWelfareFileId;
|
|
2184
|
+
const sheetId = config.tencentDocsAdminWelfareSheetId;
|
|
2185
|
+
const meta = await docsApiRequest("GET", `/spreadsheet/v3/files/${fileId}`, { concise: 1 });
|
|
2186
|
+
const metaOk = meta?.code === 0 || meta?.ret === 0;
|
|
2187
|
+
if (metaOk === false && meta?.code != null) {
|
|
2188
|
+
throw new Error(`查询工作表信息失败: ${meta.message || "未知错误"}(code=${meta.code})`);
|
|
2189
|
+
}
|
|
2190
|
+
const props = meta?.data?.properties || meta?.properties || [];
|
|
2191
|
+
const sheetInfo = props.find((s) => s.sheetId === sheetId);
|
|
2192
|
+
const rowTotal = Number(sheetInfo?.rowTotal) || 0;
|
|
2193
|
+
if (rowTotal <= 1) return [];
|
|
2194
|
+
const allRows = [];
|
|
2195
|
+
const batchSize = 1e3;
|
|
2196
|
+
for (let start = 2; start <= rowTotal; start += batchSize) {
|
|
2197
|
+
const end = Math.min(start + batchSize - 1, rowTotal);
|
|
2198
|
+
const resp = await docsApiRequest("GET", `/spreadsheet/v3/files/${fileId}/${sheetId}/A${start}:B${end}`);
|
|
2199
|
+
const respOk = resp?.code === 0 || resp?.ret === 0;
|
|
2200
|
+
if (respOk === false && resp?.code != null) {
|
|
2201
|
+
throw new Error(`查询范围 A${start}:B${end} 失败: ${resp.message || "未知错误"}(code=${resp.code})`);
|
|
2202
|
+
}
|
|
2203
|
+
const rows = resp?.data?.gridData?.rows || resp?.gridData?.rows || [];
|
|
2204
|
+
for (const row of rows) {
|
|
2205
|
+
const cells = row.values || [];
|
|
2206
|
+
allRows.push([
|
|
2207
|
+
extractCellText(cells[0]),
|
|
2208
|
+
// A: QQ号
|
|
2209
|
+
extractCellText(cells[1])
|
|
2210
|
+
// B: 游戏句柄
|
|
2211
|
+
]);
|
|
2212
|
+
}
|
|
2213
|
+
}
|
|
2214
|
+
return allRows;
|
|
2215
|
+
}, "fetchAdminWelfareFromDocs");
|
|
2216
|
+
const syncAdminWelfare = /* @__PURE__ */ __name(async () => {
|
|
2217
|
+
const rows = await fetchAdminWelfareFromDocs();
|
|
2218
|
+
const now = /* @__PURE__ */ new Date();
|
|
2219
|
+
const records = rows.map((r, i) => ({
|
|
2220
|
+
id: i + 1,
|
|
2221
|
+
// 自增 id, 对应文档行号 (id=1 → 文档第2行)
|
|
2222
|
+
qq: r[0] || "",
|
|
2223
|
+
handle: r[1] || "",
|
|
2224
|
+
update_time: now
|
|
2225
|
+
}));
|
|
2226
|
+
const oldRecords = await ctx.database.get("ggcevo_admin_welfare", {}, { fields: ["id"] });
|
|
2227
|
+
const oldCount = oldRecords.length;
|
|
2228
|
+
await ctx.database.remove("ggcevo_admin_welfare", {});
|
|
2229
|
+
if (records.length > 0) {
|
|
2230
|
+
await ctx.database.upsert("ggcevo_admin_welfare", records);
|
|
2231
|
+
}
|
|
2232
|
+
const diff = records.length - oldCount;
|
|
2233
|
+
ctx.logger("ggcevo").info(
|
|
2234
|
+
"管理员福利全量同步完成: 旧 %d 条 → 新 %d 条 (%s%d)",
|
|
2235
|
+
oldCount,
|
|
2236
|
+
records.length,
|
|
2237
|
+
diff >= 0 ? "+" : "",
|
|
2238
|
+
diff
|
|
2239
|
+
);
|
|
2240
|
+
return records.length;
|
|
2241
|
+
}, "syncAdminWelfare");
|
|
2242
|
+
if (isDocsConfigured() && config.tencentDocsAdminWelfareFileId && config.tencentDocsAdminWelfareSheetId) {
|
|
2243
|
+
const syncTask = /* @__PURE__ */ __name(async () => {
|
|
2244
|
+
try {
|
|
2245
|
+
const count = await syncAdminWelfare();
|
|
2246
|
+
ctx.logger("ggcevo").info("管理员福利记录同步完成, 共 %d 条", count);
|
|
2247
|
+
} catch (e) {
|
|
2248
|
+
ctx.logger("ggcevo").warn("管理员福利记录定时同步失败: %o", e);
|
|
2249
|
+
}
|
|
2250
|
+
}, "syncTask");
|
|
2251
|
+
ctx.setTimeout(syncTask, 5e3);
|
|
2252
|
+
ctx.setInterval(syncTask, 60 * 60 * 1e3);
|
|
2253
|
+
}
|
|
2160
2254
|
if (isDocsConfigured() && config.tencentDocsBanListFileId && config.tencentDocsBanListSheetId) {
|
|
2161
2255
|
const syncTask = /* @__PURE__ */ __name(async () => {
|
|
2162
2256
|
try {
|
|
@@ -2179,6 +2273,16 @@ ${message}
|
|
|
2179
2273
|
return `❌ 同步失败: ${e?.message || e}`;
|
|
2180
2274
|
}
|
|
2181
2275
|
});
|
|
2276
|
+
ctx.command("ggcevo/同步管理员福利", { authority: 3 }).action(async () => {
|
|
2277
|
+
if (!isDocsConfigured()) return "❌ 腾讯文档功能未启用或配置不完整。";
|
|
2278
|
+
try {
|
|
2279
|
+
const count = await syncAdminWelfare();
|
|
2280
|
+
return `✅ 管理员福利同步完成, 共 ${count} 条。`;
|
|
2281
|
+
} catch (e) {
|
|
2282
|
+
ctx.logger("ggcevo").warn("手动同步管理员福利失败: %o", e);
|
|
2283
|
+
return `❌ 同步失败: ${e?.message || e}`;
|
|
2284
|
+
}
|
|
2285
|
+
});
|
|
2182
2286
|
ctx.command("ggcevo/封禁记录").action(async (argv) => {
|
|
2183
2287
|
if (!isDocsConfigured()) return "❌ 腾讯文档功能未启用或配置不完整。";
|
|
2184
2288
|
const session = argv.session;
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -41,6 +41,8 @@ npm install koishi-plugin-ggcevo
|
|
|
41
41
|
| `tencentDocsOpenId` | `string` | `''` | Open ID(**用户级模式必填**,与 Access Token 同时获取) |
|
|
42
42
|
| `tencentDocsBanListFileId` | `string` | `DTVdYZVBDdFhEUkp6` | 封禁记录在线表格文件ID(短ID或完整ID) |
|
|
43
43
|
| `tencentDocsBanListSheetId` | `string` | `BB08J2` | 封禁记录工作表ID(表格URL中 `tab` 参数) |
|
|
44
|
+
| `tencentDocsAdminWelfareFileId` | `string` | `DVGRhUUpXUVRJVVJs` | 管理员福利在线表格文件ID(短ID或完整ID,A列QQ号/B列句柄) |
|
|
45
|
+
| `tencentDocsAdminWelfareSheetId` | `string` | `BB08J2` | 管理员福利工作表ID(表格URL中 `tab` 参数) |
|
|
44
46
|
|
|
45
47
|
**授权模式说明:**
|
|
46
48
|
- **用户级模式**(推荐用于无 `Client Secret` 的场景):填写 `tencentDocsClientId` + `tencentDocsAccessToken` + `tencentDocsOpenId`,三者均通过腾讯文档开放平台扫码授权流程获取。Token 过期后需重新获取并在控制台更新。
|
|
@@ -63,7 +65,7 @@ npm install koishi-plugin-ggcevo
|
|
|
63
65
|
|
|
64
66
|
| 指令 | 别名 | 权限 | 说明 |
|
|
65
67
|
| --- | --- | --- | --- |
|
|
66
|
-
| `签到` | — | — |
|
|
68
|
+
| `签到` | — | — | 每日签到,获取奖励并发放签到券/补签券等。每月首次签到时,若签到者QQ号与句柄同时匹配管理员福利文档记录,额外发放 50 咕咕币津贴 |
|
|
67
69
|
| `兑换 <name>` | — | — | 使用兑换券兑换指定物品。消耗兑换券数量随物品品质而定(T3皮肤=3、T2=4、T1=5、T3宠物=3、T2=4、T1=5、T0宠物=6、入场特效=5、角色冠名权=10、赎罪券=2) |
|
|
68
70
|
| `抽奖` | — | — | 抽奖。选项:`-p <poolId>` 奖池ID,`-c <count>` 抽奖次数。各池消耗:金币池=100金币/次、普通池=1咕咕币/次、皮肤池/宠物池=3兑换券/次 |
|
|
69
71
|
| `背包` | — | — | 查看自己的物品背包 |
|
|
@@ -79,6 +81,7 @@ npm install koishi-plugin-ggcevo
|
|
|
79
81
|
| `使用 <name>` | — | — | 使用指定物品(如赎罪券等) |
|
|
80
82
|
| `封禁记录` | — | — | 查询当前绑定句柄在腾讯文档封禁记录表中的全部记录,每页显示 1 条,支持翻页(回复"下一页/上一页/页码/退出") |
|
|
81
83
|
| `同步封禁记录` | — | 3 | 立即从腾讯文档拉取全量数据并写入数据库(管理员指令) |
|
|
84
|
+
| `同步管理员福利` | — | 3 | 立即从腾讯文档拉取管理员福利表(A列QQ号/B列句柄)全量数据并写入数据库(管理员指令) |
|
|
82
85
|
|
|
83
86
|
### 腾讯文档
|
|
84
87
|
|
|
@@ -123,9 +126,37 @@ npm install koishi-plugin-ggcevo
|
|
|
123
126
|
- `ggcevo_activity_claim_log` — 活动领取记录(主键 `activity_id` + `user_id`)
|
|
124
127
|
- `ggcevo_docs_token` — 腾讯文档令牌缓存(应用级模式使用,主键 `user_id`,应用级账号固定为 `app_account`)
|
|
125
128
|
- `ggcevo_ban_record` — 封禁记录(主键 `id` 自增,对应文档行号 `id+1`,每小时全量同步)
|
|
129
|
+
- `ggcevo_admin_welfare` — 管理员福利(主键 `id` 自增,对应文档行号 `id+1`,每小时全量同步;A列QQ号/B列句柄)
|
|
126
130
|
|
|
127
131
|
## 更新日志
|
|
128
132
|
|
|
133
|
+
### v1.0.8
|
|
134
|
+
|
|
135
|
+
修复腾讯文档单选项下拉框单元格读取为空的问题。
|
|
136
|
+
|
|
137
|
+
- **修复**:`extractCellText` 新增 `select` 数据类型处理。腾讯文档 V3 API 中,单选/多选下拉框单元格的值不在 `cellValue.text`,而在 `cellValue.select`,其中 `select.value` 存储的是已选项 ID(非文本),需在 `select.options` 中按 `id` 反查才能得到可读文本
|
|
138
|
+
- 影响字段:封禁记录表 C 列「封禁等级」(单选下拉框,选项 A/B/C/D/仅记录)。修复前同步入库为空字符串,修复后可正确读取选项文本
|
|
139
|
+
- 多选下拉框同样兼容,多个已选项以 `、` 连接
|
|
140
|
+
|
|
141
|
+
### v1.0.7
|
|
142
|
+
|
|
143
|
+
新增**管理员福利**功能,签到每月津贴改为基于管理员福利文档数据库匹配。
|
|
144
|
+
|
|
145
|
+
**新增功能:**
|
|
146
|
+
- **管理员福利同步**:每小时自动从腾讯文档在线表格全量拉取管理员福利数据写入数据库(启动后延迟 5 秒首次同步),采用全量替换策略保证 `id` 与文档行号严格对应
|
|
147
|
+
- **手动同步**:`同步管理员福利` 指令(管理员权限 3)立即触发同步
|
|
148
|
+
- **签到每月津贴改写**:原通过 QQ 群管理员身份判断发放 50 咕咕币津贴,现改为从管理员福利数据库读取——签到者 **QQ号与游戏句柄必须同时匹配** 文档记录,且每月首次签到才发放 50 咕咕币津贴
|
|
149
|
+
|
|
150
|
+
**新增配置项:**
|
|
151
|
+
- `tencentDocsAdminWelfareFileId`(默认 `DVGRhUUpXUVRJVVJs`)
|
|
152
|
+
- `tencentDocsAdminWelfareSheetId`(默认 `BB08J2`)
|
|
153
|
+
|
|
154
|
+
**新增数据库表:**
|
|
155
|
+
- `ggcevo_admin_welfare` — 管理员福利(主键自增 `id`,对应文档行号 `id+1`;字段:`qq`、`handle`、`update_time`)
|
|
156
|
+
|
|
157
|
+
**表格结构要求:**
|
|
158
|
+
- A列:QQ号 / B列:游戏句柄
|
|
159
|
+
|
|
129
160
|
### v1.0.6
|
|
130
161
|
|
|
131
162
|
修复封禁记录同步时长文本写入失败的问题。
|