woodenfish-bot 4.4.4 → 4.4.6

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.
Files changed (3) hide show
  1. package/es/index.js +266 -72
  2. package/lib/index.js +266 -72
  3. package/package.json +1 -1
package/es/index.js CHANGED
@@ -1,10 +1,11 @@
1
+ import _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator';
1
2
  import _classCallCheck from '@babel/runtime/helpers/classCallCheck';
2
3
  import _createClass from '@babel/runtime/helpers/createClass';
3
4
  import _defineProperty from '@babel/runtime/helpers/defineProperty';
5
+ import _regeneratorRuntime from '@babel/runtime/regenerator';
4
6
  import resty from 'resty-client';
5
- import _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator';
6
7
  import _typeof from '@babel/runtime/helpers/typeof';
7
- import _regeneratorRuntime from '@babel/runtime/regenerator';
8
+ import _slicedToArray from '@babel/runtime/helpers/slicedToArray';
8
9
  import fs from 'fs';
9
10
  import https from 'https';
10
11
  import _possibleConstructorReturn from '@babel/runtime/helpers/possibleConstructorReturn';
@@ -297,7 +298,7 @@ var Guild = /*#__PURE__*/function () {
297
298
  }]);
298
299
  }();
299
300
 
300
- var version = "4.4.4";
301
+ var version = "4.4.6";
301
302
 
302
303
  function getDefaultExportFromCjs (x) {
303
304
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -677,6 +678,45 @@ log.setLevel('info');
677
678
  log.setLevel('trace');
678
679
  var BotLogger = log;
679
680
 
681
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: true } : { done: false, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = true, u = false; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = true, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
682
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
683
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
684
+
685
+ // 全局token刷新锁,避免并发刷新
686
+ var tokenRefreshPromises = new Map();
687
+
688
+ // 定期清理可能遗留的Promise(防止内存泄漏)
689
+ setInterval(function () {
690
+ var now = Date.now();
691
+ var keysToDelete = [];
692
+ var _iterator = _createForOfIteratorHelper(tokenRefreshPromises.entries()),
693
+ _step;
694
+ try {
695
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
696
+ var _step$value = _slicedToArray(_step.value, 2),
697
+ key = _step$value[0],
698
+ _step$value$ = _step$value[1],
699
+ promise = _step$value$.promise,
700
+ timestamp = _step$value$.timestamp;
701
+ // 如果Promise已经存在超过5分钟,可能出现了问题,清理掉
702
+ if (now - timestamp > 5 * 60 * 1000) {
703
+ keysToDelete.push(key);
704
+ console.warn("\u6E05\u7406\u8D85\u65F6\u7684token\u5237\u65B0Promise (key: ".concat(key, ", \u5B58\u5728\u65F6\u95F4: ").concat((now - timestamp) / 1000, "s)"));
705
+ }
706
+ }
707
+ } catch (err) {
708
+ _iterator.e(err);
709
+ } finally {
710
+ _iterator.f();
711
+ }
712
+ keysToDelete.forEach(function (key) {
713
+ return tokenRefreshPromises["delete"](key);
714
+ });
715
+ if (keysToDelete.length > 0) {
716
+ console.log("\u6E05\u7406\u4E86 ".concat(keysToDelete.length, " \u4E2A\u8D85\u65F6\u7684token\u5237\u65B0Promise"));
717
+ }
718
+ }, 60000); // 每分钟清理一次
719
+
680
720
  // 转为对象
681
721
  var toObject = function toObject(data) {
682
722
  if (Buffer.isBuffer(data)) return JSON.parse(data.toString());
@@ -755,17 +795,40 @@ function getAccessToken(access_token_file, url, data, appID) {
755
795
  });
756
796
  }
757
797
 
