uapi-browser-sdk 0.1.13 → 0.1.14

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/README.md CHANGED
@@ -18,11 +18,13 @@ npm i uapi-browser-sdk
18
18
  ```ts
19
19
  import { UapiClient } from 'uapi-browser-sdk'
20
20
 
21
- const client = new UapiClient('https://uapis.cn/api/v1')
22
- const result = await client.social.getSocialQqUserinfo({ qq: '10001' })
21
+ const client = new UapiClient('https://uapis.cn', 'YOUR_API_KEY')
22
+ const result = await client.misc.getMiscHotboard({ type: 'weibo' })
23
23
  console.log(result)
24
24
  ```
25
25
 
26
+ 这个接口默认只要传 `type` 就可以拿当前热榜。`time`、`keyword`、`timeStart`、`timeEnd`、`limit`、`sources` 都是按场景再传的可选参数。
27
+
26
28
  ## CDN 引入
27
29
 
28
30
  `uapi-browser-sdk` 发布在 npm,因而可以直接通过常见 CDN 以 ES Module 方式加载。建议在生产环境固定版本号(`@latest` 仅用于快速试用)。
@@ -33,7 +35,7 @@ console.log(result)
33
35
  <script type="module">
34
36
  import { UapiClient } from 'https://cdn.jsdelivr.net/npm/uapi-browser-sdk@latest/dist/index.js';
35
37
 
36
- const client = new UapiClient('https://uapis.cn/api/v1');
38
+ const client = new UapiClient('https://uapis.cn', 'YOUR_API_KEY');
37
39
  const data = await client.social.getSocialQqUserinfo({ qq: '10001' });
38
40
  console.log(data);
39
41
  </script>
@@ -70,6 +72,63 @@ SDK 采用原生 `fetch`,自动补上 `Authorization` 头且不依赖任何 No
70
72
 
71
73
  如果你需要查看字段细节或内部逻辑,仓库中的 `./internal` 目录同步保留了由 `openapi-generator` 生成的完整结构体,随时可供参考。
72
74
 
75
+ ## 响应元信息
76
+
77
+ 每次请求完成后,SDK 会自动把响应 Header 解析成结构化的 `ResponseMeta`,你不用自己拆原始字符串。
78
+
79
+ 成功时可以通过 `client.lastResponseMeta` 读取,失败时可以通过 `err.meta` 读取,两条路径拿到的是同一套字段。
80
+
81
+ ```ts
82
+ import { UapiClient, UapiError } from 'uapi-browser-sdk'
83
+
84
+ const client = new UapiClient('https://uapis.cn', 'YOUR_API_KEY')
85
+
86
+ // 成功路径
87
+ await client.social.getSocialQqUserinfo({ qq: '10001' })
88
+ const meta = client.lastResponseMeta
89
+ if (meta) {
90
+ console.log('这次请求原价:', meta.creditsRequested ?? 0, '积分')
91
+ console.log('这次实际扣费:', meta.creditsCharged ?? 0, '积分')
92
+ console.log('特殊计价:', meta.creditsPricing ?? '原价')
93
+ console.log('余额剩余:', meta.balanceRemainingCents ?? 0, '分')
94
+ console.log('资源包剩余:', meta.quotaRemainingCredits ?? 0, '积分')
95
+ console.log('当前有效额度桶:', meta.activeQuotaBuckets ?? 0)
96
+ console.log('额度用空即停:', meta.stopOnEmpty ?? false)
97
+ console.log('Key QPS:', meta.billingKeyRateRemaining ?? 0, '/', meta.billingKeyRateLimit ?? 0, meta.billingKeyRateUnit ?? 'req')
98
+ console.log('Request ID:', meta.requestId)
99
+ }
100
+
101
+ // 失败路径
102
+ try {
103
+ await client.social.getSocialQqUserinfo({ qq: '10001' })
104
+ } catch (err) {
105
+ if (err instanceof UapiError && err.meta) {
106
+ console.log('Retry-After 秒数:', err.meta.retryAfterSeconds ?? null)
107
+ console.log('Retry-After 原始值:', err.meta.retryAfterRaw ?? null)
108
+ console.log('访客 QPS:', err.meta.visitorRateRemaining ?? 0, '/', err.meta.visitorRateLimit ?? 0)
109
+ console.log('Request ID:', err.meta.requestId)
110
+ }
111
+ }
112
+ ```
113
+
114
+ 常用字段一览:
115
+
116
+ | 字段 | 说明 |
117
+ |------|------|
118
+ | `creditsRequested` | 这次请求原本要扣多少积分,也就是请求价 |
119
+ | `creditsCharged` | 这次请求实际扣了多少积分 |
120
+ | `creditsPricing` | 特殊计价原因,例如缓存半价 `cache-hit-half-price` |
121
+ | `balanceRemainingCents` | 账户余额剩余(分) |
122
+ | `quotaRemainingCredits` | 资源包剩余积分 |
123
+ | `activeQuotaBuckets` | 当前还有多少个有效额度桶参与计费 |
124
+ | `stopOnEmpty` | 额度耗尽后是否直接停止服务 |
125
+ | `retryAfterSeconds` / `retryAfterRaw` | 限流后的等待时长;当服务端返回 HTTP 时间字符串时看 `retryAfterRaw` |
126
+ | `requestId` | 请求唯一 ID,排障时使用 |
127
+ | `billingKeyRateLimit` / `billingKeyRateRemaining` | Billing Key 当前 QPS 规则的上限与剩余 |
128
+ | `billingIpRateLimit` / `billingIpRateRemaining` | Billing Key 单 IP 当前 QPS 规则的上限与剩余 |
129
+ | `visitorRateLimit` / `visitorRateRemaining` | 访客当前 QPS 规则的上限与剩余 |
130
+ | `rateLimitPolicies` / `rateLimits` | 完整结构化限流策略数据 |
131
+
73
132
  ## 错误模型概览
