koishi-plugin-ggcevo 1.0.6 → 1.0.9
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 +170 -37
- package/package.json +2 -2
- package/readme.md +44 -4
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 {
|
|
@@ -523,7 +534,8 @@ ${message}
|
|
|
523
534
|
const session = argv.session;
|
|
524
535
|
const handle = await getHandle(session);
|
|
525
536
|
if (!handle) {
|
|
526
|
-
return "
|
|
537
|
+
return `<quote id="${session.messageId}"/>🔒 需要先绑定游戏句柄。
|
|
538
|
+
💡 使用 \`绑定句柄\` 命令进行绑定。`;
|
|
527
539
|
}
|
|
528
540
|
const now = /* @__PURE__ */ new Date();
|
|
529
541
|
const today = new Date(now.getFullYear(), now.getMonth(), now.getDate());
|
|
@@ -533,7 +545,7 @@ ${message}
|
|
|
533
545
|
signin_date: { $gte: today }
|
|
534
546
|
});
|
|
535
547
|
if (existingLog) {
|
|
536
|
-
return "
|
|
548
|
+
return `<quote id="${session.messageId}"/>📅 今日已签到,明天再来吧!`;
|
|
537
549
|
}
|
|
538
550
|
const goldReward = Math.floor(Math.random() * 11) + 10;
|
|
539
551
|
let gugubReward = 3;
|
|
@@ -562,9 +574,13 @@ ${message}
|
|
|
562
574
|
}
|
|
563
575
|
lastAllowanceMonth = summary.last_allowance_month || 0;
|
|
564
576
|
}
|
|
565
|
-
const
|
|
566
|
-
const
|
|
567
|
-
|
|
577
|
+
const qq = String(session.userId || "");
|
|
578
|
+
const normalizedHandle = normalizeHandle(handle);
|
|
579
|
+
const welfareRecords = await ctx.database.get("ggcevo_admin_welfare", {});
|
|
580
|
+
const isWelfareMember = welfareRecords.some(
|
|
581
|
+
(r) => String(r.qq || "").trim() === qq.trim() && r.handle && normalizeHandle(r.handle) === normalizedHandle
|
|
582
|
+
);
|
|
583
|
+
if (isWelfareMember && lastAllowanceMonth !== currentMonth) {
|
|
568
584
|
monthlyAllowance = 50;
|
|
569
585
|
gugubReward += monthlyAllowance;
|
|
570
586
|
lastAllowanceMonth = currentMonth;
|
|
@@ -628,7 +644,7 @@ ${message}
|
|
|
628
644
|
message += `
|
|
629
645
|
💰 每月津贴:+${monthlyAllowance} 咕咕币`;
|
|
630
646
|
}
|
|
631
|
-
return message
|
|
647
|
+
return `<quote id="${session.messageId}"/>${message}`;
|
|
632
648
|
});
|
|
633
649
|
ctx.command("ggcevo/兑换 <name:string>").action(async (argv, name2) => {
|
|
634
650
|
const session = argv.session;
|
|
@@ -1203,11 +1219,12 @@ ${message}
|
|
|
1203
1219
|
const session = argv.session;
|
|
1204
1220
|
const handle = await getHandle(session);
|
|
1205
1221
|
if (!handle) {
|
|
1206
|
-
return "
|
|
1222
|
+
return `<quote id="${session.messageId}"/>🔒 需要先绑定游戏句柄。
|
|
1223
|
+
💡 使用 \`绑定句柄\` 命令进行绑定。`;
|
|
1207
1224
|
}
|
|
1208
1225
|
const backpackItems = await ctx.database.get("ggcevo_backpack", { user_id: handle });
|
|
1209
1226
|
if (backpackItems.length === 0) {
|
|
1210
|
-
return "
|
|
1227
|
+
return `<quote id="${session.messageId}"/>🎒 背包是空的,快去签到或抽奖获得物品吧!`;
|
|
1211
1228
|
}
|
|
1212
1229
|
let message = `🎒 ${session.username}的背包物品:
|
|
1213
1230
|
`;
|
|
@@ -1217,13 +1234,14 @@ ${message}
|
|
|
1217
1234
|
message += `${itemName} x${item.count}
|
|
1218
1235
|
`;
|
|
1219
1236
|
}
|
|
1220
|
-
return message
|
|
1237
|
+
return `<quote id="${session.messageId}"/>${message}`;
|
|
1221
1238
|
});
|
|
1222
1239
|
ctx.command("ggcevo/个人信息").action(async (argv) => {
|
|
1223
1240
|
const session = argv.session;
|
|
1224
1241
|
const handle = await getHandle(session);
|
|
1225
1242
|
if (!handle) {
|
|
1226
|
-
return "
|
|
1243
|
+
return `<quote id="${session.messageId}"/>🔒 需要先绑定游戏句柄。
|
|
1244
|
+
💡 使用 \`绑定句柄\` 命令进行绑定。`;
|
|
1227
1245
|
}
|
|
1228
1246
|
const username = session.username || "未知用户";
|
|
1229
1247
|
let message = `👤 个人信息
|
|
@@ -1320,7 +1338,7 @@ ${message}
|
|
|
1320
1338
|
`;
|
|
1321
1339
|
}
|
|
1322
1340
|
}
|
|
1323
|
-
return message
|
|
1341
|
+
return `<quote id="${session.messageId}"/>${message}`;
|
|
1324
1342
|
});
|
|
1325
1343
|
ctx.command("ggcevo/给予 <targetId> <itemId> <count:number>", { authority: 3 }).action(async (argv, targetId, itemId, count) => {
|
|
1326
1344
|
const session = argv.session;
|
|
@@ -1588,13 +1606,9 @@ ${message}
|
|
|
1588
1606
|
}
|
|
1589
1607
|
message += `─────────────
|
|
1590
1608
|
`;
|
|
1591
|
-
message += `⚠️
|
|
1592
|
-
`;
|
|
1593
|
-
message += `⚠️ 兑换功能仅限工作日使用(周一至周五)
|
|
1609
|
+
message += `⚠️ 兑换功能仅限工作日使用(周一至周五兑换,周六/周日实装进咕咕虫)
|
|
1594
1610
|
`;
|
|
1595
1611
|
message += `⚠️ 兑换完成后请找活动管理员登记
|
|
1596
|
-
`;
|
|
1597
|
-
message += `⚠️ 兑换实装进咕咕虫将在休息日完成(周六/周日)
|
|
1598
1612
|
`;
|
|
1599
1613
|
return message;
|
|
1600
1614
|
});
|
|
@@ -1676,39 +1690,40 @@ ${message}
|
|
|
1676
1690
|
const session = argv.session;
|
|
1677
1691
|
const handle = await getHandle(session);
|
|
1678
1692
|
if (!handle) {
|
|
1679
|
-
return "
|
|
1693
|
+
return `<quote id="${session.messageId}"/>🔒 需要先绑定游戏句柄。
|
|
1694
|
+
💡 使用 \`绑定句柄\` 命令进行绑定。`;
|
|
1680
1695
|
}
|
|
1681
1696
|
if (activityId === void 0 || activityId === null) {
|
|
1682
|
-
return
|
|
1697
|
+
return `<quote id="${session.messageId}"/>❌ 请输入活动ID!
|
|
1683
1698
|
使用 活动列表 查询可领取的活动`;
|
|
1684
1699
|
}
|
|
1685
1700
|
const [activity] = await ctx.database.get("ggcevo_activity", { id: activityId });
|
|
1686
1701
|
if (!activity) {
|
|
1687
|
-
return
|
|
1702
|
+
return `<quote id="${session.messageId}"/>❌ 不存在ID为 ${activityId} 的活动!`;
|
|
1688
1703
|
}
|
|
1689
1704
|
const now = /* @__PURE__ */ new Date();
|
|
1690
1705
|
const startTime = new Date(activity.start_time);
|
|
1691
1706
|
const endTime = new Date(activity.end_time);
|
|
1692
1707
|
if (now < startTime) {
|
|
1693
|
-
return
|
|
1708
|
+
return `<quote id="${session.messageId}"/>⏰ 活动尚未开始,开始时间:${startTime.toLocaleString("zh-CN")}`;
|
|
1694
1709
|
}
|
|
1695
1710
|
if (now > endTime) {
|
|
1696
|
-
return
|
|
1711
|
+
return `<quote id="${session.messageId}"/>⏱️ 活动已结束,结束时间:${endTime.toLocaleString("zh-CN")}`;
|
|
1697
1712
|
}
|
|
1698
1713
|
if (activity.required_group && activity.required_group !== "") {
|
|
1699
1714
|
const currentChannelId = session.channelId || session.event?.channel?.id;
|
|
1700
1715
|
if (!currentChannelId) {
|
|
1701
|
-
return
|
|
1716
|
+
return `<quote id="${session.messageId}"/>❌ 无法获取当前群聊信息!`;
|
|
1702
1717
|
}
|
|
1703
1718
|
if (currentChannelId !== activity.required_group) {
|
|
1704
|
-
return
|
|
1719
|
+
return `<quote id="${session.messageId}"/>❌ 该活动仅限在指定群聊内领取!
|
|
1705
1720
|
请前往群聊ID: ${activity.required_group} 领取`;
|
|
1706
1721
|
}
|
|
1707
1722
|
}
|
|
1708
1723
|
if (activity.max_claims > 0) {
|
|
1709
1724
|
const totalClaims = await ctx.database.get("ggcevo_activity_claim_log", { activity_id: activityId });
|
|
1710
1725
|
if (totalClaims.length >= activity.max_claims) {
|
|
1711
|
-
return
|
|
1726
|
+
return `<quote id="${session.messageId}"/>❌ 该活动领取次数已用尽!`;
|
|
1712
1727
|
}
|
|
1713
1728
|
}
|
|
1714
1729
|
const [existingClaim] = await ctx.database.get("ggcevo_activity_claim_log", {
|
|
@@ -1716,7 +1731,7 @@ ${message}
|
|
|
1716
1731
|
user_id: handle
|
|
1717
1732
|
});
|
|
1718
1733
|
if (existingClaim) {
|
|
1719
|
-
return
|
|
1734
|
+
return `<quote id="${session.messageId}"/>❌ 您已经领取过该活动了!`;
|
|
1720
1735
|
}
|
|
1721
1736
|
await ctx.database.create("ggcevo_activity_claim_log", {
|
|
1722
1737
|
activity_id: activityId,
|
|
@@ -1742,22 +1757,32 @@ ${message}
|
|
|
1742
1757
|
}
|
|
1743
1758
|
}, "updateBackpackItem");
|
|
1744
1759
|
await updateBackpackItem(activity.reward_item, activity.reward_amount);
|
|
1745
|
-
return
|
|
1760
|
+
return `<quote id="${session.messageId}"/>🎉 领取成功!
|
|
1746
1761
|
─────────────
|
|
1747
1762
|
📛 活动:${activity.name}
|
|
1748
1763
|
🎁 获得:${rewardItemName} x${activity.reward_amount}`;
|
|
1749
1764
|
});
|
|
1750
|
-
ctx.command("ggcevo/活动列表").action(async () => {
|
|
1765
|
+
ctx.command("ggcevo/活动列表 [showAll]").action(async (argv, showAll) => {
|
|
1751
1766
|
const activities = await ctx.database.get("ggcevo_activity", {});
|
|
1752
1767
|
if (activities.length === 0) {
|
|
1753
1768
|
return `📋 暂无活动`;
|
|
1754
1769
|
}
|
|
1755
1770
|
const now = /* @__PURE__ */ new Date();
|
|
1771
|
+
const showExpired = !!showAll;
|
|
1772
|
+
const visibleActivities = activities.filter((activity) => {
|
|
1773
|
+
const endTime = new Date(activity.end_time);
|
|
1774
|
+
if (now > endTime && !showExpired) return false;
|
|
1775
|
+
return true;
|
|
1776
|
+
});
|
|
1777
|
+
if (visibleActivities.length === 0) {
|
|
1778
|
+
return `📋 暂无进行中或未开始的活动
|
|
1779
|
+
💡 输入 "活动列表 全部" 可查看包含已过期在内的全部活动`;
|
|
1780
|
+
}
|
|
1756
1781
|
let message = `📋 活动列表
|
|
1757
1782
|
`;
|
|
1758
1783
|
message += `─────────────
|
|
1759
1784
|
`;
|
|
1760
|
-
for (const activity of
|
|
1785
|
+
for (const activity of visibleActivities) {
|
|
1761
1786
|
const startTime = new Date(activity.start_time);
|
|
1762
1787
|
const endTime = new Date(activity.end_time);
|
|
1763
1788
|
const rewardItemName = ItemConfig[activity.reward_item] || "未知物品";
|
|
@@ -2045,7 +2070,7 @@ ${message}
|
|
|
2045
2070
|
if (method === "PUT") return ctx.http.put(url, form, { headers });
|
|
2046
2071
|
return ctx.http.post(url, form, { headers });
|
|
2047
2072
|
}, "docsApiRequest");
|
|
2048
|
-
ctx.command("
|
|
2073
|
+
ctx.command("腾讯文档/授权状态", "查看腾讯文档授权状态").action(async () => {
|
|
2049
2074
|
if (!isDocsConfigured()) return "❌ 腾讯文档功能未启用或配置不完整。";
|
|
2050
2075
|
const token = await getValidDocsToken();
|
|
2051
2076
|
if (!token) {
|
|
@@ -2075,6 +2100,13 @@ ${message}
|
|
|
2075
2100
|
}
|
|
2076
2101
|
if (v.link) return v.link.text || v.link.url || "";
|
|
2077
2102
|
if (v.location) return v.location.name || "";
|
|
2103
|
+
if (v.select) {
|
|
2104
|
+
const sel = v.select;
|
|
2105
|
+
const ids = Array.isArray(sel.value) ? sel.value.map(String) : [];
|
|
2106
|
+
const opts = Array.isArray(sel.options) ? sel.options : [];
|
|
2107
|
+
const texts = ids.map((id) => opts.find((o) => String(o.id) === String(id))?.text || "");
|
|
2108
|
+
return texts.filter(Boolean).join("、");
|
|
2109
|
+
}
|
|
2078
2110
|
return "";
|
|
2079
2111
|
}, "extractCellText");
|
|
2080
2112
|
const normalizeHandle = /* @__PURE__ */ __name((raw) => {
|
|
@@ -2157,6 +2189,78 @@ ${message}
|
|
|
2157
2189
|
);
|
|
2158
2190
|
return records.length;
|
|
2159
2191
|
}, "syncBanRecords");
|
|
2192
|
+
const fetchAdminWelfareFromDocs = /* @__PURE__ */ __name(async () => {
|
|
2193
|
+
const fileId = config.tencentDocsAdminWelfareFileId;
|
|
2194
|
+
const sheetId = config.tencentDocsAdminWelfareSheetId;
|
|
2195
|
+
const meta = await docsApiRequest("GET", `/spreadsheet/v3/files/${fileId}`, { concise: 1 });
|
|
2196
|
+
const metaOk = meta?.code === 0 || meta?.ret === 0;
|
|
2197
|
+
if (metaOk === false && meta?.code != null) {
|
|
2198
|
+
throw new Error(`查询工作表信息失败: ${meta.message || "未知错误"}(code=${meta.code})`);
|
|
2199
|
+
}
|
|
2200
|
+
const props = meta?.data?.properties || meta?.properties || [];
|
|
2201
|
+
const sheetInfo = props.find((s) => s.sheetId === sheetId);
|
|
2202
|
+
const rowTotal = Number(sheetInfo?.rowTotal) || 0;
|
|
2203
|
+
if (rowTotal <= 1) return [];
|
|
2204
|
+
const allRows = [];
|
|
2205
|
+
const batchSize = 1e3;
|
|
2206
|
+
for (let start = 2; start <= rowTotal; start += batchSize) {
|
|
2207
|
+
const end = Math.min(start + batchSize - 1, rowTotal);
|
|
2208
|
+
const resp = await docsApiRequest("GET", `/spreadsheet/v3/files/${fileId}/${sheetId}/A${start}:B${end}`);
|
|
2209
|
+
const respOk = resp?.code === 0 || resp?.ret === 0;
|
|
2210
|
+
if (respOk === false && resp?.code != null) {
|
|
2211
|
+
throw new Error(`查询范围 A${start}:B${end} 失败: ${resp.message || "未知错误"}(code=${resp.code})`);
|
|
2212
|
+
}
|
|
2213
|
+
const rows = resp?.data?.gridData?.rows || resp?.gridData?.rows || [];
|
|
2214
|
+
for (const row of rows) {
|
|
2215
|
+
const cells = row.values || [];
|
|
2216
|
+
allRows.push([
|
|
2217
|
+
extractCellText(cells[0]),
|
|
2218
|
+
// A: QQ号
|
|
2219
|
+
extractCellText(cells[1])
|
|
2220
|
+
// B: 游戏句柄
|
|
2221
|
+
]);
|
|
2222
|
+
}
|
|
2223
|
+
}
|
|
2224
|
+
return allRows;
|
|
2225
|
+
}, "fetchAdminWelfareFromDocs");
|
|
2226
|
+
const syncAdminWelfare = /* @__PURE__ */ __name(async () => {
|
|
2227
|
+
const rows = await fetchAdminWelfareFromDocs();
|
|
2228
|
+
const now = /* @__PURE__ */ new Date();
|
|
2229
|
+
const records = rows.map((r, i) => ({
|
|
2230
|
+
id: i + 1,
|
|
2231
|
+
// 自增 id, 对应文档行号 (id=1 → 文档第2行)
|
|
2232
|
+
qq: r[0] || "",
|
|
2233
|
+
handle: r[1] || "",
|
|
2234
|
+
update_time: now
|
|
2235
|
+
}));
|
|
2236
|
+
const oldRecords = await ctx.database.get("ggcevo_admin_welfare", {}, { fields: ["id"] });
|
|
2237
|
+
const oldCount = oldRecords.length;
|
|
2238
|
+
await ctx.database.remove("ggcevo_admin_welfare", {});
|
|
2239
|
+
if (records.length > 0) {
|
|
2240
|
+
await ctx.database.upsert("ggcevo_admin_welfare", records);
|
|
2241
|
+
}
|
|
2242
|
+
const diff = records.length - oldCount;
|
|
2243
|
+
ctx.logger("ggcevo").info(
|
|
2244
|
+
"管理员福利全量同步完成: 旧 %d 条 → 新 %d 条 (%s%d)",
|
|
2245
|
+
oldCount,
|
|
2246
|
+
records.length,
|
|
2247
|
+
diff >= 0 ? "+" : "",
|
|
2248
|
+
diff
|
|
2249
|
+
);
|
|
2250
|
+
return records.length;
|
|
2251
|
+
}, "syncAdminWelfare");
|
|
2252
|
+
if (isDocsConfigured() && config.tencentDocsAdminWelfareFileId && config.tencentDocsAdminWelfareSheetId) {
|
|
2253
|
+
const syncTask = /* @__PURE__ */ __name(async () => {
|
|
2254
|
+
try {
|
|
2255
|
+
const count = await syncAdminWelfare();
|
|
2256
|
+
ctx.logger("ggcevo").info("管理员福利记录同步完成, 共 %d 条", count);
|
|
2257
|
+
} catch (e) {
|
|
2258
|
+
ctx.logger("ggcevo").warn("管理员福利记录定时同步失败: %o", e);
|
|
2259
|
+
}
|
|
2260
|
+
}, "syncTask");
|
|
2261
|
+
ctx.setTimeout(syncTask, 5e3);
|
|
2262
|
+
ctx.setInterval(syncTask, 60 * 60 * 1e3);
|
|
2263
|
+
}
|
|
2160
2264
|
if (isDocsConfigured() && config.tencentDocsBanListFileId && config.tencentDocsBanListSheetId) {
|
|
2161
2265
|
const syncTask = /* @__PURE__ */ __name(async () => {
|
|
2162
2266
|
try {
|
|
@@ -2179,31 +2283,60 @@ ${message}
|
|
|
2179
2283
|
return `❌ 同步失败: ${e?.message || e}`;
|
|
2180
2284
|
}
|
|
2181
2285
|
});
|
|
2182
|
-
ctx.command("ggcevo
|
|
2286
|
+
ctx.command("ggcevo/同步管理员福利", { authority: 3 }).action(async () => {
|
|
2287
|
+
if (!isDocsConfigured()) return "❌ 腾讯文档功能未启用或配置不完整。";
|
|
2288
|
+
try {
|
|
2289
|
+
const count = await syncAdminWelfare();
|
|
2290
|
+
return `✅ 管理员福利同步完成, 共 ${count} 条。`;
|
|
2291
|
+
} catch (e) {
|
|
2292
|
+
ctx.logger("ggcevo").warn("手动同步管理员福利失败: %o", e);
|
|
2293
|
+
return `❌ 同步失败: ${e?.message || e}`;
|
|
2294
|
+
}
|
|
2295
|
+
});
|
|
2296
|
+
ctx.command("ggcevo/封禁记录 [user]").usage("不带参数查询自己的封禁记录, 携带 @用户 可查询对方句柄的封禁记录").example("封禁记录, 查询自己的封禁记录\n 封禁记录 @用户, 查询对方句柄的封禁记录").action(async (argv, user) => {
|
|
2183
2297
|
if (!isDocsConfigured()) return "❌ 腾讯文档功能未启用或配置不完整。";
|
|
2184
2298
|
const session = argv.session;
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2299
|
+
let targetHandle;
|
|
2300
|
+
let isOther = false;
|
|
2301
|
+
if (!user) {
|
|
2302
|
+
targetHandle = await getHandle(session);
|
|
2303
|
+
} else {
|
|
2304
|
+
const parsedUser = import_koishi.h.parse(user)[0];
|
|
2305
|
+
if (!parsedUser || parsedUser.type !== "at" || !parsedUser.attrs.id) {
|
|
2306
|
+
return `❌ 参数错误, 请输入"封禁记录 @用户"查询对方的封禁记录。`;
|
|
2307
|
+
}
|
|
2308
|
+
const targetUserId = parsedUser.attrs.id;
|
|
2309
|
+
const [profile] = await ctx.database.get("sc2arcade_player", { userId: targetUserId, isActive: true });
|
|
2310
|
+
if (!profile) {
|
|
2311
|
+
return `❌ 对方暂未绑定游戏句柄。`;
|
|
2312
|
+
}
|
|
2313
|
+
targetHandle = `${profile.regionId}-S2-${profile.realmId}-${profile.profileId}`;
|
|
2314
|
+
isOther = true;
|
|
2315
|
+
}
|
|
2316
|
+
if (!targetHandle) {
|
|
2317
|
+
return `🔒 需要先绑定游戏句柄。
|
|
2318
|
+
💡 使用 \`绑定句柄\` 命令进行绑定。`;
|
|
2188
2319
|
}
|
|
2320
|
+
const handle = targetHandle;
|
|
2189
2321
|
const [lastSync] = await ctx.database.get("ggcevo_ban_record", {}, { limit: 1 });
|
|
2190
2322
|
if (!lastSync) {
|
|
2191
|
-
return
|
|
2323
|
+
return `⚠️ 封禁记录尚未同步, 请稍后再试或联系管理员。`;
|
|
2192
2324
|
}
|
|
2193
2325
|
const syncTime = lastSync.update_time;
|
|
2194
2326
|
const allRecords = await ctx.database.get("ggcevo_ban_record", {}, { sort: { id: "asc" } });
|
|
2195
2327
|
const target = normalizeHandle(handle);
|
|
2196
2328
|
const records = allRecords.filter((r) => r.handle && normalizeHandle(r.handle) === target);
|
|
2329
|
+
const ownerLabel = isOther ? "对方句柄" : "句柄";
|
|
2197
2330
|
if (records.length === 0) {
|
|
2198
2331
|
return [
|
|
2199
|
-
`✅
|
|
2332
|
+
`✅ ${ownerLabel} ${handle} 暂无封禁记录。`,
|
|
2200
2333
|
`📊 数据最近同步: ${toBeijingTime(syncTime.toISOString())}`
|
|
2201
2334
|
].join("\n");
|
|
2202
2335
|
}
|
|
2203
2336
|
const formatRecord = /* @__PURE__ */ __name((idx) => {
|
|
2204
2337
|
const r = records[idx];
|
|
2205
2338
|
return [
|
|
2206
|
-
`📋 封禁记录 (${idx + 1}/${records.length})`,
|
|
2339
|
+
`📋 ${ownerLabel}封禁记录 (${idx + 1}/${records.length})`,
|
|
2207
2340
|
`文档行号: ${r.id + 1} 行`,
|
|
2208
2341
|
// id 对应文档行号 (跳过表头)
|
|
2209
2342
|
`句柄: ${r.handle}`,
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "koishi-plugin-ggcevo",
|
|
3
3
|
"description": "GGCEVO 综合插件:星际争霸2游戏句柄绑定 + 签到/抽奖/兑换/背包/活动系统等虚拟物品玩法",
|
|
4
|
-
"version": "1.0.
|
|
4
|
+
"version": "1.0.9",
|
|
5
5
|
"main": "lib/index.js",
|
|
6
6
|
"typings": "lib/index.d.ts",
|
|
7
7
|
"files": [
|
|
@@ -30,4 +30,4 @@
|
|
|
30
30
|
"peerDependencies": {
|
|
31
31
|
"koishi": "^4.18.7"
|
|
32
32
|
}
|
|
33
|
-
}
|
|
33
|
+
}
|
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
|
| `背包` | — | — | 查看自己的物品背包 |
|
|
@@ -71,14 +73,15 @@ npm install koishi-plugin-ggcevo
|
|
|
71
73
|
| `给予 <targetId> <itemId> <count>` | — | 3 | 给予指定用户物品(管理指令) |
|
|
72
74
|
| `抽奖概率` | — | — | 查看各奖池的抽奖概率与保底说明 |
|
|
73
75
|
| `签到奖励` | — | — | 查看签到奖励规则说明 |
|
|
74
|
-
| `兑换列表` | 兑换表 | — |
|
|
76
|
+
| `兑换列表` | 兑换表 | — | 查看可兑换物品列表(含皮肤、宠物、入场特效、角色冠名权、赎罪券及各自兑换券消耗) |
|
|
75
77
|
| `创建活动` | — | 3 | 创建活动。选项:`-n 名称 -d 描述 -r 奖励物品ID -a 数量 -s 开始时间 -e 结束时间 -m 领取上限 -g 限制群聊ID` |
|
|
76
78
|
| `领取活动 [activityId]` | — | — | 领取指定(或最新)活动奖励 |
|
|
77
|
-
|
|
|
79
|
+
| `活动列表 [showAll]` | — | — | 查看活动列表。默认只显示进行中与未开始的活动,携带参数(如"全部")显示含已过期在内的全部活动 |
|
|
78
80
|
| `补签` | — | — | 使用补签券补签漏签的日期 |
|
|
79
81
|
| `使用 <name>` | — | — | 使用指定物品(如赎罪券等) |
|
|
80
|
-
|
|
|
82
|
+
| `封禁记录 [user]` | — | — | 查询封禁记录。不带参数查询自己,携带 @用户 查询对方句柄的封禁记录。每页显示 1 条,支持翻页(回复"下一页/上一页/页码/退出") |
|
|
81
83
|
| `同步封禁记录` | — | 3 | 立即从腾讯文档拉取全量数据并写入数据库(管理员指令) |
|
|
84
|
+
| `同步管理员福利` | — | 3 | 立即从腾讯文档拉取管理员福利表(A列QQ号/B列句柄)全量数据并写入数据库(管理员指令) |
|
|
82
85
|
|
|
83
86
|
### 腾讯文档
|
|
84
87
|
|
|
@@ -123,9 +126,46 @@ 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.9
|
|
134
|
+
|
|
135
|
+
优化指令交互体验与活动列表过滤。
|
|
136
|
+
|
|
137
|
+
- **活动列表**:新增可选参数 `[showAll]`,默认(不带参数)只显示进行中与未开始的活动,隐藏已过期活动;输入 `活动列表 全部` 可查看含已过期在内的全部活动
|
|
138
|
+
- **封禁记录**:新增可选参数 `[user]`,支持 `@用户` 查询对方句柄的封禁记录;无参数时查询自己当前使用中的句柄
|
|
139
|
+
- **引用消息**:为 `签到`、`背包`、`个人信息`、`领取活动` 指令的所有返回消息添加引用消息效果
|
|
140
|
+
- **兑换列表**:删除"限定物品不可兑换"提示,合并兑换开放时间与实装时间为一条提示
|
|
141
|
+
|
|
142
|
+
### v1.0.8
|
|
143
|
+
|
|
144
|
+
修复腾讯文档单选项下拉框单元格读取为空的问题。
|
|
145
|
+
|
|
146
|
+
- **修复**:`extractCellText` 新增 `select` 数据类型处理。腾讯文档 V3 API 中,单选/多选下拉框单元格的值不在 `cellValue.text`,而在 `cellValue.select`,其中 `select.value` 存储的是已选项 ID(非文本),需在 `select.options` 中按 `id` 反查才能得到可读文本
|
|
147
|
+
- 影响字段:封禁记录表 C 列「封禁等级」(单选下拉框,选项 A/B/C/D/仅记录)。修复前同步入库为空字符串,修复后可正确读取选项文本
|
|
148
|
+
- 多选下拉框同样兼容,多个已选项以 `、` 连接
|
|
149
|
+
|
|
150
|
+
### v1.0.7
|
|
151
|
+
|
|
152
|
+
新增**管理员福利**功能,签到每月津贴改为基于管理员福利文档数据库匹配。
|
|
153
|
+
|
|
154
|
+
**新增功能:**
|
|
155
|
+
- **管理员福利同步**:每小时自动从腾讯文档在线表格全量拉取管理员福利数据写入数据库(启动后延迟 5 秒首次同步),采用全量替换策略保证 `id` 与文档行号严格对应
|
|
156
|
+
- **手动同步**:`同步管理员福利` 指令(管理员权限 3)立即触发同步
|
|
157
|
+
- **签到每月津贴改写**:原通过 QQ 群管理员身份判断发放 50 咕咕币津贴,现改为从管理员福利数据库读取——签到者 **QQ号与游戏句柄必须同时匹配** 文档记录,且每月首次签到才发放 50 咕咕币津贴
|
|
158
|
+
|
|
159
|
+
**新增配置项:**
|
|
160
|
+
- `tencentDocsAdminWelfareFileId`(默认 `DVGRhUUpXUVRJVVJs`)
|
|
161
|
+
- `tencentDocsAdminWelfareSheetId`(默认 `BB08J2`)
|
|
162
|
+
|
|
163
|
+
**新增数据库表:**
|
|
164
|
+
- `ggcevo_admin_welfare` — 管理员福利(主键自增 `id`,对应文档行号 `id+1`;字段:`qq`、`handle`、`update_time`)
|
|
165
|
+
|
|
166
|
+
**表格结构要求:**
|
|
167
|
+
- A列:QQ号 / B列:游戏句柄
|
|
168
|
+
|
|
129
169
|
### v1.0.6
|
|
130
170
|
|
|
131
171
|
修复封禁记录同步时长文本写入失败的问题。
|