758
- // 添加 User-Agent
759
- var addAuthorization = /*#__PURE__*/function () {
760
- var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(header, appID, token, clientSecret, apiVersion) {
761
- var now_time, url, data, access_token_file, default_token_json, access_token, expires_in, access_token_json;
798
+ // 等待Promise完成,带超时机制
799
+ var waitForPromiseWithTimeout = /*#__PURE__*/function () {
800
+ var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(promise) {
801
+ var timeoutMs,
802
+ timeoutPromise,
803
+ _args = arguments;
762
804
  return _regeneratorRuntime.wrap(function (_context) {
763
805
  while (1) switch (_context.prev = _context.next) {
764
806
  case 0:
765
- if (!(apiVersion == 'v2')) {
766
- _context.next = 3;
767
- break;
768
- }
807
+ timeoutMs = _args.length > 1 && _args[1] !== undefined ? _args[1] : 10000;
808
+ timeoutPromise = new Promise(function (_, reject) {
809
+ setTimeout(function () {
810
+ return reject(new Error("Token refresh timeout after ".concat(timeoutMs, "ms")));
811
+ }, timeoutMs);
812
+ });
813
+ return _context.abrupt("return", Promise.race([promise, timeoutPromise]));
814
+ case 1:
815
+ case "end":
816
+ return _context.stop();
817
+ }
818
+ }, _callee);
819
+ }));
820
+ return function waitForPromiseWithTimeout(_x) {
821
+ return _ref.apply(this, arguments);
822
+ };
823
+ }();
824
+
825
+ // 获取或刷新token(带并发控制和超时保护)
826
+ var getOrRefreshToken = /*#__PURE__*/function () {
827
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(appID, clientSecret) {
828
+ var now_time, url, data, access_token_file, access_token, expires_in, needsRefresh, access_token_json, expiresStr, parsed, default_token_json, key, existingEntry, refreshPromise, _t2;
829
+ return _regeneratorRuntime.wrap(function (_context3) {
830
+ while (1) switch (_context3.prev = _context3.next) {
831
+ case 0:
769
832
  now_time = getTimeStampNumber();
770
833
  url = 'https://bots.qq.com/app/getAppAccessToken';
771
834
  data = {
@@ -773,55 +836,149 @@ var addAuthorization = /*#__PURE__*/function () {
773
836
  clientSecret: clientSecret
774
837
  };
775
838
  access_token_file = './access_token.json';
776
- default_token_json = {
777
- bot: _defineProperty({}, appID, {
778
- access_token: "",
779
- expires_in: ""
780
- })
781
- };
782
839
  access_token = '';
783
840
  expires_in = 0;
841
+ needsRefresh = false; // 首先读取当前token状态
784
842
  try {
785
- // 判断文件是否存在
786
843
  if (fs.existsSync(access_token_file)) {
787
- // 执行文件存在时的处理逻辑
788
- access_token_json = JSON.parse(fs.readFileSync(access_token_file).toString()); // 判断是否存在appID
844
+ access_token_json = JSON.parse(fs.readFileSync(access_token_file).toString());
789
845
  if (access_token_json.hasOwnProperty('bot') && access_token_json['bot'].hasOwnProperty(appID)) {
790
846
  access_token = access_token_json['bot'][appID]['access_token'];
791
- expires_in = parseInt(access_token_json['bot'][appID]['expires_in']);
847
+ expiresStr = access_token_json['bot'][appID]['expires_in'];
848
+ if (expiresStr && typeof expiresStr === 'string') {
849
+ parsed = parseInt(expiresStr);
850
+ if (!isNaN(parsed) && parsed > 0) {
851
+ expires_in = parsed;
852
+ } else {
853
+ console.log('expires_in解析失败,重置为0:', expiresStr);
854
+ expires_in = 0;
855
+ }
856
+ } else {
857
+ expires_in = 0;
858
+ }
792
859
  }
793
860
  } else {
794
- // 执行文件不存在时的处理逻辑
861
+ default_token_json = {
862
+ bot: _defineProperty({}, appID, {
863
+ access_token: "",
864
+ expires_in: ""
865
+ })
866
+ };
795
867
  fs.writeFileSync(access_token_file, JSON.stringify(default_token_json, null, 2), 'utf-8');
868
+ needsRefresh = true;
796
869
  }
797
870
  } catch (err) {
798
871
  console.error('读取文件时发生错误:', err);
872
+ expires_in = 0;
873
+ needsRefresh = true;
799
874
  }
800
- // 判断access_token是否存在,不存在或者已经过期就重新获取
801
- if (!(access_token === '' || now_time > expires_in - 50)) {
802
- _context.next = 2;
875
+
876
+ // 如果token有效且不需要刷新,直接返回
877
+ if (!(access_token !== '' && now_time <= expires_in - 50 && !needsRefresh)) {
878
+ _context3.next = 1;
803
879
  break;
804
880
  }
805
- console.log('access_token', access_token, 'expire_time', expires_in);
806
- _context.next = 1;
807
- return getAccessToken(access_token_file, url, data, appID);
881
+ return _context3.abrupt("return", access_token);
808
882
  case 1:
809
- access_token = _context.sent;
810
- case 2:
883
+ // 需要刷新token,进入并发控制逻辑
884
+ key = "".concat(appID, ":").concat(clientSecret);
885
+ existingEntry = tokenRefreshPromises.get(key);
886
+ if (!existingEntry) {
887
+ _context3.next = 5;
888
+ break;
889
+ }
890
+ _context3.prev = 2;
891
+ // 等待已有的刷新完成,但设置超时防止无限等待
892
+ console.log("\u7B49\u5F85\u5DF2\u6709\u7684token\u5237\u65B0\u5B8C\u6210 (appID: ".concat(appID, ")"));
893
+ _context3.next = 3;
894
+ return waitForPromiseWithTimeout(existingEntry.promise, 10000);
895
+ case 3:
896
+ return _context3.abrupt("return", _context3.sent);
897
+ case 4:
898
+ _context3.prev = 4;
899
+ _t2 = _context3["catch"](2);
900
+ console.warn("\u7B49\u5F85token\u5237\u65B0\u8D85\u65F6\u6216\u5931\u8D25\uFF0C\u91CD\u65B0\u53D1\u8D77\u5237\u65B0 (appID: ".concat(appID, "):"), _t2);
901
+ // 移除失败的Promise,让新的请求可以重新发起刷新
902
+ tokenRefreshPromises["delete"](key);
903
+ case 5:
904
+ // 创建新的刷新Promise
905
+ refreshPromise = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
906
+ var new_token, _t;
907
+ return _regeneratorRuntime.wrap(function (_context2) {
908
+ while (1) switch (_context2.prev = _context2.next) {
909
+ case 0:
910
+ _context2.prev = 0;
911
+ console.log('开始刷新access_token - 当前token:', access_token, '过期时间:', expires_in, '当前时间:', now_time);
912
+ _context2.next = 1;
913
+ return getAccessToken(access_token_file, url, data, appID);
914
+ case 1:
915
+ new_token = _context2.sent;
916
+ console.log('access_token刷新成功,新token:', new_token);
917
+ return _context2.abrupt("return", new_token);
918
+ case 2:
919
+ _context2.prev = 2;
920
+ _t = _context2["catch"](0);
921
+ console.error("Token\u5237\u65B0\u5931\u8D25 (appID: ".concat(appID, "):"), _t);
922
+ throw _t;
923
+ case 3:
924
+ _context2.prev = 3;
925
+ // 刷新完成后移除Promise
926
+ tokenRefreshPromises["delete"](key);
927
+ return _context2.finish(3);
928
+ case 4:
929
+ case "end":
930
+ return _context2.stop();
931
+ }
932
+ }, _callee2, null, [[0, 2, 3, 4]]);
933
+ }))(); // 存储Promise和时间戳以便其他并发请求使用和超时清理
934
+ tokenRefreshPromises.set(key, {
935
+ promise: refreshPromise,
936
+ timestamp: Date.now()
937
+ });
938
+ _context3.next = 6;
939
+ return refreshPromise;
940
+ case 6:
941
+ return _context3.abrupt("return", _context3.sent);
942
+ case 7:
943
+ case "end":
944
+ return _context3.stop();
945
+ }
946
+ }, _callee3, null, [[2, 4]]);
947
+ }));
948
+ return function getOrRefreshToken(_x2, _x3) {
949
+ return _ref2.apply(this, arguments);
950
+ };
951
+ }();
952
+
953
+ // 添加 User-Agent
954
+ var addAuthorization = /*#__PURE__*/function () {
955
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4(header, appID, token, clientSecret, apiVersion) {
956
+ var access_token;
957
+ return _regeneratorRuntime.wrap(function (_context4) {
958
+ while (1) switch (_context4.prev = _context4.next) {
959
+ case 0:
960
+ if (!(apiVersion == 'v2')) {
961
+ _context4.next = 2;
962
+ break;
963
+ }
964
+ _context4.next = 1;
965
+ return getOrRefreshToken(appID, clientSecret);
966
+ case 1:
967
+ access_token = _context4.sent;
811
968
  // 更新header['Authorization']
812
969
  header['Authorization'] = "QQBot ".concat(access_token);
813
- _context.next = 4;
970
+ _context4.next = 3;
814
971
  break;
815
- case 3:
972
+ case 2:
816
973
  header['Authorization'] = "Bot ".concat(appID, ".").concat(token);
817
- case 4:
974
+ case 3:
818
975
  case "end":
819
- return _context.stop();
976
+ return _context4.stop();
820
977
  }
821
- }, _callee);
978
+ }, _callee4);
822
979
  }));