74
133
 
75
134
  | HTTP 状态码 | SDK 错误类型 | 附加信息 |
@@ -12,6 +12,7 @@ export interface RateLimitStateEntry {
12
12
  }
13
13
  export interface ResponseMeta {
14
14
  requestId?: string;
15
+ retryAfterRaw?: string;
15
16
  retryAfterSeconds?: number;
16
17
  debitStatus?: string;
17
18
  creditsRequested?: number;
@@ -29,6 +30,21 @@ export interface ResponseMeta {
29
30
  quotaRemainingCredits?: number;
30
31
  visitorQuotaLimitCredits?: number;
31
32
  visitorQuotaRemainingCredits?: number;
33
+ billingKeyRateLimit?: number;
34
+ billingKeyRateRemaining?: number;
35
+ billingKeyRateUnit?: string;
36
+ billingKeyRateWindowSeconds?: number;
37
+ billingKeyRateResetAfterSeconds?: number;
38
+ billingIpRateLimit?: number;
39
+ billingIpRateRemaining?: number;
40
+ billingIpRateUnit?: string;
41
+ billingIpRateWindowSeconds?: number;
42
+ billingIpRateResetAfterSeconds?: number;
43
+ visitorRateLimit?: number;
44
+ visitorRateRemaining?: number;
45
+ visitorRateUnit?: string;
46
+ visitorRateWindowSeconds?: number;
47
+ visitorRateResetAfterSeconds?: number;
32
48
  rawHeaders: Record<string, string>;
33
49
  }
34
50
  export declare class UapiError extends Error {
@@ -161,8 +161,15 @@ export function extractMetaFromHeaders(headers) {
161
161
  resetAfterSeconds: parseNumber(item.params.t),
162
162
  };
163
163
  }
164
+ const billingKeyRatePolicy = rateLimitPolicies['billing-key-rate'];
165
+ const billingKeyRateState = rateLimits['billing-key-rate'];
166
+ const billingIpRatePolicy = rateLimitPolicies['billing-ip-rate'];
167
+ const billingIpRateState = rateLimits['billing-ip-rate'];
168
+ const visitorRatePolicy = rateLimitPolicies['visitor-rate'];
169
+ const visitorRateState = rateLimits['visitor-rate'];
164
170
  return {
165
171
  requestId: rawHeaders['x-request-id'],
172
+ retryAfterRaw: rawHeaders['retry-after'],
166
173
  retryAfterSeconds: parseNumber(rawHeaders['retry-after']),
167
174
  debitStatus: rawHeaders['uapi-debit-status'],
168
175
  creditsRequested: parseNumber(rawHeaders['uapi-credits-requested']),
@@ -180,6 +187,21 @@ export function extractMetaFromHeaders(headers) {
180
187
  quotaRemainingCredits: rateLimits['billing-quota']?.remaining,
181
188
  visitorQuotaLimitCredits: rateLimitPolicies['visitor-quota']?.quota,
182
189
  visitorQuotaRemainingCredits: rateLimits['visitor-quota']?.remaining,
190
+ billingKeyRateLimit: billingKeyRatePolicy?.quota,
191
+ billingKeyRateRemaining: billingKeyRateState?.remaining,
192
+ billingKeyRateUnit: billingKeyRatePolicy?.unit ?? billingKeyRateState?.unit,
193
+ billingKeyRateWindowSeconds: billingKeyRatePolicy?.windowSeconds,
194
+ billingKeyRateResetAfterSeconds: billingKeyRateState?.resetAfterSeconds,
195
+ billingIpRateLimit: billingIpRatePolicy?.quota,
196
+ billingIpRateRemaining: billingIpRateState?.remaining,
197
+ billingIpRateUnit: billingIpRatePolicy?.unit ?? billingIpRateState?.unit,
198
+ billingIpRateWindowSeconds: billingIpRatePolicy?.windowSeconds,
199
+ billingIpRateResetAfterSeconds: billingIpRateState?.resetAfterSeconds,
200
+ visitorRateLimit: visitorRatePolicy?.quota,
201
+ visitorRateRemaining: visitorRateState?.remaining,
202
+ visitorRateUnit: visitorRatePolicy?.unit ?? visitorRateState?.unit,
203
+ visitorRateWindowSeconds: visitorRatePolicy?.windowSeconds,
204
+ visitorRateResetAfterSeconds: visitorRateState?.resetAfterSeconds,
183
205
  rawHeaders,
184
206
  };
185
207
  }
package/dist/src/index.js CHANGED
@@ -1,8 +1,13 @@
1
1
  import { extractMetaFromHeaders, mapError } from './errors.js';
2
2
  export { UapiError, mapError, extractMetaFromHeaders } from './errors.js';
3
+ const API_PREFIX = '/api/v1';
4
+ function normalizeBaseURL(baseURL) {
5
+ const trimmed = baseURL.replace(/\/+$/, '');
6
+ return trimmed.endsWith(API_PREFIX) ? trimmed.slice(0, -API_PREFIX.length) : trimmed;
7
+ }
3
8
  export class UapiClient {
4
9
  constructor(baseURL, token) {
5
- this.baseURL = baseURL;
10
+ this.baseURL = normalizeBaseURL(baseURL);
6
11
  this.token = token;
7
12
  const clipzyZaiXianJianTieBan = new ClipzyZaiXianJianTieBanApi(this);
8
13
  this.clipzyZaiXianJianTieBan = clipzyZaiXianJianTieBan;
@@ -57,7 +62,7 @@ export class UapiClient {
57
62
  return this._lastResponseMeta;
58
63
  }
59
64
  async _request(method, path, params, body, headers, responseKind = 'json') {
60
- const url = new URL(this.baseURL.replace(/\/$/, '') + path);
65
+ const url = new URL(this.baseURL + path);
61
66
  if (params) {
62
67
  Object.entries(params).forEach(([key, value]) => {
63
68
  if (value !== undefined) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "uapi-browser-sdk",
3
- "version": "0.1.13",
3
+ "version": "0.1.14",
4
4
  "type": "module",
5
5
  "main": "dist/src/index.js",
6
6
  "types": "dist/src/index.d.ts",
package/src/errors.ts CHANGED
@@ -14,6 +14,7 @@ export interface RateLimitStateEntry {
14
14
 
15
15
  export interface ResponseMeta {
16
16
  requestId?: string
17
+ retryAfterRaw?: string
17
18
  retryAfterSeconds?: number
18
19
  debitStatus?: string
19
20
  creditsRequested?: number
@@ -31,6 +32,21 @@ export interface ResponseMeta {
31
32
  quotaRemainingCredits?: number
32
33
  visitorQuotaLimitCredits?: number
33
34
  visitorQuotaRemainingCredits?: number
35
+ billingKeyRateLimit?: number
36
+ billingKeyRateRemaining?: number
37
+ billingKeyRateUnit?: string
38
+ billingKeyRateWindowSeconds?: number
39
+ billingKeyRateResetAfterSeconds?: number
40
+ billingIpRateLimit?: number
41
+ billingIpRateRemaining?: number
42
+ billingIpRateUnit?: string
43
+ billingIpRateWindowSeconds?: number
44
+ billingIpRateResetAfterSeconds?: number
45
+ visitorRateLimit?: number
46
+ visitorRateRemaining?: number
47
+ visitorRateUnit?: string
48
+ visitorRateWindowSeconds?: number
49
+ visitorRateResetAfterSeconds?: number
34
50
  rawHeaders: Record<string, string>
35
51
  }
36
52
 
@@ -192,8 +208,16 @@ export function extractMetaFromHeaders(headers: Headers | Record<string, unknown
192
208
  }
193
209
  }
194
210
 
211
+ const billingKeyRatePolicy = rateLimitPolicies['billing-key-rate']
212
+ const billingKeyRateState = rateLimits['billing-key-rate']
213
+ const billingIpRatePolicy = rateLimitPolicies['billing-ip-rate']
214
+ const billingIpRateState = rateLimits['billing-ip-rate']
215
+ const visitorRatePolicy = rateLimitPolicies['visitor-rate']
216
+ const visitorRateState = rateLimits['visitor-rate']
217
+
195
218
  return {
196
219
  requestId: rawHeaders['x-request-id'],
220
+ retryAfterRaw: rawHeaders['retry-after'],
197
221
  retryAfterSeconds: parseNumber(rawHeaders['retry-after']),
198
222
  debitStatus: rawHeaders['uapi-debit-status'],
199
223
  creditsRequested: parseNumber(rawHeaders['uapi-credits-requested']),
@@ -211,6 +235,21 @@ export function extractMetaFromHeaders(headers: Headers | Record<string, unknown
211
235
  quotaRemainingCredits: rateLimits['billing-quota']?.remaining,
212
236
  visitorQuotaLimitCredits: rateLimitPolicies['visitor-quota']?.quota,
213
237
  visitorQuotaRemainingCredits: rateLimits['visitor-quota']?.remaining,
238
+ billingKeyRateLimit: billingKeyRatePolicy?.quota,
239
+ billingKeyRateRemaining: billingKeyRateState?.remaining,
240
+ billingKeyRateUnit: billingKeyRatePolicy?.unit ?? billingKeyRateState?.unit,
241
+ billingKeyRateWindowSeconds: billingKeyRatePolicy?.windowSeconds,
242
+ billingKeyRateResetAfterSeconds: billingKeyRateState?.resetAfterSeconds,
243
+ billingIpRateLimit: billingIpRatePolicy?.quota,
244
+ billingIpRateRemaining: billingIpRateState?.remaining,
245
+ billingIpRateUnit: billingIpRatePolicy?.unit ?? billingIpRateState?.unit,
246
+ billingIpRateWindowSeconds: billingIpRatePolicy?.windowSeconds,
247
+ billingIpRateResetAfterSeconds: billingIpRateState?.resetAfterSeconds,
248
+ visitorRateLimit: visitorRatePolicy?.quota,
249
+ visitorRateRemaining: visitorRateState?.remaining,
250
+ visitorRateUnit: visitorRatePolicy?.unit ?? visitorRateState?.unit,
251
+ visitorRateWindowSeconds: visitorRatePolicy?.windowSeconds,
252
+ visitorRateResetAfterSeconds: visitorRateState?.resetAfterSeconds,
214
253
  rawHeaders,
215
254
  }
216
255
  }
package/src/index.ts CHANGED
@@ -2,6 +2,14 @@ import type * as Internal from '../internal/src/index.js'
2
2
  import { UapiError, type ResponseMeta, extractMetaFromHeaders, mapError } from './errors.js'
3
3
  export { UapiError, mapError, extractMetaFromHeaders } from './errors.js'
4
4
  export type { RateLimitPolicyEntry, RateLimitStateEntry, ResponseMeta } from './errors.js'
5
+
6
+ const API_PREFIX = '/api/v1'
7
+
8
+ function normalizeBaseURL(baseURL: string): string {
9
+ const trimmed = baseURL.replace(/\/+$/, '')
10
+ return trimmed.endsWith(API_PREFIX) ? trimmed.slice(0, -API_PREFIX.length) : trimmed
11
+ }
12
+
5
13
  export type GetClipzyGetResponse =
6
14
  Internal.GetClipzyGet200Response
7
15
  export interface GetClipzyGetArgs {
@@ -830,7 +838,7 @@ export class UapiClient {
830
838
  readonly "智能搜索": ZhiNengSouSuoApi
831
839
 
832
840
  constructor(baseURL: string, token?: string) {
833
- this.baseURL = baseURL
841
+ this.baseURL = normalizeBaseURL(baseURL)
834
842
  this.token = token
835
843
  const clipzyZaiXianJianTieBan = new ClipzyZaiXianJianTieBanApi(this)
836
844
  this.clipzyZaiXianJianTieBan = clipzyZaiXianJianTieBan
@@ -894,7 +902,7 @@ export class UapiClient {
894
902
  headers?: Record<string, string>,
895
903
  responseKind: 'json' | 'text' | 'arrayBuffer' = 'json',
896
904
  ) {
897
- const url = new URL(this.baseURL.replace(/\/$/, '') + path)
905
+ const url = new URL(this.baseURL + path)
898
906
  if (params) {
899
907
  Object.entries(params).forEach(([key, value]) => {
900
908
  if (value !== undefined) {