lxns-rhythm-api 0.1.0

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 ADDED
@@ -0,0 +1,105 @@
1
+ # lxns-sdk
2
+
3
+ 一个用于访问 Lxns API 的轻量 TypeScript SDK。
4
+
5
+ - 零运行时依赖(仅使用 `ky` 发起请求)。
6
+ - 类型安全:根据传入的 token 自动在 `maimai` 命名空间上暴露 `public` / `dev` / `personal` 子 API。
7
+ - 现代构建(ESM/CJS/类型声明)。
8
+
9
+ ## 安装
10
+
11
+ ```bash
12
+ # npm
13
+ npm i lxns-sdk
14
+
15
+ # pnpm
16
+ pnpm add lxns-sdk
17
+
18
+ # yarn
19
+ yarn add lxns-sdk
20
+ ```
21
+
22
+ ## 快速开始
23
+
24
+ ```ts
25
+ import { LxnsApiClient } from "lxns-sdk";
26
+
27
+ // 无 token:仅可用 public API
28
+ const client = new LxnsApiClient();
29
+ const song = await client.maimai.public.getSong(114);
30
+ console.log(song.standard?.master);
31
+
32
+ // 开发者 API:传入 devAccessToken 后可用 maimai.dev
33
+ const devClient = new LxnsApiClient({
34
+ devAccessToken: "<your-dev-token>",
35
+ });
36
+ const player = await devClient.maimai.dev.getPlayerByQQ(1507524536);
37
+ console.log(player);
38
+
39
+ // 个人 API:传入 personalAccessToken 后可用 maimai.personal
40
+ const personalClient = new LxnsApiClient({
41
+ personalAccessToken: "<your-personal-token>",
42
+ });
43
+ const me = await personalClient.maimai.personal.getPlayer();
44
+ console.log(me);
45
+ ```
46
+
47
+ CommonJS 引用:
48
+
49
+ ```js
50
+ const { LxnsApiClient } = require("lxns-sdk");
51
+ ```
52
+
53
+ ## 配置项
54
+
55
+ 构造函数签名:
56
+
57
+ ```ts
58
+ new LxnsApiClient(options?: {
59
+ personalAccessToken?: string;
60
+ devAccessToken?: string;
61
+ baseURL?: string; // 默认:https://maimai.lxns.net/api/v0/
62
+ });
63
+ ```
64
+
65
+ - 当提供 `devAccessToken` 时:
66
+ - SDK 会在 `maimai.dev` 命名空间下启用开发者接口。
67
+ - 认证头:`Authorization: <devAccessToken>`。
68
+ - 基础路径:`<baseURL>/maimai/`。
69
+ - 当提供 `personalAccessToken` 时:
70
+ - SDK 会在 `maimai.personal` 命名空间下启用个人接口。
71
+ - 认证头:`X-User-Token: <personalAccessToken>`。
72
+ - 基础路径:`<baseURL>/user/maimai/`。
73
+ - `public` 始终可用:
74
+ - 基础路径:`<baseURL>/maimai/`。
75
+
76
+ > 注意:`baseURL` 默认值为 `https://maimai.lxns.net/api/v0/`。
77
+
78
+ ## API 概览
79
+
80
+ - `maimai.public`
81
+ - `getSongList(version?: number, notes?: boolean)`
82
+ - `getSong(id: number)`
83
+ - `getAliasList()`
84
+ - `getCollectionList(type, options)` 等
85
+ - `maimai.dev`(需 `devAccessToken`)
86
+ - `getPlayer(friendCode)`、`getPlayerByQQ(qq)`、`getBests(...)` 等
87
+ - `maimai.personal`(需 `personalAccessToken`)
88
+ - `getPlayer()`、`getScores()`、`postScores(scores)` 等
89
+
90
+ 详细定义请参见源码:
91
+
92
+ - `src/apis/maimai/public.ts`
93
+ - `src/apis/maimai/dev.ts`
94
+ - `src/apis/maimai/personal.ts`
95
+
96
+ ## 构建与测试
97
+
98
+ ```bash
99
+ pnpm run build
100
+ pnpm run test
101
+ ```
102
+
103
+ ## 许可
104
+
105
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1 @@
1
+ "use strict";var v=Object.create;var o=Object.defineProperty;var P=Object.getOwnPropertyDescriptor;var T=Object.getOwnPropertyNames;var j=Object.getPrototypeOf,R=Object.prototype.hasOwnProperty;var h=(r,e)=>{for(var t in e)o(r,t,{get:e[t],enumerable:!0})},x=(r,e,t,n)=>{if(e&&typeof e=="object"||typeof e=="function")for(let i of T(e))!R.call(r,i)&&i!==t&&o(r,i,{get:()=>e[i],enumerable:!(n=P(e,i))||n.enumerable});return r};var C=(r,e,t)=>(t=r!=null?v(j(r)):{},x(e||!r||!r.__esModule?o(t,"default",{value:r,enumerable:!0}):t,r)),A=r=>x(o({},"__esModule",{value:!0}),r);var L={};h(L,{LxnsApiClient:()=>d,MaimaiModels:()=>y});module.exports=A(L);var y={};h(y,{LevelIndex:()=>S});var S=(s=>(s[s.BASIC=0]="BASIC",s[s.ADVANCED=1]="ADVANCED",s[s.EXPERT=2]="EXPERT",s[s.MASTER=3]="MASTER",s[s.RE_MASTER=4]="RE_MASTER",s))(S||{});var u=C(require("ky"),1);var D=["basic","advanced","expert","master","remaster"];function g(r){return r.reduce((e,t)=>(e[D[t.difficulty]]=t,e),{})}var a=class{id;title;artist;genre;version;bpm;difficulties;locked;disabled;rights;constructor(e){this.id=e.id,this.title=e.title,this.artist=e.artist,this.genre=e.genre,this.version=e.version,this.bpm=e.bpm,this.difficulties=e.difficulties,this.locked=e.locked??!1,this.disabled=e.disabled??!1,this.rights=e.rights}get standard(){return this.difficulties.standard.length===0?null:g(this.difficulties.standard)}get dx(){return this.difficulties.dx.length===0?null:g(this.difficulties.dx)}get utage(){return this.difficulties.utage?g(this.difficulties.utage):null}};var l=class{constructor(e){this.http=e}async getSongList(e,t){return this.http.get("song/list?",{searchParams:{version:e,notes:t}}).json()}async getSong(e){let t=await this.http.get(`song/${e}`).json();return new a(t)}async getAliasList(){return this.http.get("alias/list").json()}async getCollectionList(e,t){let n={trophy:"trophies",icon:"icons",plate:"plates",frame:"frames"};return this.http.get(`${e}/list`,{searchParams:{...t}}).json().then(i=>i[n[e]])}async getCollectionInfo(e,t,n){return this.http.get(`${e}/${t}`,{searchParams:{...n}}).json()}async getCollectionGenreList(e){return this.http.get("collection-genre/list",{searchParams:{...e}}).json()}async getCollectionGenreInfo(e,t){return this.http.get(`collection-genre/${e}`,{searchParams:{...t}}).json()}};var c=class{constructor(e){this.http=e}async postPlayer(e){return this.http.post("player",{json:e}).json()}async getPlayer(e){return this.http.get(`player/${e}`).json()}async getPlayerByQQ(e){return this.http.get(`player/qq/${e}`).json()}async getBests(e){return this.http.get(`player/${e}/bests`).json()}async getApBests(e){return this.http.get(`player/${e}/bests/ap`).json()}async getRecents(e){return this.http.get(`player/${e}/recents`).json()}async getAllBestScores(e){return this.http.get(`player/${e}/scores`).json()}async getHeatmap(e){return this.http.get(`player/${e}/heatmap`).json()}async getTrend(e){return this.http.get(`player/${e}/trend`).json()}async getScoreHistory(e){return this.http.get(`player/${e}/score/history`).json()}async getCollectionProgress(e,t,n){return this.http.get(`player/${e}/${t}/${n}`).json()}async postScores(e,t){let n={scores:t};return this.http.post(`player/${e}/scores`,{json:n}).json()}async postHtml(e,t){return this.http.post(`player/${e}/html`,{body:t,headers:{"content-type":"text/plain"}}).json()}};var p=class{constructor(e){this.http=e}async getPlayer(){return this.http.get("player").json()}async getScores(){return this.http.get("scores").json()}async postScores(e){let t={scores:e};return this.http.post("scores",{json:t}).json()}};var d=class{config={baseURL:"https://maimai.lxns.net/api/v0/"};maimai;constructor(e){this.config={baseURL:e?.baseURL??this.config.baseURL,personalAccessToken:e?.personalAccessToken,devAccessToken:e?.devAccessToken};let{baseURL:t,devAccessToken:n,personalAccessToken:i}=this.config,s=u.default.create({prefixUrl:new URL("maimai/",t)}),f=n?u.default.create({prefixUrl:new URL("maimai/",t),headers:{Authorization:n}}):void 0,b=i?u.default.create({prefixUrl:new URL("user/maimai/",t),headers:{"X-User-Token":i}}):void 0,m={public:new l(s)};f&&(m.dev=new c(f)),b&&(m.personal=new p(b)),this.maimai=m}};0&&(module.exports={LxnsApiClient,MaimaiModels});
@@ -0,0 +1,540 @@
1
+ import { KyInstance } from 'ky';
2
+
3
+ interface Notes {
4
+ total: number;
5
+ tap: number;
6
+ hold: number;
7
+ slide: number;
8
+ touch: number;
9
+ break: number;
10
+ }
11
+ type SongType = "standard" | "dx" | "utage";
12
+ declare enum LevelIndex {
13
+ BASIC = 0,
14
+ ADVANCED = 1,
15
+ EXPERT = 2,
16
+ MASTER = 3,
17
+ RE_MASTER = 4
18
+ }
19
+ interface SongDifficulty {
20
+ /** 类型 */
21
+ type: SongType;
22
+ /** 难度 */
23
+ difficulty: LevelIndex;
24
+ /** 等级 */
25
+ level: string;
26
+ /** 等级值 */
27
+ level_value: number;
28
+ /** 谱师 */
29
+ note_designer: string;
30
+ /** 版本 */
31
+ version: number;
32
+ /** 乐谱 */
33
+ notes?: Notes;
34
+ }
35
+ interface BuddyNotes {
36
+ /** 1P 谱面物量 */
37
+ left: Notes;
38
+ /** 2P 谱面物量 */
39
+ right: Notes;
40
+ }
41
+ interface SongDifficultyUtage {
42
+ /** 谱面属性(汉字) */
43
+ kanji: string;
44
+ /** 谱面描述 */
45
+ description: string;
46
+ /** 是否为 BUDDY 谱面 */
47
+ is_buddy: boolean;
48
+ /** 谱面物量(BUDDY 时为 BuddyNotes) */
49
+ notes?: Notes | BuddyNotes;
50
+ /** 以下与 SongDifficulty 相同 */
51
+ type: SongType;
52
+ difficulty: LevelIndex;
53
+ level: string;
54
+ level_value: number;
55
+ note_designer: string;
56
+ version: number;
57
+ }
58
+ interface SongDifficulties {
59
+ /** 标准 */
60
+ standard: SongDifficulty[] | [];
61
+ /** DX */
62
+ dx: SongDifficulty[] | [];
63
+ /** 宴会场 */
64
+ utage?: SongDifficultyUtage[];
65
+ }
66
+ interface Song$1 {
67
+ /** 歌曲 ID */
68
+ id: number;
69
+ /** 歌曲名称 */
70
+ title: string;
71
+ /** 歌手 */
72
+ artist: string;
73
+ /** 流派 */
74
+ genre: string;
75
+ /** BPM */
76
+ bpm: number;
77
+ /** 地图 */
78
+ map?: string;
79
+ /** 版本 */
80
+ version: number;
81
+ /** 权限 */
82
+ rights?: string;
83
+ /** 禁用 */
84
+ disabled?: boolean;
85
+ /** 难度 */
86
+ difficulties: SongDifficulties;
87
+ /** 锁定 */
88
+ locked?: boolean;
89
+ }
90
+ interface Genre {
91
+ /** ID */
92
+ id: number;
93
+ /** 标题 */
94
+ title: string;
95
+ /** 流派 */
96
+ genre: string;
97
+ }
98
+ interface Version {
99
+ /** ID */
100
+ id: number;
101
+ /** 标题 */
102
+ title: string;
103
+ /** 主要版本 ID */
104
+ version: number;
105
+ }
106
+ interface Alias {
107
+ /** 歌曲 ID */
108
+ song_id: number;
109
+ /** 别名 */
110
+ aliases: string[] | [];
111
+ }
112
+ interface Player {
113
+ /** 游戏内名称 */
114
+ name: string;
115
+ /** 玩家 DX Rating */
116
+ rating: number;
117
+ /** 好友码 */
118
+ friend_code: number;
119
+ /** 段位 ID */
120
+ course_rank: number;
121
+ /** 阶级 ID */
122
+ class_rank: number;
123
+ /** 搭档觉醒数 */
124
+ star: number;
125
+ /** 称号(仅上传时可空) */
126
+ trophy?: Trophy;
127
+ /** 头像 */
128
+ icon?: Icon;
129
+ /** 姓名框 */
130
+ name_plate?: NamePlate;
131
+ /** 背景 */
132
+ frame?: Frame;
133
+ /** 玩家被同步时的 UTC 时间(仅获取时返回) */
134
+ upload_time?: string;
135
+ }
136
+ interface Collection {
137
+ /** 收藏品 ID */
138
+ id: number;
139
+ /** 收藏品名称 */
140
+ name: string;
141
+ /** 称号颜色(仅玩家称号) */
142
+ color?: string;
143
+ /** 收藏品说明 */
144
+ description?: string;
145
+ /** 收藏品分类(日文,称号外) */
146
+ genre?: string;
147
+ /** 收藏品要求 */
148
+ required?: CollectionRequired[];
149
+ }
150
+ interface CollectionRequired {
151
+ /** 要求的谱面难度,长度为 0 时代表任意难度 */
152
+ difficulties?: LevelIndex[];
153
+ /** 要求的评级类型 */
154
+ rate?: RateType;
155
+ /** 要求的 FULL COMBO 类型 */
156
+ fc?: FCType;
157
+ /** 要求的 FULL SYNC 类型 */
158
+ fs?: FSType;
159
+ /** 要求的曲目列表 */
160
+ songs?: CollectionRequiredSong[];
161
+ /** 要求是否全部完成 */
162
+ completed?: boolean;
163
+ }
164
+ interface CollectionRequiredSong {
165
+ /** 曲目 ID */
166
+ id: number;
167
+ /** 曲名 */
168
+ title: string;
169
+ /** 谱面类型 */
170
+ type: SongType;
171
+ /** 要求的曲目是否完成 */
172
+ completed?: boolean;
173
+ /** 已完成的难度 */
174
+ completed_difficulties?: LevelIndex[];
175
+ }
176
+ interface CollectionGenre {
177
+ /** 收藏品分类 ID */
178
+ id: number;
179
+ /** 分类标题 */
180
+ title: string;
181
+ /** 分类标题(日文) */
182
+ genre: string;
183
+ }
184
+ type FCType = "app" | "ap" | "fcp" | "fc";
185
+ type FSType = "fsdp" | "fsd" | "fsp" | "fs" | "sync";
186
+ type RateType = "sssp" | "sss" | "ssp" | "ss" | "sp" | "s" | "aaa" | "aa" | "a" | "bbb" | "bb" | "b" | "c" | "d";
187
+ interface Score {
188
+ /** 曲目 ID */
189
+ id: number;
190
+ /** 曲名(仅获取时返回) */
191
+ song_name?: string;
192
+ /** 难度标级,如 14+(仅获取时返回) */
193
+ level?: string;
194
+ /** 难度 */
195
+ level_index: LevelIndex;
196
+ /** 达成率 */
197
+ achievements: number;
198
+ /** FULL COMBO 类型 */
199
+ fc?: FCType | null;
200
+ /** FULL SYNC 类型 */
201
+ fs?: FSType | null;
202
+ /** DX 分数 */
203
+ dx_score: number;
204
+ /** DX 星级,最大值为 5 */
205
+ dx_star?: number;
206
+ /** DX Rating(仅获取时返回,计算需向下取整) */
207
+ dx_rating?: number;
208
+ /** 评级类型(仅获取时返回) */
209
+ rate?: RateType;
210
+ /** 谱面类型 */
211
+ type: SongType;
212
+ /** 游玩的 UTC 时间,精确到分钟 */
213
+ play_time?: string;
214
+ /** 成绩被同步时的 UTC 时间(仅获取时返回) */
215
+ upload_time?: string;
216
+ /** 谱面最后游玩的 UTC 时间(仅获取成绩列表、获取最佳成绩时返回) */
217
+ last_played_time?: string;
218
+ }
219
+ interface SimpleScore {
220
+ /** 曲目 ID */
221
+ id: number;
222
+ /** 曲名 */
223
+ song_name: string;
224
+ /** 难度标级,如 14+ */
225
+ level: string;
226
+ /** 难度 */
227
+ level_index: LevelIndex;
228
+ /** FULL COMBO 类型 */
229
+ fc?: FCType | null;
230
+ /** FULL SYNC 类型 */
231
+ fs?: FSType | null;
232
+ /** 评级类型 */
233
+ rate: RateType;
234
+ /** 谱面类型 */
235
+ type: SongType;
236
+ }
237
+ interface RatingTrend {
238
+ /** 总 DX Rating */
239
+ total: number;
240
+ /** 旧版本谱面总 DX Rating */
241
+ standard: number;
242
+ /** 现版本谱面总 DX Rating */
243
+ dx: number;
244
+ /** 日期 */
245
+ date: string;
246
+ }
247
+ interface Trophy extends Collection {
248
+ }
249
+ interface Icon extends Collection {
250
+ }
251
+ interface NamePlate extends Collection {
252
+ }
253
+ interface Frame extends Collection {
254
+ }
255
+
256
+ type models_Alias = Alias;
257
+ type models_BuddyNotes = BuddyNotes;
258
+ type models_Collection = Collection;
259
+ type models_CollectionGenre = CollectionGenre;
260
+ type models_CollectionRequired = CollectionRequired;
261
+ type models_CollectionRequiredSong = CollectionRequiredSong;
262
+ type models_FCType = FCType;
263
+ type models_FSType = FSType;
264
+ type models_Frame = Frame;
265
+ type models_Genre = Genre;
266
+ type models_Icon = Icon;
267
+ type models_LevelIndex = LevelIndex;
268
+ declare const models_LevelIndex: typeof LevelIndex;
269
+ type models_NamePlate = NamePlate;
270
+ type models_Notes = Notes;
271
+ type models_Player = Player;
272
+ type models_RateType = RateType;
273
+ type models_RatingTrend = RatingTrend;
274
+ type models_Score = Score;
275
+ type models_SimpleScore = SimpleScore;
276
+ type models_SongDifficulties = SongDifficulties;
277
+ type models_SongDifficulty = SongDifficulty;
278
+ type models_SongDifficultyUtage = SongDifficultyUtage;
279
+ type models_SongType = SongType;
280
+ type models_Trophy = Trophy;
281
+ type models_Version = Version;
282
+ declare namespace models {
283
+ export { type models_Alias as Alias, 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 };
284
+ }
285
+
286
+ interface SongList {
287
+ songs: Song$1[];
288
+ genres: Genre[];
289
+ versions: Version[];
290
+ }
291
+ interface AliasList {
292
+ aliases: Alias[];
293
+ }
294
+ interface CollectionGenreList {
295
+ collectionGenres: CollectionGenre[];
296
+ }
297
+
298
+ declare class Song implements Song$1 {
299
+ readonly id: Song$1["id"];
300
+ readonly title: Song$1["title"];
301
+ readonly artist: Song$1["artist"];
302
+ readonly genre: Song$1["genre"];
303
+ readonly version: Song$1["version"];
304
+ readonly bpm: Song$1["bpm"];
305
+ readonly difficulties: Song$1["difficulties"];
306
+ readonly locked: boolean;
307
+ readonly disabled: boolean;
308
+ readonly rights: Song$1["rights"];
309
+ constructor(song: Song$1);
310
+ get standard(): Record<"basic" | "advanced" | "expert" | "master" | "remaster", SongDifficulty> | null;
311
+ get dx(): Record<"basic" | "advanced" | "expert" | "master" | "remaster", SongDifficulty> | null;
312
+ get utage(): Record<"basic" | "advanced" | "expert" | "master" | "remaster", SongDifficultyUtage> | null;
313
+ }
314
+
315
+ /**
316
+ * maimai 公共 API
317
+ */
318
+ declare class MaimaiPublicApi {
319
+ private readonly http;
320
+ constructor(http: KyInstance);
321
+ /**
322
+ * 获取歌曲列表
323
+ * @param version 版本,不填写遵循api默认行为
324
+ * @param notes 是否包含谱面信息,不填写遵循api默认行为
325
+ * @returns 歌曲列表
326
+ */
327
+ getSongList(version?: number, notes?: boolean): Promise<SongList>;
328
+ /**
329
+ * 获取歌曲信息
330
+ * @param id 歌曲 ID
331
+ * @returns 歌曲信息
332
+ */
333
+ getSong(id: number): Promise<Song>;
334
+ /**
335
+ * 获取歌曲别名列表
336
+ * @returns 歌曲别名列表
337
+ */
338
+ getAliasList(): Promise<AliasList>;
339
+ /**
340
+ * 获取收藏品列表
341
+ * @param collectionType trophy | icon | plate | frame
342
+ * @param options.version 版本,不填写遵循api默认行为
343
+ * @param options.required 是否包含曲目需求,默认值为 false
344
+ * @returns 收藏品列表
345
+ */
346
+ getCollectionList(collectionType: "trophy" | "icon" | "plate" | "frame", options?: {
347
+ version?: number;
348
+ required?: boolean;
349
+ }): Promise<Collection[] | undefined>;
350
+ /**
351
+ * 获取收藏品信息
352
+ * @param collectionType trophy | icon | plate | frame
353
+ * @param id 收藏品 ID
354
+ * @param options.version 版本,不填写遵循api默认行为
355
+ * @returns 收藏品信息
356
+ */
357
+ getCollectionInfo(collectionType: "trophy" | "icon" | "plate" | "frame", id: number, options?: {
358
+ version?: number;
359
+ }): Promise<Collection>;
360
+ /**
361
+ * 获取收藏品分类列表
362
+ * @param options.version 版本,不填写遵循api默认行为
363
+ * @returns 收藏品分类列表
364
+ */
365
+ getCollectionGenreList(options?: {
366
+ version?: number;
367
+ }): Promise<CollectionGenreList>;
368
+ /**
369
+ * 获取收藏品分类信息
370
+ * @param id 收藏品分类 ID
371
+ * @param options.version 版本,不填写遵循api默认行为
372
+ * @returns 收藏品分类信息
373
+ */
374
+ getCollectionGenreInfo(id: number, options?: {
375
+ version?: number;
376
+ }): Promise<CollectionGenre>;
377
+ }
378
+
379
+ interface Bests {
380
+ standard_total: number;
381
+ dx_total: number;
382
+ standard: Score[];
383
+ dx: Score[];
384
+ }
385
+ type RecentList = Score[];
386
+ type BestScoreList = SimpleScore[];
387
+ type Heatmap = Record<string, number>;
388
+ type TrendList = RatingTrend[];
389
+ type ScoreHistory = Score[];
390
+
391
+ /**
392
+ * maimai 开发者 API(路径风格,与文档一致,不使用查询参数)
393
+ */
394
+ declare class MaimaiDevApi {
395
+ private readonly http;
396
+ constructor(http: KyInstance);
397
+ /**
398
+ * 创建或修改玩家信息
399
+ * @param body 玩家信息
400
+ * @returns 玩家信息
401
+ */
402
+ postPlayer(body: Player): Promise<Player>;
403
+ /**
404
+ * 获取玩家信息(通过好友码)
405
+ * @param friendCode 好友码
406
+ * @returns 玩家信息
407
+ */
408
+ getPlayer(friendCode: number): Promise<Player>;
409
+ /**
410
+ * 获取玩家信息(通过 QQ 号)
411
+ * @param qq QQ 号
412
+ * @returns 玩家信息
413
+ */
414
+ getPlayerByQQ(qq: number): Promise<Player>;
415
+ /**
416
+ * 获取 Best 50(standard 35 + dx 15)
417
+ * @param friendCode 好友码
418
+ * @returns Best 50
419
+ */
420
+ getBests(friendCode: number): Promise<Bests>;
421
+ /**
422
+ * 获取 AP 50
423
+ * @param friendCode 好友码
424
+ * @returns AP 50
425
+ */
426
+ getApBests(friendCode: number): Promise<Bests>;
427
+ /**
428
+ * 获取 Recent 50(仅增量爬取可用)
429
+ * @param friendCode 好友码
430
+ * @returns Recent 50
431
+ */
432
+ getRecents(friendCode: number): Promise<RecentList>;
433
+ /**
434
+ * 获取玩家缓存的所有最佳成绩(简化)
435
+ * @param friendCode 好友码
436
+ * @returns BestScoreList
437
+ */
438
+ getAllBestScores(friendCode: number): Promise<BestScoreList>;
439
+ /**
440
+ * 成绩上传热力图(YYYY-MM-DD -> 数量)
441
+ * @param friendCode 好友码
442
+ * @returns Heatmap
443
+ */
444
+ getHeatmap(friendCode: number): Promise<Heatmap>;
445
+ /**
446
+ * DX Rating 趋势
447
+ * @param friendCode 好友码
448
+ * @returns TrendList
449
+ */
450
+ getTrend(friendCode: number): Promise<TrendList>;
451
+ /**
452
+ * 成绩游玩历史记录(仅返回带有 play_time 的成绩)
453
+ * @param friendCode 好友码
454
+ * @returns 游玩历史记录
455
+ */
456
+ getScoreHistory(friendCode: number): Promise<ScoreHistory>;
457
+ /**
458
+ * 获取玩家收藏品进度
459
+ * @param friendCode 好友码
460
+ * @param collectionType 收藏品类型
461
+ * @param collectionId 收藏品 ID
462
+ * @returns 收藏品进度
463
+ */
464
+ getCollectionProgress(friendCode: number, collectionType: "trophy" | "icon" | "plate" | "frame", collectionId: number): Promise<Collection>;
465
+ /**
466
+ * 上传玩家成绩
467
+ * @param friendCode 好友码
468
+ * @param scores 成绩列表
469
+ * @returns 上传结果
470
+ */
471
+ postScores(friendCode: number, scores: Score[]): Promise<unknown>;
472
+ /**
473
+ * 通过 NET 的 HTML 源代码上传玩家数据
474
+ * @param friendCode 好友码
475
+ * @param htmlSource HTML 源代码
476
+ * @returns 上传结果
477
+ */
478
+ postHtml(friendCode: number, htmlSource: string): Promise<unknown>;
479
+ }
480
+
481
+ type PlayerScores = Score[];
482
+
483
+ /**
484
+ * maimai 个人 API(需用户身份,路径遵循文档)
485
+ */
486
+ declare class MaimaiPersonalApi {
487
+ private readonly http;
488
+ constructor(http: KyInstance);
489
+ /**
490
+ * 获取玩家信息
491
+ * GET /api/v0/user/maimai/player
492
+ * @returns PlayerInfo
493
+ */
494
+ getPlayer(): Promise<Player>;
495
+ /**
496
+ * 获取玩家所有成绩
497
+ * GET /api/v0/user/maimai/player/scores
498
+ * @returns PlayerScores
499
+ */
500
+ getScores(): Promise<PlayerScores>;
501
+ /**
502
+ * 上传玩家成绩
503
+ * POST /api/v0/user/maimai/player/scores
504
+ * @param scores 成绩列表
505
+ * @returns 上传结果
506
+ */
507
+ postScores(scores: Score[]): Promise<unknown>;
508
+ }
509
+
510
+ type LxnsApiClientTokens = Omit<LxnsApiClientOptions, "baseURL">;
511
+ type Flags = Readonly<LxnsApiClientTokens>;
512
+ type Simplify<T> = {
513
+ [K in keyof T]: T[K];
514
+ } & {};
515
+ type IfDefined<T, Then, Else = {}> = [T] extends [NonNullable<T>] ? Then : Else;
516
+ type MaiMaiOf<O extends Flags> = Simplify<{
517
+ public: MaimaiPublicApi;
518
+ } & IfDefined<O["devAccessToken"], {
519
+ dev: MaimaiDevApi;
520
+ }> & IfDefined<O["personalAccessToken"], {
521
+ personal: MaimaiPersonalApi;
522
+ }>>;
523
+
524
+ interface LxnsApiClientOptions {
525
+ personalAccessToken?: string;
526
+ devAccessToken?: string;
527
+ baseURL?: string;
528
+ }
529
+ declare class LxnsApiClient<O extends LxnsApiClientOptions> {
530
+ config: LxnsApiClientOptions & {
531
+ baseURL: string;
532
+ };
533
+ /**
534
+ * maimai API
535
+ */
536
+ readonly maimai: MaiMaiOf<O>;
537
+ constructor(config?: Readonly<O>);
538
+ }
539
+
540
+ export { LxnsApiClient, models as MaimaiModels };
package/dist/index.js ADDED
@@ -0,0 +1 @@
1
+ var h=Object.defineProperty;var x=(s,e)=>{for(var t in e)h(s,t,{get:e[t],enumerable:!0})};var b={};x(b,{LevelIndex:()=>f});var f=(r=>(r[r.BASIC=0]="BASIC",r[r.ADVANCED=1]="ADVANCED",r[r.EXPERT=2]="EXPERT",r[r.MASTER=3]="MASTER",r[r.RE_MASTER=4]="RE_MASTER",r))(f||{});import d from"ky";var S=["basic","advanced","expert","master","remaster"];function u(s){return s.reduce((e,t)=>(e[S[t.difficulty]]=t,e),{})}var o=class{id;title;artist;genre;version;bpm;difficulties;locked;disabled;rights;constructor(e){this.id=e.id,this.title=e.title,this.artist=e.artist,this.genre=e.genre,this.version=e.version,this.bpm=e.bpm,this.difficulties=e.difficulties,this.locked=e.locked??!1,this.disabled=e.disabled??!1,this.rights=e.rights}get standard(){return this.difficulties.standard.length===0?null:u(this.difficulties.standard)}get dx(){return this.difficulties.dx.length===0?null:u(this.difficulties.dx)}get utage(){return this.difficulties.utage?u(this.difficulties.utage):null}};var a=class{constructor(e){this.http=e}async getSongList(e,t){return this.http.get("song/list?",{searchParams:{version:e,notes:t}}).json()}async getSong(e){let t=await this.http.get(`song/${e}`).json();return new o(t)}async getAliasList(){return this.http.get("alias/list").json()}async getCollectionList(e,t){let n={trophy:"trophies",icon:"icons",plate:"plates",frame:"frames"};return this.http.get(`${e}/list`,{searchParams:{...t}}).json().then(i=>i[n[e]])}async getCollectionInfo(e,t,n){return this.http.get(`${e}/${t}`,{searchParams:{...n}}).json()}async getCollectionGenreList(e){return this.http.get("collection-genre/list",{searchParams:{...e}}).json()}async getCollectionGenreInfo(e,t){return this.http.get(`collection-genre/${e}`,{searchParams:{...t}}).json()}};var l=class{constructor(e){this.http=e}async postPlayer(e){return this.http.post("player",{json:e}).json()}async getPlayer(e){return this.http.get(`player/${e}`).json()}async getPlayerByQQ(e){return this.http.get(`player/qq/${e}`).json()}async getBests(e){return this.http.get(`player/${e}/bests`).json()}async getApBests(e){return this.http.get(`player/${e}/bests/ap`).json()}async getRecents(e){return this.http.get(`player/${e}/recents`).json()}async getAllBestScores(e){return this.http.get(`player/${e}/scores`).json()}async getHeatmap(e){return this.http.get(`player/${e}/heatmap`).json()}async getTrend(e){return this.http.get(`player/${e}/trend`).json()}async getScoreHistory(e){return this.http.get(`player/${e}/score/history`).json()}async getCollectionProgress(e,t,n){return this.http.get(`player/${e}/${t}/${n}`).json()}async postScores(e,t){let n={scores:t};return this.http.post(`player/${e}/scores`,{json:n}).json()}async postHtml(e,t){return this.http.post(`player/${e}/html`,{body:t,headers:{"content-type":"text/plain"}}).json()}};var c=class{constructor(e){this.http=e}async getPlayer(){return this.http.get("player").json()}async getScores(){return this.http.get("scores").json()}async postScores(e){let t={scores:e};return this.http.post("scores",{json:t}).json()}};var m=class{config={baseURL:"https://maimai.lxns.net/api/v0/"};maimai;constructor(e){this.config={baseURL:e?.baseURL??this.config.baseURL,personalAccessToken:e?.personalAccessToken,devAccessToken:e?.devAccessToken};let{baseURL:t,devAccessToken:n,personalAccessToken:i}=this.config,r=d.create({prefixUrl:new URL("maimai/",t)}),y=n?d.create({prefixUrl:new URL("maimai/",t),headers:{Authorization:n}}):void 0,g=i?d.create({prefixUrl:new URL("user/maimai/",t),headers:{"X-User-Token":i}}):void 0,p={public:new a(r)};y&&(p.dev=new l(y)),g&&(p.personal=new c(g)),this.maimai=p}};export{m as LxnsApiClient,b as MaimaiModels};
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "lxns-rhythm-api",
3
+ "description": "A simple SDK for lxns api.",
4
+ "author": "amatsuka <amatsukamao@qq.com>",
5
+ "version": "0.1.0",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "https://github.com/wsyzxjn/lxns-rhythm-api.git"
9
+ },
10
+ "exports": {
11
+ ".": {
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ },
15
+ "./package.json": "./package.json"
16
+ },
17
+ "files": [
18
+ "dist",
19
+ "package.json",
20
+ "README.md",
21
+ "LICENSE"
22
+ ],
23
+ "type": "module",
24
+ "main": "./dist/index.cjs",
25
+ "module": "./dist/index.js",
26
+ "types": "./dist/index.d.ts",
27
+ "devDependencies": {
28
+ "@types/node": "^24.5.1",
29
+ "@typescript-eslint/eslint-plugin": "^8.44.0",
30
+ "@typescript-eslint/parser": "^8.44.0",
31
+ "cross-env": "^10.0.0",
32
+ "eslint": "^9.35.0",
33
+ "eslint-config-prettier": "^10.1.8",
34
+ "prettier": "^3.6.2",
35
+ "tsup": "^8.5.0",
36
+ "tsx": "^4.20.5",
37
+ "typescript": "^5.9.2"
38
+ },
39
+ "dependencies": {
40
+ "ky": "^1.10.0"
41
+ },
42
+ "scripts": {
43
+ "lint": "eslint --ext .ts,.js src",
44
+ "prettier": "prettier --write 'src/**/*.ts'",
45
+ "test": "tsx tests/index.ts",
46
+ "build": "tsup",
47
+ "build:production": "cross-env NODE_ENV=production tsup"
48
+ }
49
+ }