823
- return function addAuthorization(_x, _x2, _x3, _x4, _x5) {
824
- return _ref.apply(this, arguments);
980
+ return function addAuthorization(_x4, _x5, _x6, _x7, _x8) {
981
+ return _ref4.apply(this, arguments);
825
982
  };
826
983
  }();
827
984
  // 组装完整Url
@@ -1966,41 +2123,78 @@ var OpenAPI = /*#__PURE__*/function () {
1966
2123
  // 基础rest请求
1967
2124
  }, {
1968
2125
  key: "request",
1969
- value: function request(options) {
1970
- var _this$config = this.config,
1971
- appID = _this$config.appID,
1972
- token = _this$config.token,
1973
- clientSecret = _this$config.clientSecret;
1974
- options.headers = _objectSpread({}, options.headers);
1975
- var apiVersion = options.apiVersion || 'v2';
1976
- // 添加 UA
1977
- addUserAgent(options.headers, appID, apiVersion);
1978
- // 添加鉴权信息
1979
- addAuthorization(options.headers, appID, token, clientSecret, apiVersion);
1980
- // 组装完整Url
1981
- var botUrl = buildUrl(options.url, this.config.sandbox);
1982
-
1983
- // 简化错误信息,后续可考虑通过中间件形式暴露给用户自行处理
1984
- resty.useRes(function (result) {
1985
- return result;
1986
- }, function (error) {
1987
- var _error$response, _error$response2;
1988
- var traceid = error === null || error === void 0 || (_error$response = error.response) === null || _error$response === void 0 || (_error$response = _error$response.headers) === null || _error$response === void 0 ? void 0 : _error$response['x-tps-trace-id'];
1989
- if (error !== null && error !== void 0 && (_error$response2 = error.response) !== null && _error$response2 !== void 0 && _error$response2.data) {
1990
- return Promise.reject(_objectSpread(_objectSpread({}, error.response.data), {}, {
1991
- traceid: traceid
1992
- }));
1993
- }
1994
- if (error !== null && error !== void 0 && error.response) {
1995
- return Promise.reject(_objectSpread(_objectSpread({}, error.response), {}, {
1996
- traceid: traceid
1997
- }));
1998
- }
1999
- return Promise.reject(error);
2000
- });
2001
- var client = resty.create(options);
2002
- return client.request(botUrl, options);
2003
- }
2126
+ value: function () {
2127
+ var _request = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
2128
+ var _this$config, appID, token, clientSecret, apiVersion, botUrl, client, retryClient, _t, _t2;
2129
+ return _regeneratorRuntime.wrap(function (_context) {
2130
+ while (1) switch (_context.prev = _context.next) {
2131
+ case 0:
2132
+ _this$config = this.config, appID = _this$config.appID, token = _this$config.token, clientSecret = _this$config.clientSecret;
2133
+ options.headers = _objectSpread({}, options.headers);
2134
+ apiVersion = options.apiVersion || 'v2'; // 添加 UA
2135
+ addUserAgent(options.headers, appID, apiVersion);
2136
+ // 添加鉴权信息
2137
+ _context.next = 1;
2138
+ return addAuthorization(options.headers, appID, token, clientSecret, apiVersion);
2139
+ case 1:
2140
+ // 组装完整Url
2141
+ botUrl = buildUrl(options.url, this.config.sandbox); // 简化错误信息,后续可考虑通过中间件形式暴露给用户自行处理
2142
+ resty.useRes(function (result) {
2143
+ return result;
2144
+ }, function (error) {
2145
+ var _error$response, _error$response2;
2146
+ var traceid = error === null || error === void 0 || (_error$response = error.response) === null || _error$response === void 0 || (_error$response = _error$response.headers) === null || _error$response === void 0 ? void 0 : _error$response['x-tps-trace-id'];
2147
+ if (error !== null && error !== void 0 && (_error$response2 = error.response) !== null && _error$response2 !== void 0 && _error$response2.data) {
2148
+ return Promise.reject(_objectSpread(_objectSpread({}, error.response.data), {}, {
2149
+ traceid: traceid
2150
+ }));
2151
+ }
2152
+ if (error !== null && error !== void 0 && error.response) {
2153
+ return Promise.reject(_objectSpread(_objectSpread({}, error.response), {}, {
2154
+ traceid: traceid
2155
+ }));
2156
+ }
2157
+ return Promise.reject(error);
2158
+ });
2159
+ client = resty.create(options);
2160
+ _context.prev = 2;
2161
+ _context.next = 3;
2162
+ return client.request(botUrl, options);
2163
+ case 3:
2164
+ return _context.abrupt("return", _context.sent);
2165
+ case 4:
2166
+ _context.prev = 4;
2167
+ _t = _context["catch"](2);
2168
+ if (!((_t === null || _t === void 0 ? void 0 : _t.code) === 11201 || (_t === null || _t === void 0 ? void 0 : _t.err_code) === 40012001)) {
2169
+ _context.next = 8;
2170
+ break;
2171
+ }
2172
+ console.log('检测到认证失败,尝试重试请求...');
2173
+ _context.prev = 5;
2174
+ // 由于addAuthorization现在是并发安全的,重试时会自动获取最新token
2175
+ retryClient = resty.create(options);
2176
+ _context.next = 6;
2177
+ return retryClient.request(botUrl, options);
2178
+ case 6:
2179
+ return _context.abrupt("return", _context.sent);
2180
+ case 7:
2181
+ _context.prev = 7;
2182
+ _t2 = _context["catch"](5);
2183
+ console.log('重试请求仍然失败:', _t2);
2184
+ throw _t2;
2185
+ case 8:
2186
+ throw _t;
2187
+ case 9:
2188
+ case "end":
2189
+ return _context.stop();
2190
+ }
2191
+ }, _callee, this, [[2, 4], [5, 7]]);
2192
+ }));
2193
+ function request(_x) {
2194
+ return _request.apply(this, arguments);
2195
+ }
2196
+ return request;
2197
+ }()
2004
2198
  }], [{
2005
2199
  key: "newClient",
2006
2200
  value: function newClient(config) {
package/lib/index.js CHANGED
@@ -1,12 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator');
3
4
  var _classCallCheck = require('@babel/runtime/helpers/classCallCheck');
4
5
  var _createClass = require('@babel/runtime/helpers/createClass');
5
6
  var _defineProperty = require('@babel/runtime/helpers/defineProperty');
7
+ var _regeneratorRuntime = require('@babel/runtime/regenerator');
6
8
  var resty = require('resty-client');
7
- var _asyncToGenerator = require('@babel/runtime/helpers/asyncToGenerator');
8
9
  var _typeof = require('@babel/runtime/helpers/typeof');
9
- var _regeneratorRuntime = require('@babel/runtime/regenerator');
10
+ var _slicedToArray = require('@babel/runtime/helpers/slicedToArray');
10
11
  var fs = require('fs');
11
12
  var https = require('https');
12
13
  var _possibleConstructorReturn = require('@babel/runtime/helpers/possibleConstructorReturn');
@@ -299,7 +300,7 @@ var Guild = /*#__PURE__*/function () {
299
300
  }]);
300
301
  }();
