dna-api 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of dna-api might be problematic. Click here for more details.
- package/build.ts +10 -0
- package/dist/index.d.ts +355 -0
- package/dist/index.js +42 -0
- package/dist/index.js.map +57 -0
- package/package.json +47 -0
- package/src/index.ts +832 -0
- package/tsconfig.build.json +109 -0
package/src/index.ts
ADDED
|
@@ -0,0 +1,832 @@
|
|
|
1
|
+
import * as forge from "node-forge"
|
|
2
|
+
//#region const
|
|
3
|
+
const MAIN_URL = "https://dnabbs-api.yingxiong.com"
|
|
4
|
+
const LOGIN_URL = `${MAIN_URL}/user/sdkLogin`
|
|
5
|
+
const GET_RSA_PUBLIC_KEY_URL = `${MAIN_URL}/config/getRsaPublicKey`
|
|
6
|
+
const LOGIN_LOG_URL = `${MAIN_URL}/user/login/log`
|
|
7
|
+
const ROLE_LIST_URL = `${MAIN_URL}/role/list`
|
|
8
|
+
const ROLE_FOR_TOOL_URL = `${MAIN_URL}/role/defaultRoleForTool`
|
|
9
|
+
const ROLE_DETAIL_URL = `${MAIN_URL}/role/getCharDetail`
|
|
10
|
+
const WEAPON_DETAIL_URL = `${MAIN_URL}/weapon/detail`
|
|
11
|
+
const SHORT_NOTE_URL = `${MAIN_URL}/role/getShortNoteInfo`
|
|
12
|
+
const SIGN_CALENDAR_URL = `${MAIN_URL}/encourage/signin/show`
|
|
13
|
+
const GAME_SIGN_URL = `${MAIN_URL}/encourage/signin/signin`
|
|
14
|
+
const BBS_SIGN_URL = `${MAIN_URL}/user/signIn`
|
|
15
|
+
const HAVE_SIGN_IN_URL = `${MAIN_URL}/user/haveSignInNew`
|
|
16
|
+
const GET_TASK_PROCESS_URL = `${MAIN_URL}/encourage/level/getTaskProcess`
|
|
17
|
+
const GET_POST_LIST_URL = `${MAIN_URL}/forum/list`
|
|
18
|
+
const GET_POST_DETAIL_URL = `${MAIN_URL}/forum/getPostDetail`
|
|
19
|
+
const LIKE_POST_URL = `${MAIN_URL}/forum/like`
|
|
20
|
+
const SHARE_POST_URL = `${MAIN_URL}/encourage/level/shareTask`
|
|
21
|
+
const REPLY_POST_URL = `${MAIN_URL}/forum/comment/createComment`
|
|
22
|
+
const ANN_LIST_URL = `${MAIN_URL}/user/mine`
|
|
23
|
+
|
|
24
|
+
enum RespCode {
|
|
25
|
+
ERROR = -999,
|
|
26
|
+
OK_ZERO = 0,
|
|
27
|
+
OK_HTTP = 200,
|
|
28
|
+
BAD_REQUEST = 400,
|
|
29
|
+
SERVER_ERROR = 500,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const DNA_GAME_ID = 268
|
|
33
|
+
//#endregion
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* DNA API类,用于与DNA游戏服务器交互
|
|
37
|
+
*/
|
|
38
|
+
export class DNAAPI {
|
|
39
|
+
constructor(
|
|
40
|
+
public dev_code: string,
|
|
41
|
+
public uid = "",
|
|
42
|
+
public token = "",
|
|
43
|
+
public rsa_public_key = "MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGpdbezK+eknQZQzPOjp8mr/dP+QHwk8CRkQh6C6qFnfLH3tiyl0pnt3dePuFDnM1PUXGhCkQ157ePJCQgkDU2+mimDmXh0oLFn9zuWSp+U8uLSLX3t3PpJ8TmNCROfUDWvzdbnShqg7JfDmnrOJz49qd234W84nrfTHbzdqeigQIDAQAB",
|
|
44
|
+
public is_h5 = false,
|
|
45
|
+
) {}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* 获取RSA公钥
|
|
49
|
+
* @returns RSA公钥(base64)
|
|
50
|
+
*/
|
|
51
|
+
async getRsaPublicKey() {
|
|
52
|
+
if (this.rsa_public_key) {
|
|
53
|
+
return this.rsa_public_key
|
|
54
|
+
}
|
|
55
|
+
const res = await this._dna_request<{ key: string }>(GET_RSA_PUBLIC_KEY_URL)
|
|
56
|
+
|
|
57
|
+
if (res.is_success && res.data) {
|
|
58
|
+
const key = res.data.key
|
|
59
|
+
if (typeof key === "string") {
|
|
60
|
+
this.rsa_public_key = key
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return this.rsa_public_key
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* 登录
|
|
68
|
+
*/
|
|
69
|
+
async login(mobile: string, code: string, dev_code: string) {
|
|
70
|
+
const data = { mobile, code, gameList: DNA_GAME_ID }
|
|
71
|
+
const res = await this._dna_request<DNALoginRes>(LOGIN_URL, data, { sign: true, refer: true })
|
|
72
|
+
if (res.is_success && res.data) {
|
|
73
|
+
const data = res.data
|
|
74
|
+
if (typeof data.userId === "string") {
|
|
75
|
+
this.uid = data.userId
|
|
76
|
+
}
|
|
77
|
+
if (typeof data.token === "string") {
|
|
78
|
+
this.token = data.token
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return res
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 获取登录日志
|
|
86
|
+
*/
|
|
87
|
+
async loginLog() {
|
|
88
|
+
return await this._dna_request<DNALoginRes>(LOGIN_LOG_URL)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* 获取角色列表
|
|
93
|
+
*/
|
|
94
|
+
async getRoleList() {
|
|
95
|
+
return await this._dna_request<DNARoleListRes>(ROLE_LIST_URL)
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* 获取默认角色
|
|
100
|
+
*/
|
|
101
|
+
async getDefaultRoleForTool() {
|
|
102
|
+
const data = { type: 1 }
|
|
103
|
+
return await this._dna_request<DNARoleForToolRes>(ROLE_FOR_TOOL_URL, data, { sign: true })
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* 获取角色详情
|
|
108
|
+
*/
|
|
109
|
+
async getRoleDetail(char_id: string, char_eid: string) {
|
|
110
|
+
const data = { charId: char_id, charEid: char_eid, type: 1 }
|
|
111
|
+
return await this._dna_request<DNARoleDetailRes>(ROLE_DETAIL_URL, data)
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* 获取武器详情
|
|
116
|
+
*/
|
|
117
|
+
async getWeaponDetail(weapon_id: string) {
|
|
118
|
+
const data = { weaponId: weapon_id, type: 1 }
|
|
119
|
+
return await this._dna_request<DNARoleDetailRes>(WEAPON_DETAIL_URL, data)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
/**
|
|
123
|
+
* 获取角色简讯
|
|
124
|
+
*/
|
|
125
|
+
async getShortNoteInfo() {
|
|
126
|
+
return await this._dna_request<DNARoleShortNoteRes>(SHORT_NOTE_URL)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* 检查是否签到
|
|
131
|
+
*/
|
|
132
|
+
async haveSignIn() {
|
|
133
|
+
const data = { gameId: DNA_GAME_ID }
|
|
134
|
+
return await this._dna_request<DNAHaveSignInRes>(HAVE_SIGN_IN_URL, data)
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* 签到日历
|
|
139
|
+
*/
|
|
140
|
+
async signCalendar() {
|
|
141
|
+
const data = { gameId: DNA_GAME_ID }
|
|
142
|
+
return await this._dna_request<DNACalendarSignRes>(SIGN_CALENDAR_URL, data)
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* 游戏签到
|
|
147
|
+
*/
|
|
148
|
+
async gameSign(day_award_id: number, period: number) {
|
|
149
|
+
const data = {
|
|
150
|
+
dayAwardId: day_award_id,
|
|
151
|
+
periodId: period,
|
|
152
|
+
signinType: 1,
|
|
153
|
+
}
|
|
154
|
+
return await this._dna_request(GAME_SIGN_URL, data)
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* 皎皎角签到
|
|
159
|
+
*/
|
|
160
|
+
async bbsSign() {
|
|
161
|
+
const data = { gameId: DNA_GAME_ID }
|
|
162
|
+
return await this._dna_request(BBS_SIGN_URL, data)
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* 获取任务进度
|
|
167
|
+
*/
|
|
168
|
+
async getTaskProcess() {
|
|
169
|
+
const data = { gameId: DNA_GAME_ID }
|
|
170
|
+
return await this._dna_request<DNATaskProcessRes>(GET_TASK_PROCESS_URL, data)
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
/**
|
|
174
|
+
* 获取帖子列表
|
|
175
|
+
*/
|
|
176
|
+
async getPostList() {
|
|
177
|
+
const data = {
|
|
178
|
+
forumId: 46, // 全部
|
|
179
|
+
gameId: DNA_GAME_ID,
|
|
180
|
+
pageIndex: 1,
|
|
181
|
+
pageSize: 20,
|
|
182
|
+
searchType: 1, // 1:最新 2:热门
|
|
183
|
+
timeType: 0,
|
|
184
|
+
}
|
|
185
|
+
return await this._dna_request<DNAPostListRes>(GET_POST_LIST_URL, data)
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* 获取帖子详情
|
|
190
|
+
*/
|
|
191
|
+
async getPostDetail(post_id: string) {
|
|
192
|
+
const data = { postId: post_id }
|
|
193
|
+
try {
|
|
194
|
+
return await this._dna_request<DNAPostDetailRes>(GET_POST_DETAIL_URL, data)
|
|
195
|
+
} catch (e) {
|
|
196
|
+
console.error("get_post_detail", e as Error)
|
|
197
|
+
return DNAApiResponse.err("请求皎皎角服务失败")
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* 点赞帖子
|
|
203
|
+
*/
|
|
204
|
+
async doLike(post: { gameForumId: string; postId: string; postType: string; userId: string }) {
|
|
205
|
+
const data = {
|
|
206
|
+
forumId: post.gameForumId,
|
|
207
|
+
gameId: DNA_GAME_ID,
|
|
208
|
+
likeType: "1",
|
|
209
|
+
operateType: "1",
|
|
210
|
+
postCommentId: "",
|
|
211
|
+
postCommentReplyId: "",
|
|
212
|
+
postId: post.postId,
|
|
213
|
+
postType: post.postType,
|
|
214
|
+
toUserId: post.userId,
|
|
215
|
+
}
|
|
216
|
+
try {
|
|
217
|
+
return await this._dna_request(LIKE_POST_URL, data)
|
|
218
|
+
} catch (e) {
|
|
219
|
+
console.error("do_like", e as Error)
|
|
220
|
+
return DNAApiResponse.err("请求皎皎角服务失败")
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// 分享帖子
|
|
225
|
+
async doShare() {
|
|
226
|
+
const data = { gameId: DNA_GAME_ID }
|
|
227
|
+
try {
|
|
228
|
+
return await this._dna_request(SHARE_POST_URL, data)
|
|
229
|
+
} catch (e) {
|
|
230
|
+
console.error("do_share", e as Error)
|
|
231
|
+
return DNAApiResponse.err("请求皎皎角服务失败")
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// 回复帖子
|
|
236
|
+
async doReply(post: Record<string, any>, content: string) {
|
|
237
|
+
const content_json = JSON.stringify([
|
|
238
|
+
{
|
|
239
|
+
content,
|
|
240
|
+
contentType: "1",
|
|
241
|
+
imgHeight: 0,
|
|
242
|
+
imgWidth: 0,
|
|
243
|
+
url: "",
|
|
244
|
+
},
|
|
245
|
+
])
|
|
246
|
+
const data = {
|
|
247
|
+
postId: post.postId,
|
|
248
|
+
forumId: post.gameForumId || 47,
|
|
249
|
+
postType: "1",
|
|
250
|
+
content: content_json,
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
return await this._dna_request(REPLY_POST_URL, data, { sign: true, refer: true, params: { toUserId: post.userId } })
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// 获取游戏公告列表
|
|
257
|
+
async getAnnList() {
|
|
258
|
+
const data = {
|
|
259
|
+
otherUserId: "709542994134436647",
|
|
260
|
+
searchType: 1,
|
|
261
|
+
type: 2,
|
|
262
|
+
}
|
|
263
|
+
return await this._dna_request<DNAPostListRes>(ANN_LIST_URL, data)
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async getHeaders(options?: {
|
|
267
|
+
payload?: Record<string, any> | string
|
|
268
|
+
exparams?: Record<string, any>
|
|
269
|
+
dev_code?: string
|
|
270
|
+
refer?: boolean
|
|
271
|
+
token?: string
|
|
272
|
+
}) {
|
|
273
|
+
let { payload, exparams, dev_code = this.dev_code, refer, token = this.token } = options || {}
|
|
274
|
+
|
|
275
|
+
const CONTENT_TYPE = "application/x-www-form-urlencoded; charset=utf-8"
|
|
276
|
+
const iosBaseHeader = {
|
|
277
|
+
version: "1.1.3",
|
|
278
|
+
source: "ios",
|
|
279
|
+
"Content-Type": CONTENT_TYPE,
|
|
280
|
+
"User-Agent": "DoubleHelix/4 CFNetwork/3860.100.1 Darwin/25.0.0",
|
|
281
|
+
}
|
|
282
|
+
const h5BaseHeader = {
|
|
283
|
+
version: "3.11.0",
|
|
284
|
+
source: "h5",
|
|
285
|
+
"Content-Type": CONTENT_TYPE,
|
|
286
|
+
"User-Agent":
|
|
287
|
+
"Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36",
|
|
288
|
+
}
|
|
289
|
+
// 默认获取ios头
|
|
290
|
+
const headers = { ...(this.is_h5 ? h5BaseHeader : iosBaseHeader) } as Record<string, any>
|
|
291
|
+
if (dev_code) {
|
|
292
|
+
headers.devCode = dev_code
|
|
293
|
+
}
|
|
294
|
+
if (refer) {
|
|
295
|
+
headers.origin = "https://dnabbs.yingxiong.com"
|
|
296
|
+
headers.refer = "https://dnabbs.yingxiong.com/"
|
|
297
|
+
}
|
|
298
|
+
if (token) {
|
|
299
|
+
headers.token = token
|
|
300
|
+
}
|
|
301
|
+
if (typeof payload === "object") {
|
|
302
|
+
const si = build_signature(payload)
|
|
303
|
+
Object.assign(payload, { sign: si.s, timestamp: si.t })
|
|
304
|
+
if (exparams) {
|
|
305
|
+
Object.assign(payload, exparams)
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const params = new URLSearchParams()
|
|
309
|
+
Object.entries(payload).forEach(([key, value]) => {
|
|
310
|
+
params.append(key, String(value))
|
|
311
|
+
})
|
|
312
|
+
payload = params.toString()
|
|
313
|
+
|
|
314
|
+
const rk = si.k
|
|
315
|
+
const pk = await this.getRsaPublicKey()
|
|
316
|
+
const ek = rsa_encrypt(rk, pk)
|
|
317
|
+
if (this.is_h5) {
|
|
318
|
+
headers.k = ek
|
|
319
|
+
} else {
|
|
320
|
+
headers.rk = rk
|
|
321
|
+
headers.key = ek
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
return { headers, payload }
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
private async _dna_request<T = any>(
|
|
328
|
+
url: string,
|
|
329
|
+
data?: any,
|
|
330
|
+
options?: {
|
|
331
|
+
method?: "GET" | "POST"
|
|
332
|
+
sign?: boolean
|
|
333
|
+
refer?: boolean
|
|
334
|
+
params?: Record<string, any>
|
|
335
|
+
max_retries?: number
|
|
336
|
+
retry_delay?: number
|
|
337
|
+
timeout?: number
|
|
338
|
+
},
|
|
339
|
+
): Promise<DNAApiResponse<T>> {
|
|
340
|
+
let { method = "POST", sign, refer, params, max_retries = 3, retry_delay = 1, timeout = 10000 } = options || {}
|
|
341
|
+
let headers: Record<string, any>
|
|
342
|
+
if (sign) {
|
|
343
|
+
const { payload: p, headers: h } = await this.getHeaders({ payload: data, refer, exparams: params })
|
|
344
|
+
data = p
|
|
345
|
+
headers = h
|
|
346
|
+
} else {
|
|
347
|
+
const { headers: h } = await this.getHeaders()
|
|
348
|
+
headers = h
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
for (let attempt = 0; attempt < max_retries; attempt++) {
|
|
352
|
+
try {
|
|
353
|
+
const fetchOptions: RequestInit = {
|
|
354
|
+
method,
|
|
355
|
+
headers,
|
|
356
|
+
body: data,
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// 实现超时控制
|
|
360
|
+
const controller = new AbortController()
|
|
361
|
+
const timeoutId = setTimeout(() => controller.abort(), timeout)
|
|
362
|
+
|
|
363
|
+
const response = await fetch(url, {
|
|
364
|
+
...fetchOptions,
|
|
365
|
+
signal: controller.signal,
|
|
366
|
+
})
|
|
367
|
+
clearTimeout(timeoutId)
|
|
368
|
+
|
|
369
|
+
// 获取响应头的 content-type
|
|
370
|
+
const contentType = response.headers.get("content-type") || ""
|
|
371
|
+
let raw_res: any
|
|
372
|
+
|
|
373
|
+
// 根据 content-type 处理响应数据
|
|
374
|
+
if (contentType.includes("text/")) {
|
|
375
|
+
const textData = await response.text()
|
|
376
|
+
raw_res = {
|
|
377
|
+
code: RespCode.ERROR,
|
|
378
|
+
data: textData,
|
|
379
|
+
}
|
|
380
|
+
} else {
|
|
381
|
+
raw_res = await response.json()
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (typeof raw_res === "object" && raw_res !== null) {
|
|
385
|
+
try {
|
|
386
|
+
if (typeof raw_res.data === "string") {
|
|
387
|
+
raw_res.data = JSON.parse(raw_res.data)
|
|
388
|
+
}
|
|
389
|
+
} catch (e) {
|
|
390
|
+
// 忽略解析错误
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
console.debug(
|
|
395
|
+
`[DNA] url:[${url}] headers:[${JSON.stringify(headers)}] data:[${JSON.stringify(data)}] raw_res:${JSON.stringify(raw_res)}`,
|
|
396
|
+
)
|
|
397
|
+
return new DNAApiResponse<T>(raw_res)
|
|
398
|
+
} catch (e) {
|
|
399
|
+
console.error(`请求失败: ${(e as Error).message}`)
|
|
400
|
+
if (attempt < max_retries - 1) {
|
|
401
|
+
await new Promise((resolve) => setTimeout(resolve, retry_delay * Math.pow(2, attempt)))
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
return DNAApiResponse.err("请求服务器失败,已达最大重试次数")
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
//#region 接口定义
|
|
411
|
+
|
|
412
|
+
export interface UserGame {
|
|
413
|
+
gameId: number // gameId
|
|
414
|
+
gameName: string // gameName
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
export interface DNALoginRes {
|
|
418
|
+
applyCancel?: number // applyCancel
|
|
419
|
+
gender?: number // gender
|
|
420
|
+
signature?: string // signature
|
|
421
|
+
headUrl: string // headUrl
|
|
422
|
+
userName: string // userName
|
|
423
|
+
userId: string // userId
|
|
424
|
+
isOfficial: number // isOfficial
|
|
425
|
+
token: string // token
|
|
426
|
+
userGameList: UserGame[] // userGameList
|
|
427
|
+
isRegister: number // isRegister
|
|
428
|
+
status: number // status
|
|
429
|
+
isComplete: number // isComplete 是否完成绑定 0: 未绑定, 1: 已绑定
|
|
430
|
+
refreshToken: string // refreshToken
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export interface DNARoleShowVo {
|
|
434
|
+
roleId: string // roleId
|
|
435
|
+
headUrl?: string // headUrl
|
|
436
|
+
level?: number // level
|
|
437
|
+
roleName?: string // roleName
|
|
438
|
+
isDefault?: number // isDefault
|
|
439
|
+
roleRegisterTime?: string // roleRegisterTime
|
|
440
|
+
boundType?: number // boundType
|
|
441
|
+
roleBoundId: string // roleBoundId
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
export interface DNARole {
|
|
445
|
+
gameName: string // gameName
|
|
446
|
+
showVoList: DNARoleShowVo[] // showVoList
|
|
447
|
+
gameId: number // gameId
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
export interface DNARoleListRes {
|
|
451
|
+
roles: DNARole[] // roles
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
export interface DNARoleForToolInstance {
|
|
455
|
+
id: number // id
|
|
456
|
+
name: string // name
|
|
457
|
+
code: string // 密函编码
|
|
458
|
+
on: number // 0
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
enum DNAInstanceMHType {
|
|
462
|
+
"角色" = "role",
|
|
463
|
+
"武器" = "weapon",
|
|
464
|
+
"魔之楔" = "mzx",
|
|
465
|
+
"role" = "角色",
|
|
466
|
+
"weapon" = "武器",
|
|
467
|
+
"mzx" = "魔之楔",
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
export function getDNAInstanceMHType(key: keyof typeof DNAInstanceMHType) {
|
|
471
|
+
return DNAInstanceMHType[key]
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
export interface DNARoleForToolInstanceInfo {
|
|
475
|
+
instances: DNARoleForToolInstance[] // instances
|
|
476
|
+
mh_type?: DNAInstanceMHType // 密函类型
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
export interface DraftDoingInfo {
|
|
480
|
+
draftCompleteNum: number // draftCompleteNum
|
|
481
|
+
draftDoingNum: number // draftDoingNum
|
|
482
|
+
endTime: string // 结束时间
|
|
483
|
+
productId?: number // productId
|
|
484
|
+
productName: string // productName
|
|
485
|
+
startTime: string // 开始时间
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
export interface DraftInfo {
|
|
489
|
+
draftDoingInfo?: DraftDoingInfo[] // draftDoingInfo
|
|
490
|
+
draftDoingNum: number // 正在做的锻造
|
|
491
|
+
draftMaxNum: number // 最大锻造数量
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
export interface DNARoleShortNoteRes {
|
|
495
|
+
rougeLikeRewardCount: number // 迷津进度
|
|
496
|
+
rougeLikeRewardTotal: number // 迷津总数
|
|
497
|
+
currentTaskProgress: number // 备忘手记进度
|
|
498
|
+
maxDailyTaskProgress: number // 备忘手记总数
|
|
499
|
+
hardBossRewardCount: number // 梦魇进度
|
|
500
|
+
hardBossRewardTotal: number // 梦魇总数
|
|
501
|
+
draftInfo: DraftInfo // 锻造信息
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export interface WeaponInsForTool {
|
|
505
|
+
elementIcon: string // 武器类型图标
|
|
506
|
+
icon: string // 武器图标
|
|
507
|
+
level: number // 武器等级
|
|
508
|
+
name: string // 武器名称
|
|
509
|
+
unLocked: boolean // 是否解锁
|
|
510
|
+
weaponEid?: string // weaponEid
|
|
511
|
+
weaponId: number // weaponId
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
export interface RoleInsForTool {
|
|
515
|
+
charEid?: string // charEid
|
|
516
|
+
charId: number // charId
|
|
517
|
+
elementIcon: string // 元素图标
|
|
518
|
+
gradeLevel: number // 命座等级
|
|
519
|
+
icon: string // 角色图标
|
|
520
|
+
level: number // 角色等级
|
|
521
|
+
name: string // 角色名称
|
|
522
|
+
unLocked: boolean // 是否解锁
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
export interface RoleAchievement {
|
|
526
|
+
paramKey: string // paramKey
|
|
527
|
+
paramValue: string // paramValue
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
export interface RoleShowForTool {
|
|
531
|
+
roleChars: RoleInsForTool[] // 角色列表
|
|
532
|
+
langRangeWeapons: WeaponInsForTool[] // 武器列表
|
|
533
|
+
closeWeapons: WeaponInsForTool[] // 武器列表
|
|
534
|
+
level: number // 等级
|
|
535
|
+
params: RoleAchievement[] // 成就列表
|
|
536
|
+
roleId: string // 角色id
|
|
537
|
+
roleName: string // 角色名称
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
export interface RoleInfoForTool {
|
|
541
|
+
roleShow: RoleShowForTool // 角色信息
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
export interface DNARoleForToolRes {
|
|
545
|
+
roleInfo: RoleInfoForTool // 角色信息
|
|
546
|
+
instanceInfo: DNARoleForToolInstanceInfo[] // 密函
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
export interface RoleAttribute {
|
|
550
|
+
skillRange: string // 技能范围
|
|
551
|
+
strongValue: string // strongValue
|
|
552
|
+
skillIntensity: string // 技能威力
|
|
553
|
+
weaponTags: string[] // 武器精通
|
|
554
|
+
defense: number // 防御
|
|
555
|
+
enmityValue: string // enmityValue
|
|
556
|
+
skillEfficiency: string // 技能效益
|
|
557
|
+
skillSustain: string // 技能耐久
|
|
558
|
+
maxHp: number // 最大生命值
|
|
559
|
+
atk: number // 攻击
|
|
560
|
+
maxES: number // 护盾
|
|
561
|
+
maxSp: number // 最大神志
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
export interface RoleSkill {
|
|
565
|
+
skillId: number // 技能id
|
|
566
|
+
icon: string // 技能图标
|
|
567
|
+
level: number // 技能等级
|
|
568
|
+
skillName: string // 技能名称
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
export interface RoleTrace {
|
|
572
|
+
icon: string // 溯源图标
|
|
573
|
+
description: string // 溯源描述
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
export interface Mode {
|
|
577
|
+
id: number // id 没佩戴为-1
|
|
578
|
+
icon?: string // 图标
|
|
579
|
+
quality?: number // 质量
|
|
580
|
+
name?: string // 名称
|
|
581
|
+
}
|
|
582
|
+
|
|
583
|
+
export interface RoleDetail {
|
|
584
|
+
attribute: RoleAttribute // 角色属性
|
|
585
|
+
skills: RoleSkill[] // 角色技能
|
|
586
|
+
paint: string // 立绘
|
|
587
|
+
charName: string // 角色名称
|
|
588
|
+
elementIcon: string // 元素图标
|
|
589
|
+
traces: RoleTrace[] // 溯源
|
|
590
|
+
currentVolume: number // 当前魔之楔
|
|
591
|
+
sumVolume: number // 最大魔之楔
|
|
592
|
+
level: number // 角色等级
|
|
593
|
+
icon: string // 角色头像
|
|
594
|
+
gradeLevel: number // 溯源等级 0-6
|
|
595
|
+
elementName: string // 元素名称
|
|
596
|
+
modes: Mode[] // mode
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
export interface DNARoleDetailRes {
|
|
600
|
+
charDetail: RoleDetail // 角色详情
|
|
601
|
+
}
|
|
602
|
+
|
|
603
|
+
export interface DNADayAward {
|
|
604
|
+
gameId: number // gameId
|
|
605
|
+
periodId: number // periodId
|
|
606
|
+
iconUrl: string // iconUrl
|
|
607
|
+
id: number // id
|
|
608
|
+
dayInPeriod: number // dayInPeriod
|
|
609
|
+
updateTime: number // updateTime
|
|
610
|
+
awardNum: number // awardNum
|
|
611
|
+
thirdProductId: string // thirdProductId
|
|
612
|
+
createTime: number // createTime
|
|
613
|
+
awardName: string // awardName
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
export interface DNACaSignPeriod {
|
|
617
|
+
gameId: number // gameId
|
|
618
|
+
retryCos: number // retryCos
|
|
619
|
+
endDate: number // endDate
|
|
620
|
+
id: number // id
|
|
621
|
+
startDate: number // startDate
|
|
622
|
+
retryTimes: number // retryTimes
|
|
623
|
+
overDays: number // overDays
|
|
624
|
+
createTime: number // createTime
|
|
625
|
+
name: string // name
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
export interface DNACaSignRoleInfo {
|
|
629
|
+
headUrl: string // headUrl
|
|
630
|
+
roleId: string // roleId
|
|
631
|
+
roleName: string // roleName
|
|
632
|
+
level: number // level
|
|
633
|
+
roleBoundId: string // roleBoundId
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
export interface DNAHaveSignInRes {
|
|
637
|
+
totalSignInDay: number // 已签到天数
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
export interface DNACalendarSignRes {
|
|
641
|
+
todaySignin: boolean // todaySignin
|
|
642
|
+
userGoldNum: number // userGoldNum
|
|
643
|
+
dayAward: DNADayAward[] // dayAward
|
|
644
|
+
signinTime: number // signinTime
|
|
645
|
+
period: DNACaSignPeriod // period
|
|
646
|
+
roleInfo: DNACaSignRoleInfo // roleInfo
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
export interface DNABBSTask {
|
|
650
|
+
remark: string // 备注
|
|
651
|
+
completeTimes: number // 完成次数
|
|
652
|
+
times: number // 需要次数
|
|
653
|
+
skipType: number // skipType
|
|
654
|
+
gainExp: number // 获取经验
|
|
655
|
+
process: number // 进度
|
|
656
|
+
gainGold: number // 获取金币
|
|
657
|
+
markName?: string // 任务标识名
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
export interface DNATaskProcessRes {
|
|
661
|
+
dailyTask: DNABBSTask[] // dailyTask
|
|
662
|
+
}
|
|
663
|
+
|
|
664
|
+
export interface DNAPostListRes {
|
|
665
|
+
postList: DNAPost[] // posts
|
|
666
|
+
}
|
|
667
|
+
|
|
668
|
+
export interface DNAPost {
|
|
669
|
+
postId: number // postId
|
|
670
|
+
title: string // title
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
export interface DNAPostDetailRes {
|
|
674
|
+
postDetail: DNAPostDetail // post
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
export interface DNAPostDetail {
|
|
678
|
+
postId: number // postId
|
|
679
|
+
postTime: number // postTime
|
|
680
|
+
postContent: DNAPostContent[] // postContent
|
|
681
|
+
title: string // title
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
export enum PostContentType {
|
|
685
|
+
TEXT = 1,
|
|
686
|
+
IMAGE = 2,
|
|
687
|
+
VIDEO = 5,
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
export interface DNAPostContent {
|
|
691
|
+
contentType: PostContentType // content
|
|
692
|
+
content: string // content
|
|
693
|
+
url?: string // url
|
|
694
|
+
contentVideo?: DNAPostContentVideo // contentVideo
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
export interface DNAPostContentVideo {
|
|
698
|
+
videoUrl: string // videoUrl
|
|
699
|
+
coverUrl?: string // coverUrl
|
|
700
|
+
}
|
|
701
|
+
|
|
702
|
+
// interface DNAWikiDetail {
|
|
703
|
+
// name: string // name
|
|
704
|
+
// }
|
|
705
|
+
|
|
706
|
+
// interface DNAWikiRes {
|
|
707
|
+
// wikis: DNAWikiDetail[] // wikis
|
|
708
|
+
// }
|
|
709
|
+
|
|
710
|
+
class DNAApiResponse<T = any> {
|
|
711
|
+
code: number = 0
|
|
712
|
+
msg: string = ""
|
|
713
|
+
success: boolean = false
|
|
714
|
+
data?: T
|
|
715
|
+
|
|
716
|
+
constructor(raw_data: any) {
|
|
717
|
+
this.code = raw_data.code || 0
|
|
718
|
+
this.msg = raw_data.msg || ""
|
|
719
|
+
this.success = raw_data.success || false
|
|
720
|
+
this.data = raw_data.data
|
|
721
|
+
}
|
|
722
|
+
|
|
723
|
+
// 判断是否成功
|
|
724
|
+
get is_success() {
|
|
725
|
+
return this.success && [RespCode.OK_ZERO, RespCode.OK_HTTP].includes(this.code)
|
|
726
|
+
}
|
|
727
|
+
|
|
728
|
+
// 错误响应静态方法
|
|
729
|
+
static err<T = undefined>(msg: string, code: number = RespCode.ERROR): DNAApiResponse<T> {
|
|
730
|
+
return new DNAApiResponse<T>({ code, msg, data: undefined, success: false })
|
|
731
|
+
}
|
|
732
|
+
}
|
|
733
|
+
//#endregion
|
|
734
|
+
|
|
735
|
+
//#region utils
|
|
736
|
+
|
|
737
|
+
// RSA加密函数
|
|
738
|
+
function rsa_encrypt(text: string, public_key_b64: string): string {
|
|
739
|
+
try {
|
|
740
|
+
// 将base64公钥转换为PEM格式
|
|
741
|
+
const lines: string[] = []
|
|
742
|
+
for (let i = 0; i < public_key_b64.length; i += 64) {
|
|
743
|
+
lines.push(public_key_b64.slice(i, i + 64))
|
|
744
|
+
}
|
|
745
|
+
const pem = `-----BEGIN PUBLIC KEY-----\n${lines.join("\n")}\n-----END PUBLIC KEY-----`
|
|
746
|
+
|
|
747
|
+
// 导入PEM格式的RSA公钥
|
|
748
|
+
const publicKey = forge.pki.publicKeyFromPem(pem)
|
|
749
|
+
|
|
750
|
+
// 执行PKCS1_v1_5加密
|
|
751
|
+
const textBytes = forge.util.encodeUtf8(text)
|
|
752
|
+
const encrypted = publicKey.encrypt(textBytes)
|
|
753
|
+
|
|
754
|
+
return forge.util.encode64(encrypted)
|
|
755
|
+
} catch (e) {
|
|
756
|
+
throw new Error(`[DNA] RSA 加密失败: ${(e as Error).message}`)
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
// 生成随机字符串
|
|
761
|
+
function rand_str(length: number = 16): string {
|
|
762
|
+
const chars = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
|
763
|
+
let result = ""
|
|
764
|
+
for (let i = 0; i < length; i++) {
|
|
765
|
+
result += chars.charAt(Math.floor(Math.random() * chars.length))
|
|
766
|
+
}
|
|
767
|
+
return result
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
// MD5加密并转换为大写
|
|
771
|
+
function md5_upper(text: string): string {
|
|
772
|
+
const md = forge.md.md5.create()
|
|
773
|
+
md.update(text)
|
|
774
|
+
return md.digest().toHex().toUpperCase()
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
// 签名哈希函数
|
|
778
|
+
function signature_hash(text: string): string {
|
|
779
|
+
function swap_positions(text: string, positions: number[]): string {
|
|
780
|
+
const chars = text.split("")
|
|
781
|
+
for (let i = 1; i < positions.length; i += 2) {
|
|
782
|
+
const p1 = positions[i - 1]
|
|
783
|
+
const p2 = positions[i]
|
|
784
|
+
if (p1 >= 0 && p1 < chars.length && p2 >= 0 && p2 < chars.length) {
|
|
785
|
+
;[chars[p1], chars[p2]] = [chars[p2], chars[p1]]
|
|
786
|
+
}
|
|
787
|
+
}
|
|
788
|
+
return chars.join("")
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
return swap_positions(md5_upper(text), [1, 13, 5, 17, 7, 23])
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
// 签名函数
|
|
795
|
+
function sign_fI(data: Record<string, any>, secret: string): string {
|
|
796
|
+
const pairs: string[] = []
|
|
797
|
+
const sortedKeys = Object.keys(data).sort()
|
|
798
|
+
for (const k of sortedKeys) {
|
|
799
|
+
const v = data[k]
|
|
800
|
+
if (v !== null && v !== undefined && v !== "") {
|
|
801
|
+
pairs.push(`${k}=${v}`)
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
const qs = pairs.join("&")
|
|
805
|
+
return signature_hash(`${qs}&${secret}`)
|
|
806
|
+
}
|
|
807
|
+
|
|
808
|
+
// XOR编码函数
|
|
809
|
+
function xor_encode(text: string, key: string): string {
|
|
810
|
+
const encoder = new TextEncoder()
|
|
811
|
+
const tb = encoder.encode(text)
|
|
812
|
+
const kb = encoder.encode(key)
|
|
813
|
+
const out: string[] = []
|
|
814
|
+
for (let i = 0; i < tb.length; i++) {
|
|
815
|
+
const b = tb[i]
|
|
816
|
+
const e = (b & 255) + (kb[i % kb.length] & 255)
|
|
817
|
+
out.push(`@${e}`)
|
|
818
|
+
}
|
|
819
|
+
return out.join("")
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
// 构建签名
|
|
823
|
+
function build_signature(data: Record<string, any>, token?: string): Record<string, any> {
|
|
824
|
+
const ts = Date.now()
|
|
825
|
+
const sign_data = { ...data, timestamp: ts, token }
|
|
826
|
+
const sec = rand_str(16)
|
|
827
|
+
const sig = sign_fI(sign_data, sec)
|
|
828
|
+
const enc = xor_encode(sig, sec)
|
|
829
|
+
return { s: enc, t: ts, k: sec }
|
|
830
|
+
}
|
|
831
|
+
|
|
832
|
+
//#endregion
|