@karinjs/adapter-qqbot 3.0.0 → 3.0.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/dist/apps/cleanup.js +1 -1
- package/dist/apps/login.d.ts +2 -16
- package/dist/apps/login.js +138 -85
- package/dist/{config-BUuU6FnQ.js → config-D1_LqrcJ.js} +2 -99
- package/dist/index.d.ts +10 -29
- package/dist/index.js +102 -115
- package/dist/logger-sza6kuy3.js +27 -0
- package/dist/{message-BTGL3TSn.js → message-CVJVFAGR.js} +2 -1
- package/dist/utils-lb9ZdWay.js +32 -0
- package/dist/web.config.d.ts +0 -5
- package/dist/web.config.js +4 -87
- package/package.json +3 -2
- package/dist/common-by1GpV0b.js +0 -49
- package/dist/onboard-vIEoe9dB.js +0 -252
- package/dist/utils-N9PNiHjE.js +0 -4
package/dist/index.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import "./utils-
|
|
4
|
-
import { t as getMessageStore } from "./message-
|
|
5
|
-
import {
|
|
6
|
-
import
|
|
1
|
+
import { l as basename, n as config, r as getConfig, s as pkg, t as bindHandlers } from "./config-D1_LqrcJ.js";
|
|
2
|
+
import { t as log } from "./logger-sza6kuy3.js";
|
|
3
|
+
import { i as random, n as fakeEvent, r as getImageSize, t as QQAPIBASEURL } from "./utils-lb9ZdWay.js";
|
|
4
|
+
import { t as getMessageStore } from "./message-CVJVFAGR.js";
|
|
5
|
+
import karin, { AdapterBase, app, buttonHandle, common, createDirectMessage, createFriendDecreaseNotice, createFriendIncreaseNotice, createFriendMessage, createGroupApplyRequest, createGroupInviteRequest, createGroupMemberAddNotice, createGroupMemberDelNotice, createGroupMessage, createGuildMessage, fileToUrl, fileToUrlHandlerKey, handler, karin as karin$1, karinToQQBot, logger, registerBot, segment, senderGroup, unregisterBot } from "node-karin";
|
|
6
|
+
import path from "path";
|
|
7
7
|
import { URL } from "url";
|
|
8
8
|
import lodash from "node-karin/lodash";
|
|
9
9
|
import { createReadStream } from "node:fs";
|
|
@@ -11,7 +11,7 @@ import FormData from "form-data";
|
|
|
11
11
|
import axios from "node-karin/axios";
|
|
12
12
|
import crypto from "node:crypto";
|
|
13
13
|
import { open, readFile, stat } from "node:fs/promises";
|
|
14
|
-
import path from "node:path";
|
|
14
|
+
import path$1 from "node:path";
|
|
15
15
|
import { fileURLToPath as fileURLToPath$1 } from "node:url";
|
|
16
16
|
import os from "node:os";
|
|
17
17
|
import EventEmitter from "node:events";
|
|
@@ -688,7 +688,7 @@ const formatBytes = (bytes) => {
|
|
|
688
688
|
const normalizeUploadFileName = (type, name) => {
|
|
689
689
|
const value = name?.trim();
|
|
690
690
|
if (!value) return DEFAULT_FILENAME$1[type];
|
|
691
|
-
return path.basename(value) || DEFAULT_FILENAME$1[type];
|
|
691
|
+
return path$1.basename(value) || DEFAULT_FILENAME$1[type];
|
|
692
692
|
};
|
|
693
693
|
/**
|
|
694
694
|
* 检查 fallback 资源大小是否超过 QQ 对应类型上限。
|
|
@@ -774,7 +774,7 @@ const buildFallbackSource = async (type, source, fileName) => {
|
|
|
774
774
|
if (isLocalPathSource(value)) {
|
|
775
775
|
const file = normalizeLocalPath(value);
|
|
776
776
|
const info = await stat(file);
|
|
777
|
-
const name = normalizeUploadFileName(type, fileName || path.basename(file));
|
|
777
|
+
const name = normalizeUploadFileName(type, fileName || path$1.basename(file));
|
|
778
778
|
return {
|
|
779
779
|
kind: "local",
|
|
780
780
|
file,
|
|
@@ -1195,13 +1195,26 @@ var GroupsApi = class extends Http {
|
|
|
1195
1195
|
/**
|
|
1196
1196
|
* 获取群成员详情
|
|
1197
1197
|
*
|
|
1198
|
-
* 未公开文档,来自腾讯官方 openclaw 插件(v2.0.0)的接口速查。
|
|
1199
1198
|
* @param groupOpenid 群 openid
|
|
1200
1199
|
* @param memberOpenid 群成员 openid
|
|
1201
1200
|
*/
|
|
1202
1201
|
getGroupMember(groupOpenid, memberOpenid) {
|
|
1203
1202
|
return this.get(`/v2/groups/${groupOpenid}/members/${memberOpenid}`);
|
|
1204
1203
|
}
|
|
1204
|
+
/**
|
|
1205
|
+
* 设置群成员禁言
|
|
1206
|
+
* @param groupOpenid 群 openid
|
|
1207
|
+
* @param memberOpenid 群成员 openid
|
|
1208
|
+
* @param op 操作: add 增加禁言,update 更新禁言到期时间,del 解除禁言
|
|
1209
|
+
* @param duration 禁言时间使用RFC3339 格式
|
|
1210
|
+
*/
|
|
1211
|
+
setGroupMetu(groupOpenid, memberOpenid, op, duration) {
|
|
1212
|
+
return this.post(`/v2/groups/${groupOpenid}/restrict_chat_setting`, { members: [{
|
|
1213
|
+
op,
|
|
1214
|
+
member_openid: memberOpenid,
|
|
1215
|
+
mute_expire_at: duration
|
|
1216
|
+
}] });
|
|
1217
|
+
}
|
|
1205
1218
|
};
|
|
1206
1219
|
|
|
1207
1220
|
//#endregion
|
|
@@ -1327,11 +1340,11 @@ const tokens = /* @__PURE__ */ new Map();
|
|
|
1327
1340
|
* - 提前 50s 刷新,防止边界过期
|
|
1328
1341
|
* - 同一 appId 旧定时器会被清理,避免并发刷新链
|
|
1329
1342
|
*/
|
|
1330
|
-
const getAccessToken = async (
|
|
1331
|
-
const tokenUrl = normalizeHttpUrl(url);
|
|
1343
|
+
const getAccessToken = async (appId, secret) => {
|
|
1332
1344
|
let res;
|
|
1333
1345
|
try {
|
|
1334
|
-
|
|
1346
|
+
const tokenurl = path.join(QQAPIBASEURL, "/app/getAppAccessToken");
|
|
1347
|
+
res = await axios.post(tokenurl, {
|
|
1335
1348
|
appId: String(appId),
|
|
1336
1349
|
clientSecret: secret
|
|
1337
1350
|
}, { headers: { "User-Agent": getUserAgent() } });
|
|
@@ -1353,7 +1366,7 @@ const getAccessToken = async (url, appId, secret) => {
|
|
|
1353
1366
|
tokens.set(appId, state);
|
|
1354
1367
|
const delay = Math.max(5e3, expiresIn * 1e3 - 5e4);
|
|
1355
1368
|
state.refreshTimer = setTimeout(() => {
|
|
1356
|
-
getAccessToken(
|
|
1369
|
+
getAccessToken(appId, secret).catch(() => {});
|
|
1357
1370
|
}, delay);
|
|
1358
1371
|
return {
|
|
1359
1372
|
accessToken,
|
|
@@ -1378,9 +1391,9 @@ const stopTokenRefresh = (appId) => {
|
|
|
1378
1391
|
/**
|
|
1379
1392
|
* 创建 axios 实例,自动从 tokens map 取最新 access_token
|
|
1380
1393
|
*/
|
|
1381
|
-
const createAxiosInstance = (
|
|
1394
|
+
const createAxiosInstance = (appId) => {
|
|
1382
1395
|
const instance = axios.create({
|
|
1383
|
-
baseURL:
|
|
1396
|
+
baseURL: QQAPIBASEURL,
|
|
1384
1397
|
headers: {
|
|
1385
1398
|
"Content-Type": "application/json",
|
|
1386
1399
|
"User-Agent": getUserAgent()
|
|
@@ -1626,36 +1639,8 @@ const resolvePreferredMediaSource = async (ctx, type, source, filename) => {
|
|
|
1626
1639
|
|
|
1627
1640
|
//#endregion
|
|
1628
1641
|
//#region src/core/adapter/text-to-md.ts
|
|
1629
|
-
const MAX_BUTTONS = 5;
|
|
1630
1642
|
const MARKDOWN_IMAGE_RE = /!\[[^\]]*]\(\s*(<[^>]+>|[^\s)]+)(?:\s+["'][^"']*["'])?\s*\)/g;
|
|
1631
1643
|
/**
|
|
1632
|
-
* 提取文本中的 URL,将其替换为 `[按钮 N]` 占位文本并返回 button 列表
|
|
1633
|
-
*
|
|
1634
|
-
* @param text 文本
|
|
1635
|
-
* @param friendly 是否为单聊(true 时清除残余的 at 标签)
|
|
1636
|
-
*/
|
|
1637
|
-
const extractUrlButtons = (text, friendly = false) => {
|
|
1638
|
-
let str = text.replace(/@everyone/g, "everyone").replace(/\n/g, "\r");
|
|
1639
|
-
if (friendly) str = str.replace(/<qqbot-at-user id="[^"]+"\s*\/?>/g, "").replace(/<@!\d+>/g, "");
|
|
1640
|
-
const urls = handleUrl(str);
|
|
1641
|
-
if (urls.length === 0) return {
|
|
1642
|
-
text: str,
|
|
1643
|
-
buttons: []
|
|
1644
|
-
};
|
|
1645
|
-
const buttons = [];
|
|
1646
|
-
urls.forEach((url, i) => {
|
|
1647
|
-
str = str.replace(new RegExp(url, "g"), `[请点击 按钮${i} 查看]`);
|
|
1648
|
-
buttons.push(segment.button({
|
|
1649
|
-
text: `${i}. ${url}`,
|
|
1650
|
-
link: url
|
|
1651
|
-
}));
|
|
1652
|
-
});
|
|
1653
|
-
return {
|
|
1654
|
-
text: str,
|
|
1655
|
-
buttons: buttons.slice(0, MAX_BUTTONS)
|
|
1656
|
-
};
|
|
1657
|
-
};
|
|
1658
|
-
/**
|
|
1659
1644
|
* 将图片列表合并到一段 markdown 文本内(每张图占一行)
|
|
1660
1645
|
*/
|
|
1661
1646
|
const imagesToMarkdown = async (urls) => {
|
|
@@ -1731,11 +1716,6 @@ const sendQQ = async (ctx, contact, elements) => {
|
|
|
1731
1716
|
const target = contact.scene === "friend" ? "user" : "group";
|
|
1732
1717
|
const grouping = groupElements(contact.scene, elements);
|
|
1733
1718
|
await resolveOutgoingReferenceQQ(ctx, contact, grouping);
|
|
1734
|
-
if (ctx.cfg.markdown.enable && ctx.cfg.keyboard.enable && grouping.text.length) {
|
|
1735
|
-
const { text, buttons } = extractUrlButtons(grouping.text.join(""), contact.scene === "friend");
|
|
1736
|
-
grouping.text = [text];
|
|
1737
|
-
grouping.buttons.push(...buttons);
|
|
1738
|
-
}
|
|
1739
1719
|
/**
|
|
1740
1720
|
* 自动 Markdown 通道:
|
|
1741
1721
|
* text / at / 可转换为公网 URL 的图片通过 markdown content 渲染;无法转换的图片
|
|
@@ -2089,11 +2069,6 @@ const BUTTON_ONLY_MARKDOWN = "";
|
|
|
2089
2069
|
*/
|
|
2090
2070
|
const sendGuild = async (ctx, contact, elements) => {
|
|
2091
2071
|
const grouping = groupElements(contact.scene, elements);
|
|
2092
|
-
if (ctx.cfg.markdown.enable && ctx.cfg.keyboard.enable && grouping.text.length) {
|
|
2093
|
-
const { text, buttons } = extractUrlButtons(grouping.text.join(""), false);
|
|
2094
|
-
grouping.text = [text];
|
|
2095
|
-
grouping.buttons.push(...buttons);
|
|
2096
|
-
}
|
|
2097
2072
|
if (ctx.cfg.markdown.enable) return sendGuildMarkdown(ctx, contact, grouping);
|
|
2098
2073
|
return sendGuildClassic(ctx, contact, grouping);
|
|
2099
2074
|
};
|
|
@@ -2176,7 +2151,7 @@ const buildGuildImageItem = async (source) => {
|
|
|
2176
2151
|
};
|
|
2177
2152
|
const resolveImageFilename = (source) => {
|
|
2178
2153
|
const file = source.split(/[?#]/)[0] || "";
|
|
2179
|
-
const name = path.basename(file);
|
|
2154
|
+
const name = path$1.basename(file);
|
|
2180
2155
|
return name.includes(".") ? name : "image.jpg";
|
|
2181
2156
|
};
|
|
2182
2157
|
const buildKeyboard = (grouping) => {
|
|
@@ -2220,7 +2195,7 @@ const warnUnsupportedCommandEnterMarkdowns = (ctx, scene, markdowns) => {
|
|
|
2220
2195
|
const flushGuild = async (ctx, contact, grouping, items) => {
|
|
2221
2196
|
const result = ctx.initSendMsgResults();
|
|
2222
2197
|
const passive = buildPassiveGuild(grouping);
|
|
2223
|
-
const send = contact.scene === "guild" ? (
|
|
2198
|
+
const send = contact.scene === "guild" ? (_peer, subPeer, item) => ctx.super.messages.sendChannelMsg(subPeer, item) : (peer, _subPeer, item) => ctx.super.messages.sendDmsMsg(peer, item);
|
|
2224
2199
|
for (const item of items) {
|
|
2225
2200
|
passive(item);
|
|
2226
2201
|
const res = await send(contact.peer, contact.subPeer, item);
|
|
@@ -2424,7 +2399,7 @@ const prepareStaticElement = async (ctx, element) => {
|
|
|
2424
2399
|
*/
|
|
2425
2400
|
const selfSender = (ctx, contact) => {
|
|
2426
2401
|
const userId = ctx.selfSubId("id") || ctx.selfId;
|
|
2427
|
-
const name = ctx.selfName ||
|
|
2402
|
+
const name = ctx.selfName || "";
|
|
2428
2403
|
if (contact.scene === "friend" || contact.scene === "direct") return karin$1.friendSender(userId, name);
|
|
2429
2404
|
return karin$1.groupSender(userId, "member", name);
|
|
2430
2405
|
};
|
|
@@ -2756,8 +2731,13 @@ var AdapterQQBot = class extends AdapterBase {
|
|
|
2756
2731
|
/**
|
|
2757
2732
|
* 禁言群成员
|
|
2758
2733
|
*/
|
|
2759
|
-
async setGroupMute(_groupId, _targetId,
|
|
2760
|
-
|
|
2734
|
+
async setGroupMute(_groupId, _targetId, duration) {
|
|
2735
|
+
let rfc3339 = "";
|
|
2736
|
+
if (duration > 0) {
|
|
2737
|
+
const time = Date.now() + duration * 1e3;
|
|
2738
|
+
rfc3339 = new Date(time).toISOString();
|
|
2739
|
+
}
|
|
2740
|
+
this.super.groups.setGroupMetu(_groupId, _targetId, rfc3339 ? "add" : "del", rfc3339);
|
|
2761
2741
|
}
|
|
2762
2742
|
/**
|
|
2763
2743
|
* 群全员禁言
|
|
@@ -2840,7 +2820,7 @@ var AdapterQQBot = class extends AdapterBase {
|
|
|
2840
2820
|
avatar: await this.getGroupAvatarUrl(_groupId)
|
|
2841
2821
|
};
|
|
2842
2822
|
} catch (err) {
|
|
2843
|
-
this.logger("warn", `[getGroupInfo]
|
|
2823
|
+
this.logger("warn", `[getGroupInfo] 获取群信息失败: ${err instanceof Error ? err.message : err}`);
|
|
2844
2824
|
return empty;
|
|
2845
2825
|
}
|
|
2846
2826
|
}
|
|
@@ -3783,6 +3763,50 @@ const onC2CMsgReject = (client, event) => {
|
|
|
3783
3763
|
const userId = event.d.openid;
|
|
3784
3764
|
client.logger("mark", `好友 ${userId} 已拒绝机器人主动发送消息`);
|
|
3785
3765
|
};
|
|
3766
|
+
/** 用户申请加群事件 */
|
|
3767
|
+
const onGroupJoinRequest = (client, event) => {
|
|
3768
|
+
const eventId = event.id;
|
|
3769
|
+
const contact = karin$1.contactGroup(event.d.group_openid);
|
|
3770
|
+
const srcReply = (elements) => client.sendMsg(contact, [...elements, segment.pasmsg(eventId, "event")]);
|
|
3771
|
+
const time = Date.parse(event.d.apply_at);
|
|
3772
|
+
if (event.d.member_openid === client.selfId) createGroupInviteRequest({
|
|
3773
|
+
bot: client,
|
|
3774
|
+
time,
|
|
3775
|
+
eventId,
|
|
3776
|
+
contact,
|
|
3777
|
+
rawEvent: event,
|
|
3778
|
+
subEvent: "groupInvite",
|
|
3779
|
+
sender: karin$1.groupSender(event.d.invited_by, "unknown"),
|
|
3780
|
+
srcReply,
|
|
3781
|
+
content: {
|
|
3782
|
+
inviterId: event.d.invited_by,
|
|
3783
|
+
flag: event.d.join_request_id
|
|
3784
|
+
}
|
|
3785
|
+
});
|
|
3786
|
+
else createGroupApplyRequest({
|
|
3787
|
+
bot: client,
|
|
3788
|
+
time,
|
|
3789
|
+
eventId,
|
|
3790
|
+
contact,
|
|
3791
|
+
rawEvent: event,
|
|
3792
|
+
subEvent: "groupApply",
|
|
3793
|
+
sender: karin$1.groupSender(event.d.member_openid, "unknown"),
|
|
3794
|
+
srcReply,
|
|
3795
|
+
content: {
|
|
3796
|
+
inviterId: event.d.invited_by ?? "",
|
|
3797
|
+
flag: event.d.join_request_id,
|
|
3798
|
+
groupId: event.d.group_openid,
|
|
3799
|
+
applierId: event.d.member_openid,
|
|
3800
|
+
reason: (() => {
|
|
3801
|
+
if (event.d.verify_info) if (event.d.verify_info.method === "admin_review_qa") {
|
|
3802
|
+
const reason = event.d.verify_info.review_qa_list[0];
|
|
3803
|
+
if (reason) return `问题: ${reason.question}\n答案: ${reason.answer}`;
|
|
3804
|
+
} else return event.d.verify_info.verify_message;
|
|
3805
|
+
return "";
|
|
3806
|
+
})()
|
|
3807
|
+
}
|
|
3808
|
+
});
|
|
3809
|
+
};
|
|
3786
3810
|
|
|
3787
3811
|
//#endregion
|
|
3788
3812
|
//#region src/core/event/interaction.ts
|
|
@@ -4017,6 +4041,9 @@ const dispatch$1 = (client, ev) => {
|
|
|
4017
4041
|
return;
|
|
4018
4042
|
case "MESSAGE_AUDIT_PASS": return client.logger("debug", `消息审核通过: audit_id=${ev.d.audit_id}`);
|
|
4019
4043
|
case "MESSAGE_AUDIT_REJECT": return client.logger("debug", `消息审核不通过: audit_id=${ev.d.audit_id}`);
|
|
4044
|
+
case "GROUP_JOIN_REQUEST":
|
|
4045
|
+
onGroupJoinRequest(client, ev);
|
|
4046
|
+
return;
|
|
4020
4047
|
default: client.logger("debug", `[QQ Official Bot] 未处理事件: ${ev.t}`);
|
|
4021
4048
|
}
|
|
4022
4049
|
};
|
|
@@ -4419,34 +4446,17 @@ const QUICK_DISCONNECT_THRESHOLD_MS = 5e3;
|
|
|
4419
4446
|
const QUICK_DISCONNECT_LIMIT = 3;
|
|
4420
4447
|
const QUICK_DISCONNECT_DELAY_MS = 6e4;
|
|
4421
4448
|
/**
|
|
4422
|
-
* 从 proxy.prodApi / proxy.sandboxApi 推导兜底的 WS 地址
|
|
4423
|
-
* 官方实际长期返回的也是 wss://{host}/websocket/,不依赖 /gateway 也能直连
|
|
4424
|
-
*/
|
|
4425
|
-
const buildFallbackWsUrl = (cfg) => {
|
|
4426
|
-
return buildFallbackWsUrlFromApi(cfg.sandbox ? cfg.proxy.sandboxApi : cfg.proxy.prodApi);
|
|
4427
|
-
};
|
|
4428
|
-
/**
|
|
4429
4449
|
* 获取网关地址:优先调 /gateway;失败 / 超时 / 限频 → 直接 fallback 到硬编码
|
|
4430
4450
|
*/
|
|
4431
4451
|
const fetchGateway = async (cfg) => {
|
|
4432
|
-
const apiUrl =
|
|
4433
|
-
const fallback = buildFallbackWsUrl(cfg);
|
|
4434
|
-
const proxyWsValue = cfg.sandbox ? cfg.proxy.sandboxWs : cfg.proxy.prodWs;
|
|
4435
|
-
if (proxyWsValue.trim()) try {
|
|
4436
|
-
const proxyWsUrl = normalizeWsGatewayUrl(proxyWsValue);
|
|
4437
|
-
log("debug", `${cfg.appId}: 使用自定义 WS ${proxyWsUrl}`);
|
|
4438
|
-
return proxyWsUrl;
|
|
4439
|
-
} catch (err) {
|
|
4440
|
-
const reason = err?.message || "unknown";
|
|
4441
|
-
log("warn", `${cfg.appId}: 自定义 WS 地址无效 (${reason}),尝试获取 /gateway`);
|
|
4442
|
-
}
|
|
4452
|
+
const apiUrl = "https://api.bot.qq.com";
|
|
4443
4453
|
const accessToken = getBotAccessToken(cfg.appId);
|
|
4444
4454
|
if (!accessToken) {
|
|
4445
|
-
log("warn", `${cfg.appId}: 无 access_token,使用硬编码 WS
|
|
4446
|
-
return
|
|
4455
|
+
log("warn", `${cfg.appId}: 无 access_token,使用硬编码 WS`);
|
|
4456
|
+
return "wss://api.bot.qq.com/websocket";
|
|
4447
4457
|
}
|
|
4448
4458
|
try {
|
|
4449
|
-
const { data } = await axios.get(
|
|
4459
|
+
const { data } = await axios.get(path$1.join(apiUrl, "gateway"), {
|
|
4450
4460
|
headers: {
|
|
4451
4461
|
Authorization: `QQBot ${accessToken}`,
|
|
4452
4462
|
"User-Agent": getUserAgent()
|
|
@@ -4454,20 +4464,20 @@ const fetchGateway = async (cfg) => {
|
|
|
4454
4464
|
timeout: GATEWAY_FETCH_TIMEOUT_MS
|
|
4455
4465
|
});
|
|
4456
4466
|
if (data?.url) return data.url;
|
|
4457
|
-
log("warn", `${cfg.appId}: /gateway 返回为空,使用硬编码 WS
|
|
4458
|
-
return
|
|
4467
|
+
log("warn", `${cfg.appId}: /gateway 返回为空,使用硬编码 WS`);
|
|
4468
|
+
return "wss://api.bot.qq.com/websocket";
|
|
4459
4469
|
} catch (err) {
|
|
4460
4470
|
if (axios.isAxiosError(err)) {
|
|
4461
4471
|
const response = err.response;
|
|
4462
4472
|
const status = response?.status ?? 0;
|
|
4463
4473
|
const data = response?.data;
|
|
4464
4474
|
const detail = formatOpenAPIError(status, typeof data?.err_code === "number" ? data.err_code : typeof data?.code === "number" ? data.code : void 0, typeof data?.message === "string" ? data.message : void 0);
|
|
4465
|
-
log("warn", `${cfg.appId}: /gateway 调用失败 | ${detail},使用硬编码 WS
|
|
4475
|
+
log("warn", `${cfg.appId}: /gateway 调用失败 | ${detail},使用硬编码 WS`);
|
|
4466
4476
|
} else {
|
|
4467
4477
|
const reason = err?.message || "unknown";
|
|
4468
|
-
log("warn", `${cfg.appId}: /gateway 调用失败 (${reason}),使用硬编码 WS
|
|
4478
|
+
log("warn", `${cfg.appId}: /gateway 调用失败 (${reason}),使用硬编码 WS`);
|
|
4469
4479
|
}
|
|
4470
|
-
return
|
|
4480
|
+
return "wss://api.bot.qq.com/websocket";
|
|
4471
4481
|
}
|
|
4472
4482
|
};
|
|
4473
4483
|
/** 重连退避(毫秒) */
|
|
@@ -4597,7 +4607,7 @@ const onClose = (cfg, intents, attempt, event, client) => {
|
|
|
4597
4607
|
if (code === 4004) {
|
|
4598
4608
|
log("warn", `${cfg.appId}: access_token 已失效,刷新 token 后重连...`);
|
|
4599
4609
|
schedule(cfg.appId, 1e3, () => {
|
|
4600
|
-
getAccessToken(cfg.
|
|
4610
|
+
getAccessToken(cfg.appId, cfg.secret).then(() => connect(cfg, intents, 0)).catch(() => {
|
|
4601
4611
|
log("error", `${cfg.appId}: access_token 刷新失败,${RATE_LIMIT_DELAY_MS}ms 后重试连接...`);
|
|
4602
4612
|
schedule(cfg.appId, RATE_LIMIT_DELAY_MS, () => connect(cfg, intents, attempt + 1));
|
|
4603
4613
|
});
|
|
@@ -4678,10 +4688,7 @@ const initState = /* @__PURE__ */ new Map();
|
|
|
4678
4688
|
const hashConfig = (bot) => JSON.stringify({
|
|
4679
4689
|
appId: bot.appId,
|
|
4680
4690
|
secret: bot.secret,
|
|
4681
|
-
proxy: bot.proxy,
|
|
4682
|
-
sandbox: bot.sandbox,
|
|
4683
4691
|
event: bot.event,
|
|
4684
|
-
keyboard: bot.keyboard,
|
|
4685
4692
|
markdown: bot.markdown,
|
|
4686
4693
|
messageCache: bot.messageCache
|
|
4687
4694
|
});
|
|
@@ -4689,10 +4696,7 @@ const hashConfig = (bot) => JSON.stringify({
|
|
|
4689
4696
|
* 创建单个 Bot 实例
|
|
4690
4697
|
*/
|
|
4691
4698
|
const createBot = async (input) => {
|
|
4692
|
-
const bot = {
|
|
4693
|
-
...input,
|
|
4694
|
-
proxy: normalizeProxyConfig(input.proxy)
|
|
4695
|
-
};
|
|
4699
|
+
const bot = { ...input };
|
|
4696
4700
|
const appId = String(bot.appId);
|
|
4697
4701
|
if (!appId) {
|
|
4698
4702
|
logger.warn("[QQ Official Bot] 配置缺少 appId,跳过");
|
|
@@ -4709,14 +4713,13 @@ const createBot = async (input) => {
|
|
|
4709
4713
|
await existing.promise.catch(() => {});
|
|
4710
4714
|
}
|
|
4711
4715
|
const promise = (async () => {
|
|
4712
|
-
unregisterBot("selfId", appId);
|
|
4716
|
+
if (karin.getBot(appId)) unregisterBot("selfId", appId);
|
|
4713
4717
|
stop(appId);
|
|
4714
4718
|
offAll(appId);
|
|
4715
4719
|
stopTokenRefresh(appId);
|
|
4716
4720
|
logger.info(`[QQ Official Bot][${appId}] 获取 access_token...`);
|
|
4717
|
-
await getAccessToken(
|
|
4718
|
-
const
|
|
4719
|
-
const api = new QQBotApi(createAxiosInstance(baseUrl, appId));
|
|
4721
|
+
await getAccessToken(appId, bot.secret);
|
|
4722
|
+
const api = new QQBotApi(createAxiosInstance(appId));
|
|
4720
4723
|
const me = await api.meta.getMe();
|
|
4721
4724
|
const client = new AdapterQQBot(bot, api);
|
|
4722
4725
|
client.account.name = me.username;
|
|
@@ -4730,7 +4733,7 @@ const createBot = async (input) => {
|
|
|
4730
4733
|
bus.on(appId, (ev) => {
|
|
4731
4734
|
dispatch$1(client, ev);
|
|
4732
4735
|
});
|
|
4733
|
-
client.adapter.address =
|
|
4736
|
+
client.adapter.address = QQAPIBASEURL;
|
|
4734
4737
|
client.adapter.secret = "*********";
|
|
4735
4738
|
client.adapter.version = pkg().version;
|
|
4736
4739
|
if (bot.event.type === 2) {
|
|
@@ -4898,23 +4901,7 @@ const createRouting = () => {
|
|
|
4898
4901
|
//#region src/index.ts
|
|
4899
4902
|
logger.info(`${logger.violet(`[插件:${pkg().version}]`)} ${logger.green(basename)} 初始化完成~`);
|
|
4900
4903
|
createRouting();
|
|
4901
|
-
const printOnboardBanner = () => {
|
|
4902
|
-
console.log();
|
|
4903
|
-
console.log(" ==========================================");
|
|
4904
|
-
console.log(" 未检测到 QQBot 配置");
|
|
4905
|
-
console.log(" 请使用任意可用 bot(如 OneBot / Console 适配器)");
|
|
4906
|
-
console.log(" 对机器人发送指令: #QQ登录");
|
|
4907
|
-
console.log(" 扫码授权完成后会自动写入配置并完成初始化");
|
|
4908
|
-
console.log();
|
|
4909
|
-
console.log(" ⚠ 注意:扫码授权会刷新该机器人的 secret,旧 secret 立即失效");
|
|
4910
|
-
console.log(" ==========================================");
|
|
4911
|
-
console.log();
|
|
4912
|
-
};
|
|
4913
4904
|
const bootstrap = async () => {
|
|
4914
|
-
if (needQrOnboard()) {
|
|
4915
|
-
printOnboardBanner();
|
|
4916
|
-
return;
|
|
4917
|
-
}
|
|
4918
4905
|
try {
|
|
4919
4906
|
await initQQBotAdapter();
|
|
4920
4907
|
} catch (err) {
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { logger } from "node-karin";
|
|
2
|
+
|
|
3
|
+
//#region src/utils/logger.ts
|
|
4
|
+
const PREFIX = "[QQ Official Bot]";
|
|
5
|
+
const m = {
|
|
6
|
+
trace: (...a) => logger.trace(PREFIX, ...a),
|
|
7
|
+
debug: (...a) => logger.debug(PREFIX, ...a),
|
|
8
|
+
info: (...a) => logger.info(PREFIX, ...a),
|
|
9
|
+
warn: (...a) => logger.warn(PREFIX, ...a),
|
|
10
|
+
error: (...a) => logger.error(PREFIX, ...a),
|
|
11
|
+
fatal: (...a) => logger.fatal(PREFIX, ...a),
|
|
12
|
+
mark: (...a) => logger.mark(PREFIX, ...a)
|
|
13
|
+
};
|
|
14
|
+
/**
|
|
15
|
+
* QQBot 日志函数
|
|
16
|
+
* - log('info', '消息')
|
|
17
|
+
* - log.info('消息')
|
|
18
|
+
*
|
|
19
|
+
* 日志级别由 karin 框架的 log.level 控制
|
|
20
|
+
*/
|
|
21
|
+
function log(level, ...args) {
|
|
22
|
+
m[level](...args);
|
|
23
|
+
}
|
|
24
|
+
Object.assign(log, m);
|
|
25
|
+
|
|
26
|
+
//#endregion
|
|
27
|
+
export { log as t };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { c as pluginDirName, n as config } from "./config-D1_LqrcJ.js";
|
|
2
|
+
import { t as log } from "./logger-sza6kuy3.js";
|
|
2
3
|
import { karinPathBase, karinPathTemp } from "node-karin";
|
|
3
4
|
import { createWriteStream } from "node:fs";
|
|
4
5
|
import axios from "node-karin/axios";
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import "./config-D1_LqrcJ.js";
|
|
2
|
+
import "./logger-sza6kuy3.js";
|
|
3
|
+
import { common, logger } from "node-karin";
|
|
4
|
+
import qrcode from "qrcode";
|
|
5
|
+
import size from "image-size";
|
|
6
|
+
import lodash from "node-karin/lodash";
|
|
7
|
+
import "@karinjs/geturls";
|
|
8
|
+
|
|
9
|
+
//#region src/utils/common.ts
|
|
10
|
+
/** QQBot统一API地址 */
|
|
11
|
+
const QQAPIBASEURL = "https://api.bot.q.qq.com";
|
|
12
|
+
/** 随机数 */
|
|
13
|
+
const random = (min, max) => lodash.random(min, max);
|
|
14
|
+
/**
|
|
15
|
+
* 伪事件日志(红字)
|
|
16
|
+
*/
|
|
17
|
+
const fakeEvent = (log) => {
|
|
18
|
+
logger.fatal(`${logger.red("[QQ Official Bot][虚假事件]")} ${log.replace(/\n/g, "\\n")}`);
|
|
19
|
+
};
|
|
20
|
+
/**
|
|
21
|
+
* 读取图片宽高
|
|
22
|
+
*/
|
|
23
|
+
const getImageSize = async (url) => {
|
|
24
|
+
const { width = 100, height = 100 } = size(await common.buffer(url));
|
|
25
|
+
return {
|
|
26
|
+
width,
|
|
27
|
+
height
|
|
28
|
+
};
|
|
29
|
+
};
|
|
30
|
+
|
|
31
|
+
//#endregion
|
|
32
|
+
export { random as i, fakeEvent as n, getImageSize as r, QQAPIBASEURL as t };
|
package/dist/web.config.d.ts
CHANGED
|
@@ -1,16 +1,11 @@
|
|
|
1
1
|
//#region src/web.config.d.ts
|
|
2
2
|
type WebQQBotInput = {
|
|
3
|
-
name: unknown;
|
|
4
3
|
appId: unknown;
|
|
5
4
|
secret: unknown;
|
|
6
|
-
'proxy:api': unknown;
|
|
7
|
-
'proxy:ws': unknown;
|
|
8
|
-
sandbox: unknown;
|
|
9
5
|
qqEnable: unknown;
|
|
10
6
|
guildEnable: unknown;
|
|
11
7
|
guildMode: unknown;
|
|
12
8
|
regex: unknown;
|
|
13
|
-
'keyboard:enable': unknown;
|
|
14
9
|
'markdown:enable': unknown;
|
|
15
10
|
'messageCache:enable': unknown;
|
|
16
11
|
'messageCache:self': unknown;
|