301
302
 
302
- var version = "4.4.4";
303
+ var version = "4.4.6";
303
304
 
304
305
  function getDefaultExportFromCjs (x) {
305
306
  return x && x.__esModule && Object.prototype.hasOwnProperty.call(x, 'default') ? x['default'] : x;
@@ -679,6 +680,45 @@ log.setLevel('info');
679
680
  log.setLevel('trace');
680
681
  var BotLogger = log;
681
682
 
683
+ function _createForOfIteratorHelper(r, e) { var t = "undefined" != typeof Symbol && r[Symbol.iterator] || r["@@iterator"]; if (!t) { if (Array.isArray(r) || (t = _unsupportedIterableToArray(r)) || e) { t && (r = t); var _n = 0, F = function F() {}; return { s: F, n: function n() { return _n >= r.length ? { done: true } : { done: false, value: r[_n++] }; }, e: function e(r) { throw r; }, f: F }; } throw new TypeError("Invalid attempt to iterate non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } var o, a = true, u = false; return { s: function s() { t = t.call(r); }, n: function n() { var r = t.next(); return a = r.done, r; }, e: function e(r) { u = true, o = r; }, f: function f() { try { a || null == t["return"] || t["return"](); } finally { if (u) throw o; } } }; }
684
+ function _unsupportedIterableToArray(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } }
685
+ function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; }
686
+
687
+ // 全局token刷新锁,避免并发刷新
688
+ var tokenRefreshPromises = new Map();
689
+
690
+ // 定期清理可能遗留的Promise(防止内存泄漏)
691
+ setInterval(function () {
692
+ var now = Date.now();
693
+ var keysToDelete = [];
694
+ var _iterator = _createForOfIteratorHelper(tokenRefreshPromises.entries()),
695
+ _step;
696
+ try {
697
+ for (_iterator.s(); !(_step = _iterator.n()).done;) {
698
+ var _step$value = _slicedToArray(_step.value, 2),
699
+ key = _step$value[0],
700
+ _step$value$ = _step$value[1],
701
+ promise = _step$value$.promise,
702
+ timestamp = _step$value$.timestamp;
703
+ // 如果Promise已经存在超过5分钟,可能出现了问题,清理掉
704
+ if (now - timestamp > 5 * 60 * 1000) {
705
+ keysToDelete.push(key);
706
+ console.warn("\u6E05\u7406\u8D85\u65F6\u7684token\u5237\u65B0Promise (key: ".concat(key, ", \u5B58\u5728\u65F6\u95F4: ").concat((now - timestamp) / 1000, "s)"));
707
+ }
708
+ }
709
+ } catch (err) {
710
+ _iterator.e(err);
711
+ } finally {
712
+ _iterator.f();
713
+ }
714
+ keysToDelete.forEach(function (key) {
715
+ return tokenRefreshPromises["delete"](key);
716
+ });
717
+ if (keysToDelete.length > 0) {
718
+ console.log("\u6E05\u7406\u4E86 ".concat(keysToDelete.length, " \u4E2A\u8D85\u65F6\u7684token\u5237\u65B0Promise"));
719
+ }
720
+ }, 60000); // 每分钟清理一次
721
+
682
722
  // 转为对象
683
723
  var toObject = function toObject(data) {
684
724
  if (Buffer.isBuffer(data)) return JSON.parse(data.toString());
@@ -757,17 +797,40 @@ function getAccessToken(access_token_file, url, data, appID) {
757
797
  });
758
798
  }
759
799
 
760
- // 添加 User-Agent
761
- var addAuthorization = /*#__PURE__*/function () {
762
- var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(header, appID, token, clientSecret, apiVersion) {
763
- var now_time, url, data, access_token_file, default_token_json, access_token, expires_in, access_token_json;
800
+ // 等待Promise完成,带超时机制
801
+ var waitForPromiseWithTimeout = /*#__PURE__*/function () {
802
+ var _ref = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(promise) {
803
+ var timeoutMs,
804
+ timeoutPromise,
805
+ _args = arguments;
764
806
  return _regeneratorRuntime.wrap(function (_context) {
765
807
  while (1) switch (_context.prev = _context.next) {
766
808
  case 0:
767
- if (!(apiVersion == 'v2')) {
768
- _context.next = 3;
769
- break;
770
- }
809
+ timeoutMs = _args.length > 1 && _args[1] !== undefined ? _args[1] : 10000;
810
+ timeoutPromise = new Promise(function (_, reject) {
811
+ setTimeout(function () {
812
+ return reject(new Error("Token refresh timeout after ".concat(timeoutMs, "ms")));
813
+ }, timeoutMs);
814
+ });
815
+ return _context.abrupt("return", Promise.race([promise, timeoutPromise]));
816
+ case 1:
817
+ case "end":
818
+ return _context.stop();
819
+ }
820
+ }, _callee);
821
+ }));
822
+ return function waitForPromiseWithTimeout(_x) {
823
+ return _ref.apply(this, arguments);
824
+ };
825
+ }();
826
+
827
+ // 获取或刷新token(带并发控制和超时保护)
828
+ var getOrRefreshToken = /*#__PURE__*/function () {
829
+ var _ref2 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee3(appID, clientSecret) {
830
+ var now_time, url, data, access_token_file, access_token, expires_in, needsRefresh, access_token_json, expiresStr, parsed, default_token_json, key, existingEntry, refreshPromise, _t2;
831
+ return _regeneratorRuntime.wrap(function (_context3) {
832
+ while (1) switch (_context3.prev = _context3.next) {
833
+ case 0:
771
834
  now_time = getTimeStampNumber();
772
835
  url = 'https://bots.qq.com/app/getAppAccessToken';
773
836
  data = {
@@ -775,55 +838,149 @@ var addAuthorization = /*#__PURE__*/function () {
775
838
  clientSecret: clientSecret
776
839
  };
777
840
  access_token_file = './access_token.json';
778
- default_token_json = {
779
- bot: _defineProperty({}, appID, {
780
- access_token: "",
781
- expires_in: ""
782
- })
783
- };
784
841
  access_token = '';
785
842
  expires_in = 0;
843
+ needsRefresh = false; // 首先读取当前token状态
786
844
  try {
787
- // 判断文件是否存在
788
845
  if (fs.existsSync(access_token_file)) {
789
- // 执行文件存在时的处理逻辑
790
- access_token_json = JSON.parse(fs.readFileSync(access_token_file).toString()); // 判断是否存在appID
846
+ access_token_json = JSON.parse(fs.readFileSync(access_token_file).toString());
791
847
  if (access_token_json.hasOwnProperty('bot') && access_token_json['bot'].hasOwnProperty(appID)) {
792
848
  access_token = access_token_json['bot'][appID]['access_token'];
793
- expires_in = parseInt(access_token_json['bot'][appID]['expires_in']);
849
+ expiresStr = access_token_json['bot'][appID]['expires_in'];
850
+ if (expiresStr && typeof expiresStr === 'string') {
851
+ parsed = parseInt(expiresStr);
852
+ if (!isNaN(parsed) && parsed > 0) {
853
+ expires_in = parsed;
854
+ } else {
855
+ console.log('expires_in解析失败,重置为0:', expiresStr);
856
+ expires_in = 0;
857
+ }
858
+ } else {
859
+ expires_in = 0;
860
+ }
794
861
  }
795
862
  } else {
796
- // 执行文件不存在时的处理逻辑
863
+ default_token_json = {
864
+ bot: _defineProperty({}, appID, {
865
+ access_token: "",
866
+ expires_in: ""
867
+ })
868
+ };
797
869
  fs.writeFileSync(access_token_file, JSON.stringify(default_token_json, null, 2), 'utf-8');
870
+ needsRefresh = true;
798
871
  }
799
872
  } catch (err) {
800
873
  console.error('读取文件时发生错误:', err);
874
+ expires_in = 0;
875
+ needsRefresh = true;
801
876
  }
802
- // 判断access_token是否存在,不存在或者已经过期就重新获取
803
- if (!(access_token === '' || now_time > expires_in - 50)) {
804
- _context.next = 2;
877
+
878
+ // 如果token有效且不需要刷新,直接返回
879
+ if (!(access_token !== '' && now_time <= expires_in - 50 && !needsRefresh)) {
880
+ _context3.next = 1;
805
881
  break;
806
882
  }
807
- console.log('access_token', access_token, 'expire_time', expires_in);
808
- _context.next = 1;
809
- return getAccessToken(access_token_file, url, data, appID);
883
+ return _context3.abrupt("return", access_token);
810
884
  case 1:
811
- access_token = _context.sent;
812
- case 2:
885
+ // 需要刷新token,进入并发控制逻辑
886
+ key = "".concat(appID, ":").concat(clientSecret);
887
+ existingEntry = tokenRefreshPromises.get(key);
888
+ if (!existingEntry) {
889
+ _context3.next = 5;
890
+ break;
891
+ }
892
+ _context3.prev = 2;
893
+ // 等待已有的刷新完成,但设置超时防止无限等待
894
+ console.log("\u7B49\u5F85\u5DF2\u6709\u7684token\u5237\u65B0\u5B8C\u6210 (appID: ".concat(appID, ")"));
895
+ _context3.next = 3;
896
+ return waitForPromiseWithTimeout(existingEntry.promise, 10000);
897
+ case 3:
898
+ return _context3.abrupt("return", _context3.sent);
899
+ case 4:
900
+ _context3.prev = 4;
901
+ _t2 = _context3["catch"](2);
902
+ console.warn("\u7B49\u5F85token\u5237\u65B0\u8D85\u65F6\u6216\u5931\u8D25\uFF0C\u91CD\u65B0\u53D1\u8D77\u5237\u65B0 (appID: ".concat(appID, "):"), _t2);
903
+ // 移除失败的Promise,让新的请求可以重新发起刷新
904
+ tokenRefreshPromises["delete"](key);
905
+ case 5:
906
+ // 创建新的刷新Promise
907
+ refreshPromise = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee2() {
908
+ var new_token, _t;
909
+ return _regeneratorRuntime.wrap(function (_context2) {
910
+ while (1) switch (_context2.prev = _context2.next) {
911
+ case 0:
912
+ _context2.prev = 0;
913
+ console.log('开始刷新access_token - 当前token:', access_token, '过期时间:', expires_in, '当前时间:', now_time);
914
+ _context2.next = 1;
915
+ return getAccessToken(access_token_file, url, data, appID);
916
+ case 1:
917
+ new_token = _context2.sent;
918
+ console.log('access_token刷新成功,新token:', new_token);
919
+ return _context2.abrupt("return", new_token);
920
+ case 2:
921
+ _context2.prev = 2;
922
+ _t = _context2["catch"](0);
923
+ console.error("Token\u5237\u65B0\u5931\u8D25 (appID: ".concat(appID, "):"), _t);
924
+ throw _t;
925
+ case 3:
926
+ _context2.prev = 3;
927
+ // 刷新完成后移除Promise
928
+ tokenRefreshPromises["delete"](key);
929
+ return _context2.finish(3);
930
+ case 4:
931
+ case "end":
932
+ return _context2.stop();
933
+ }
934
+ }, _callee2, null, [[0, 2, 3, 4]]);
935
+ }))(); // 存储Promise和时间戳以便其他并发请求使用和超时清理
936
+ tokenRefreshPromises.set(key, {
937
+ promise: refreshPromise,
938
+ timestamp: Date.now()
939
+ });
940
+ _context3.next = 6;
941
+ return refreshPromise;
942
+ case 6:
943
+ return _context3.abrupt("return", _context3.sent);
944
+ case 7:
945
+ case "end":
946
+ return _context3.stop();
947
+ }
948
+ }, _callee3, null, [[2, 4]]);
949
+ }));
950
+ return function getOrRefreshToken(_x2, _x3) {
951
+ return _ref2.apply(this, arguments);
952
+ };
953
+ }();
954
+
955
+ // 添加 User-Agent
956
+ var addAuthorization = /*#__PURE__*/function () {
957
+ var _ref4 = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee4(header, appID, token, clientSecret, apiVersion) {
958
+ var access_token;
959
+ return _regeneratorRuntime.wrap(function (_context4) {
960
+ while (1) switch (_context4.prev = _context4.next) {
961
+ case 0:
962
+ if (!(apiVersion == 'v2')) {
963
+ _context4.next = 2;
964
+ break;
965
+ }
966
+ _context4.next = 1;
967
+ return getOrRefreshToken(appID, clientSecret);
968
+ case 1:
969
+ access_token = _context4.sent;
813
970
  // 更新header['Authorization']
814
971
  header['Authorization'] = "QQBot ".concat(access_token);
815
- _context.next = 4;
972
+ _context4.next = 3;
816
973
  break;
817
- case 3:
974
+ case 2:
818
975
  header['Authorization'] = "Bot ".concat(appID, ".").concat(token);
819
- case 4:
976
+ case 3:
820
977
  case "end":
821
- return _context.stop();
978
+ return _context4.stop();
822
979
  }
823
- }, _callee);
980
+ }, _callee4);
824
981
  }));
