lxns-rhythm-api 0.1.9 → 0.1.10
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 +42 -0
- package/dist/index.cjs +253 -81
- package/dist/index.d.ts +224 -56
- package/dist/index.js +251 -81
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -72,6 +72,45 @@ const jacket = await client.maimai.getAsset("jacket", 114);
|
|
|
72
72
|
const icon = await client.chunithm.getAsset("icon", 1);
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
+
## OAuth 流程
|
|
76
|
+
|
|
77
|
+
```ts
|
|
78
|
+
import { LxnsOAuthClient } from "lxns-rhythm-api";
|
|
79
|
+
|
|
80
|
+
const client = new LxnsOAuthClient({
|
|
81
|
+
clientId: "<your-client-id>",
|
|
82
|
+
redirectURI: "https://example.com/callback",
|
|
83
|
+
clientSecret: "<your-client-secret>", // PKCE 可不传
|
|
84
|
+
});
|
|
85
|
+
|
|
86
|
+
// 1) 生成授权链接,跳转给用户
|
|
87
|
+
const authorizeURL = client.createAuthorizeURL({
|
|
88
|
+
scope: ["read_user_profile", "read_player"],
|
|
89
|
+
state: "nonce", // 可选
|
|
90
|
+
codeChallenge: "<code-challenge>", // PKCE 可选
|
|
91
|
+
codeChallengeMethod: "S256",
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
// 2) 回调拿到 code 后换 token
|
|
95
|
+
const token = await client.exchangeCodeForToken({
|
|
96
|
+
code: "<auth-code>",
|
|
97
|
+
codeVerifier: "<code-verifier>", // PKCE 模式传这个
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// 3) 用 access_token 创建授权态客户端
|
|
101
|
+
const authorized = client.createAuthorizedClient(token.access_token);
|
|
102
|
+
|
|
103
|
+
// 4) 调用 OAuth 用户接口和 personal 接口
|
|
104
|
+
const profile = await authorized.user.getProfile();
|
|
105
|
+
const maimaiPlayer = await authorized.maimai.getPlayer();
|
|
106
|
+
const chunithmPlayer = await authorized.chunithm.getPlayer();
|
|
107
|
+
|
|
108
|
+
// 5) 刷新 token
|
|
109
|
+
const nextToken = await client.refreshAccessToken({
|
|
110
|
+
refreshToken: token.refresh_token,
|
|
111
|
+
});
|
|
112
|
+
```
|
|
113
|
+
|
|
75
114
|
## 错误处理
|
|
76
115
|
|
|
77
116
|
SDK 会将 API 错误统一抛为 `LxnsApiError`。
|
|
@@ -112,6 +151,7 @@ try {
|
|
|
112
151
|
```ts
|
|
113
152
|
import {
|
|
114
153
|
LxnsApiClient,
|
|
154
|
+
LxnsOAuthClient,
|
|
115
155
|
LxnsApiError,
|
|
116
156
|
isLxnsApiError,
|
|
117
157
|
MaimaiModels,
|
|
@@ -123,6 +163,8 @@ import {
|
|
|
123
163
|
|
|
124
164
|
- [Lxns maimai API](https://maimai.lxns.net/docs/api/maimai)
|
|
125
165
|
- [Lxns chunithm API](https://maimai.lxns.net/docs/api/chunithm)
|
|
166
|
+
- [Lxns OAuth API](https://maimai.lxns.net/docs/api/oauth)
|
|
167
|
+
- [Lxns OAuth 接入指南](https://maimai.lxns.net/docs/oauth-guide)
|
|
126
168
|
|
|
127
169
|
## 构建与测试
|
|
128
170
|
|
package/dist/index.cjs
CHANGED
|
@@ -23,7 +23,10 @@ __export(index_exports, {
|
|
|
23
23
|
ChunithmModels: () => models_exports,
|
|
24
24
|
LxnsApiClient: () => LxnsApiClient,
|
|
25
25
|
LxnsApiError: () => LxnsApiError,
|
|
26
|
+
LxnsOAuthClient: () => LxnsOAuthClient,
|
|
26
27
|
MaimaiModels: () => models_exports2,
|
|
28
|
+
OAuthAuthorizedApi: () => OAuthAuthorizedApi,
|
|
29
|
+
OAuthUserApi: () => OAuthUserApi,
|
|
27
30
|
isAuthError: () => isAuthError,
|
|
28
31
|
isLxnsApiError: () => isLxnsApiError,
|
|
29
32
|
isNotFoundError: () => isNotFoundError,
|
|
@@ -61,6 +64,106 @@ var LevelIndex2 = /* @__PURE__ */ ((LevelIndex3) => {
|
|
|
61
64
|
return LevelIndex3;
|
|
62
65
|
})(LevelIndex2 || {});
|
|
63
66
|
|
|
67
|
+
// src/api/chunithm/personal-api.ts
|
|
68
|
+
var ChunithmPersonalApi = class {
|
|
69
|
+
constructor(http) {
|
|
70
|
+
this.http = http;
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* 获取玩家信息
|
|
74
|
+
* GET /api/v0/user/chunithm/player
|
|
75
|
+
*/
|
|
76
|
+
async getPlayer() {
|
|
77
|
+
return this.http.get("player").json();
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* 获取玩家所有成绩
|
|
81
|
+
* GET /api/v0/user/chunithm/player/scores
|
|
82
|
+
*/
|
|
83
|
+
async getScores() {
|
|
84
|
+
return this.http.get("scores").json();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 上传玩家成绩
|
|
88
|
+
* POST /api/v0/user/chunithm/player/scores
|
|
89
|
+
*/
|
|
90
|
+
async postScores(scores) {
|
|
91
|
+
const body = { scores };
|
|
92
|
+
return this.http.post("scores", { json: body }).json();
|
|
93
|
+
}
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
// src/api/maimai/personal-api.ts
|
|
97
|
+
var MaimaiPersonalApi = class {
|
|
98
|
+
constructor(http) {
|
|
99
|
+
this.http = http;
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* 获取玩家信息
|
|
103
|
+
* GET /api/v0/user/maimai/player
|
|
104
|
+
* @returns PlayerInfo
|
|
105
|
+
*/
|
|
106
|
+
async getPlayer() {
|
|
107
|
+
return this.http.get("player").json();
|
|
108
|
+
}
|
|
109
|
+
/**
|
|
110
|
+
* 获取玩家所有成绩
|
|
111
|
+
* GET /api/v0/user/maimai/player/scores
|
|
112
|
+
* @returns PlayerScores
|
|
113
|
+
*/
|
|
114
|
+
async getScores() {
|
|
115
|
+
return this.http.get("scores").json();
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* 上传玩家成绩
|
|
119
|
+
* POST /api/v0/user/maimai/player/scores
|
|
120
|
+
* @param scores 成绩列表
|
|
121
|
+
* @returns 上传结果
|
|
122
|
+
*/
|
|
123
|
+
async postScores(scores) {
|
|
124
|
+
const body = { scores };
|
|
125
|
+
return this.http.post("scores", { json: body }).json();
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// src/api/oauth/user.ts
|
|
130
|
+
var OAuthUserApi = class {
|
|
131
|
+
constructor(http) {
|
|
132
|
+
this.http = http;
|
|
133
|
+
}
|
|
134
|
+
/**
|
|
135
|
+
* 获取用户基本信息
|
|
136
|
+
* GET /api/v0/user/profile
|
|
137
|
+
* @returns 用户基本信息
|
|
138
|
+
*/
|
|
139
|
+
async getProfile() {
|
|
140
|
+
return this.http.get("profile").json();
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* 获取用户个人 API 密钥
|
|
144
|
+
* GET /api/v0/user/token
|
|
145
|
+
* @returns 用户个人 API 密钥
|
|
146
|
+
*/
|
|
147
|
+
async getToken() {
|
|
148
|
+
return this.http.get("token").json();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
// src/api/oauth/oauth.ts
|
|
153
|
+
var OAuthAuthorizedApi = class {
|
|
154
|
+
/** GET /api/v0/user/profile 与 GET /api/v0/user/token */
|
|
155
|
+
user;
|
|
156
|
+
/** OAuth 授权后可访问的 maimai personal API */
|
|
157
|
+
maimai;
|
|
158
|
+
/** OAuth 授权后可访问的 chunithm personal API */
|
|
159
|
+
chunithm;
|
|
160
|
+
constructor(userHttp, maimaiHttp, chunithmHttp) {
|
|
161
|
+
this.user = new OAuthUserApi(userHttp);
|
|
162
|
+
this.maimai = new MaimaiPersonalApi(maimaiHttp);
|
|
163
|
+
this.chunithm = new ChunithmPersonalApi(chunithmHttp);
|
|
164
|
+
}
|
|
165
|
+
};
|
|
166
|
+
|
|
64
167
|
// node_modules/.pnpm/ky@1.10.0/node_modules/ky/distribution/errors/HTTPError.js
|
|
65
168
|
var HTTPError = class extends Error {
|
|
66
169
|
response;
|
|
@@ -683,7 +786,7 @@ var createInstance = (defaults) => {
|
|
|
683
786
|
var ky = createInstance();
|
|
684
787
|
var distribution_default = ky;
|
|
685
788
|
|
|
686
|
-
// src/api/chunithm/dev.ts
|
|
789
|
+
// src/api/chunithm/dev-api.ts
|
|
687
790
|
var ChunithmDevApi = class {
|
|
688
791
|
constructor(http) {
|
|
689
792
|
this.http = http;
|
|
@@ -783,36 +886,7 @@ var ChunithmDevApi = class {
|
|
|
783
886
|
}
|
|
784
887
|
};
|
|
785
888
|
|
|
786
|
-
// src/api/chunithm/
|
|
787
|
-
var ChunithmPersonalApi = class {
|
|
788
|
-
constructor(http) {
|
|
789
|
-
this.http = http;
|
|
790
|
-
}
|
|
791
|
-
/**
|
|
792
|
-
* 获取玩家信息
|
|
793
|
-
* GET /api/v0/user/chunithm/player
|
|
794
|
-
*/
|
|
795
|
-
async getPlayer() {
|
|
796
|
-
return this.http.get("player").json();
|
|
797
|
-
}
|
|
798
|
-
/**
|
|
799
|
-
* 获取玩家所有成绩
|
|
800
|
-
* GET /api/v0/user/chunithm/player/scores
|
|
801
|
-
*/
|
|
802
|
-
async getScores() {
|
|
803
|
-
return this.http.get("scores").json();
|
|
804
|
-
}
|
|
805
|
-
/**
|
|
806
|
-
* 上传玩家成绩
|
|
807
|
-
* POST /api/v0/user/chunithm/player/scores
|
|
808
|
-
*/
|
|
809
|
-
async postScores(scores) {
|
|
810
|
-
const body = { scores };
|
|
811
|
-
return this.http.post("scores", { json: body }).json();
|
|
812
|
-
}
|
|
813
|
-
};
|
|
814
|
-
|
|
815
|
-
// src/api/chunithm/public.ts
|
|
889
|
+
// src/api/chunithm/public-api.ts
|
|
816
890
|
var ChunithmPublicApi = class {
|
|
817
891
|
constructor(http) {
|
|
818
892
|
this.http = http;
|
|
@@ -855,7 +929,7 @@ var ChunithmPublicApi = class {
|
|
|
855
929
|
}
|
|
856
930
|
};
|
|
857
931
|
|
|
858
|
-
// src/api/maimai/dev.ts
|
|
932
|
+
// src/api/maimai/dev-api.ts
|
|
859
933
|
var MaimaiDevApi = class {
|
|
860
934
|
constructor(http) {
|
|
861
935
|
this.http = http;
|
|
@@ -974,39 +1048,6 @@ var MaimaiDevApi = class {
|
|
|
974
1048
|
}
|
|
975
1049
|
};
|
|
976
1050
|
|
|
977
|
-
// src/api/maimai/personal.ts
|
|
978
|
-
var MaimaiPersonalApi = class {
|
|
979
|
-
constructor(http) {
|
|
980
|
-
this.http = http;
|
|
981
|
-
}
|
|
982
|
-
/**
|
|
983
|
-
* 获取玩家信息
|
|
984
|
-
* GET /api/v0/user/maimai/player
|
|
985
|
-
* @returns PlayerInfo
|
|
986
|
-
*/
|
|
987
|
-
async getPlayer() {
|
|
988
|
-
return this.http.get("player").json();
|
|
989
|
-
}
|
|
990
|
-
/**
|
|
991
|
-
* 获取玩家所有成绩
|
|
992
|
-
* GET /api/v0/user/maimai/player/scores
|
|
993
|
-
* @returns PlayerScores
|
|
994
|
-
*/
|
|
995
|
-
async getScores() {
|
|
996
|
-
return this.http.get("scores").json();
|
|
997
|
-
}
|
|
998
|
-
/**
|
|
999
|
-
* 上传玩家成绩
|
|
1000
|
-
* POST /api/v0/user/maimai/player/scores
|
|
1001
|
-
* @param scores 成绩列表
|
|
1002
|
-
* @returns 上传结果
|
|
1003
|
-
*/
|
|
1004
|
-
async postScores(scores) {
|
|
1005
|
-
const body = { scores };
|
|
1006
|
-
return this.http.post("scores", { json: body }).json();
|
|
1007
|
-
}
|
|
1008
|
-
};
|
|
1009
|
-
|
|
1010
1051
|
// src/api/maimai/entities/song.ts
|
|
1011
1052
|
var LevelArray = [
|
|
1012
1053
|
"basic",
|
|
@@ -1067,7 +1108,7 @@ var Song = class {
|
|
|
1067
1108
|
}
|
|
1068
1109
|
};
|
|
1069
1110
|
|
|
1070
|
-
// src/api/maimai/public.ts
|
|
1111
|
+
// src/api/maimai/public-api.ts
|
|
1071
1112
|
var MaimaiPublicApi = class {
|
|
1072
1113
|
constructor(http) {
|
|
1073
1114
|
this.http = http;
|
|
@@ -1175,7 +1216,7 @@ function isServerError(error) {
|
|
|
1175
1216
|
return typeof code === "number" && code >= 500;
|
|
1176
1217
|
}
|
|
1177
1218
|
|
|
1178
|
-
// src/client/
|
|
1219
|
+
// src/client/http-options.ts
|
|
1179
1220
|
function parseLxnsJson(text) {
|
|
1180
1221
|
const payload = JSON.parse(text);
|
|
1181
1222
|
if (payload.success === false) {
|
|
@@ -1212,7 +1253,16 @@ async function wrapKyError(error) {
|
|
|
1212
1253
|
status
|
|
1213
1254
|
);
|
|
1214
1255
|
}
|
|
1215
|
-
var
|
|
1256
|
+
var LXNS_HTTP_OPTIONS = {
|
|
1257
|
+
parseJson: parseLxnsJson,
|
|
1258
|
+
throwHttpErrors: true,
|
|
1259
|
+
hooks: {
|
|
1260
|
+
beforeError: [wrapKyError]
|
|
1261
|
+
}
|
|
1262
|
+
};
|
|
1263
|
+
|
|
1264
|
+
// src/client/lxns-api-client.ts
|
|
1265
|
+
var LxnsApiClient = class {
|
|
1216
1266
|
config;
|
|
1217
1267
|
/**
|
|
1218
1268
|
* maimai API
|
|
@@ -1222,13 +1272,6 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
1222
1272
|
* chunithm API
|
|
1223
1273
|
*/
|
|
1224
1274
|
chunithm;
|
|
1225
|
-
static BASE_OPTIONS = {
|
|
1226
|
-
parseJson: parseLxnsJson,
|
|
1227
|
-
throwHttpErrors: true,
|
|
1228
|
-
hooks: {
|
|
1229
|
-
beforeError: [wrapKyError]
|
|
1230
|
-
}
|
|
1231
|
-
};
|
|
1232
1275
|
mountGameApi({
|
|
1233
1276
|
public: publicApi,
|
|
1234
1277
|
dev,
|
|
@@ -1255,21 +1298,21 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
1255
1298
|
const { baseURL, devAccessToken, personalAccessToken } = this.config;
|
|
1256
1299
|
const maimaiHttpPublic = distribution_default.create({
|
|
1257
1300
|
prefixUrl: new URL("maimai/", baseURL),
|
|
1258
|
-
...
|
|
1301
|
+
...LXNS_HTTP_OPTIONS
|
|
1259
1302
|
});
|
|
1260
1303
|
const maimaiHttpDev = devAccessToken ? distribution_default.create({
|
|
1261
1304
|
prefixUrl: new URL("maimai/", baseURL),
|
|
1262
1305
|
headers: {
|
|
1263
1306
|
Authorization: devAccessToken
|
|
1264
1307
|
},
|
|
1265
|
-
...
|
|
1308
|
+
...LXNS_HTTP_OPTIONS
|
|
1266
1309
|
}) : void 0;
|
|
1267
1310
|
const maimaiHttpPersonal = personalAccessToken ? distribution_default.create({
|
|
1268
1311
|
prefixUrl: new URL("user/maimai/", baseURL),
|
|
1269
1312
|
headers: {
|
|
1270
1313
|
"X-User-Token": personalAccessToken
|
|
1271
1314
|
},
|
|
1272
|
-
...
|
|
1315
|
+
...LXNS_HTTP_OPTIONS
|
|
1273
1316
|
}) : void 0;
|
|
1274
1317
|
this.maimai = this.mountGameApi({
|
|
1275
1318
|
public: new MaimaiPublicApi(maimaiHttpPublic),
|
|
@@ -1287,21 +1330,21 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
1287
1330
|
});
|
|
1288
1331
|
const chunithmHttpPublic = distribution_default.create({
|
|
1289
1332
|
prefixUrl: new URL("chunithm/", baseURL),
|
|
1290
|
-
...
|
|
1333
|
+
...LXNS_HTTP_OPTIONS
|
|
1291
1334
|
});
|
|
1292
1335
|
const chunithmHttpDev = devAccessToken ? distribution_default.create({
|
|
1293
1336
|
prefixUrl: new URL("chunithm/", baseURL),
|
|
1294
1337
|
headers: {
|
|
1295
1338
|
Authorization: devAccessToken
|
|
1296
1339
|
},
|
|
1297
|
-
...
|
|
1340
|
+
...LXNS_HTTP_OPTIONS
|
|
1298
1341
|
}) : void 0;
|
|
1299
1342
|
const chunithmHttpPersonal = personalAccessToken ? distribution_default.create({
|
|
1300
1343
|
prefixUrl: new URL("user/chunithm/", baseURL),
|
|
1301
1344
|
headers: {
|
|
1302
1345
|
"X-User-Token": personalAccessToken
|
|
1303
1346
|
},
|
|
1304
|
-
...
|
|
1347
|
+
...LXNS_HTTP_OPTIONS
|
|
1305
1348
|
}) : void 0;
|
|
1306
1349
|
this.chunithm = this.mountGameApi({
|
|
1307
1350
|
public: new ChunithmPublicApi(chunithmHttpPublic),
|
|
@@ -1317,12 +1360,141 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
1317
1360
|
});
|
|
1318
1361
|
}
|
|
1319
1362
|
};
|
|
1363
|
+
|
|
1364
|
+
// src/client/lxns-oauth-client.ts
|
|
1365
|
+
var LxnsOAuthClient = class {
|
|
1366
|
+
/** 当前 OAuth 客户端配置 */
|
|
1367
|
+
config;
|
|
1368
|
+
http;
|
|
1369
|
+
/**
|
|
1370
|
+
* @param config OAuth 配置
|
|
1371
|
+
*/
|
|
1372
|
+
constructor(config) {
|
|
1373
|
+
this.config = {
|
|
1374
|
+
...config,
|
|
1375
|
+
baseURL: config.baseURL ?? "https://maimai.lxns.net/api/v0/"
|
|
1376
|
+
};
|
|
1377
|
+
this.http = distribution_default.create({
|
|
1378
|
+
prefixUrl: new URL("oauth/", this.config.baseURL),
|
|
1379
|
+
...LXNS_HTTP_OPTIONS
|
|
1380
|
+
});
|
|
1381
|
+
}
|
|
1382
|
+
/**
|
|
1383
|
+
* 生成 OAuth 授权链接(/oauth/authorize)。
|
|
1384
|
+
* @returns 可直接跳转的授权 URL
|
|
1385
|
+
*/
|
|
1386
|
+
createAuthorizeURL({
|
|
1387
|
+
scope,
|
|
1388
|
+
state,
|
|
1389
|
+
codeChallenge,
|
|
1390
|
+
codeChallengeMethod
|
|
1391
|
+
}) {
|
|
1392
|
+
const url = new URL("/oauth/authorize", this.config.baseURL);
|
|
1393
|
+
const search = new URLSearchParams({
|
|
1394
|
+
response_type: "code",
|
|
1395
|
+
client_id: this.config.clientId,
|
|
1396
|
+
redirect_uri: this.config.redirectURI,
|
|
1397
|
+
scope: Array.isArray(scope) ? scope.join(" ") : scope
|
|
1398
|
+
});
|
|
1399
|
+
if (state) {
|
|
1400
|
+
search.set("state", state);
|
|
1401
|
+
}
|
|
1402
|
+
if (codeChallenge) {
|
|
1403
|
+
search.set("code_challenge", codeChallenge);
|
|
1404
|
+
}
|
|
1405
|
+
if (codeChallengeMethod) {
|
|
1406
|
+
search.set("code_challenge_method", codeChallengeMethod);
|
|
1407
|
+
}
|
|
1408
|
+
url.search = search.toString();
|
|
1409
|
+
return url.toString();
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* 使用授权码换取访问令牌(grant_type=authorization_code)。
|
|
1413
|
+
* - 机密客户端:依赖 constructor 传入的 clientSecret
|
|
1414
|
+
* - PKCE 客户端:通过 codeVerifier 交换
|
|
1415
|
+
*/
|
|
1416
|
+
async exchangeCodeForToken({ code, codeVerifier }) {
|
|
1417
|
+
const payload = codeVerifier ? {
|
|
1418
|
+
client_id: this.config.clientId,
|
|
1419
|
+
code,
|
|
1420
|
+
redirect_uri: this.config.redirectURI,
|
|
1421
|
+
code_verifier: codeVerifier
|
|
1422
|
+
} : {
|
|
1423
|
+
client_id: this.config.clientId,
|
|
1424
|
+
code,
|
|
1425
|
+
redirect_uri: this.config.redirectURI,
|
|
1426
|
+
client_secret: this.config.clientSecret ?? ""
|
|
1427
|
+
};
|
|
1428
|
+
if (!codeVerifier && !this.config.clientSecret) {
|
|
1429
|
+
throw new Error(
|
|
1430
|
+
"clientSecret is required when codeVerifier is not provided."
|
|
1431
|
+
);
|
|
1432
|
+
}
|
|
1433
|
+
return this.http.post("token", {
|
|
1434
|
+
json: {
|
|
1435
|
+
...payload,
|
|
1436
|
+
grant_type: "authorization_code"
|
|
1437
|
+
}
|
|
1438
|
+
}).json();
|
|
1439
|
+
}
|
|
1440
|
+
/**
|
|
1441
|
+
* 使用 refresh_token 刷新访问令牌(grant_type=refresh_token)。
|
|
1442
|
+
*/
|
|
1443
|
+
async refreshAccessToken({ refreshToken }) {
|
|
1444
|
+
const payload = {
|
|
1445
|
+
client_id: this.config.clientId,
|
|
1446
|
+
refresh_token: refreshToken,
|
|
1447
|
+
client_secret: this.config.clientSecret
|
|
1448
|
+
};
|
|
1449
|
+
return this.http.post("token", {
|
|
1450
|
+
json: {
|
|
1451
|
+
...payload,
|
|
1452
|
+
grant_type: "refresh_token"
|
|
1453
|
+
}
|
|
1454
|
+
}).json();
|
|
1455
|
+
}
|
|
1456
|
+
/**
|
|
1457
|
+
* 基于 access_token 创建授权态 API 客户端。
|
|
1458
|
+
*/
|
|
1459
|
+
createAuthorizedClient(accessToken) {
|
|
1460
|
+
const authorization = `Bearer ${accessToken}`;
|
|
1461
|
+
const userHttp = distribution_default.create({
|
|
1462
|
+
prefixUrl: new URL("user/", this.config.baseURL),
|
|
1463
|
+
headers: {
|
|
1464
|
+
Authorization: authorization
|
|
1465
|
+
},
|
|
1466
|
+
...LXNS_HTTP_OPTIONS
|
|
1467
|
+
});
|
|
1468
|
+
const maimaiPersonalHttp = distribution_default.create({
|
|
1469
|
+
prefixUrl: new URL("user/maimai/", this.config.baseURL),
|
|
1470
|
+
headers: {
|
|
1471
|
+
Authorization: authorization
|
|
1472
|
+
},
|
|
1473
|
+
...LXNS_HTTP_OPTIONS
|
|
1474
|
+
});
|
|
1475
|
+
const chunithmPersonalHttp = distribution_default.create({
|
|
1476
|
+
prefixUrl: new URL("user/chunithm/", this.config.baseURL),
|
|
1477
|
+
headers: {
|
|
1478
|
+
Authorization: authorization
|
|
1479
|
+
},
|
|
1480
|
+
...LXNS_HTTP_OPTIONS
|
|
1481
|
+
});
|
|
1482
|
+
return new OAuthAuthorizedApi(
|
|
1483
|
+
userHttp,
|
|
1484
|
+
maimaiPersonalHttp,
|
|
1485
|
+
chunithmPersonalHttp
|
|
1486
|
+
);
|
|
1487
|
+
}
|
|
1488
|
+
};
|
|
1320
1489
|
// Annotate the CommonJS export names for ESM import in node:
|
|
1321
1490
|
0 && (module.exports = {
|
|
1322
1491
|
ChunithmModels,
|
|
1323
1492
|
LxnsApiClient,
|
|
1324
1493
|
LxnsApiError,
|
|
1494
|
+
LxnsOAuthClient,
|
|
1325
1495
|
MaimaiModels,
|
|
1496
|
+
OAuthAuthorizedApi,
|
|
1497
|
+
OAuthUserApi,
|
|
1326
1498
|
isAuthError,
|
|
1327
1499
|
isLxnsApiError,
|
|
1328
1500
|
isNotFoundError,
|
package/dist/index.d.ts
CHANGED
|
@@ -456,6 +456,164 @@ declare namespace models {
|
|
|
456
456
|
export { type models_Alias as Alias, type models_AssetType as AssetType, type models_BuddyNotes as BuddyNotes, type models_Collection as Collection, type models_CollectionGenre as CollectionGenre, type models_CollectionRequired as CollectionRequired, type models_CollectionRequiredSong as CollectionRequiredSong, type models_FCType as FCType, type models_FSType as FSType, type models_Frame as Frame, type models_Genre as Genre, type models_Icon as Icon, models_LevelIndex as LevelIndex, type models_NamePlate as NamePlate, type models_Notes as Notes, type models_Player as Player, type models_RateType as RateType, type models_RatingTrend as RatingTrend, type models_Score as Score, type models_SimpleScore as SimpleScore, type Song$1 as Song, type models_SongDifficulties as SongDifficulties, type models_SongDifficulty as SongDifficulty, type models_SongDifficultyUtage as SongDifficultyUtage, type models_SongType as SongType, type models_Trophy as Trophy, type models_Version as Version };
|
|
457
457
|
}
|
|
458
458
|
|
|
459
|
+
type PlayerScores$1 = Score$1[];
|
|
460
|
+
|
|
461
|
+
/**
|
|
462
|
+
* chunithm 个人 API(需用户身份)
|
|
463
|
+
*/
|
|
464
|
+
declare class ChunithmPersonalApi {
|
|
465
|
+
readonly http: KyInstance;
|
|
466
|
+
constructor(http: KyInstance);
|
|
467
|
+
/**
|
|
468
|
+
* 获取玩家信息
|
|
469
|
+
* GET /api/v0/user/chunithm/player
|
|
470
|
+
*/
|
|
471
|
+
getPlayer(): Promise<Player$1>;
|
|
472
|
+
/**
|
|
473
|
+
* 获取玩家所有成绩
|
|
474
|
+
* GET /api/v0/user/chunithm/player/scores
|
|
475
|
+
*/
|
|
476
|
+
getScores(): Promise<PlayerScores$1>;
|
|
477
|
+
/**
|
|
478
|
+
* 上传玩家成绩
|
|
479
|
+
* POST /api/v0/user/chunithm/player/scores
|
|
480
|
+
*/
|
|
481
|
+
postScores(scores: Score$1[]): Promise<unknown>;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
type PlayerScores = Score[];
|
|
485
|
+
|
|
486
|
+
/**
|
|
487
|
+
* maimai 个人 API(需用户身份,路径遵循文档)
|
|
488
|
+
*/
|
|
489
|
+
declare class MaimaiPersonalApi {
|
|
490
|
+
readonly http: KyInstance;
|
|
491
|
+
constructor(http: KyInstance);
|
|
492
|
+
/**
|
|
493
|
+
* 获取玩家信息
|
|
494
|
+
* GET /api/v0/user/maimai/player
|
|
495
|
+
* @returns PlayerInfo
|
|
496
|
+
*/
|
|
497
|
+
getPlayer(): Promise<Player>;
|
|
498
|
+
/**
|
|
499
|
+
* 获取玩家所有成绩
|
|
500
|
+
* GET /api/v0/user/maimai/player/scores
|
|
501
|
+
* @returns PlayerScores
|
|
502
|
+
*/
|
|
503
|
+
getScores(): Promise<PlayerScores>;
|
|
504
|
+
/**
|
|
505
|
+
* 上传玩家成绩
|
|
506
|
+
* POST /api/v0/user/maimai/player/scores
|
|
507
|
+
* @param scores 成绩列表
|
|
508
|
+
* @returns 上传结果
|
|
509
|
+
*/
|
|
510
|
+
postScores(scores: Score[]): Promise<unknown>;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
interface OAuthUserProfile {
|
|
514
|
+
/** 用户 ID */
|
|
515
|
+
id: number;
|
|
516
|
+
/** 用户名 */
|
|
517
|
+
username: string;
|
|
518
|
+
/** 昵称 */
|
|
519
|
+
nickname: string;
|
|
520
|
+
/** 头像 URL */
|
|
521
|
+
avatar: string;
|
|
522
|
+
/** 账号角色 */
|
|
523
|
+
role: string;
|
|
524
|
+
/** 地区 */
|
|
525
|
+
region: string;
|
|
526
|
+
}
|
|
527
|
+
interface OAuthUserToken {
|
|
528
|
+
/** 用户的个人 API 密钥 */
|
|
529
|
+
token: string;
|
|
530
|
+
}
|
|
531
|
+
/**
|
|
532
|
+
* OAuth 用户 API
|
|
533
|
+
*/
|
|
534
|
+
declare class OAuthUserApi {
|
|
535
|
+
readonly http: KyInstance;
|
|
536
|
+
constructor(http: KyInstance);
|
|
537
|
+
/**
|
|
538
|
+
* 获取用户基本信息
|
|
539
|
+
* GET /api/v0/user/profile
|
|
540
|
+
* @returns 用户基本信息
|
|
541
|
+
*/
|
|
542
|
+
getProfile(): Promise<OAuthUserProfile>;
|
|
543
|
+
/**
|
|
544
|
+
* 获取用户个人 API 密钥
|
|
545
|
+
* GET /api/v0/user/token
|
|
546
|
+
* @returns 用户个人 API 密钥
|
|
547
|
+
*/
|
|
548
|
+
getToken(): Promise<OAuthUserToken>;
|
|
549
|
+
}
|
|
550
|
+
|
|
551
|
+
type OAuthAuthorizeCodeTokenRequest = {
|
|
552
|
+
client_id: string;
|
|
553
|
+
code: string;
|
|
554
|
+
redirect_uri: string;
|
|
555
|
+
client_secret: string;
|
|
556
|
+
code_verifier?: never;
|
|
557
|
+
} | {
|
|
558
|
+
client_id: string;
|
|
559
|
+
code: string;
|
|
560
|
+
redirect_uri: string;
|
|
561
|
+
code_verifier: string;
|
|
562
|
+
client_secret?: never;
|
|
563
|
+
};
|
|
564
|
+
interface OAuthRefreshTokenRequest {
|
|
565
|
+
/** OAuth 应用 ID */
|
|
566
|
+
client_id: string;
|
|
567
|
+
/** refresh_token */
|
|
568
|
+
refresh_token: string;
|
|
569
|
+
/** 机密客户端可传,PKCE 可省略 */
|
|
570
|
+
client_secret?: string;
|
|
571
|
+
}
|
|
572
|
+
interface OAuthTokenResponse {
|
|
573
|
+
/** 访问令牌 */
|
|
574
|
+
access_token: string;
|
|
575
|
+
/** 令牌类型(通常为 Bearer) */
|
|
576
|
+
token_type: string;
|
|
577
|
+
/** access_token 过期时间(秒) */
|
|
578
|
+
expires_in: number;
|
|
579
|
+
/** 刷新令牌 */
|
|
580
|
+
refresh_token: string;
|
|
581
|
+
/** 授权范围(空格分隔) */
|
|
582
|
+
scope: string;
|
|
583
|
+
}
|
|
584
|
+
interface OAuthAuthorizeURLParams {
|
|
585
|
+
/** OAuth 应用 ID */
|
|
586
|
+
client_id: string;
|
|
587
|
+
/** 回调地址,必须与应用配置一致 */
|
|
588
|
+
redirect_uri: string;
|
|
589
|
+
/** 权限范围,数组会自动以空格拼接 */
|
|
590
|
+
scope: OAuthScope | OAuthScope[];
|
|
591
|
+
/** 可选状态参数,用于防 CSRF 或携带上下文 */
|
|
592
|
+
state?: string;
|
|
593
|
+
/** PKCE 的 code_challenge(公共客户端推荐) */
|
|
594
|
+
code_challenge?: string;
|
|
595
|
+
/** PKCE 挑战方式,目前固定为 S256 */
|
|
596
|
+
code_challenge_method?: "S256";
|
|
597
|
+
}
|
|
598
|
+
/**
|
|
599
|
+
* OAuth scope。
|
|
600
|
+
* 提供常用 scope 的字面量补全,同时保留 string 扩展以兼容未来新增 scope。
|
|
601
|
+
*/
|
|
602
|
+
type OAuthScope = "read_user_profile" | "read_user_token" | "read_player" | "write_player" | (string & {});
|
|
603
|
+
/**
|
|
604
|
+
* OAuth access_token 绑定后的 API 客户端。
|
|
605
|
+
* 使用 Bearer Token 访问用户信息和各游戏 personal 接口。
|
|
606
|
+
*/
|
|
607
|
+
declare class OAuthAuthorizedApi {
|
|
608
|
+
/** GET /api/v0/user/profile 与 GET /api/v0/user/token */
|
|
609
|
+
readonly user: OAuthUserApi;
|
|
610
|
+
/** OAuth 授权后可访问的 maimai personal API */
|
|
611
|
+
readonly maimai: MaimaiPersonalApi;
|
|
612
|
+
/** OAuth 授权后可访问的 chunithm personal API */
|
|
613
|
+
readonly chunithm: ChunithmPersonalApi;
|
|
614
|
+
constructor(userHttp: KyInstance, maimaiHttp: KyInstance, chunithmHttp: KyInstance);
|
|
615
|
+
}
|
|
616
|
+
|
|
459
617
|
interface Bests$1 {
|
|
460
618
|
bests: Score$1[];
|
|
461
619
|
selections: Score$1[];
|
|
@@ -534,31 +692,6 @@ declare class ChunithmDevApi {
|
|
|
534
692
|
postHtml(friendCode: number, htmlSource: string): Promise<unknown>;
|
|
535
693
|
}
|
|
536
694
|
|
|
537
|
-
type PlayerScores$1 = Score$1[];
|
|
538
|
-
|
|
539
|
-
/**
|
|
540
|
-
* chunithm 个人 API(需用户身份)
|
|
541
|
-
*/
|
|
542
|
-
declare class ChunithmPersonalApi {
|
|
543
|
-
readonly http: KyInstance;
|
|
544
|
-
constructor(http: KyInstance);
|
|
545
|
-
/**
|
|
546
|
-
* 获取玩家信息
|
|
547
|
-
* GET /api/v0/user/chunithm/player
|
|
548
|
-
*/
|
|
549
|
-
getPlayer(): Promise<Player$1>;
|
|
550
|
-
/**
|
|
551
|
-
* 获取玩家所有成绩
|
|
552
|
-
* GET /api/v0/user/chunithm/player/scores
|
|
553
|
-
*/
|
|
554
|
-
getScores(): Promise<PlayerScores$1>;
|
|
555
|
-
/**
|
|
556
|
-
* 上传玩家成绩
|
|
557
|
-
* POST /api/v0/user/chunithm/player/scores
|
|
558
|
-
*/
|
|
559
|
-
postScores(scores: Score$1[]): Promise<unknown>;
|
|
560
|
-
}
|
|
561
|
-
|
|
562
695
|
interface SongList$1 {
|
|
563
696
|
songs: Song$2[];
|
|
564
697
|
genres: Genre$1[];
|
|
@@ -698,35 +831,6 @@ declare class MaimaiDevApi {
|
|
|
698
831
|
postHtml(friendCode: number, htmlSource: string): Promise<unknown>;
|
|
699
832
|
}
|
|
700
833
|
|
|
701
|
-
type PlayerScores = Score[];
|
|
702
|
-
|
|
703
|
-
/**
|
|
704
|
-
* maimai 个人 API(需用户身份,路径遵循文档)
|
|
705
|
-
*/
|
|
706
|
-
declare class MaimaiPersonalApi {
|
|
707
|
-
readonly http: KyInstance;
|
|
708
|
-
constructor(http: KyInstance);
|
|
709
|
-
/**
|
|
710
|
-
* 获取玩家信息
|
|
711
|
-
* GET /api/v0/user/maimai/player
|
|
712
|
-
* @returns PlayerInfo
|
|
713
|
-
*/
|
|
714
|
-
getPlayer(): Promise<Player>;
|
|
715
|
-
/**
|
|
716
|
-
* 获取玩家所有成绩
|
|
717
|
-
* GET /api/v0/user/maimai/player/scores
|
|
718
|
-
* @returns PlayerScores
|
|
719
|
-
*/
|
|
720
|
-
getScores(): Promise<PlayerScores>;
|
|
721
|
-
/**
|
|
722
|
-
* 上传玩家成绩
|
|
723
|
-
* POST /api/v0/user/maimai/player/scores
|
|
724
|
-
* @param scores 成绩列表
|
|
725
|
-
* @returns 上传结果
|
|
726
|
-
*/
|
|
727
|
-
postScores(scores: Score[]): Promise<unknown>;
|
|
728
|
-
}
|
|
729
|
-
|
|
730
834
|
type DifficultyMap<T> = {
|
|
731
835
|
0: T;
|
|
732
836
|
1: T;
|
|
@@ -878,11 +982,75 @@ declare class LxnsApiClient<O extends LxnsApiClientOptions> {
|
|
|
878
982
|
* chunithm API
|
|
879
983
|
*/
|
|
880
984
|
readonly chunithm: ChunithmApiOf<O>;
|
|
881
|
-
private static readonly BASE_OPTIONS;
|
|
882
985
|
private mountGameApi;
|
|
883
986
|
constructor(config?: Readonly<O>);
|
|
884
987
|
}
|
|
885
988
|
|
|
989
|
+
interface LxnsOAuthClientOptions {
|
|
990
|
+
/** OAuth 应用的 client_id */
|
|
991
|
+
clientId: string;
|
|
992
|
+
/** OAuth 回调地址,需与应用配置一致 */
|
|
993
|
+
redirectURI: string;
|
|
994
|
+
/** 机密客户端使用的 client_secret;PKCE 场景可不传 */
|
|
995
|
+
clientSecret?: string;
|
|
996
|
+
/** API 基础地址,默认 https://maimai.lxns.net/api/v0/ */
|
|
997
|
+
baseURL?: string;
|
|
998
|
+
}
|
|
999
|
+
interface OAuthAuthorizeURLOptions {
|
|
1000
|
+
/** 申请的权限范围 */
|
|
1001
|
+
scope: OAuthAuthorizeURLParams["scope"];
|
|
1002
|
+
/** 可选状态参数,用于防 CSRF 或携带上下文 */
|
|
1003
|
+
state?: string;
|
|
1004
|
+
/** PKCE code_challenge */
|
|
1005
|
+
codeChallenge?: string;
|
|
1006
|
+
/** PKCE 挑战方式,当前仅支持 S256 */
|
|
1007
|
+
codeChallengeMethod?: OAuthAuthorizeURLParams["code_challenge_method"];
|
|
1008
|
+
}
|
|
1009
|
+
interface OAuthExchangeCodeOptions {
|
|
1010
|
+
/** OAuth 回调返回的 code */
|
|
1011
|
+
code: string;
|
|
1012
|
+
/** PKCE 场景对应的 code_verifier */
|
|
1013
|
+
codeVerifier?: string;
|
|
1014
|
+
}
|
|
1015
|
+
interface OAuthRefreshTokenOptions {
|
|
1016
|
+
/** 刷新令牌 */
|
|
1017
|
+
refreshToken: string;
|
|
1018
|
+
}
|
|
1019
|
+
/**
|
|
1020
|
+
* Lxns OAuth 客户端。
|
|
1021
|
+
* 负责授权链接生成、code 换 token、refresh token,以及基于 access_token 创建授权态 API。
|
|
1022
|
+
*/
|
|
1023
|
+
declare class LxnsOAuthClient {
|
|
1024
|
+
/** 当前 OAuth 客户端配置 */
|
|
1025
|
+
readonly config: LxnsOAuthClientOptions & {
|
|
1026
|
+
baseURL: string;
|
|
1027
|
+
};
|
|
1028
|
+
private readonly http;
|
|
1029
|
+
/**
|
|
1030
|
+
* @param config OAuth 配置
|
|
1031
|
+
*/
|
|
1032
|
+
constructor(config: Readonly<LxnsOAuthClientOptions>);
|
|
1033
|
+
/**
|
|
1034
|
+
* 生成 OAuth 授权链接(/oauth/authorize)。
|
|
1035
|
+
* @returns 可直接跳转的授权 URL
|
|
1036
|
+
*/
|
|
1037
|
+
createAuthorizeURL({ scope, state, codeChallenge, codeChallengeMethod, }: OAuthAuthorizeURLOptions): string;
|
|
1038
|
+
/**
|
|
1039
|
+
* 使用授权码换取访问令牌(grant_type=authorization_code)。
|
|
1040
|
+
* - 机密客户端:依赖 constructor 传入的 clientSecret
|
|
1041
|
+
* - PKCE 客户端:通过 codeVerifier 交换
|
|
1042
|
+
*/
|
|
1043
|
+
exchangeCodeForToken({ code, codeVerifier }: OAuthExchangeCodeOptions): Promise<OAuthTokenResponse>;
|
|
1044
|
+
/**
|
|
1045
|
+
* 使用 refresh_token 刷新访问令牌(grant_type=refresh_token)。
|
|
1046
|
+
*/
|
|
1047
|
+
refreshAccessToken({ refreshToken }: OAuthRefreshTokenOptions): Promise<OAuthTokenResponse>;
|
|
1048
|
+
/**
|
|
1049
|
+
* 基于 access_token 创建授权态 API 客户端。
|
|
1050
|
+
*/
|
|
1051
|
+
createAuthorizedClient(accessToken: string): OAuthAuthorizedApi;
|
|
1052
|
+
}
|
|
1053
|
+
|
|
886
1054
|
declare class LxnsApiError extends Error {
|
|
887
1055
|
readonly code: number;
|
|
888
1056
|
readonly status?: number;
|
|
@@ -896,4 +1064,4 @@ declare function isAuthError(error: unknown): error is LxnsApiError;
|
|
|
896
1064
|
declare function isRateLimitError(error: unknown): error is LxnsApiError;
|
|
897
1065
|
declare function isServerError(error: unknown): error is LxnsApiError;
|
|
898
1066
|
|
|
899
|
-
export { models$1 as ChunithmModels, LxnsApiClient, LxnsApiError, models as MaimaiModels, isAuthError, isLxnsApiError, isNotFoundError, isRateLimitError, isServerError };
|
|
1067
|
+
export { models$1 as ChunithmModels, LxnsApiClient, type LxnsApiClientOptions, LxnsApiError, LxnsOAuthClient, type LxnsOAuthClientOptions, models as MaimaiModels, type OAuthAuthorizeCodeTokenRequest, type OAuthAuthorizeURLOptions, type OAuthAuthorizeURLParams, OAuthAuthorizedApi, type OAuthExchangeCodeOptions, type OAuthRefreshTokenOptions, type OAuthRefreshTokenRequest, type OAuthScope, type OAuthTokenResponse, OAuthUserApi, isAuthError, isLxnsApiError, isNotFoundError, isRateLimitError, isServerError };
|
package/dist/index.js
CHANGED
|
@@ -33,10 +33,110 @@ var LevelIndex2 = /* @__PURE__ */ ((LevelIndex3) => {
|
|
|
33
33
|
return LevelIndex3;
|
|
34
34
|
})(LevelIndex2 || {});
|
|
35
35
|
|
|
36
|
+
// src/api/chunithm/personal-api.ts
|
|
37
|
+
var ChunithmPersonalApi = class {
|
|
38
|
+
constructor(http) {
|
|
39
|
+
this.http = http;
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* 获取玩家信息
|
|
43
|
+
* GET /api/v0/user/chunithm/player
|
|
44
|
+
*/
|
|
45
|
+
async getPlayer() {
|
|
46
|
+
return this.http.get("player").json();
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* 获取玩家所有成绩
|
|
50
|
+
* GET /api/v0/user/chunithm/player/scores
|
|
51
|
+
*/
|
|
52
|
+
async getScores() {
|
|
53
|
+
return this.http.get("scores").json();
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 上传玩家成绩
|
|
57
|
+
* POST /api/v0/user/chunithm/player/scores
|
|
58
|
+
*/
|
|
59
|
+
async postScores(scores) {
|
|
60
|
+
const body = { scores };
|
|
61
|
+
return this.http.post("scores", { json: body }).json();
|
|
62
|
+
}
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
// src/api/maimai/personal-api.ts
|
|
66
|
+
var MaimaiPersonalApi = class {
|
|
67
|
+
constructor(http) {
|
|
68
|
+
this.http = http;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* 获取玩家信息
|
|
72
|
+
* GET /api/v0/user/maimai/player
|
|
73
|
+
* @returns PlayerInfo
|
|
74
|
+
*/
|
|
75
|
+
async getPlayer() {
|
|
76
|
+
return this.http.get("player").json();
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* 获取玩家所有成绩
|
|
80
|
+
* GET /api/v0/user/maimai/player/scores
|
|
81
|
+
* @returns PlayerScores
|
|
82
|
+
*/
|
|
83
|
+
async getScores() {
|
|
84
|
+
return this.http.get("scores").json();
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* 上传玩家成绩
|
|
88
|
+
* POST /api/v0/user/maimai/player/scores
|
|
89
|
+
* @param scores 成绩列表
|
|
90
|
+
* @returns 上传结果
|
|
91
|
+
*/
|
|
92
|
+
async postScores(scores) {
|
|
93
|
+
const body = { scores };
|
|
94
|
+
return this.http.post("scores", { json: body }).json();
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
// src/api/oauth/user.ts
|
|
99
|
+
var OAuthUserApi = class {
|
|
100
|
+
constructor(http) {
|
|
101
|
+
this.http = http;
|
|
102
|
+
}
|
|
103
|
+
/**
|
|
104
|
+
* 获取用户基本信息
|
|
105
|
+
* GET /api/v0/user/profile
|
|
106
|
+
* @returns 用户基本信息
|
|
107
|
+
*/
|
|
108
|
+
async getProfile() {
|
|
109
|
+
return this.http.get("profile").json();
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* 获取用户个人 API 密钥
|
|
113
|
+
* GET /api/v0/user/token
|
|
114
|
+
* @returns 用户个人 API 密钥
|
|
115
|
+
*/
|
|
116
|
+
async getToken() {
|
|
117
|
+
return this.http.get("token").json();
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// src/api/oauth/oauth.ts
|
|
122
|
+
var OAuthAuthorizedApi = class {
|
|
123
|
+
/** GET /api/v0/user/profile 与 GET /api/v0/user/token */
|
|
124
|
+
user;
|
|
125
|
+
/** OAuth 授权后可访问的 maimai personal API */
|
|
126
|
+
maimai;
|
|
127
|
+
/** OAuth 授权后可访问的 chunithm personal API */
|
|
128
|
+
chunithm;
|
|
129
|
+
constructor(userHttp, maimaiHttp, chunithmHttp) {
|
|
130
|
+
this.user = new OAuthUserApi(userHttp);
|
|
131
|
+
this.maimai = new MaimaiPersonalApi(maimaiHttp);
|
|
132
|
+
this.chunithm = new ChunithmPersonalApi(chunithmHttp);
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
36
136
|
// src/client/lxns-api-client.ts
|
|
37
137
|
import ky from "ky";
|
|
38
138
|
|
|
39
|
-
// src/api/chunithm/dev.ts
|
|
139
|
+
// src/api/chunithm/dev-api.ts
|
|
40
140
|
var ChunithmDevApi = class {
|
|
41
141
|
constructor(http) {
|
|
42
142
|
this.http = http;
|
|
@@ -136,36 +236,7 @@ var ChunithmDevApi = class {
|
|
|
136
236
|
}
|
|
137
237
|
};
|
|
138
238
|
|
|
139
|
-
// src/api/chunithm/
|
|
140
|
-
var ChunithmPersonalApi = class {
|
|
141
|
-
constructor(http) {
|
|
142
|
-
this.http = http;
|
|
143
|
-
}
|
|
144
|
-
/**
|
|
145
|
-
* 获取玩家信息
|
|
146
|
-
* GET /api/v0/user/chunithm/player
|
|
147
|
-
*/
|
|
148
|
-
async getPlayer() {
|
|
149
|
-
return this.http.get("player").json();
|
|
150
|
-
}
|
|
151
|
-
/**
|
|
152
|
-
* 获取玩家所有成绩
|
|
153
|
-
* GET /api/v0/user/chunithm/player/scores
|
|
154
|
-
*/
|
|
155
|
-
async getScores() {
|
|
156
|
-
return this.http.get("scores").json();
|
|
157
|
-
}
|
|
158
|
-
/**
|
|
159
|
-
* 上传玩家成绩
|
|
160
|
-
* POST /api/v0/user/chunithm/player/scores
|
|
161
|
-
*/
|
|
162
|
-
async postScores(scores) {
|
|
163
|
-
const body = { scores };
|
|
164
|
-
return this.http.post("scores", { json: body }).json();
|
|
165
|
-
}
|
|
166
|
-
};
|
|
167
|
-
|
|
168
|
-
// src/api/chunithm/public.ts
|
|
239
|
+
// src/api/chunithm/public-api.ts
|
|
169
240
|
var ChunithmPublicApi = class {
|
|
170
241
|
constructor(http) {
|
|
171
242
|
this.http = http;
|
|
@@ -208,7 +279,7 @@ var ChunithmPublicApi = class {
|
|
|
208
279
|
}
|
|
209
280
|
};
|
|
210
281
|
|
|
211
|
-
// src/api/maimai/dev.ts
|
|
282
|
+
// src/api/maimai/dev-api.ts
|
|
212
283
|
var MaimaiDevApi = class {
|
|
213
284
|
constructor(http) {
|
|
214
285
|
this.http = http;
|
|
@@ -327,39 +398,6 @@ var MaimaiDevApi = class {
|
|
|
327
398
|
}
|
|
328
399
|
};
|
|
329
400
|
|
|
330
|
-
// src/api/maimai/personal.ts
|
|
331
|
-
var MaimaiPersonalApi = class {
|
|
332
|
-
constructor(http) {
|
|
333
|
-
this.http = http;
|
|
334
|
-
}
|
|
335
|
-
/**
|
|
336
|
-
* 获取玩家信息
|
|
337
|
-
* GET /api/v0/user/maimai/player
|
|
338
|
-
* @returns PlayerInfo
|
|
339
|
-
*/
|
|
340
|
-
async getPlayer() {
|
|
341
|
-
return this.http.get("player").json();
|
|
342
|
-
}
|
|
343
|
-
/**
|
|
344
|
-
* 获取玩家所有成绩
|
|
345
|
-
* GET /api/v0/user/maimai/player/scores
|
|
346
|
-
* @returns PlayerScores
|
|
347
|
-
*/
|
|
348
|
-
async getScores() {
|
|
349
|
-
return this.http.get("scores").json();
|
|
350
|
-
}
|
|
351
|
-
/**
|
|
352
|
-
* 上传玩家成绩
|
|
353
|
-
* POST /api/v0/user/maimai/player/scores
|
|
354
|
-
* @param scores 成绩列表
|
|
355
|
-
* @returns 上传结果
|
|
356
|
-
*/
|
|
357
|
-
async postScores(scores) {
|
|
358
|
-
const body = { scores };
|
|
359
|
-
return this.http.post("scores", { json: body }).json();
|
|
360
|
-
}
|
|
361
|
-
};
|
|
362
|
-
|
|
363
401
|
// src/api/maimai/entities/song.ts
|
|
364
402
|
var LevelArray = [
|
|
365
403
|
"basic",
|
|
@@ -420,7 +458,7 @@ var Song = class {
|
|
|
420
458
|
}
|
|
421
459
|
};
|
|
422
460
|
|
|
423
|
-
// src/api/maimai/public.ts
|
|
461
|
+
// src/api/maimai/public-api.ts
|
|
424
462
|
var MaimaiPublicApi = class {
|
|
425
463
|
constructor(http) {
|
|
426
464
|
this.http = http;
|
|
@@ -528,7 +566,7 @@ function isServerError(error) {
|
|
|
528
566
|
return typeof code === "number" && code >= 500;
|
|
529
567
|
}
|
|
530
568
|
|
|
531
|
-
// src/client/
|
|
569
|
+
// src/client/http-options.ts
|
|
532
570
|
function parseLxnsJson(text) {
|
|
533
571
|
const payload = JSON.parse(text);
|
|
534
572
|
if (payload.success === false) {
|
|
@@ -565,7 +603,16 @@ async function wrapKyError(error) {
|
|
|
565
603
|
status
|
|
566
604
|
);
|
|
567
605
|
}
|
|
568
|
-
var
|
|
606
|
+
var LXNS_HTTP_OPTIONS = {
|
|
607
|
+
parseJson: parseLxnsJson,
|
|
608
|
+
throwHttpErrors: true,
|
|
609
|
+
hooks: {
|
|
610
|
+
beforeError: [wrapKyError]
|
|
611
|
+
}
|
|
612
|
+
};
|
|
613
|
+
|
|
614
|
+
// src/client/lxns-api-client.ts
|
|
615
|
+
var LxnsApiClient = class {
|
|
569
616
|
config;
|
|
570
617
|
/**
|
|
571
618
|
* maimai API
|
|
@@ -575,13 +622,6 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
575
622
|
* chunithm API
|
|
576
623
|
*/
|
|
577
624
|
chunithm;
|
|
578
|
-
static BASE_OPTIONS = {
|
|
579
|
-
parseJson: parseLxnsJson,
|
|
580
|
-
throwHttpErrors: true,
|
|
581
|
-
hooks: {
|
|
582
|
-
beforeError: [wrapKyError]
|
|
583
|
-
}
|
|
584
|
-
};
|
|
585
625
|
mountGameApi({
|
|
586
626
|
public: publicApi,
|
|
587
627
|
dev,
|
|
@@ -608,21 +648,21 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
608
648
|
const { baseURL, devAccessToken, personalAccessToken } = this.config;
|
|
609
649
|
const maimaiHttpPublic = ky.create({
|
|
610
650
|
prefixUrl: new URL("maimai/", baseURL),
|
|
611
|
-
...
|
|
651
|
+
...LXNS_HTTP_OPTIONS
|
|
612
652
|
});
|
|
613
653
|
const maimaiHttpDev = devAccessToken ? ky.create({
|
|
614
654
|
prefixUrl: new URL("maimai/", baseURL),
|
|
615
655
|
headers: {
|
|
616
656
|
Authorization: devAccessToken
|
|
617
657
|
},
|
|
618
|
-
...
|
|
658
|
+
...LXNS_HTTP_OPTIONS
|
|
619
659
|
}) : void 0;
|
|
620
660
|
const maimaiHttpPersonal = personalAccessToken ? ky.create({
|
|
621
661
|
prefixUrl: new URL("user/maimai/", baseURL),
|
|
622
662
|
headers: {
|
|
623
663
|
"X-User-Token": personalAccessToken
|
|
624
664
|
},
|
|
625
|
-
...
|
|
665
|
+
...LXNS_HTTP_OPTIONS
|
|
626
666
|
}) : void 0;
|
|
627
667
|
this.maimai = this.mountGameApi({
|
|
628
668
|
public: new MaimaiPublicApi(maimaiHttpPublic),
|
|
@@ -640,21 +680,21 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
640
680
|
});
|
|
641
681
|
const chunithmHttpPublic = ky.create({
|
|
642
682
|
prefixUrl: new URL("chunithm/", baseURL),
|
|
643
|
-
...
|
|
683
|
+
...LXNS_HTTP_OPTIONS
|
|
644
684
|
});
|
|
645
685
|
const chunithmHttpDev = devAccessToken ? ky.create({
|
|
646
686
|
prefixUrl: new URL("chunithm/", baseURL),
|
|
647
687
|
headers: {
|
|
648
688
|
Authorization: devAccessToken
|
|
649
689
|
},
|
|
650
|
-
...
|
|
690
|
+
...LXNS_HTTP_OPTIONS
|
|
651
691
|
}) : void 0;
|
|
652
692
|
const chunithmHttpPersonal = personalAccessToken ? ky.create({
|
|
653
693
|
prefixUrl: new URL("user/chunithm/", baseURL),
|
|
654
694
|
headers: {
|
|
655
695
|
"X-User-Token": personalAccessToken
|
|
656
696
|
},
|
|
657
|
-
...
|
|
697
|
+
...LXNS_HTTP_OPTIONS
|
|
658
698
|
}) : void 0;
|
|
659
699
|
this.chunithm = this.mountGameApi({
|
|
660
700
|
public: new ChunithmPublicApi(chunithmHttpPublic),
|
|
@@ -670,11 +710,141 @@ var LxnsApiClient = class _LxnsApiClient {
|
|
|
670
710
|
});
|
|
671
711
|
}
|
|
672
712
|
};
|
|
713
|
+
|
|
714
|
+
// src/client/lxns-oauth-client.ts
|
|
715
|
+
import ky2 from "ky";
|
|
716
|
+
var LxnsOAuthClient = class {
|
|
717
|
+
/** 当前 OAuth 客户端配置 */
|
|
718
|
+
config;
|
|
719
|
+
http;
|
|
720
|
+
/**
|
|
721
|
+
* @param config OAuth 配置
|
|
722
|
+
*/
|
|
723
|
+
constructor(config) {
|
|
724
|
+
this.config = {
|
|
725
|
+
...config,
|
|
726
|
+
baseURL: config.baseURL ?? "https://maimai.lxns.net/api/v0/"
|
|
727
|
+
};
|
|
728
|
+
this.http = ky2.create({
|
|
729
|
+
prefixUrl: new URL("oauth/", this.config.baseURL),
|
|
730
|
+
...LXNS_HTTP_OPTIONS
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
/**
|
|
734
|
+
* 生成 OAuth 授权链接(/oauth/authorize)。
|
|
735
|
+
* @returns 可直接跳转的授权 URL
|
|
736
|
+
*/
|
|
737
|
+
createAuthorizeURL({
|
|
738
|
+
scope,
|
|
739
|
+
state,
|
|
740
|
+
codeChallenge,
|
|
741
|
+
codeChallengeMethod
|
|
742
|
+
}) {
|
|
743
|
+
const url = new URL("/oauth/authorize", this.config.baseURL);
|
|
744
|
+
const search = new URLSearchParams({
|
|
745
|
+
response_type: "code",
|
|
746
|
+
client_id: this.config.clientId,
|
|
747
|
+
redirect_uri: this.config.redirectURI,
|
|
748
|
+
scope: Array.isArray(scope) ? scope.join(" ") : scope
|
|
749
|
+
});
|
|
750
|
+
if (state) {
|
|
751
|
+
search.set("state", state);
|
|
752
|
+
}
|
|
753
|
+
if (codeChallenge) {
|
|
754
|
+
search.set("code_challenge", codeChallenge);
|
|
755
|
+
}
|
|
756
|
+
if (codeChallengeMethod) {
|
|
757
|
+
search.set("code_challenge_method", codeChallengeMethod);
|
|
758
|
+
}
|
|
759
|
+
url.search = search.toString();
|
|
760
|
+
return url.toString();
|
|
761
|
+
}
|
|
762
|
+
/**
|
|
763
|
+
* 使用授权码换取访问令牌(grant_type=authorization_code)。
|
|
764
|
+
* - 机密客户端:依赖 constructor 传入的 clientSecret
|
|
765
|
+
* - PKCE 客户端:通过 codeVerifier 交换
|
|
766
|
+
*/
|
|
767
|
+
async exchangeCodeForToken({ code, codeVerifier }) {
|
|
768
|
+
const payload = codeVerifier ? {
|
|
769
|
+
client_id: this.config.clientId,
|
|
770
|
+
code,
|
|
771
|
+
redirect_uri: this.config.redirectURI,
|
|
772
|
+
code_verifier: codeVerifier
|
|
773
|
+
} : {
|
|
774
|
+
client_id: this.config.clientId,
|
|
775
|
+
code,
|
|
776
|
+
redirect_uri: this.config.redirectURI,
|
|
777
|
+
client_secret: this.config.clientSecret ?? ""
|
|
778
|
+
};
|
|
779
|
+
if (!codeVerifier && !this.config.clientSecret) {
|
|
780
|
+
throw new Error(
|
|
781
|
+
"clientSecret is required when codeVerifier is not provided."
|
|
782
|
+
);
|
|
783
|
+
}
|
|
784
|
+
return this.http.post("token", {
|
|
785
|
+
json: {
|
|
786
|
+
...payload,
|
|
787
|
+
grant_type: "authorization_code"
|
|
788
|
+
}
|
|
789
|
+
}).json();
|
|
790
|
+
}
|
|
791
|
+
/**
|
|
792
|
+
* 使用 refresh_token 刷新访问令牌(grant_type=refresh_token)。
|
|
793
|
+
*/
|
|
794
|
+
async refreshAccessToken({ refreshToken }) {
|
|
795
|
+
const payload = {
|
|
796
|
+
client_id: this.config.clientId,
|
|
797
|
+
refresh_token: refreshToken,
|
|
798
|
+
client_secret: this.config.clientSecret
|
|
799
|
+
};
|
|
800
|
+
return this.http.post("token", {
|
|
801
|
+
json: {
|
|
802
|
+
...payload,
|
|
803
|
+
grant_type: "refresh_token"
|
|
804
|
+
}
|
|
805
|
+
}).json();
|
|
806
|
+
}
|
|
807
|
+
/**
|
|
808
|
+
* 基于 access_token 创建授权态 API 客户端。
|
|
809
|
+
*/
|
|
810
|
+
createAuthorizedClient(accessToken) {
|
|
811
|
+
const authorization = `Bearer ${accessToken}`;
|
|
812
|
+
const userHttp = ky2.create({
|
|
813
|
+
prefixUrl: new URL("user/", this.config.baseURL),
|
|
814
|
+
headers: {
|
|
815
|
+
Authorization: authorization
|
|
816
|
+
},
|
|
817
|
+
...LXNS_HTTP_OPTIONS
|
|
818
|
+
});
|
|
819
|
+
const maimaiPersonalHttp = ky2.create({
|
|
820
|
+
prefixUrl: new URL("user/maimai/", this.config.baseURL),
|
|
821
|
+
headers: {
|
|
822
|
+
Authorization: authorization
|
|
823
|
+
},
|
|
824
|
+
...LXNS_HTTP_OPTIONS
|
|
825
|
+
});
|
|
826
|
+
const chunithmPersonalHttp = ky2.create({
|
|
827
|
+
prefixUrl: new URL("user/chunithm/", this.config.baseURL),
|
|
828
|
+
headers: {
|
|
829
|
+
Authorization: authorization
|
|
830
|
+
},
|
|
831
|
+
...LXNS_HTTP_OPTIONS
|
|
832
|
+
});
|
|
833
|
+
return new OAuthAuthorizedApi(
|
|
834
|
+
userHttp,
|
|
835
|
+
maimaiPersonalHttp,
|
|
836
|
+
chunithmPersonalHttp
|
|
837
|
+
);
|
|
838
|
+
}
|
|
839
|
+
};
|
|
673
840
|
export {
|
|
674
841
|
models_exports as ChunithmModels,
|
|
675
842
|
LxnsApiClient,
|
|
676
843
|
LxnsApiError,
|
|
844
|
+
LxnsOAuthClient,
|
|
677
845
|
models_exports2 as MaimaiModels,
|
|
846
|
+
OAuthAuthorizedApi,
|
|
847
|
+
OAuthUserApi,
|
|
678
848
|
isAuthError,
|
|
679
849
|
isLxnsApiError,
|
|
680
850
|
isNotFoundError,
|