dna-api 0.6.2 → 0.6.3

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/dist/index.js.map CHANGED
@@ -4,7 +4,7 @@
4
4
  "sourcesContent": [
5
5
  "export enum RespCode {\n ERROR = -999,\n OK_ZERO = 0,\n OK_HTTP = 200,\n BAD_REQUEST = 400,\n SERVER_ERROR = 500,\n}\n\nexport class TimeBasicResponse<T = any> {\n code: number = 0\n msg: string = \"\"\n data?: T\n\n constructor(raw_data: any) {\n this.code = raw_data.code || 0\n this.msg = raw_data.msg || \"\"\n this.data = raw_data.data\n }\n\n get is_success(): boolean {\n return this.code === RespCode.OK_HTTP || this.code === RespCode.OK_ZERO\n }\n\n get success(): boolean {\n return this.is_success\n }\n\n static err<T = undefined>(msg: string, code: number = RespCode.ERROR): TimeBasicResponse<T> {\n return new TimeBasicResponse<T>({ code, msg, data: undefined })\n }\n}\n",
6
6
  "import * as forge from \"node-forge\"\n\nexport interface RequestOptions {\n method?: \"GET\" | \"POST\"\n sign?: boolean\n file?: File\n kf?: boolean\n h5?: boolean\n refer?: boolean\n params?: Record<string, any>\n max_retries?: number\n retry_delay?: number\n timeout?: number\n}\n\nexport interface HeadersPayload {\n headers: Record<string, any>\n payload: string | FormData | undefined\n}\n\nexport function rsa_encrypt(text: string, public_key_b64: string): string {\n try {\n const lines: string[] = []\n for (let i = 0; i < public_key_b64.length; i += 64) {\n lines.push(public_key_b64.slice(i, i + 64))\n }\n const pem = `-----BEGIN PUBLIC KEY-----\\n${lines.join(\"\\n\")}\\n-----END PUBLIC KEY-----`\n const publicKey = forge.pki.publicKeyFromPem(pem)\n const textBytes = forge.util.encodeUtf8(text)\n const chunk_size = 0x75\n let encrypted_bytes = \"\"\n for (let i = 0; i < textBytes.length; i += chunk_size) {\n encrypted_bytes += publicKey.encrypt(textBytes.slice(i, i + chunk_size))\n }\n return forge.util.encode64(encrypted_bytes)\n } catch (e) {\n throw new Error(`[DNA] RSA 加密失败: ${(e as Error).message}`)\n }\n}\n\nexport function rand_str(length: number = 16): string {\n const chars = \"abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\"\n let result = \"\"\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return result\n}\nexport function rand_str2(length: number = 16): string {\n const chars = \"abcdefghijklmnopqrstuvwxyz0123456789\"\n let result = \"\"\n for (let i = 0; i < length; i++) {\n result += chars.charAt(Math.floor(Math.random() * chars.length))\n }\n return result\n}\n\n/**\n * 兼容 Java.util.Random 的最小实现,用于还原原生签名逻辑。\n */\nclass JavaRandom {\n private readonly multiplier = 0x5deece66dn\n private readonly addend = 0xbn\n private readonly mask = (1n << 48n) - 1n\n private seed: bigint\n\n constructor(seed: bigint = BigInt(Date.now())) {\n this.seed = (seed ^ this.multiplier) & this.mask\n }\n\n /**\n * 生成指定 bit 数的随机值。\n */\n private next(bits: number): number {\n this.seed = (this.seed * this.multiplier + this.addend) & this.mask\n return Number(this.seed >> BigInt(48 - bits))\n }\n\n /**\n * 生成指定上界内的随机整数。\n */\n public nextInt(bound: number): number {\n if (bound <= 0) {\n throw new Error(\"bound must be positive\")\n }\n if ((bound & -bound) === bound) {\n return Number((BigInt(bound) * BigInt(this.next(31))) >> 31n)\n }\n let bits = 0\n let value = 0\n do {\n bits = this.next(31)\n value = bits % bound\n } while (bits - value + (bound - 1) < 0)\n return value\n }\n}\n\n/**\n * 生成原生 `p63.b(30)` 对应的 30 位数字串。\n */\nfunction rand_digit_str(length: number): string {\n const chars = \"01234567890123456789012345678901234567890123456789010123456789\"\n const random = new JavaRandom()\n let result = \"\"\n for (let i = 0; i < length; i++) {\n result += chars.charAt(random.nextInt(0x3e))\n }\n return result\n}\n\nexport function md5_upper(text: string): string {\n const md = forge.md.md5.create()\n md.update(text)\n return md.digest().toHex().toUpperCase()\n}\n\n/**\n * MD5 结果位置混淆: 1↔13, 5↔17, 7↔23\n */\nexport function shuffle_md5(md5_hex: string): string {\n if (md5_hex.length <= 23) {\n return md5_hex\n }\n const chars = md5_hex.split(\"\")\n const swaps = [\n [1, 13],\n [5, 17],\n [7, 23],\n ]\n for (const [i, j] of swaps) {\n ;[chars[i], chars[j]] = [chars[j], chars[i]]\n }\n return chars.join(\"\")\n}\n\n/**\n * 生成 sa 参数 (30位字符串)\n * @returns [raw_sa, shuffled_sa]\n */\nexport function generate_sa(): [string, string] {\n const random_part = rand_str(17)\n const timestamp = Date.now().toString()\n\n const result: string[] = []\n let rand_idx = 0\n let time_idx = 0\n\n for (let i = 0; i < 30; i++) {\n if (8 <= i && i <= 12) {\n result.push(timestamp[time_idx])\n time_idx++\n } else if (16 <= i && i <= 20) {\n result.push(timestamp[time_idx])\n time_idx++\n } else if (22 <= i && i <= 24) {\n result.push(timestamp[time_idx])\n time_idx++\n } else {\n result.push(random_part[rand_idx])\n rand_idx++\n }\n }\n\n const raw_sa = result.join(\"\")\n\n const chars = raw_sa.split(\"\")\n for (const [i, j] of [\n [2, 23],\n [9, 17],\n [13, 25],\n ]) {\n ;[chars[i], chars[j]] = [chars[j], chars[i]]\n }\n const shuffled_sa = chars.join(\"\")\n\n return [raw_sa, shuffled_sa]\n}\n\n/**\n * 构建签名字符串\n */\nexport function build_sign_string(params: Record<string, any>, app_key: string): string {\n const sorted_keys = Object.keys(params).sort()\n const pairs: string[] = []\n for (const key of sorted_keys) {\n const value = params[key]\n if (value !== null && value !== undefined && value !== \"\") {\n pairs.push(`${key}=${value}`)\n }\n }\n pairs.push(app_key)\n return pairs.join(\"&\")\n}\n\n/**\n * 生成混淆后的签名\n */\nexport function sign_shuffled(params: Record<string, any>, app_key: string): string {\n const sign_str = build_sign_string(params, app_key)\n const md5_res = md5_upper(sign_str)\n return shuffle_md5(md5_res)\n}\n\n// XOR编码函数\nexport function xor_encode(text: string, key: string): string {\n const encoder = new TextEncoder()\n const tb = encoder.encode(text)\n const kb = encoder.encode(key)\n const out: string[] = []\n for (let i = 0; i < tb.length; i++) {\n const e = (tb[i] & 255) + (kb[i % kb.length] & 255)\n out.push(`@${e}`)\n }\n return out.join(\"\")\n}\n\nexport function build_signature120(pk: string, payload: Record<string, any>, token?: string): Record<string, any> {\n const rk = rand_str(16)\n const [raw_sa, shuffled_sa] = generate_sa()\n const str_params: Record<string, any> = {}\n\n // 将所有参数转换为字符串\n for (const [k, v] of Object.entries(payload)) {\n str_params[k] = String(v)\n }\n\n const sign_params = { ...str_params }\n if (token) {\n sign_params.token = token\n }\n sign_params.sa = raw_sa\n\n // 生成签名\n const sign_val = sign_shuffled(sign_params, rk)\n const sign_encoded = xor_encode(sign_val, rk)\n\n // 对 rk 进行 RSA 加密\n const rk_encrypted = rsa_encrypt(rk, pk)\n\n // 生成 tn 值\n const tn = `${rk_encrypted},${sign_encoded}`\n\n return { rk, tn, sa: shuffled_sa }\n}\n\nfunction swap_chars(text: string, i: number, j: number): string {\n if (i < 0 || j < 0 || i >= text.length || j >= text.length) {\n return text\n }\n const chars = text.split(\"\")\n ;[chars[i], chars[j]] = [chars[j], chars[i]]\n return chars.join(\"\")\n}\n\nfunction build_sa_header122(raw_sa: string, timestamp: number = Date.now()): string {\n let sa = raw_sa\n sa = swap_chars(sa, 7, 11)\n sa = swap_chars(sa, 18, 26)\n sa = swap_chars(sa, 12, 22)\n sa = swap_chars(sa, 3, 15)\n\n const ts = String(timestamp)\n if (sa.length !== 30 || ts.length < 13) {\n return sa\n }\n\n let timeIndex = 0\n const out: string[] = []\n for (let i = 0; i < sa.length; i++) {\n if (i === 8 || i === 16) {\n out.push(ts.slice(timeIndex, timeIndex + 5))\n timeIndex += 5\n } else if (i === 22) {\n out.push(ts.slice(timeIndex, timeIndex + 3))\n timeIndex += 3\n }\n out.push(sa[i])\n }\n\n return out.join(\"\")\n}\n\nexport function build_signature122(pk: string, payload: Record<string, any>, token?: string): Record<string, any> {\n const rk = rand_str(16)\n\n const raw_sa = rand_str(30)\n const sa = build_sa_header122(raw_sa)\n const str_params: Record<string, any> = {}\n\n for (const [k, v] of Object.entries(payload)) {\n str_params[k] = String(v)\n }\n\n const sign_params = { ...str_params }\n if (token) {\n sign_params.token = token\n }\n sign_params.sa = raw_sa\n\n const sign_val = sign_shuffled(sign_params, rk)\n const sign_encoded = xor_encode(sign_val, rk)\n const rk_encrypted = rsa_encrypt(rk, pk)\n const tn = `${rk_encrypted},${sign_encoded}`\n\n return { rk, tn, sa }\n}\n\n/**\n * 生成 1.3.0 版本的请求签名。\n * @param pk RSA 公钥\n * @param payload 请求参数\n * @param token 可选 token\n * @returns 包含 rk、tn、sa 的签名数据\n */\nexport function build_signature130(pk: string, payload: Record<string, any>, token?: string): Record<string, any> {\n const rk = rand_str(16)\n const raw_sa = rand_digit_str(30)\n\n const sign_params: Record<string, any> = {}\n for (const [k, v] of Object.entries(payload)) {\n sign_params[k] = String(v)\n }\n if (token) {\n sign_params.token = token\n }\n sign_params.sa = raw_sa\n\n const sign_val = sign_shuffled(sign_params, rk)\n const sign_encoded = xor_encode(sign_val, rk)\n const rk_encrypted = rsa_encrypt(rk, pk)\n const tn = `${rk_encrypted},${sign_encoded}`\n\n return { rk, tn, sa: raw_sa }\n}\n\n// 构建上传图片签名(返回签名和密钥)\nexport function build_upload_signature(public_key: string): { signature: string; key: string } {\n const key = rand_str(16)\n const encrypted = rsa_encrypt(key, public_key)\n const signature = swapForUploadImgApp(encrypted)\n return { signature, key }\n}\n\n// 上传图片签名 - 字符交换\nexport function swapForUploadImgApp(text: string): string {\n const chars = text.split(\"\")\n const swaps = [\n [3, 23],\n [11, 32],\n [22, 42],\n [25, 48],\n ]\n for (const [p1, p2] of swaps) {\n if (p1 < chars.length && p2 < chars.length) {\n ;[chars[p1], chars[p2]] = [chars[p2], chars[p1]]\n }\n }\n return chars.join(\"\")\n}\n\n// AES 解密图片 URL\nexport function aesDecryptImageUrl(encryptedUrl: string, key: string): string {\n const swapped = swapForUploadImgApp(encryptedUrl)\n\n const encryptedData = forge.util.decode64(swapped)\n\n const decipher = forge.cipher.createDecipher(\"AES-CBC\", key)\n decipher.start({ iv: \"A-16-Byte-String\" })\n decipher.update(forge.util.createBuffer(encryptedData))\n decipher.finish()\n\n return decipher.output.getBytes()\n}\n\nfunction signature_hash(text: string): string {\n function swap_positions(text: string, positions: number[]): string {\n const chars = text.split(\"\")\n for (let i = 1; i < positions.length; i += 2) {\n const p1 = positions[i - 1]\n const p2 = positions[i]\n if (p1 >= 0 && p1 < chars.length && p2 >= 0 && p2 < chars.length) {\n ;[chars[p1], chars[p2]] = [chars[p2], chars[p1]]\n }\n }\n return chars.join(\"\")\n }\n return swap_positions(md5_upper(text), [1, 13, 5, 17, 7, 23])\n}\n\nfunction sign_fI(data: Record<string, any>, secret: string): string {\n const pairs: string[] = []\n const sortedKeys = Object.keys(data).sort()\n for (const k of sortedKeys) {\n const v = data[k]\n if (v !== null && v !== undefined && v !== \"\") {\n pairs.push(`${k}=${v}`)\n }\n }\n const qs = pairs.join(\"&\")\n return signature_hash(`${qs}&${secret}`)\n}\n\n/** 1.1.1版本加密 */\nexport function build_signature111(data: Record<string, any>, token?: string): Record<string, any> {\n const ts = Date.now()\n const sign_data = { ...data, timestamp: ts, token }\n const sec = rand_str(16)\n const sig = sign_fI(sign_data, sec)\n const enc = xor_encode(sig, sec)\n return { s: enc, t: ts, k: sec }\n}\n",
7
- "import { RespCode, TimeBasicResponse } from \"../TimeBasicResponse\"\nimport type { DNACommonConfigEntity } from \"../type-generated\"\nimport {\n aesDecryptImageUrl,\n build_signature111,\n build_signature120,\n build_upload_signature,\n type HeadersPayload,\n type RequestOptions,\n rand_str2,\n rsa_encrypt,\n build_signature122,\n} from \"./utils\"\n\nexport interface DNABaseAPIConfig {\n server?: \"cn\" | \"global\"\n lang?: string //\"zh-Hans\" | \"en\"\n dev_code?: string\n token?: string\n kf_token?: string\n fetchFn?: typeof fetch\n is_h5?: boolean\n rsa_public_key?: string\n mode?: \"android\" | \"ios\"\n debug?: boolean\n}\n\nexport class DNABaseAPI {\n public fetchFn?: typeof fetch\n public RSA_PUBLIC_KEY =\n \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGpdbezK+eknQZQzPOjp8mr/dP+QHwk8CRkQh6C6qFnfLH3tiyl0pnt3dePuFDnM1PUXGhCkQ157ePJCQgkDU2+mimDmXh0oLFn9zuWSp+U8uLSLX3t3PpJ8TmNCROfUDWvzdbnShqg7JfDmnrOJz49qd234W84nrfTHbzdqeigQIDAQAB\"\n public BASE_URL = \"https://dnabbs-api.yingxiong.com/\"\n public BASE_WEB_SOCKET_URL = \"wss://dnabbs-api.yingxiong.com:8180/ws-community-websocket\"\n public KF_BASE_URL = \"https://kf.yingxiong.com/\"\n public uploadKey: string = \"\"\n public sign_api_urls = new Set<string>()\n public baseHeaders: Record<string, string> = {}\n\n public dev_code = \"\"\n public token = \"\"\n public kf_token = \"\"\n public debug = false\n public cookieToken = \"\"\n private _server: \"cn\" | \"global\" = \"cn\"\n public get server() {\n return this._server\n }\n public set server(value: \"cn\" | \"global\") {\n this._server = value\n if (value === \"global\") {\n this.BASE_URL = \"https://lunoloft-api.yingxiong.com/\"\n this.BASE_WEB_SOCKET_URL = \"wss://lunoloft-api.yingxiong.com:8181/ws-community-websocket\"\n } else {\n this.BASE_URL = \"https://dnabbs-api.yingxiong.com/\"\n this.BASE_WEB_SOCKET_URL = \"wss://dnabbs-api.yingxiong.com:8180/ws-community-websocket\"\n }\n this.updateHeaders()\n }\n private _mode: \"android\" | \"ios\" = \"ios\"\n public get mode() {\n return this._mode\n }\n public set mode(value: \"android\" | \"ios\") {\n this._mode = value\n this.updateHeaders()\n }\n private _lang: string = \"zh-Hans\"\n public get lang() {\n return this._lang\n }\n public set lang(value: string) {\n this._lang = value\n this.updateHeaders()\n }\n\n constructor(options: DNABaseAPIConfig = {}) {\n if (options.server === \"global\") {\n this.server = \"global\"\n } else {\n this.server = \"cn\"\n }\n this.fetchFn = options.fetchFn\n if (options.mode !== undefined) this.mode = options.mode\n if (options.lang !== undefined) this.lang = options.lang\n if (options.rsa_public_key !== undefined) this.RSA_PUBLIC_KEY = options.rsa_public_key\n if (options.dev_code !== undefined) {\n this.dev_code = options.dev_code\n } else {\n this.dev_code = DNABaseAPI.generateDeviceCode()\n }\n if (options.token !== undefined) this.token = options.token\n if (options.kf_token !== undefined) this.kf_token = options.kf_token\n if (options.debug) this.debug = true\n }\n /**\n * 生成设备码\n */\n static generateDeviceCode() {\n return `2${rand_str2(32)}`\n }\n\n updateHeaders() {\n if (this.mode === \"android\") {\n this.baseHeaders = {\n countrycode: \"CN\",\n version: this.server === \"cn\" ? \"1.2.2\" : \"1.1.1\",\n versioncode: this.server === \"cn\" ? \"9\" : \"5\",\n source: \"android\",\n lang: this.lang,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\": this.server === \"cn\" ? \"okhttp/3.10.0\" : \"okhttp/5.3.2\",\n }\n } else {\n this.baseHeaders = {\n countrycode: \"CN\",\n version: this.server === \"cn\" ? \"1.2.0\" : \"1.1.1\",\n versioncode: this.server === \"cn\" ? \"7\" : \"5\",\n source: \"ios\",\n lang: this.lang,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\":\n this.server === \"cn\"\n ? \"DoubleHelix/3 CFNetwork/3860.300.31 Darwin/25.2.0\"\n : \"DoubleHelix/4 CFNetwork/3860.100.1 Darwin/25.0.0\",\n }\n }\n }\n\n async fileUpload(url: string, data: FormData) {\n const res = await this._dna_request<string[]>(url, data, { sign: true })\n if (res.is_success && res.data) {\n res.data = res.data.map(url => aesDecryptImageUrl(url, this.uploadKey))\n }\n return res\n }\n\n async getRsaPublicKey() {\n if (this.RSA_PUBLIC_KEY) {\n return this.RSA_PUBLIC_KEY\n }\n const res = await this._dna_request<{ key: string }>(\"config/getRsaPublicKey\")\n\n if (res.is_success && res.data) {\n const key = res.data.key\n if (typeof key === \"string\") {\n this.RSA_PUBLIC_KEY = key\n }\n }\n return this.RSA_PUBLIC_KEY\n }\n\n public async getHeaders(options?: {\n payload?: Record<string, any> | string | FormData\n exparams?: Record<string, any>\n dev_code?: string\n refer?: boolean\n token?: string\n kf_token?: string\n h5?: boolean\n kf?: boolean\n }): Promise<HeadersPayload> {\n let {\n payload = {},\n exparams,\n dev_code = this.dev_code,\n refer,\n token = this.token,\n kf_token = this.kf_token,\n h5,\n kf,\n } = options || {}\n\n const h5BaseHeader = {\n version: \"3.11.0\",\n source: \"h5\",\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\":\n \"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\",\n }\n const kfBaseHeader = {\n Authorization: kf_token,\n Referer: `https://kf.yingxiong.com/kf2.0/im-chat`,\n \"X-Requested-With\": \"com.hero.dna\",\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"User-Agent\":\n \"Mozilla/5.0 (Linux; Android 16; PLQ110 Build/BP2A.250605.015; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.7499.192 Mobile Safari/537.36CP6.TgzO Hero/1.1.4\",\n }\n const is_h5 = h5 || false\n const headers: Record<string, any> = kf ? kfBaseHeader : is_h5 ? h5BaseHeader : { ...this.baseHeaders }\n if (dev_code && !kf) {\n headers.devCode = dev_code\n }\n if (refer || is_h5) {\n headers.origin = \"https://dnabbs.yingxiong.com\"\n headers.refer = \"https://dnabbs.yingxiong.com/\"\n }\n if (token && !kf) {\n headers.token = token\n }\n if (kf && this.cookieToken) {\n headers.Cookie = this.cookieToken\n }\n if (payload instanceof FormData) {\n const pk = await this.getRsaPublicKey()\n const { signature, key } = build_upload_signature(pk)\n headers.t = signature\n this.uploadKey = key\n if (exparams) {\n for (const [key, value] of Object.entries(exparams)) {\n payload.append(key, String(value))\n }\n }\n delete headers[\"Content-Type\"]\n } else if (typeof payload === \"object\") {\n if (!kf) {\n const pk = await this.getRsaPublicKey()\n if (this.server === \"cn\") {\n if (this.mode === \"android\") {\n const { tn, sa } = build_signature122(pk, payload, token)\n\n // 更新 headers\n if (options?.token) {\n headers.tn = tn\n headers.sa = sa\n }\n } else {\n const { tn, sa, rk } = build_signature120(pk, payload, token)\n\n // 更新 headers\n headers.rk = rk\n headers.tn = tn\n headers.sa = sa\n }\n }\n\n if (exparams) {\n Object.assign(payload, exparams)\n }\n const params = new URLSearchParams()\n Object.entries(payload).forEach(([key, value]) => {\n params.append(key, String(value))\n })\n const objPayload = payload\n payload = params.toString()\n\n if (!kf && this.server !== \"cn\") {\n // 国际服貌似没签名 姑且加上\n const si = kf ? {} : build_signature111(objPayload, \"\")\n Object.assign(objPayload, { sign: si.s, timestamp: si.t })\n const rk = si.k\n const pk = await this.getRsaPublicKey()\n const ek = rsa_encrypt(rk, pk)\n if (h5) {\n headers.k = ek\n } else {\n headers.rk = rk\n headers.key = ek\n }\n }\n } else {\n payload = JSON.stringify(payload)\n }\n }\n return { headers, payload }\n }\n\n async needSign(url: string): Promise<boolean> {\n if (url === \"config/getCommonConfig\") return false\n if (this.sign_api_urls.size === 0) {\n try {\n await this.initializeSignConfig()\n } catch (error) {\n console.error(\"初始化签名配置失败:\", error)\n } finally {\n if (this.sign_api_urls.size === 0)\n this.sign_api_urls = new Set(\n [\n \"/user/sdkLogin\",\n \"/user/getSmsCode\",\n \"/role/defaultRoleForTool\",\n \"/media/av/cfg/getVideos\",\n \"/media/av/cfg/getAudios\",\n \"/media/av/cfg/getImages\",\n \"/encourage/signin/signin\",\n \"/user/refreshToken\",\n \"/user/signIn\",\n \"/role/defaultRole\",\n \"/role/list\",\n \"/role/getShortNoteInfo\",\n \"/forum/like\",\n \"/encourage/calendar/Activity/list\",\n ].map(item => item.replace(/^\\/+/, \"\"))\n )\n }\n }\n return this.sign_api_urls.has(url)\n }\n\n async initializeSignConfig(): Promise<void> {\n try {\n const configRes = await this._dna_request<DNACommonConfigEntity>(\"config/getCommonConfig\", undefined, { sign: false })\n if (configRes.is_success && configRes.data?.signApiConfigVo?.signApiList) {\n this.sign_api_urls = new Set(configRes.data.signApiConfigVo.signApiList.map(item => item.replace(/^\\/+/, \"\")))\n }\n } catch (error) {\n console.error(\"初始化签名配置失败:\", error)\n }\n }\n\n public async _dna_request_h5<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return await this._dna_request(url, data, { ...options, h5: true })\n }\n\n public async _dna_request_kf<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return await this._dna_request(url, data, { ...options, kf: true })\n }\n\n public async _dna_request<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n let { method = \"POST\", sign, h5, kf, refer, params, max_retries = 3, retry_delay = 2000, timeout = 3000 } = options || {}\n if (url.startsWith(\"/\")) url = url.slice(1)\n\n // 如果未明确指定 sign,则根据 URL 自动判断\n if (sign === undefined && (await this.needSign(url))) {\n sign = true\n }\n let headers: Record<string, any>\n if (sign) {\n const { payload: p, headers: h } = await this.getHeaders({\n payload: data,\n refer,\n exparams: params,\n token: this.token,\n h5,\n })\n data = p\n headers = h\n } else {\n const { headers: h } = await this.getHeaders({ refer, h5, kf })\n headers = h\n }\n if (this.debug) console.debug(\"[_dna_request] url:\", url, \"headers:\", headers, \"data:\", data)\n let lastError: Error | null = null\n for (let attempt = 0; attempt < max_retries; attempt++) {\n try {\n let body: string | FormData | undefined = data\n if (data && typeof data === \"object\" && !(data instanceof FormData)) {\n if (!kf || method === \"GET\") {\n const p = new URLSearchParams()\n Object.entries(data).forEach(([key, value]) => {\n if (value !== undefined) p.append(key, String(value))\n })\n body = p.toString()\n } else {\n body = JSON.stringify(data)\n }\n }\n const fetchOptions: RequestInit =\n method === \"GET\"\n ? {\n method,\n headers,\n }\n : {\n method,\n headers,\n body,\n }\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const initOptions = {\n ...fetchOptions,\n signal: controller.signal,\n }\n const base = kf ? this.KF_BASE_URL : this.BASE_URL\n const fullUrl = method === \"GET\" ? `${base}${url}${body ? `?${body}` : \"\"}` : `${base}${url}`\n const response = this.fetchFn ? await this.fetchFn(fullUrl, initOptions) : await fetch(fullUrl, initOptions)\n clearTimeout(timeoutId)\n\n const contentType = response.headers.get(\"content-type\") || \"\"\n // 处理kf的cookie\n if (kf) {\n const cookie = response.headers.get(\"set-cookie\")\n if (cookie) {\n this.cookieToken = cookie.split(\";\")[0]\n }\n }\n let raw_res: any\n\n if (contentType.includes(\"text/\")) {\n const textData = await response.text()\n raw_res = {\n code: RespCode.ERROR,\n data: textData,\n }\n } else {\n raw_res = await response.json()\n }\n\n if (typeof raw_res === \"object\" && raw_res !== null) {\n try {\n if (typeof raw_res.data === \"string\") {\n raw_res.data = JSON.parse(raw_res.data)\n }\n } catch {}\n }\n if (this.debug) console.debug(\"[_dna_request] raw_res:\", raw_res)\n if (\n typeof raw_res.data === \"object\" &&\n raw_res.data !== null &&\n Object.keys(raw_res.data).length === 0 &&\n url.includes(\"role\")\n ) {\n throw new Error(\"空返回值\")\n }\n\n return new TimeBasicResponse<T>(raw_res)\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"AbortError\")) {\n await new Promise(resolve => setTimeout(resolve, retry_delay))\n } else {\n console.trace(`请求失败: ${(e as Error).message}`)\n lastError = e as Error\n break\n }\n }\n }\n\n return TimeBasicResponse.err(lastError?.message || \"请求服务器失败,已达最大重试次数\")\n }\n}\n\n/**\n * 子模块基类,代理 DNABaseAPI 的方法和属性\n * 所有子模块都通过这个类代理到共享的 BaseAPI 实例\n */\nexport abstract class DNASubModule {\n base: DNABaseAPI\n\n constructor(base: DNABaseAPI) {\n this.base = base\n }\n\n // 代理属性访问\n get dev_code(): string {\n return this.base.dev_code\n }\n\n get token(): string {\n return this.base.token\n }\n set token(value: string) {\n this.base.token = value\n }\n\n get kf_token(): string {\n return this.base.kf_token\n }\n set kf_token(value: string) {\n this.base.kf_token = value\n }\n\n get fetchFn(): typeof fetch | undefined {\n return this.base.fetchFn\n }\n\n get RSA_PUBLIC_KEY(): string {\n return this.base.RSA_PUBLIC_KEY\n }\n\n get BASE_URL(): string {\n return this.base.BASE_URL\n }\n\n get uploadKey(): string {\n return this.base.uploadKey\n }\n\n // 代理方法\n async getRsaPublicKey(): Promise<string> {\n return this.base.getRsaPublicKey()\n }\n\n /**\n * 图片上传\n */\n async fileUpload(url: string, data: FormData) {\n return await this.base.fileUpload(url, data)\n }\n\n public async _dna_request<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return this.base._dna_request(url, data, options)\n }\n\n public async _dna_request_h5<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return this.base._dna_request_h5(url, data, options)\n }\n\n public async _dna_request_kf<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return this.base._dna_request_kf(url, data, options)\n }\n\n public async getHeaders(options?: {\n payload?: Record<string, any> | string | FormData\n exparams?: Record<string, any>\n dev_code?: string\n refer?: boolean\n token?: string\n h5?: boolean\n }): Promise<HeadersPayload> {\n return this.base.getHeaders(options)\n }\n}\n",
7
+ "import { RespCode, TimeBasicResponse } from \"../TimeBasicResponse\"\nimport type { DNACommonConfigEntity } from \"../type-generated\"\nimport {\n aesDecryptImageUrl,\n build_signature111,\n build_signature120,\n build_signature122,\n build_upload_signature,\n type HeadersPayload,\n type RequestOptions,\n rand_str2,\n rsa_encrypt,\n} from \"./utils\"\n\nexport interface DNABaseAPIConfig {\n server?: \"cn\" | \"global\"\n lang?: string //\"zh-Hans\" | \"en\"\n dev_code?: string\n token?: string\n kf_token?: string\n fetchFn?: typeof fetch\n is_h5?: boolean\n rsa_public_key?: string\n mode?: \"android\" | \"ios\"\n debug?: boolean\n}\n\nexport class DNABaseAPI {\n public fetchFn?: typeof fetch\n public RSA_PUBLIC_KEY =\n \"MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQDGpdbezK+eknQZQzPOjp8mr/dP+QHwk8CRkQh6C6qFnfLH3tiyl0pnt3dePuFDnM1PUXGhCkQ157ePJCQgkDU2+mimDmXh0oLFn9zuWSp+U8uLSLX3t3PpJ8TmNCROfUDWvzdbnShqg7JfDmnrOJz49qd234W84nrfTHbzdqeigQIDAQAB\"\n public BASE_URL = \"https://dnabbs-api.yingxiong.com/\"\n public BASE_WEB_SOCKET_URL = \"wss://dnabbs-api.yingxiong.com:8180/ws-community-websocket\"\n public KF_BASE_URL = \"https://kf.yingxiong.com/\"\n public uploadKey: string = \"\"\n public sign_api_urls = new Set<string>()\n public baseHeaders: Record<string, string> = {}\n\n public dev_code = \"\"\n public token = \"\"\n public kf_token = \"\"\n public debug = false\n public cookieToken = \"\"\n private _server: \"cn\" | \"global\" = \"cn\"\n public get server() {\n return this._server\n }\n public set server(value: \"cn\" | \"global\") {\n this._server = value\n if (value === \"global\") {\n this.BASE_URL = \"https://lunoloft-api.yingxiong.com/\"\n this.BASE_WEB_SOCKET_URL = \"wss://lunoloft-api.yingxiong.com:8181/ws-community-websocket\"\n } else {\n this.BASE_URL = \"https://dnabbs-api.yingxiong.com/\"\n this.BASE_WEB_SOCKET_URL = \"wss://dnabbs-api.yingxiong.com:8180/ws-community-websocket\"\n }\n this.updateHeaders()\n }\n private _mode: \"android\" | \"ios\" = \"ios\"\n public get mode() {\n return this._mode\n }\n public set mode(value: \"android\" | \"ios\") {\n this._mode = value\n this.updateHeaders()\n }\n private _lang: string = \"zh-Hans\"\n public get lang() {\n return this._lang\n }\n public set lang(value: string) {\n this._lang = value\n this.updateHeaders()\n }\n\n constructor(options: DNABaseAPIConfig = {}) {\n if (options.server === \"global\") {\n this.server = \"global\"\n } else {\n this.server = \"cn\"\n }\n this.fetchFn = options.fetchFn\n if (options.mode !== undefined) this.mode = options.mode\n if (options.lang !== undefined) this.lang = options.lang\n if (options.rsa_public_key !== undefined) this.RSA_PUBLIC_KEY = options.rsa_public_key\n if (options.dev_code !== undefined) {\n this.dev_code = options.dev_code\n } else {\n this.dev_code = DNABaseAPI.generateDeviceCode()\n }\n if (options.token !== undefined) this.token = options.token\n if (options.kf_token !== undefined) this.kf_token = options.kf_token\n if (options.debug) this.debug = true\n }\n /**\n * 生成设备码\n */\n static generateDeviceCode() {\n return `2${rand_str2(32)}`\n }\n\n updateHeaders() {\n if (this.mode === \"android\") {\n this.baseHeaders = {\n countrycode: \"CN\",\n version: this.server === \"cn\" ? \"1.2.2\" : \"1.1.1\",\n versioncode: this.server === \"cn\" ? \"9\" : \"5\",\n source: \"android\",\n lang: this.lang,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\": this.server === \"cn\" ? \"okhttp/3.10.0\" : \"okhttp/5.3.2\",\n }\n } else {\n this.baseHeaders = {\n countrycode: \"CN\",\n version: this.server === \"cn\" ? \"1.2.0\" : \"1.1.1\",\n versioncode: this.server === \"cn\" ? \"7\" : \"5\",\n source: \"ios\",\n lang: this.lang,\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\":\n this.server === \"cn\"\n ? \"DoubleHelix/3 CFNetwork/3860.300.31 Darwin/25.2.0\"\n : \"DoubleHelix/4 CFNetwork/3860.100.1 Darwin/25.0.0\",\n }\n }\n }\n\n async fileUpload(url: string, data: FormData) {\n const res = await this._dna_request<string[]>(url, data, { sign: true })\n if (res.is_success && res.data) {\n res.data = res.data.map(url => aesDecryptImageUrl(url, this.uploadKey))\n }\n return res\n }\n\n async getRsaPublicKey() {\n if (this.RSA_PUBLIC_KEY) {\n return this.RSA_PUBLIC_KEY\n }\n const res = await this._dna_request<{ key: string }>(\"config/getRsaPublicKey\")\n\n if (res.is_success && res.data) {\n const key = res.data.key\n if (typeof key === \"string\") {\n this.RSA_PUBLIC_KEY = key\n }\n }\n return this.RSA_PUBLIC_KEY\n }\n\n public async getHeaders(options?: {\n payload?: Record<string, any> | string | FormData\n exparams?: Record<string, any>\n dev_code?: string\n refer?: boolean\n token?: string\n kf_token?: string\n h5?: boolean\n kf?: boolean\n }): Promise<HeadersPayload> {\n let {\n payload = {},\n exparams,\n dev_code = this.dev_code,\n refer,\n token = this.token,\n kf_token = this.kf_token,\n h5,\n kf,\n } = options || {}\n\n const h5BaseHeader = {\n version: \"3.11.0\",\n source: \"h5\",\n \"Content-Type\": \"application/x-www-form-urlencoded\",\n \"User-Agent\":\n \"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\",\n }\n const kfBaseHeader = {\n Authorization: kf_token,\n Referer: `https://kf.yingxiong.com/kf2.0/im-chat`,\n \"X-Requested-With\": \"com.hero.dna\",\n \"Content-Type\": \"application/json; charset=utf-8\",\n \"User-Agent\":\n \"Mozilla/5.0 (Linux; Android 16; PLQ110 Build/BP2A.250605.015; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/143.0.7499.192 Mobile Safari/537.36CP6.TgzO Hero/1.1.4\",\n }\n const is_h5 = h5 || false\n const headers: Record<string, any> = kf ? kfBaseHeader : is_h5 ? h5BaseHeader : { ...this.baseHeaders }\n if (dev_code && !kf) {\n headers.devCode = dev_code\n }\n if (refer || is_h5) {\n headers.origin = \"https://dnabbs.yingxiong.com\"\n headers.refer = \"https://dnabbs.yingxiong.com/\"\n }\n if (token && !kf) {\n headers.token = token\n }\n if (kf && this.cookieToken) {\n headers.Cookie = this.cookieToken\n }\n if (payload instanceof FormData) {\n const pk = await this.getRsaPublicKey()\n const { signature, key } = build_upload_signature(pk)\n headers.t = signature\n this.uploadKey = key\n if (exparams) {\n for (const [key, value] of Object.entries(exparams)) {\n payload.append(key, String(value))\n }\n }\n delete headers[\"Content-Type\"]\n } else if (typeof payload === \"object\") {\n if (!kf) {\n const pk = await this.getRsaPublicKey()\n if (this.server === \"cn\") {\n if (this.mode === \"android\") {\n const { tn, sa } = build_signature122(pk, payload, token)\n\n // 更新 headers\n if (options?.token) {\n headers.tn = tn\n headers.sa = sa\n }\n } else {\n const { tn, sa, rk } = build_signature120(pk, payload, token)\n\n // 更新 headers\n headers.rk = rk\n headers.tn = tn\n headers.sa = sa\n }\n }\n\n if (exparams) {\n Object.assign(payload, exparams)\n }\n const params = new URLSearchParams()\n Object.entries(payload).forEach(([key, value]) => {\n params.append(key, String(value))\n })\n const objPayload = payload\n payload = params.toString()\n\n if (!kf && this.server !== \"cn\") {\n // 国际服貌似没签名 姑且加上\n const si = kf ? {} : build_signature111(objPayload, \"\")\n Object.assign(objPayload, { sign: si.s, timestamp: si.t })\n const rk = si.k\n const pk = await this.getRsaPublicKey()\n const ek = rsa_encrypt(rk, pk)\n if (h5) {\n headers.k = ek\n } else {\n headers.rk = rk\n headers.key = ek\n }\n }\n } else {\n payload = JSON.stringify(payload)\n }\n }\n return { headers, payload }\n }\n\n async needSign(url: string): Promise<boolean> {\n if (url === \"config/getCommonConfig\") return false\n if (this.sign_api_urls.size === 0) {\n try {\n await this.initializeSignConfig()\n } catch (error) {\n console.error(\"初始化签名配置失败:\", error)\n } finally {\n if (this.sign_api_urls.size === 0)\n this.sign_api_urls = new Set(\n [\n \"/user/sdkLogin\",\n \"/user/getSmsCode\",\n \"/role/defaultRoleForTool\",\n \"/media/av/cfg/getVideos\",\n \"/media/av/cfg/getAudios\",\n \"/media/av/cfg/getImages\",\n \"/encourage/signin/signin\",\n \"/user/refreshToken\",\n \"/user/signIn\",\n \"/role/defaultRole\",\n \"/role/list\",\n \"/role/getShortNoteInfo\",\n \"/forum/like\",\n \"/encourage/calendar/Activity/list\",\n ].map(item => item.replace(/^\\/+/, \"\"))\n )\n }\n }\n return this.sign_api_urls.has(url)\n }\n\n async initializeSignConfig(): Promise<void> {\n try {\n const configRes = await this._dna_request<DNACommonConfigEntity>(\"config/getCommonConfig\", undefined, { sign: false })\n if (configRes.is_success && configRes.data?.signApiConfigVo?.signApiList) {\n this.sign_api_urls = new Set(configRes.data.signApiConfigVo.signApiList.map(item => item.replace(/^\\/+/, \"\")))\n }\n } catch (error) {\n console.error(\"初始化签名配置失败:\", error)\n }\n }\n\n public async _dna_request_h5<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return await this._dna_request(url, data, { ...options, h5: true })\n }\n\n public async _dna_request_kf<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return await this._dna_request(url, data, { ...options, kf: true })\n }\n\n public async _dna_request<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n let { method = \"POST\", sign, h5, kf, refer, params, max_retries = 3, retry_delay = 2000, timeout = 3000 } = options || {}\n if (url.startsWith(\"/\")) url = url.slice(1)\n\n // 如果未明确指定 sign,则根据 URL 自动判断\n if (sign === undefined && (await this.needSign(url))) {\n sign = true\n }\n let headers: Record<string, any>\n if (sign) {\n const { payload: p, headers: h } = await this.getHeaders({\n payload: data,\n refer,\n exparams: params,\n token: this.token,\n h5,\n })\n data = p\n headers = h\n } else {\n const { headers: h } = await this.getHeaders({ refer, h5, kf })\n headers = h\n }\n if (this.debug) console.debug(\"[_dna_request] url:\", url, \"headers:\", headers, \"data:\", data)\n let lastError: Error | null = null\n for (let attempt = 0; attempt < max_retries; attempt++) {\n try {\n let body: string | FormData | undefined = data\n if (data && typeof data === \"object\" && !(data instanceof FormData)) {\n if (!kf || method === \"GET\") {\n const p = new URLSearchParams()\n Object.entries(data).forEach(([key, value]) => {\n if (value !== undefined) p.append(key, String(value))\n })\n body = p.toString()\n } else {\n body = JSON.stringify(data)\n }\n }\n const fetchOptions: RequestInit =\n method === \"GET\"\n ? {\n method,\n headers,\n }\n : {\n method,\n headers,\n body,\n }\n\n const controller = new AbortController()\n const timeoutId = setTimeout(() => controller.abort(), timeout)\n\n const initOptions = {\n ...fetchOptions,\n signal: controller.signal,\n }\n const base = kf ? this.KF_BASE_URL : this.BASE_URL\n const fullUrl = method === \"GET\" ? `${base}${url}${body ? `?${body}` : \"\"}` : `${base}${url}`\n const response = this.fetchFn ? await this.fetchFn(fullUrl, initOptions) : await fetch(fullUrl, initOptions)\n clearTimeout(timeoutId)\n\n const contentType = response.headers.get(\"content-type\") || \"\"\n // 处理kf的cookie\n if (kf) {\n const cookie = response.headers.get(\"set-cookie\")\n if (cookie) {\n this.cookieToken = cookie.split(\";\")[0]\n }\n }\n let raw_res: any\n\n if (contentType.includes(\"text/\")) {\n const textData = await response.text()\n raw_res = {\n code: RespCode.ERROR,\n data: textData,\n }\n } else {\n raw_res = await response.json()\n }\n\n if (typeof raw_res === \"object\" && raw_res !== null) {\n try {\n if (typeof raw_res.data === \"string\") {\n raw_res.data = JSON.parse(raw_res.data)\n }\n } catch {}\n }\n if (this.debug) console.debug(\"[_dna_request] raw_res:\", raw_res)\n if (\n typeof raw_res.data === \"object\" &&\n raw_res.data !== null &&\n Object.keys(raw_res.data).length === 0 &&\n url.includes(\"role\")\n ) {\n throw new Error(\"空返回值\")\n }\n\n return new TimeBasicResponse<T>(raw_res)\n } catch (e) {\n if (e instanceof Error && e.message.includes(\"AbortError\")) {\n await new Promise(resolve => setTimeout(resolve, retry_delay))\n } else {\n console.trace(`请求失败: ${(e as Error).message}`)\n lastError = e as Error\n break\n }\n }\n }\n\n return TimeBasicResponse.err(lastError?.message || \"请求服务器失败,已达最大重试次数\")\n }\n}\n\n/**\n * 子模块基类,代理 DNABaseAPI 的方法和属性\n * 所有子模块都通过这个类代理到共享的 BaseAPI 实例\n */\nexport abstract class DNASubModule {\n base: DNABaseAPI\n\n constructor(base: DNABaseAPI) {\n this.base = base\n }\n\n // 代理属性访问\n get dev_code(): string {\n return this.base.dev_code\n }\n\n get token(): string {\n return this.base.token\n }\n set token(value: string) {\n this.base.token = value\n }\n\n get kf_token(): string {\n return this.base.kf_token\n }\n set kf_token(value: string) {\n this.base.kf_token = value\n }\n\n get fetchFn(): typeof fetch | undefined {\n return this.base.fetchFn\n }\n\n get RSA_PUBLIC_KEY(): string {\n return this.base.RSA_PUBLIC_KEY\n }\n\n get BASE_URL(): string {\n return this.base.BASE_URL\n }\n\n get uploadKey(): string {\n return this.base.uploadKey\n }\n\n // 代理方法\n async getRsaPublicKey(): Promise<string> {\n return this.base.getRsaPublicKey()\n }\n\n /**\n * 图片上传\n */\n async fileUpload(url: string, data: FormData) {\n return await this.base.fileUpload(url, data)\n }\n\n public async _dna_request<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return this.base._dna_request(url, data, options)\n }\n\n public async _dna_request_h5<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return this.base._dna_request_h5(url, data, options)\n }\n\n public async _dna_request_kf<T = any>(url: string, data?: any, options?: RequestOptions): Promise<TimeBasicResponse<T>> {\n return this.base._dna_request_kf(url, data, options)\n }\n\n public async getHeaders(options?: {\n payload?: Record<string, any> | string | FormData\n exparams?: Record<string, any>\n dev_code?: string\n refer?: boolean\n token?: string\n h5?: boolean\n }): Promise<HeadersPayload> {\n return this.base.getHeaders(options)\n }\n}\n",
8
8
  "import type {\n DNACharDetailEntity,\n DNAPushStringBean,\n DNARoleEntity,\n DNAShortNoteEntity,\n DNASoulTaskBean,\n DNAWeaponDetailEntity,\n} from \"../type-generated\"\nimport { DNASubModule } from \"./base\"\n\nexport class GameAPI extends DNASubModule {\n async defaultRoleForTool(type: number = 1, otherUserId = \"\") {\n const data = { otherUserId, type }\n return await this._dna_request<DNARoleEntity>(\"role/defaultRoleForTool\", data, { sign: true })\n }\n\n async getMhSwitchStatus() {\n return await this._dna_request<DNAPushStringBean>(\"user/push/getMhSwitchStatus\")\n }\n\n async getRoleDetail(char_id: number | string, char_eid: string, otherUserId?: string) {\n const data = { charId: char_id, charEid: char_eid, type: 1, otherUserId }\n return await this._dna_request<DNACharDetailEntity>(\"role/getCharDetail\", data)\n }\n\n /**\n * 获取铸造信息\n */\n async getShortNoteInfo() {\n return await this._dna_request<DNAShortNoteEntity>(\"role/getShortNoteInfo\", undefined, { sign: true })\n }\n\n async getWeaponDetail(weapon_id: number | string, weapon_eid: string, otherUserId?: string) {\n const data = { weaponId: weapon_id, weaponEid: weapon_eid, type: 1, otherUserId }\n return await this._dna_request<DNAWeaponDetailEntity>(\"role/getWeaponDetail\", data)\n }\n\n async updateMhSwitchStatus(config: string) {\n return await this._dna_request(\"user/push/updateMhSwitchStatus\", { config })\n }\n\n async soulTask() {\n return await this._dna_request<DNASoulTaskBean>(\"role/soul/task\")\n }\n}\n",
9
9
  "import { DNASubModule } from \"./base\"\n\nexport interface DNAMapMatterCategorizeOption {\n icon: string\n id: number\n matters: DNAMapMatter[]\n name: string\n sort?: number\n}\n\nexport interface DNAMatterCategorizeDetail {\n icon: string\n id: number\n matters: DNAMapMatterDetail[]\n name: string\n sort?: number\n}\n\nexport interface DNAMapMatter {\n icon: string\n id: number\n mapMatterCategorizeId: number\n name: string\n sort: number\n}\n\nexport interface DNAMapCategorizeListRes {\n list: DNAMatterCategorizeList[]\n}\n\nexport interface DNAMatterCategorizeList {\n id: number\n maps: DNAMap[]\n name: string\n}\n\nexport interface DNAMapDetailRes {\n floors: DNAMapFloor[]\n matterCategorizes: DNAMatterCategorizeDetail[]\n map: DNAMap\n userSites: DNAMapSite[]\n}\n\nexport interface DNAMap {\n id: number\n name: string\n pid?: number\n}\n\nexport interface DNAMapMatterDetail extends DNAMapMatter {\n sites: DNAMapSite[]\n}\n\nexport interface DNAMapSite {\n id: number\n isHide: number\n mapFloorId: number\n mapId: number\n mapMatterId: number\n x: number\n y: number\n}\n\nexport interface DNAMapFloor {\n floor: number\n id: number\n name: string\n pic: string\n}\n\nexport interface DNAMapSiteDetailRes {\n contributes: DNAMapContribute[]\n description: string\n id: number\n isDel: number\n isHide: number\n mapFloorId: number\n mapId: number\n mapMatterCategorizeId: number\n mapMatterId: number\n pic: string\n script: string\n url: string\n urlDesc: string\n urlIcon: string\n x: number\n y: number\n}\n\ninterface DNAMapContribute {\n userHeadUrl: string\n userId: string\n userName: string\n}\n\nexport interface DNAEmoji {\n content: string[]\n gameId: number\n icon: string\n size: number\n title: string\n url: string\n}\n\nexport class H5API extends DNASubModule {\n async getMapMatterCategorizeOptions() {\n return await this._dna_request_h5<DNAMapMatterCategorizeOption[]>(\"map/matter/categorize/getOptions\")\n }\n\n async getMapCategorizeList() {\n return await this._dna_request_h5<DNAMapCategorizeListRes>(\"map/categorize/list\")\n }\n\n async getMapDetail(id: number) {\n return await this._dna_request_h5<DNAMapDetailRes>(\"map/detail\", { id })\n }\n\n async getMapSiteDetail(id: number) {\n return await this._dna_request_h5<DNAMapSiteDetailRes>(\"map/site/detail\", { id })\n }\n\n async getEmojiList() {\n return await this._dna_request_h5<DNAEmoji[]>(\"config/getEmoji\")\n }\n}\n",
10
10
  "import type {\n DNAActivityListBean,\n DNABlockBean,\n DNACommentListResponse,\n DNACreateCommentResponse,\n DNADiscussAreaResponse,\n DNAFollowBean,\n DNAFraternityResponse,\n DNAGameBannerBean,\n DNAGameSignInResultBean,\n DNAGameSignInShowDataBean,\n DNAHomeOffWaterResponse,\n DNAIsRedPointBean,\n DNAPostDetailResponse,\n DNAPostListBean,\n DNAReceiveRecord,\n DNARecommendBean,\n DNAReplayCommentResponse,\n DNAReplyListResponse,\n DNASearchPostBean,\n DNASearchTopicBean,\n DNASearchUserBean,\n DNASignCenterBean,\n DNASignInBean,\n DNASoulTaskBean,\n DNAStatisticsBean,\n DNATipsBean,\n DNATopicListResponse,\n DNAUserTaskProcessEntity,\n} from \"../type-generated\"\nimport { DNASubModule } from \"./base\"\n\nconst DNA_GAME_ID = 268\n\nexport class HomeAPI extends DNASubModule {\n async adminAdjustScore(postId: string, gameForumId: number, weight: string, gameId: number = DNA_GAME_ID) {\n const data = { postId, gameId: gameId ?? DNA_GAME_ID, gameForumId, weight }\n return await this._dna_request(\"forum/moderator/setPostWeight\", data)\n }\n\n async adminDelete(post: { postId: string; gameId?: number; gameForumId: number }, content: string, reasonCode: number) {\n const data = {\n postId: post.postId,\n gameId: post.gameId ?? DNA_GAME_ID,\n gameForumId: post.gameForumId,\n content: content,\n reasonCode: reasonCode,\n }\n return await this._dna_request(\"forum/moderator/postDelete\", data)\n }\n\n async adminMovePost(\n post: { postId: string; gameId?: number; gameForumId: number },\n newGameId: number,\n newForumId: number,\n newTopicIdStr: string\n ) {\n const data = {\n postId: post.postId,\n gameId: post.gameId ?? DNA_GAME_ID,\n gameForumId: post.gameForumId,\n newGameId,\n newForumId,\n newTopicIdStr,\n }\n return await this._dna_request(\"forum/moderator/postMove\", data)\n }\n\n async adminRefreshTime(post: { postId: string; gameId?: number; gameForumId: number }, refresh: number) {\n const data = {\n postId: post.postId,\n gameId: post.gameId ?? DNA_GAME_ID,\n gameForumId: post.gameForumId,\n refresh,\n }\n return await this._dna_request(\"forum/moderator/setRefresh\", data)\n }\n\n async blockList() {\n return await this._dna_request<DNABlockBean>(\"user/block/list\")\n }\n\n async blockOther(blockPostId: string, blockUserId: string, type: number) {\n return await this._dna_request(\"user/block/other\", { blockPostId, blockUserId, type })\n }\n\n async collect(postId: string, toUserId: string, operateType = 1) {\n const data = { operateType, postId, toUserId }\n return await this._dna_request(\"forum/collect\", data)\n }\n\n async commentDelete(\n comment: { id: number; gameId: number; gameForumId: number },\n entityType: number,\n content: string,\n reasonCode: number\n ) {\n const data = { id: comment.id, gameId: comment.gameId, gameForumId: comment.gameForumId, entityType, content, reasonCode }\n return await this._dna_request(\"forum/commentReplyDelete\", data)\n }\n\n async createComment(post: { userId: string; postId: string; gameForumId: number }, content: string) {\n const content_json = JSON.stringify([\n {\n content,\n contentType: \"1\",\n },\n ])\n const data = { postId: post.postId, forumId: post.gameForumId, postType: \"1\", content: content_json }\n return await this._dna_request<DNACreateCommentResponse>(\"forum/comment/createComment\", data, {\n sign: true,\n params: { toUserId: post.userId },\n })\n }\n\n async createReply(post: { userId: string; postId: string; postCommentId: string; gameForumId: number }, content: string) {\n const content_json = JSON.stringify([\n {\n content,\n contentType: \"1\",\n imgHeight: 0,\n imgWidth: 0,\n url: \"\",\n },\n ])\n const data = {\n content: content_json,\n forumId: post.gameForumId,\n postCommentId: post.postCommentId,\n postId: post.postId,\n postType: \"1\",\n toUserId: post.userId,\n }\n return await this._dna_request<DNAReplayCommentResponse>(\"forum/comment/createReply\", data, {\n sign: true,\n params: { toUserId: post.userId },\n })\n }\n\n async createReplyList(\n post: { userId: string; postId: string; postCommentId: string; postCommentReplyId: string; gameForumId: number },\n content: string\n ) {\n const content_json = JSON.stringify([\n {\n content,\n contentType: \"1\",\n imgHeight: 0,\n imgWidth: 0,\n url: \"\",\n },\n ])\n const data = {\n content: content_json,\n forumId: post.gameForumId,\n postCommentId: post.postCommentId,\n postCommentReplyId: post.postCommentReplyId,\n postId: post.postId,\n postType: \"1\",\n toUserId: post.userId,\n }\n return await this._dna_request<DNAReplayCommentResponse>(\"forum/comment/createReply\", data, {\n sign: true,\n params: { toUserId: post.userId },\n })\n }\n\n async deletePost(deleteType: number, id: number) {\n return await this._dna_request(\"forum/more/delete\", { deleteType, id }, { sign: true, refer: true })\n }\n\n async followUser(followUserId: string, unfollow?: boolean) {\n const data = { followUserId, operateType: unfollow ? 0 : 1 }\n return await this._dna_request(\"user/followUser\", data, { sign: true })\n }\n\n async getFollowState(followUserId: string) {\n const data = { followUserId }\n return await this._dna_request<DNAFollowBean>(\"user/isFollow\", data)\n }\n\n async gameSignIn(dayAwardId: number, period: number) {\n const data = { dayAwardId, periodId: period, signinType: 1 }\n return await this._dna_request<DNAGameSignInResultBean>(\"encourage/signin/signin\", data, { sign: true })\n }\n\n async getActivityList(curTime?: string, startTime?: string, endTime?: string) {\n const now = new Date()\n function formatDate(date: Date) {\n return date\n .toLocaleString(\"zh-CN\", {\n timeZone: \"Asia/Shanghai\",\n hour12: false,\n year: \"numeric\",\n month: \"2-digit\",\n day: \"2-digit\",\n hour: \"2-digit\",\n minute: \"2-digit\",\n second: \"2-digit\",\n })\n .replace(/\\//g, \"-\")\n }\n if (!curTime) {\n curTime = formatDate(now)\n }\n if (!startTime) {\n // 往前三个月\n const minDate = new Date(\"2025-12-29 00:00:00 GMT+0800\")\n const maxDate = new Date(now.getFullYear(), now.getMonth() - 3, now.getDate())\n startTime = formatDate(maxDate > minDate ? maxDate : minDate)\n }\n if (!endTime) {\n // 往后三个月\n endTime = formatDate(new Date(now.getFullYear(), now.getMonth() + 3, now.getDate(), 23, 59))\n }\n return await this._dna_request<DNAActivityListBean>(\"encourage/calendar/Activity/list\", { curTime, startTime, endTime })\n }\n\n async getDoujin(forumId: number) {\n return await this._dna_request<DNADiscussAreaResponse>(\"forum/discuss/getDoujin\", { forumId })\n }\n\n async getExchange(forumId: number) {\n return await this._dna_request<DNADiscussAreaResponse>(\"forum/discuss/getExchange\", { forumId })\n }\n\n async getGameBanner(gameId = DNA_GAME_ID) {\n return await this._dna_request<DNAGameBannerBean>(\"forum/gameBanner\", { gameId })\n }\n\n async getPostByTopic(\n topicId: number = 177,\n pageIndex: number = 1,\n pageSize: number = 20,\n searchType: number = 1,\n timeType: number = 0\n ) {\n const data = {\n topicId,\n gameId: DNA_GAME_ID,\n pageIndex,\n pageSize,\n searchType,\n timeType,\n }\n return await this._dna_request<DNATopicListResponse>(\"forum/getPostByTopic\", data)\n }\n\n async getPostCommentList(postId: string, pageIndex: number = 1, pageSize: number = 20, isOnlyPublisher: number = 0, showOrderType = 1) {\n return await this._dna_request<DNACommentListResponse>(\"forum/comment/getPostCommentList\", {\n isOnlyPublisher,\n pageIndex,\n pageSize,\n postId,\n showOrderType,\n })\n }\n\n async getPostDetail(postId: string) {\n return await this._dna_request<DNAPostDetailResponse>(\"forum/getPostDetail\", { postId })\n }\n\n async getPostList(forumId: number = 48, pageIndex: number = 1, pageSize: number = 20, searchType: number = 1, timeType: number = 0) {\n const data = {\n forumId: forumId,\n gameId: DNA_GAME_ID,\n pageIndex: pageIndex,\n pageSize: pageSize,\n searchType: searchType,\n timeType: timeType,\n }\n return await this._dna_request<DNADiscussAreaResponse>(\"forum/list\", data)\n }\n\n async getRankList(forumId: number) {\n return await this._dna_request<DNAFraternityResponse>(\"forum/discuss/getRank\", { forumId })\n }\n\n async getRecommendPosts(gameId = DNA_GAME_ID, pageIndex: number = 1, pageSize: number = 20) {\n return await this._dna_request<DNAHomeOffWaterResponse>(\"forum/getRecommendPosts\", { gameId, pageIndex, pageSize })\n }\n\n async getReplyList(postId: string, postCommentId: number, pageIndex: number = 1, pageSize: number = 20) {\n return await this._dna_request<DNAReplyListResponse>(\"forum/comment/getReplyList\", { postId, postCommentId, pageIndex, pageSize })\n }\n\n async getStatistics(gameId = DNA_GAME_ID) {\n return await this._dna_request<DNAStatisticsBean>(\"forum/statistics\", { gameId })\n }\n\n async getTips() {\n return await this._dna_request<DNATipsBean>(\"config/getTips\")\n }\n\n async getWalkthrough(forumId: number) {\n return await this._dna_request<DNADiscussAreaResponse>(\"forum/discuss/getWalkthrough\", { forumId })\n }\n\n async hotList(type: number = 1, gameId = DNA_GAME_ID) {\n return await this._dna_request<DNAPostListBean[]>(\"forum/hot/ranking/list\", { gameId, type })\n }\n\n async haveSignIn() {\n const data = { gameId: DNA_GAME_ID }\n return await this._dna_request<DNASignInBean>(\"user/haveSignInNew\", data)\n }\n\n async isHaveSignin() {\n const data = { gameId: DNA_GAME_ID }\n return await this._dna_request<DNASignCenterBean>(\"encourage/signin/isHaveSignin\", data)\n }\n\n async isRedPoint() {\n return await this._dna_request<DNAIsRedPointBean>(\"forum/dynamic/isRedPoint\")\n }\n\n async signCalendar() {\n const data = { gameId: DNA_GAME_ID }\n return await this._dna_request<DNAGameSignInShowDataBean>(\"encourage/signin/show\", data)\n }\n\n async like(post: { gameForumId: string; postId: string; postType: string; userId: string }) {\n const data = {\n forumId: post.gameForumId,\n gameId: DNA_GAME_ID,\n likeType: \"1\",\n operateType: \"1\",\n postCommentId: \"\",\n postCommentReplyId: \"\",\n postId: post.postId,\n postType: post.postType,\n toUserId: post.userId,\n }\n return await this._dna_request(\"forum/like\", data)\n }\n\n async lockPost(post: { postId: string; gameId?: number; gameForumId: number }, operateType: number) {\n const data = { postId: post.postId, gameId: post.gameId ?? DNA_GAME_ID, gameForumId: post.gameForumId, operateType }\n return await this._dna_request(\"forum/moderator/postLock\", data)\n }\n\n async postDownOrUp(post: { postId: string; gameId?: number; gameForumId: number }, operateType: number) {\n const data = { postId: post.postId, gameId: post.gameId ?? DNA_GAME_ID, gameForumId: post.gameForumId, operateType }\n return await this._dna_request(\"forum/moderator/postDownOrUp\", data)\n }\n\n async postElite(post: { postId: string; gameId?: number; gameForumId: number }, operateType: number) {\n const data = { postId: post.postId, gameId: post.gameId ?? DNA_GAME_ID, gameForumId: post.gameForumId, operateType }\n return await this._dna_request(\"forum/moderator/postElite\", data)\n }\n\n async postHide(post: { postId: string; gameId?: number; gameForumId: number }, operateType: number) {\n const data = { postId: post.postId, gameId: post.gameId ?? DNA_GAME_ID, gameForumId: post.gameForumId, operateType }\n return await this._dna_request(\"forum/moderator/postHide\", data)\n }\n\n async reRank(post: { postId: string; gameId?: number; gameForumId: number }, weight: number) {\n const data = { postId: post.postId, gameId: post.gameId ?? DNA_GAME_ID, gameForumId: post.gameForumId, weight }\n return await this._dna_request(\"forum/moderator/reRank\", data)\n }\n\n async receiveLog(periodId: number, pageIndex: number, pageSize: number) {\n const data = { periodId, pageIndex, pageSize }\n return await this._dna_request<DNAReceiveRecord>(\"encourage/signin/receiveLog\", data)\n }\n\n async recommendList(recIndex: number, newIndex: number, size: number, history: string, gameId = DNA_GAME_ID) {\n const data = { gameId, recIndex, newIndex, size, history }\n return await this._dna_request<DNARecommendBean>(\"forum/recommend/list\", data)\n }\n\n async report(\n { commentId = 0, postId = 0, replyId = 0 }: { commentId?: number; postId?: number; replyId?: number },\n reportReason = 1,\n reportType = 1\n ) {\n const data = { commentId, postId, replyId, reportReason, reportType }\n return await this._dna_request(\"forum/more/report\", data)\n }\n\n async searchPost(keyword: string, pageIndex: number, pageSize: number, gameId = DNA_GAME_ID, searchType = 1) {\n const data = { gameId, keyword, pageIndex, pageSize, searchType }\n return await this._dna_request<DNASearchPostBean>(\"forum/searchPost\", data)\n }\n\n async searchTopic(keyword: string, pageIndex: number, pageSize = 20, gameId = DNA_GAME_ID) {\n const data = { gameId, keyword, pageIndex, pageSize }\n return await this._dna_request<DNASearchTopicBean>(\"forum/searchPost\", data)\n }\n\n async searchUser(keyword: string, pageIndex: number, pageSize: number) {\n const data = { keyword, pageIndex, pageSize }\n return await this._dna_request<DNASearchUserBean>(\"forum/searchPost\", data)\n }\n\n async shareTask() {\n const data = { gameId: DNA_GAME_ID }\n return await this._dna_request(\"encourage/level/shareTask\", data)\n }\n\n async bbsSign() {\n const data = { gameId: DNA_GAME_ID }\n return await this._dna_request<DNASignInBean>(\"user/signIn\", data)\n }\n\n async strongRecommend(post: { postId: string; gameId?: number; gameForumId: number }, operateType = 1) {\n const data = {\n postId: post.postId,\n gameId: post.gameId ?? DNA_GAME_ID,\n gameForumId: post.gameForumId,\n operateType: operateType,\n }\n return await this._dna_request(\"forum/moderator/setForceRecommend\", data)\n }\n\n async viewCommunity() {\n return await this._dna_request(\"encourage/level/viewCommunity\")\n }\n\n async viewCount(postId: string, gameId = DNA_GAME_ID) {\n return await this._dna_request(\"forum/viewCount\", { gameId, postId })\n }\n\n async getTaskProcess() {\n const data = { gameId: DNA_GAME_ID }\n return await this._dna_request<DNAUserTaskProcessEntity>(\"encourage/level/getTaskProcess\", data)\n }\n\n async soulTask() {\n return await this._dna_request<DNASoulTaskBean>(\"role/soul/task\")\n }\n}\n",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dna-api",
3
- "version": "0.6.2",
3
+ "version": "0.6.3",
4
4
  "description": "dna bbs api",
5
5
  "author": {
6
6
  "name": "pa001024",