825
- return function addAuthorization(_x, _x2, _x3, _x4, _x5) {
826
- return _ref.apply(this, arguments);
982
+ return function addAuthorization(_x4, _x5, _x6, _x7, _x8) {
983
+ return _ref4.apply(this, arguments);
827
984
  };
828
985
  }();
829
986
  // 组装完整Url
@@ -1968,41 +2125,78 @@ var OpenAPI = /*#__PURE__*/function () {
1968
2125
  // 基础rest请求
1969
2126
  }, {
1970
2127
  key: "request",
1971
- value: function request(options) {
1972
- var _this$config = this.config,
1973
- appID = _this$config.appID,
1974
- token = _this$config.token,
1975
- clientSecret = _this$config.clientSecret;
1976
- options.headers = _objectSpread({}, options.headers);
1977
- var apiVersion = options.apiVersion || 'v2';
1978
- // 添加 UA
1979
- addUserAgent(options.headers, appID, apiVersion);
1980
- // 添加鉴权信息
1981
- addAuthorization(options.headers, appID, token, clientSecret, apiVersion);
1982
- // 组装完整Url
1983
- var botUrl = buildUrl(options.url, this.config.sandbox);
1984
-
1985
- // 简化错误信息,后续可考虑通过中间件形式暴露给用户自行处理
1986
- resty.useRes(function (result) {
1987
- return result;
1988
- }, function (error) {
1989
- var _error$response, _error$response2;
1990
- var traceid = error === null || error === void 0 || (_error$response = error.response) === null || _error$response === void 0 || (_error$response = _error$response.headers) === null || _error$response === void 0 ? void 0 : _error$response['x-tps-trace-id'];
1991
- if (error !== null && error !== void 0 && (_error$response2 = error.response) !== null && _error$response2 !== void 0 && _error$response2.data) {
1992
- return Promise.reject(_objectSpread(_objectSpread({}, error.response.data), {}, {
1993
- traceid: traceid
1994
- }));
1995
- }
1996
- if (error !== null && error !== void 0 && error.response) {
1997
- return Promise.reject(_objectSpread(_objectSpread({}, error.response), {}, {
1998
- traceid: traceid
1999
- }));
2000
- }
2001
- return Promise.reject(error);
2002
- });
2003
- var client = resty.create(options);
2004
- return client.request(botUrl, options);
2005
- }
2128
+ value: function () {
2129
+ var _request = _asyncToGenerator(/*#__PURE__*/_regeneratorRuntime.mark(function _callee(options) {
2130
+ var _this$config, appID, token, clientSecret, apiVersion, botUrl, client, retryClient, _t, _t2;
2131
+ return _regeneratorRuntime.wrap(function (_context) {
2132
+ while (1) switch (_context.prev = _context.next) {
2133
+ case 0:
2134
+ _this$config = this.config, appID = _this$config.appID, token = _this$config.token, clientSecret = _this$config.clientSecret;
2135
+ options.headers = _objectSpread({}, options.headers);
2136
+ apiVersion = options.apiVersion || 'v2'; // 添加 UA
2137
+ addUserAgent(options.headers, appID, apiVersion);
2138
+ // 添加鉴权信息
2139
+ _context.next = 1;
2140
+ return addAuthorization(options.headers, appID, token, clientSecret, apiVersion);
2141
+ case 1:
2142
+ // 组装完整Url
2143
+ botUrl = buildUrl(options.url, this.config.sandbox); // 简化错误信息,后续可考虑通过中间件形式暴露给用户自行处理
2144
+ resty.useRes(function (result) {
2145
+ return result;
2146
+ }, function (error) {
2147
+ var _error$response, _error$response2;
2148
+ var traceid = error === null || error === void 0 || (_error$response = error.response) === null || _error$response === void 0 || (_error$response = _error$response.headers) === null || _error$response === void 0 ? void 0 : _error$response['x-tps-trace-id'];
2149
+ if (error !== null && error !== void 0 && (_error$response2 = error.response) !== null && _error$response2 !== void 0 && _error$response2.data) {
2150
+ return Promise.reject(_objectSpread(_objectSpread({}, error.response.data), {}, {
2151
+ traceid: traceid
2152
+ }));
2153
+ }
2154
+ if (error !== null && error !== void 0 && error.response) {
2155
+ return Promise.reject(_objectSpread(_objectSpread({}, error.response), {}, {
2156
+ traceid: traceid
2157
+ }));
2158
+ }
2159
+ return Promise.reject(error);
2160
+ });
2161
+ client = resty.create(options);
2162
+ _context.prev = 2;
2163
+ _context.next = 3;
2164
+ return client.request(botUrl, options);
2165
+ case 3:
2166
+ return _context.abrupt("return", _context.sent);
2167
+ case 4:
2168
+ _context.prev = 4;
2169
+ _t = _context["catch"](2);
2170
+ if (!((_t === null || _t === void 0 ? void 0 : _t.code) === 11201 || (_t === null || _t === void 0 ? void 0 : _t.err_code) === 40012001)) {
2171
+ _context.next = 8;
2172
+ break;
2173
+ }
2174
+ console.log('检测到认证失败,尝试重试请求...');
2175
+ _context.prev = 5;
2176
+ // 由于addAuthorization现在是并发安全的,重试时会自动获取最新token
2177
+ retryClient = resty.create(options);
2178
+ _context.next = 6;
2179
+ return retryClient.request(botUrl, options);
2180
+ case 6:
2181
+ return _context.abrupt("return", _context.sent);
2182
+ case 7:
2183
+ _context.prev = 7;
2184
+ _t2 = _context["catch"](5);
2185
+ console.log('重试请求仍然失败:', _t2);
2186
+ throw _t2;
2187
+ case 8:
2188
+ throw _t;
2189
+ case 9:
2190
+ case "end":
2191
+ return _context.stop();
2192
+ }
2193
+ }, _callee, this, [[2, 4], [5, 7]]);
2194
+ }));
2195
+ function request(_x) {
2196
+ return _request.apply(this, arguments);
2197
+ }
2198
+ return request;
2199
+ }()
2006
2200
  }], [{
2007
2201
  key: "newClient",
2008
2202
  value: function newClient(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "woodenfish-bot",
3
- "version": "4.4.4",
3
+ "version": "4.4.6",
4
4
  "description": "woodenfish-bot",
5
5
  "publishConfig": {
6
6
  "registry": "https://registry.npmjs.org/"