├── .gitignore ├── README.md ├── README_ZH.md ├── index.ts ├── package.json ├── src ├── types.ts ├── util.ts ├── v3 │ └── index.ts ├── v4 │ └── index.ts └── v5 │ ├── index.ts │ └── utils.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | # --- 2 | # IDE 3 | # --- 4 | .idea 5 | .vscode 6 | 7 | # ----- 8 | # build 9 | # ----- 10 | /build/ 11 | 12 | # ---- 13 | # Node 14 | # ---- 15 | node_modules 16 | package-lock.json 17 | yarn.lock 18 | 19 | # ---- 20 | # Test 21 | # ---- 22 | /test/ 23 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # BotArcApi Lib 2 | 3 | **English** | [中文](./README_ZH.md) 4 | 5 | A lib for **[BotArcApi](https://github.com/TheSnowfield/BotArcAPI)** with **Typescript**. 6 | 7 | ***Migration (for botarcapi_lib@1.1.4 or earlier users)***: Since 2022.4.28 BotArcAPI (now known as Arcaea Unlimited API) has changed its songinfo format. You can read [Type Definition](./src/types.ts) for more info. 8 | 9 | ## Install 10 | 11 | Install this package is very easy. 12 | 13 | Use npm: 14 | ``` 15 | npm i botarcapi_lib 16 | ``` 17 | 18 | Or use yarn: 19 | ``` 20 | yarn add botarcapi_lib 21 | ``` 22 | 23 | ## Usage 24 | 25 | ### Create API 26 | 27 | Follow the steps below: 28 | 29 | 1. Deploy a **BotArcApi** server at first. 30 | This step can just follow docs of BotArcApi. 31 | 32 | 2. Make sure you deploy the domain name of **BotArcApi**, such as `http://localhost:8088`. 33 | 34 | 3. Choose a **BotArcApi** version you like, such as `v5`, then input codes below in your project: 35 | ```typescript 36 | import BotArcApi from "botarcapi_lib"; 37 | const { BotArcApiV5 } = BotArcApi; 38 | const api = new BotArcApiV5("http://localhost:8088", 60000); 39 | ``` 40 | 41 | ...or maybe you are using **Node.js**: 42 | 43 | ```javascript 44 | const { BotArcApiV5 } = require("botarcapi_lib"); 45 | const api = new BotArcApiV5("http://localhost:8088", 60000); 46 | ``` 47 | 48 | Otherwise, maybe you want to control other request config, so you can provide `AxiosRequestConfig`: 49 | 50 | ```typescript 51 | import BotArcApi from "botarcapi_lib"; 52 | const { BotArcApiV5 } = BotArcApi; 53 | const api = new BotArcApiV4({ 54 | baseURL: "http://localhost:8088", 55 | timeout: 60000, 56 | headers: { 57 | "Authorization": "Bearer SecretAPItoken" 58 | } 59 | }); 60 | ``` 61 | 62 | 4. Use APIs you like. 63 | 64 | ### Use API 65 | 66 | For example, you want to check the best 30 of **Nagiha0798** and print result to console, you can: 67 | 68 | ```typescript 69 | import BotArcApi from "botarcapi_lib"; 70 | const { BotArcApiV4 } = BotArcApi; 71 | const api = new BotArcApiV4({ 72 | baseURL: "http://localhost:8088", 73 | timeout: 60000, 74 | headers: { 75 | "Authorization": "Bearer SecretAPItoken" 76 | } 77 | }); 78 | api.user.best30("Nagiha0798", true) 79 | .then(console.log) 80 | .catch(console.error); 81 | ``` 82 | 83 | ...or you may like using **async/await**: 84 | 85 | ```typescript 86 | import BotArcApi from "botarcapi_lib"; 87 | const { BotArcApiV4 } = BotArcApi; 88 | const api = new BotArcApiV4({ 89 | baseURL: "http://localhost:8088", 90 | timeout: 60000, 91 | headers: { 92 | "Authorization": "Bearer SecretAPItoken" 93 | } 94 | }); 95 | 96 | async function b30() { 97 | console.log(await api.user.best30("Nagiha0798", true)); 98 | } 99 | 100 | b30(); 101 | ``` 102 | 103 | ...or you have an older version of **ArcUnlimitedAPI** or **BotArcAPI** and/or use ˋUser-Agentˋ instead of ˋtokenˋ: 104 | 105 | ```typescript 106 | import BotArcApi from "botarcapi_lib"; 107 | const { BotArcApiV4 } = BotArcApi; 108 | const api = new BotArcApiV4({ 109 | baseURL: "http://localhost:8088", 110 | timeout: 60000, 111 | headers: { 112 | "User-Agent": "SecretAPIUA" 113 | } 114 | }); 115 | 116 | async function b30() { 117 | console.log(await api.user.best30("Nagiha0798", true)); 118 | } 119 | 120 | b30(); 121 | ``` 122 | 123 | ### Util 124 | 125 | This library integrates some practical functions. 126 | 127 | To use these functions, you should import command at first: 128 | 129 | ```typescript 130 | import { 131 | botArcApiDifficulty2DifficultyClass, 132 | difficultyClass2String, 133 | botArcApiDifficulty2String, 134 | formatScore 135 | } from "botarcapi_lib"; 136 | ``` 137 | 138 | Then you may use functions you imported. 139 | 140 | ### botArcApiDifficulty2DifficultyClass 141 | 142 | Convert **BotArcApi Difficulty** to **Difficulty Class**. 143 | 144 | You may know, **BotArcApi Difficulty** is a number which >=2 and <=23. This function can convert number into an object: 145 | 146 | ```typescript 147 | { 148 | rating: number, 149 | ratingPlus?: boolean 150 | } 151 | ``` 152 | 153 | This format is also used in **ArcaeaDifficultyClass**. 154 | 155 | #### Example 156 | 157 | ```typescript 158 | import BotArcApi from "botarcapi_lib"; 159 | const {util} = BotArcApi; 160 | console.log(util.botArcApiDifficulty2DifficultyClass(21)); 161 | // { rating: 10, ratingPlus: true } 162 | ``` 163 | 164 | ### difficultyClass2String 165 | 166 | Convert **Difficulty Class** to **string**. 167 | 168 | #### Example 169 | 170 | ```typescript 171 | import BotArcApi from "botarcapi_lib"; 172 | const {util} = BotArcApi; 173 | console.log(util.difficultyClass2String({ 174 | rating: 10, 175 | ratingPlus: true 176 | })); 177 | // 10+ 178 | ``` 179 | 180 | ```typescript 181 | import BotArcApi from "botarcapi_lib"; 182 | const {BotArcApiv4, util} = BotArcApi; 183 | const api = new BotArcApiv4("http://localhost:3000"); 184 | api.song.info("gl").then(info => { 185 | const futureDifficultyClass = info.difficulties.filter(c => c.ratingClass === 2)[0] 186 | console.log(util.difficultyClass2String(futureDifficultyClass)) // 11 187 | }); 188 | ``` 189 | 190 | ### botArcApiDifficulty2String 191 | 192 | Equals to `difficultyClass2String(botArcApiDifficulty2DifficultyClass(/* arg */))`. Sweet! 193 | 194 | #### Example 195 | 196 | ```typescript 197 | import BotArcApi from "botarcapi_lib"; 198 | const {util} = BotArcApi; 199 | console.log(util.botArcApiDifficulty2String(21)); 200 | // 10+ 201 | ``` 202 | 203 | ### formatScore 204 | 205 | Format score number into Arcaea score display format. 206 | 207 | `9901314` => `09'901'314` 208 | 209 | #### Example 210 | 211 | ```typescript 212 | import BotArcApi from "botarcapi_lib"; 213 | const {util} = BotArcApi; 214 | console.log(util.formatScore(10001540)); // 10'001'540 215 | console.log(util.formatScore(9993506)); // 09'993'506 216 | console.log(util.formatScore(12987)); // 00'012'987 217 | ``` 218 | 219 | -------------------------------------------------------------------------------- /README_ZH.md: -------------------------------------------------------------------------------- 1 | # BotArcApi Lib 2 | 3 | [English](./README.md) | **中文** 4 | 5 | 使用 **Typescript** 编写的 **[BotArcApi](https://github.com/TheSnowfield/BotArcAPI)** 接口封装库 6 | 7 | ***面向 botarcapi_lib@1.1.4 或更早用户的迁移说明***:2022 年 4 月 28 日 BotArcAPI (现在称作 Arcaea Unlimited API) 更改了其 songinfo 信息的格式。您可以查看 [类型定义](./src/types.ts) 获取详细信息。 8 | 9 | ## 安装 10 | 11 | 安装非常简单,仅需一条命令: 12 | 13 | 使用 npm: 14 | ``` 15 | npm i botarcapi_lib 16 | ``` 17 | 18 | 或者使用 yarn: 19 | ``` 20 | yarn add botarcapi_lib 21 | ``` 22 | 23 | ## 使用 24 | 25 | ### 创建 API 26 | 27 | 使用以下步骤创建 API: 28 | 29 | 1. 部署一个 **BotArcApi**,或者 **ArcaeaUnlimitedAPI** 服务器。这一步可以按照对应文档完成。如果您有现成的服务器可用,可用跳过这一步。 30 | 31 | 2. 获取服务器地址,例如 `http://localhost:8088`。 32 | 33 | 3. 选择你想要的 **BotArcApi** 版本,例如 `v5`,在您的项目中输入以下代码: 34 | 35 | ```typescript 36 | import BotArcApi from "botarcapi_lib"; 37 | const { BotArcApiV5 } = BotArcApi; 38 | const api = new BotArcApiV5("http://localhost:8088", 60000); 39 | ``` 40 | 41 | 或者使用 **Node.js**: 42 | 43 | ```javascript 44 | const { BotArcApiV5 } = require("botarcapi_lib"); 45 | const api = new BotArcApiV5("http://localhost:8088", 60000); 46 | ``` 47 | 48 | 再或者,如果您需要控制其他的请求配置,您可以直接提供`AxiosRequestConfig`: 49 | 50 | ```typescript 51 | import BotArcApi from "botarcapi_lib"; 52 | const { BotArcApiV5 } = BotArcApi; 53 | const api = new BotArcApiV4({ 54 | baseURL: "http://localhost:8088", 55 | timeout: 60000, 56 | headers: { 57 | "Authorization": "Bearer SecretAPItoken" 58 | } 59 | }); 60 | ``` 61 | 62 | 4. 使用您想要的 API。 63 | 64 | ### 使用 API 65 | 66 | 举个例子,如果需要查找 **Nagiha0798** 的 Best30 成绩并打印到控制台,您可以: 67 | 68 | ```typescript 69 | import BotArcApi from "botarcapi_lib"; 70 | const { BotArcApiV4 } = BotArcApi; 71 | const api = new BotArcApiV4({ 72 | baseURL: "http://localhost:8088", 73 | timeout: 60000, 74 | headers: { 75 | "Authorization": "Bearer SecretAPItoken" 76 | } 77 | }); 78 | api.user.best30("Nagiha0798", true) 79 | .then(console.log) 80 | .catch(console.error); 81 | ``` 82 | 83 | 或使用 **async/await** 的异步语法: 84 | 85 | ```typescript 86 | import BotArcApi from "botarcapi_lib"; 87 | const { BotArcApiV4 } = BotArcApi; 88 | const api = new BotArcApiV4({ 89 | baseURL: "http://localhost:8088", 90 | timeout: 60000, 91 | headers: { 92 | "Authorization": "Bearer SecretAPItoken" 93 | } 94 | }); 95 | 96 | async function b30() { 97 | console.log(await api.user.best30("Nagiha0798", true)); 98 | } 99 | 100 | b30(); 101 | ``` 102 | 103 | 或者,如果您的 **ArcaeaUnlimitedAPI** 或 **BotArcAPI** 版本较旧,您可以提供 `User-Agent` 代替 `token`: 104 | 105 | ```typescript 106 | import BotArcApi from "botarcapi_lib"; 107 | const { BotArcApiV4 } = BotArcApi; 108 | const api = new BotArcApiV4({ 109 | baseURL: "http://localhost:8088", 110 | timeout: 60000, 111 | headers: { 112 | "User-Agent": "SecretAPIUA" 113 | } 114 | }); 115 | 116 | async function b30() { 117 | console.log(await api.user.best30("Nagiha0798", true)); 118 | } 119 | 120 | b30(); 121 | ``` 122 | 123 | ### 工具 124 | 125 | 本库集成了一些使用的工具函数。首先进行导入: 126 | 127 | ```typescript 128 | import { 129 | botArcApiDifficulty2DifficultyClass, 130 | difficultyClass2String, 131 | botArcApiDifficulty2String, 132 | formatScore 133 | } from "botarcapi_lib"; 134 | ``` 135 | 136 | ### botArcApiDifficulty2DifficultyClass 137 | 138 | 将 **BotArcApi 难度** 转换为 **Difficulty Class**. 139 | 140 | 比如,**BotArcApi 难度** 是一个介于 2 和 23 之间的数字。 这个函数可以将其转换为以下的形式: 141 | 142 | ```typescript 143 | { 144 | rating: number, 145 | ratingPlus?: boolean 146 | } 147 | ``` 148 | 149 | 该格式也在 **ArcaeaDifficultyClass** 中被使用。 150 | 151 | #### 示例 152 | 153 | ```typescript 154 | import BotArcApi from "botarcapi_lib"; 155 | const {util} = BotArcApi; 156 | console.log(util.botArcApiDifficulty2DifficultyClass(21)); 157 | // { rating: 10, ratingPlus: true } 158 | ``` 159 | 160 | ### difficultyClass2String 161 | 162 | 将 **Difficulty Class** 转换为字符串。 163 | 164 | #### 示例 165 | 166 | ```typescript 167 | import BotArcApi from "botarcapi_lib"; 168 | const {util} = BotArcApi; 169 | console.log(util.difficultyClass2String({ 170 | rating: 10, 171 | ratingPlus: true 172 | })); 173 | // 10+ 174 | ``` 175 | 176 | ```typescript 177 | import BotArcApi from "botarcapi_lib"; 178 | const {BotArcApiv4, util} = BotArcApi; 179 | const api = new BotArcApiv4("http://localhost:3000"); 180 | api.song.info("gl").then(info => { 181 | const futureDifficultyClass = info.difficulties.filter(c => c.ratingClass === 2)[0] 182 | console.log(util.difficultyClass2String(futureDifficultyClass)) // 11 183 | }); 184 | ``` 185 | 186 | ### botArcApiDifficulty2String 187 | 188 | 等同于 `difficultyClass2String(botArcApiDifficulty2DifficultyClass(/* arg */))`。语法糖! 189 | 190 | #### 示例 191 | 192 | ```typescript 193 | import BotArcApi from "botarcapi_lib"; 194 | const {util} = BotArcApi; 195 | console.log(util.botArcApiDifficulty2String(21)); 196 | // 10+ 197 | ``` 198 | 199 | ### formatScore 200 | 201 | 将分数格式化为 Arcaea 游戏内展示的格式。 202 | 203 | `9901314` => `09'901'314` 204 | 205 | #### 示例 206 | 207 | ```typescript 208 | import BotArcApi from "botarcapi_lib"; 209 | const {util} = BotArcApi; 210 | console.log(util.formatScore(10001540)); // 10'001'540 211 | console.log(util.formatScore(9993506)); // 09'993'506 212 | console.log(util.formatScore(12987)); // 00'012'987 213 | ``` 214 | 215 | -------------------------------------------------------------------------------- /index.ts: -------------------------------------------------------------------------------- 1 | export * from "./src/v3"; 2 | export * from "./src/v4"; 3 | export * from "./src/v5"; 4 | export * from "./src/types"; 5 | export * from "./src/util"; 6 | export * as util from "./src/util"; 7 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "botarcapi_lib", 3 | "version": "1.1.9", 4 | "description": "Lib for BotArcApi", 5 | "private": false, 6 | "main": "build/index.js", 7 | "types": "build/index.d.ts", 8 | "typings": "build/index.d.ts", 9 | "engines": { 10 | "node": ">=12.0.0" 11 | }, 12 | "files": [ 13 | "README.md", 14 | "package.json", 15 | "tsconfig.json", 16 | "build" 17 | ], 18 | "directories": { 19 | "test": "test" 20 | }, 21 | "dependencies": { 22 | "@types/node": "^17.0.9", 23 | "axios": "^0.21.1" 24 | }, 25 | "scripts": { 26 | "test": "tsc && node test", 27 | "build": "tsc", 28 | "release": "tsc && npm publish" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git@github.com:ReiKohaku/BotArcApi_lib.git" 33 | }, 34 | "keywords": [ 35 | "arcaea", 36 | "bot", 37 | "api" 38 | ], 39 | "author": { 40 | "name": "ReiKohaku", 41 | "email": "hbsjzjwj@gmail.com", 42 | "url": "https://github.com/ReiKohaku" 43 | }, 44 | "license": "MIT", 45 | "bugs": { 46 | "url": "https://github.com/ReiKohaku/BotArcApi_lib/issues" 47 | }, 48 | "homepage": "https://github.com/ReiKohaku/BotArcApi_lib#readme", 49 | "publishConfig": { 50 | "access": "public" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export enum ArcaeaDifficulty { 2 | past = 0, 3 | pst = 0, 4 | present = 1, 5 | prs = 1, 6 | future = 2, 7 | ftr = 2, 8 | beyond = 3, 9 | byd = 3 10 | } 11 | 12 | export enum ArcaeaClearType { 13 | fail, 14 | normal, 15 | full, 16 | pure, 17 | easy, 18 | hard 19 | } 20 | 21 | export enum ArcaeaGradeType { 22 | exp, 23 | ex, 24 | aa, 25 | a, 26 | b, 27 | c, 28 | d 29 | } 30 | 31 | export type BotArcApiDifficultyRange = '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | '9p' | '10' | '10p' | '11' ; 32 | export type BotArcApiDifficulty = 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 ; 33 | export type BotArcApiRecent = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 ; 34 | 35 | export interface BotArcApiScore { 36 | song_id: string; 37 | difficulty: ArcaeaDifficulty; 38 | score: number; 39 | shiny_perfect_count: number; 40 | perfect_count: number; 41 | near_count: number; 42 | miss_count: number; 43 | clear_type: ArcaeaClearType; 44 | best_clear_type: ArcaeaClearType; 45 | health: number; 46 | time_played: number; 47 | modifier: number; 48 | rating: number; 49 | } 50 | 51 | export interface BotArcApiUserinfoV4 { 52 | user_id: number; 53 | name: string; 54 | recent_score?: BotArcApiScore[]; 55 | character: number; 56 | join_date: number; 57 | rating: number; 58 | is_skill_sealed: boolean; 59 | is_char_uncapped: boolean; 60 | is_char_uncapped_override: boolean; 61 | is_mutual: boolean; 62 | } 63 | 64 | export interface BotArcApiUserinfoV4 { 65 | user_id: number; 66 | name: string; 67 | recent_score?: BotArcApiScore[]; 68 | character: number; 69 | join_date: number; 70 | rating: number; 71 | is_skill_sealed: boolean; 72 | is_char_uncapped: boolean; 73 | is_char_uncapped_override: boolean; 74 | is_mutual: boolean; 75 | code: string; 76 | } 77 | 78 | export interface BotArcApiUserbest30 { 79 | best30_avg: number; 80 | recent10_avg: number; 81 | best30_list: Array; 82 | best30_overflow: Array; 83 | } 84 | 85 | export interface BotArcApiDifficultyClass { 86 | ratingClass: ArcaeaDifficulty; 87 | chartDesigner: string; 88 | jacketDesigner: string; 89 | jacket_night?: string; 90 | jacketOverride?: boolean; 91 | rating: number; 92 | ratingPlus?: boolean; 93 | ratingReal: number; 94 | totalNotes: number; 95 | } 96 | 97 | export interface BotArcApiSonginfo { 98 | id: string; 99 | title_localized: { 100 | en: string; 101 | ja?: string; 102 | 'zh-Hans'?: string; 103 | 'zh-Hant'?: string; 104 | } 105 | artist: string; 106 | bpm: string; 107 | bpm_base: number; 108 | set: string; 109 | audioTimeSec: number; 110 | side: 0 | 1 ; 111 | remote_dl: boolean; 112 | world_unlock: boolean; 113 | date: number; // 十位时间戳 114 | version: string; 115 | difficulties: Array; 116 | } 117 | 118 | export interface BotArcApiRatingInfo { 119 | sid: string; 120 | rating: number; 121 | rating_class: ArcaeaDifficulty; 122 | difficulty: BotArcApiDifficulty; 123 | } 124 | 125 | export interface BotArcApiResponse { 126 | status: number; 127 | content?: T; 128 | } 129 | 130 | export interface BotArcApiResponseV4 { 131 | status: number; 132 | content?: T; 133 | message?: string; 134 | } 135 | 136 | export interface BotArcApiBatchRequest { 137 | id: number; 138 | bind?: Record; 139 | endpoint: string; 140 | } 141 | 142 | export interface BotArcApiBatchResponse { 143 | id: number; 144 | result: BotArcApiResponseV4; 145 | } 146 | 147 | /* -------- BotArcApi V5 -------- */ 148 | 149 | export interface BotArcApiUserinfoV5 { 150 | code: string; 151 | user_id: number; 152 | name: string; 153 | character: number; 154 | join_date: number; 155 | rating: number; 156 | is_skill_sealed: boolean; 157 | is_char_uncapped: boolean; 158 | is_char_uncapped_override: boolean; 159 | is_mutual: boolean; 160 | } 161 | 162 | export interface BotArcApiDifficultyInfoV5 { 163 | name_en: string; 164 | name_jp: string; 165 | artist: string; 166 | bpm: string; 167 | bpm_base: number; 168 | set: string; 169 | set_friendly: string; 170 | time: number; 171 | side: 0 | 1 ; 172 | world_unlock: boolean; 173 | remote_download: boolean; 174 | bg: string; 175 | date: number; // 十位时间戳 176 | version: string; 177 | difficulty: BotArcApiDifficulty; 178 | rating: number; 179 | note: number; 180 | chart_designer: string; 181 | jacket_designer: string; 182 | jacket_override: boolean; 183 | audio_override: boolean; 184 | } 185 | 186 | export interface BotArcApiRandomSong { 187 | id: string; 188 | ratingClass?: number; 189 | songinfo: BotArcApiDifficultyInfoV5[]; 190 | } 191 | 192 | // Since botarcapi_lib@1.1.5 193 | export namespace BotArcApiContentV5 { 194 | export namespace User { 195 | export interface Info { 196 | account_info: BotArcApiUserinfoV5; 197 | recent_score: BotArcApiScore[]; 198 | songinfo: BotArcApiDifficultyInfoV5[]; 199 | } 200 | 201 | export interface Best { 202 | account_info: BotArcApiUserinfoV5; 203 | record: BotArcApiScore; 204 | songinfo: BotArcApiDifficultyInfoV5[]; 205 | } 206 | 207 | export interface Best30 { 208 | best30_avg: number; 209 | recent10_avg: number; 210 | account_info: BotArcApiUserinfoV5; 211 | best30_list: BotArcApiScore[]; // length: 30 212 | best30_overflow: BotArcApiScore[]; // length: 10 213 | best30_songinfo: BotArcApiDifficultyInfoV5[][]; // length: 30 214 | best30_overflow_songinfo: BotArcApiDifficultyInfoV5[][]; // length: 10 215 | recent_score: BotArcApiScore; 216 | recent_songinfo: BotArcApiDifficultyInfoV5[]; 217 | } 218 | } 219 | 220 | export namespace Song { 221 | export interface Random extends BotArcApiRandomSong {} 222 | } 223 | } 224 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import {BotArcApiDifficulty} from "./types"; 2 | 3 | export function botArcApiDifficulty2String(difficulty: BotArcApiDifficulty): string { 4 | return `${Math.floor(difficulty / 2)}${difficulty % 2 === 1 ? "+" : ""}` 5 | } 6 | 7 | export function botArcApiDifficulty2DifficultyClass( 8 | difficulty: BotArcApiDifficulty 9 | ): {rating: number, ratingPlus?: boolean} { 10 | const difficultyClass = { 11 | rating: Math.floor(difficulty / 2), 12 | ratingPlus: difficulty % 2 === 1 || undefined 13 | } 14 | return difficultyClass 15 | } 16 | 17 | export function difficultyClass2String (difficultyClass: {rating: number, ratingPlus?: boolean}): string { 18 | return `${difficultyClass.rating}${difficultyClass.ratingPlus ? "+" : ""}` 19 | } 20 | 21 | export function formatScore(score: number): string { 22 | let text = `${score}`; 23 | for (let i = text.length; i < 8; i++) text = '0' + text; 24 | let result = ""; 25 | for (let i = text.length - 1, j = 1; i >= 0; i--, j++) { 26 | if (j % 3 === 0 && i !== 0) { 27 | result += text[i] + "'"; 28 | continue; 29 | } 30 | result += text[i]; 31 | } 32 | return result.split('').reverse().join(""); 33 | } 34 | -------------------------------------------------------------------------------- /src/v3/index.ts: -------------------------------------------------------------------------------- 1 | import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from "axios"; 2 | import { 3 | ArcaeaDifficulty, 4 | BotArcApiResponse, 5 | BotArcApiScore, 6 | BotArcApiSonginfo, 7 | BotArcApiUserbest30, 8 | BotArcApiUserinfoV4 9 | } from "../types"; 10 | 11 | class BotArcApiV3Arc { 12 | private readonly axios: AxiosInstance; 13 | 14 | constructor(axios: AxiosInstance) { 15 | this.axios = axios; 16 | } 17 | 18 | public alloc(time?: number, clear?: boolean): Promise<{access_token: string, valid_time: number}> { 19 | const axiosInstance = this.axios; 20 | return new Promise<{access_token: string, valid_time: number}>((resolve, reject) => { 21 | axiosInstance({ 22 | method: "GET", 23 | url: "v3/arc/alloc", 24 | params: { 25 | time, 26 | clear 27 | } 28 | }).then((response: AxiosResponse) => { 29 | const data = response.data as BotArcApiResponse<{access_token: string, valid_time: number}>; 30 | if (data.status === 0 && data.content) resolve(data.content); 31 | else reject(data.status || "undefined error occurred"); 32 | }).catch(reject); 33 | }); 34 | } 35 | 36 | public forward(token: string, method: "GET" | "get" | "POST" | "post", url: string, params: Record) { 37 | const axiosInstance = this.axios; 38 | return new Promise((resolve, reject) => { 39 | axiosInstance({ 40 | method, 41 | url: "v3/arc/forward" + url.startsWith("/") ? "" : "/" + url, 42 | params, 43 | headers: { 44 | "Authorization": "Bearer " + token 45 | } 46 | }).then((response: AxiosResponse) => { 47 | const data = response.data as BotArcApiResponse; 48 | if (data.status === 0 && data.content) resolve(data.content); 49 | else reject(data.status || "undefined error occurred"); 50 | }).catch(reject); 51 | }); 52 | } 53 | 54 | public recycle(token: string) { 55 | const axiosInstance = this.axios; 56 | return new Promise((resolve, reject) => { 57 | axiosInstance({ 58 | method: "GET", 59 | url: "v3/arc/recycle", 60 | params: { 61 | token 62 | } 63 | }).then((response: AxiosResponse) => { 64 | const data = response.data as BotArcApiResponse; 65 | if (data.status === 0) resolve(void 0); 66 | else reject(data.status || "undefined error occurred"); 67 | }).catch(reject); 68 | }); 69 | } 70 | } 71 | 72 | export class BotArcApiV3 { 73 | private readonly axios: AxiosInstance; 74 | public readonly arc: BotArcApiV3Arc; 75 | 76 | constructor(axiosConfig?: AxiosRequestConfig) 77 | constructor(baseURL?: string, timeout?: number) 78 | constructor(baseURL?: string | AxiosRequestConfig, timeout?: number) { 79 | function createAxiosInstance() { 80 | if (typeof baseURL === "string") { 81 | return axios.create({ 82 | baseURL, 83 | timeout: timeout || 30000 84 | }); 85 | } else { 86 | return axios.create(baseURL); 87 | } 88 | } 89 | const axiosInstance = createAxiosInstance(); 90 | this.axios = axiosInstance; 91 | 92 | this.arc = new BotArcApiV3Arc(axiosInstance); 93 | 94 | return this; 95 | } 96 | 97 | userinfo(usercode: string, recent?: boolean): Promise { 98 | const axiosInstance = this.axios; 99 | return new Promise((resolve, reject) => { 100 | axiosInstance({ 101 | method: "GET", 102 | url: "/v3/userinfo", 103 | params: { 104 | usercode, 105 | recent 106 | } 107 | }).then((response: AxiosResponse) => { 108 | const data = response.data as {status: number, content: unknown}; 109 | if (data.status === 0) resolve(data.content as BotArcApiUserinfoV4); 110 | else { 111 | reject(data.status || "undefined error occurred"); 112 | } 113 | }).catch(reject); 114 | }); 115 | } 116 | userbest(usercode: string, songname: string, difficulty: ArcaeaDifficulty): Promise { 117 | const axiosInstance = this.axios; 118 | return new Promise((resolve, reject) => { 119 | axiosInstance({ 120 | method: "GET", 121 | url: "v3/userbest", 122 | params: { 123 | usercode, 124 | songname, 125 | difficulty 126 | } 127 | }).then((response: AxiosResponse) => { 128 | const data = response.data as {status: number, content: unknown}; 129 | if (data.status === 0) resolve(data.content as BotArcApiScore); 130 | else reject(data.status || "undefined error occurred"); 131 | }).catch(reject); 132 | }); 133 | } 134 | userbest30(usercode: string): Promise { 135 | const axiosInstance = this.axios; 136 | return new Promise((resolve, reject) => { 137 | axiosInstance({ 138 | method: "GET", 139 | url: "v3/userbest30", 140 | params: { 141 | usercode 142 | } 143 | }).then((response: AxiosResponse) => { 144 | const data = response.data as {status: number, content: unknown}; 145 | if (data.status === 0) resolve(data.content as BotArcApiUserbest30); 146 | else reject(data.status || "undefined error occurred"); 147 | }).catch(reject); 148 | }); 149 | } 150 | songinfo(songname: string): Promise { 151 | const axiosInstance = this.axios; 152 | return new Promise((resolve, reject) => { 153 | axiosInstance({ 154 | method: "GET", 155 | url: "v3/songinfo", 156 | params: { 157 | songname 158 | } 159 | }).then((response: AxiosResponse) => { 160 | const data = response.data as BotArcApiResponse; 161 | if (data.status === 0 && data.content) resolve(data.content); 162 | else reject(data.status || "undefined error occurred"); 163 | }).catch(reject); 164 | }); 165 | } 166 | songalias(songname: string): Promise<{id: string}> { 167 | const axiosInstance = this.axios; 168 | return new Promise<{id: string}>((resolve, reject) => { 169 | axiosInstance({ 170 | method: "GET", 171 | url: "v3/songalias", 172 | params: { 173 | songname 174 | } 175 | }).then((response: AxiosResponse) => { 176 | const data = response.data as BotArcApiResponse<{id: string}>; 177 | if (data.status === 0 && data.content) resolve(data.content); 178 | else reject(data.status || "undefined error occurred"); 179 | }).catch(reject); 180 | }); 181 | } 182 | update(): Promise<{url: string, version: string}> { 183 | const axiosInstance = this.axios; 184 | return new Promise<{url: string, version: string}>((resolve, reject) => { 185 | axiosInstance({ 186 | method: "GET", 187 | url: "v3/update" 188 | }).then((response: AxiosResponse) => { 189 | const data = response.data as BotArcApiResponse<{url: string, version: string}>; 190 | if (data.status === 0 && data.content) resolve(data.content); 191 | else reject(data.status || "undefined error occurred"); 192 | }).catch(reject); 193 | }); 194 | } 195 | random(start?: number, end?: number, info?: true): Promise<{id: string, ratingClass: ArcaeaDifficulty, song_info: BotArcApiSonginfo}> 196 | random(start?: number, end?: number, info?: false): Promise<{id: string, ratingClass: ArcaeaDifficulty}> 197 | random(start?: number, end?: number, info?: boolean): Promise<{id: string, ratingClass: ArcaeaDifficulty, song_info?: BotArcApiSonginfo}> { 198 | const axiosInstance = this.axios; 199 | return new Promise<{id: string, ratingClass: ArcaeaDifficulty, song_info?: BotArcApiSonginfo}>((resolve, reject) => { 200 | axiosInstance({ 201 | method: "GET", 202 | url: "v3/update", 203 | params: { 204 | start, 205 | end, 206 | info 207 | } 208 | }).then((response: AxiosResponse) => { 209 | const data = response.data as BotArcApiResponse<{id: string, ratingClass: ArcaeaDifficulty, song_info?: BotArcApiSonginfo}>; 210 | if (data.status === 0 && data.content) { 211 | if (info) resolve(data.content as {id: string, ratingClass: ArcaeaDifficulty, song_info: BotArcApiSonginfo}); 212 | else resolve(data.content as {id: string, ratingClass: ArcaeaDifficulty}); 213 | } 214 | else reject(data.status || "undefined error occurred"); 215 | }).catch(reject); 216 | }); 217 | } 218 | connect(): Promise<{key: string}> { 219 | const axiosInstance = this.axios; 220 | return new Promise<{key: string}>((resolve, reject) => { 221 | axiosInstance({ 222 | method: "GET", 223 | url: "v3/connect" 224 | }).then((response: AxiosResponse) => { 225 | const data = response.data as BotArcApiResponse<{key: string}>; 226 | if (data.status === 0 && data.content) resolve(data.content); 227 | else reject(data.status || "undefined error occurred"); 228 | }).catch(reject); 229 | }); 230 | } 231 | } 232 | -------------------------------------------------------------------------------- /src/v4/index.ts: -------------------------------------------------------------------------------- 1 | import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from "axios"; 2 | import { 3 | ArcaeaDifficulty, 4 | BotArcApiUserinfoV4, 5 | BotArcApiSonginfo, 6 | BotArcApiScore, 7 | BotArcApiUserbest30, 8 | BotArcApiBatchRequest, 9 | BotArcApiResponseV4, 10 | BotArcApiBatchResponse, 11 | BotArcApiRatingInfo, 12 | BotArcApiRecent 13 | } from "../types"; 14 | 15 | class BotArcApiV4User { 16 | private readonly axios: AxiosInstance; 17 | 18 | constructor(axios: AxiosInstance) { 19 | this.axios = axios; 20 | } 21 | 22 | /** 23 | * Search and return user info. 24 | * Use "recent" argument to get 0~7 recent score(s) of user. 25 | */ 26 | public info(user: string, fuzzy: true, recent?: BotArcApiRecent): Promise 27 | public info(usercode: string, fuzzy: false, recent?: BotArcApiRecent): Promise 28 | public info(usercode: string, recent?: BotArcApiRecent): Promise 29 | public info(usercode: string, fuzzy?: boolean | BotArcApiRecent, recent?: BotArcApiRecent): Promise { 30 | const axiosInstance = this.axios; 31 | let params: Record = {}; 32 | if (typeof fuzzy === "boolean") { 33 | if (fuzzy) params.user = usercode; 34 | else params.usercode = usercode; 35 | const _recent = (typeof recent === "number" && recent >= 0 && recent <= 7) ? recent : 0; 36 | if (_recent && _recent > 0) params.recent = _recent; 37 | } else { 38 | params.usercode = usercode; 39 | const _recent = (typeof fuzzy === "number" && fuzzy >= 0 && fuzzy <= 7) ? fuzzy : 0; 40 | if (_recent && _recent > 0) params.recent = _recent; 41 | } 42 | return new Promise((resolve, reject) => { 43 | axiosInstance({ 44 | method: "GET", 45 | url: "/v4/user/info", 46 | params: params 47 | }).then((response: AxiosResponse) => { 48 | const data = response.data as BotArcApiResponseV4; 49 | if (data.status === 0 && data.content) resolve(data.content); 50 | else { 51 | reject(data.message || "undefined error occurred"); 52 | } 53 | }).catch(reject); 54 | }); 55 | } 56 | 57 | /** 58 | * Get one of user's best scores by user code, song name and difficulty. 59 | */ 60 | public best(user: string, fuzzy: true, songname: string, difficulty?: ArcaeaDifficulty): Promise 61 | public best(usercode: string, fuzzy: false, songname: string, difficulty?: ArcaeaDifficulty): Promise 62 | public best(usercode: string, songname: string, difficulty?: ArcaeaDifficulty): Promise 63 | public best(usercode: string, fuzzy: boolean | string, songname?: string | ArcaeaDifficulty, difficulty?: ArcaeaDifficulty): Promise { 64 | const axiosInstance = this.axios; 65 | let params: Record = {}; 66 | if (typeof fuzzy === "boolean") { 67 | if (fuzzy) params.user = usercode; 68 | else params.usercode = usercode; 69 | params.songname = songname; 70 | params.difficulty = (typeof difficulty === "number" && difficulty >= 0 && difficulty <= 3) ? difficulty : 2; 71 | } else { 72 | params.usercode = usercode; 73 | params.songname = fuzzy; 74 | params.difficulty = (typeof songname === "number" && songname >= 0 && songname <= 3) ? songname : 2; 75 | } 76 | return new Promise((resolve, reject) => { 77 | axiosInstance({ 78 | method: "GET", 79 | url: "v4/user/best", 80 | params 81 | }).then((response: AxiosResponse) => { 82 | const data = response.data as BotArcApiResponseV4; 83 | if (data.status === 0 && data.content) resolve(data.content); 84 | else reject(data.message || "undefined error occurred"); 85 | }).catch(reject); 86 | }); 87 | } 88 | 89 | /** 90 | * Get user's 30 best scores. 91 | */ 92 | public best30(user: string, fuzzy: true): Promise 93 | public best30(usercode: string, fuzzy: false): Promise 94 | public best30(usercode: string): Promise 95 | public best30(usercode: string, fuzzy?: boolean): Promise { 96 | const axiosInstance = this.axios; 97 | let params: Record = {}; 98 | if (fuzzy) params.user = usercode; 99 | else params.usercode = usercode; 100 | return new Promise((resolve, reject) => { 101 | axiosInstance({ 102 | method: "GET", 103 | url: "v4/user/best30", 104 | params 105 | }).then((response: AxiosResponse) => { 106 | const data = response.data as BotArcApiResponseV4; 107 | if (data.status === 0 && data.content) resolve(data.content); 108 | else reject(data.message || "undefined error occurred"); 109 | }).catch(reject); 110 | }); 111 | } 112 | } 113 | 114 | class BotArcApiV4Song { 115 | private readonly axios: AxiosInstance; 116 | 117 | constructor(axios: AxiosInstance) { 118 | this.axios = axios; 119 | } 120 | 121 | public info(songname: string): Promise { 122 | const axiosInstance = this.axios; 123 | return new Promise((resolve, reject) => { 124 | axiosInstance({ 125 | method: "POST", 126 | url: "/v4/song/info", 127 | params: { 128 | songname 129 | } 130 | }).then((response: AxiosResponse) => { 131 | const data = response.data as BotArcApiResponseV4; 132 | if (data.status === 0 && data.content) resolve(data.content); 133 | else { 134 | reject(data.message || "undefined error occurred"); 135 | } 136 | }).catch(reject); 137 | }); 138 | } 139 | 140 | public alias(songid: string): Promise<{ alias: Array }> { 141 | const axiosInstance = this.axios; 142 | return new Promise<{ alias: Array }>((resolve, reject) => { 143 | axiosInstance({ 144 | method: "POST", 145 | url: "/v4/song/alias", 146 | params: { 147 | songid 148 | } 149 | }).then((response: AxiosResponse) => { 150 | const data = response.data as BotArcApiResponseV4<{ alias: Array }>; 151 | if (data.status === 0 && data.content) resolve(data.content); 152 | else { 153 | reject(data.message || "undefined error occurred"); 154 | } 155 | }).catch(reject); 156 | }); 157 | } 158 | 159 | public id(songname: string): Promise<{ id: string }> { 160 | const axiosInstance = this.axios; 161 | return new Promise<{id: string}>((resolve, reject) => { 162 | axiosInstance({ 163 | method: "POST", 164 | url: "/v4/song/id", 165 | params: { 166 | songname 167 | } 168 | }).then((response: AxiosResponse) => { 169 | const data = response.data as BotArcApiResponseV4<{ id: string }>; 170 | if (data.status === 0 && data.content) resolve(data.content); 171 | else { 172 | reject(data.message || "undefined error occurred"); 173 | } 174 | }).catch(reject); 175 | }); 176 | } 177 | 178 | /** 179 | * Roll a song from a given difficulty range. 180 | */ 181 | public random(start?: number, end?: number): Promise<{ id: string, rating_class: number }> { 182 | const axiosInstance = this.axios; 183 | return new Promise<{ id: string, rating_class: number }>((resolve, reject) => { 184 | axiosInstance({ 185 | method: "POST", 186 | url: "/v4/song/random", 187 | params: { 188 | start, 189 | end 190 | } 191 | }).then((response: AxiosResponse) => { 192 | const data = response.data as BotArcApiResponseV4<{ id: string, rating_class: number }>; 193 | if (data.status === 0 && data.content) resolve(data.content); 194 | else { 195 | reject(data.message || "undefined error occurred"); 196 | } 197 | }).catch(reject); 198 | }); 199 | } 200 | 201 | public rating(start: number, end?: number): Promise<{ rating: Array }> { 202 | const axiosInstance = this.axios; 203 | return new Promise<{ rating: Array }>((resolve, reject) => { 204 | axiosInstance({ 205 | method: "POST", 206 | url: "/v4/song/rating", 207 | params: { 208 | start, 209 | end 210 | } 211 | }).then((response: AxiosResponse) => { 212 | const data = response.data as BotArcApiResponseV4<{ rating: Array }>; 213 | if (data.status === 0 && data.content) resolve(data.content); 214 | else { 215 | reject(data.message || "undefined error occurred"); 216 | } 217 | }).catch(reject); 218 | }); 219 | } 220 | } 221 | 222 | class BotArcApiV4Forward { 223 | private readonly axios: AxiosInstance; 224 | 225 | constructor(axios: AxiosInstance) { 226 | this.axios = axios; 227 | } 228 | 229 | public alloc(time?: number, clear?: boolean): Promise<{ access_token: string, valid_time: number }> { 230 | const axiosInstance = this.axios; 231 | return new Promise<{ access_token: string, valid_time: number }>((resolve, reject) => { 232 | axiosInstance({ 233 | method: "POST", 234 | url: "/v4/forward/alloc", 235 | params: { 236 | time, 237 | clear 238 | } 239 | }).then((response: AxiosResponse) => { 240 | const data = response.data as BotArcApiResponseV4<{ access_token: string, valid_time: number }>; 241 | if (data.status === 0 && data.content) resolve(data.content); 242 | else { 243 | reject(data.message || "undefined error occurred"); 244 | } 245 | }).catch(reject); 246 | }); 247 | } 248 | 249 | public forward(token: string, ...args: any): Promise { 250 | const axiosInstance = this.axios; 251 | return new Promise((resolve, reject) => { 252 | axiosInstance({ 253 | method: "POST", 254 | url: "/v4/forward/forward", 255 | params: { 256 | token, 257 | ...args 258 | } 259 | }).then((response: AxiosResponse) => { 260 | const data = response.data as BotArcApiResponseV4; 261 | if (data.status === 0 && data.content) resolve(data.content); 262 | else { 263 | reject(data.message || "undefined error occurred"); 264 | } 265 | }).catch(reject); 266 | }); 267 | } 268 | 269 | public recycle(token: string): Promise { 270 | const axiosInstance = this.axios; 271 | return new Promise((resolve, reject) => { 272 | axiosInstance({ 273 | method: "POST", 274 | url: "/v4/forward/recycle", 275 | params: { 276 | token 277 | } 278 | }).then((response: AxiosResponse) => { 279 | const data = response.data as BotArcApiResponseV4; 280 | if (data.status === 0) resolve(void 0); 281 | else { 282 | reject(data.message || "undefined error occurred"); 283 | } 284 | }).catch(reject); 285 | }); 286 | } 287 | 288 | public feed(token: string): Promise<{ valid_time: number }> { 289 | const axiosInstance = this.axios; 290 | return new Promise<{ valid_time: number }>((resolve, reject) => { 291 | axiosInstance({ 292 | method: "POST", 293 | url: "/v4/forward/feed", 294 | params: { 295 | token 296 | } 297 | }).then((response: AxiosResponse) => { 298 | const data = response.data as BotArcApiResponseV4<{ valid_time: number }>; 299 | if (data.status === 0 && data.content) resolve(data.content); 300 | else { 301 | reject(data.message || "undefined error occurred"); 302 | } 303 | }).catch(reject); 304 | }); 305 | } 306 | } 307 | 308 | export type BotArcApiScoreWithSongInfo = BotArcApiScore & { songInfo: BotArcApiSonginfo }; 309 | export type BotArcApiUserInfoV4WithSongInfo = BotArcApiUserinfoV4 & { recent_score?: Array }; 310 | export type BotArcApiUserBest30WithSongInfo = BotArcApiUserbest30 & { best30_list: Array }; 311 | 312 | class BotArcApiV4Util { 313 | private readonly axios: AxiosInstance; 314 | private readonly api: BotArcApiV4; 315 | 316 | constructor(axios: AxiosInstance, api: BotArcApiV4) { 317 | this.axios = axios; 318 | this.api = api; 319 | } 320 | 321 | /** 322 | * Get user/info, user/best, song/info of user/best, song/alias of user/best in one request 323 | */ 324 | public userBest(user: string, fuzzy: true, songname: string, difficulty?: ArcaeaDifficulty): Promise<{ 325 | userBest: BotArcApiScore, 326 | songInfo: BotArcApiSonginfo, 327 | songAlias: Array 328 | }> 329 | public userBest(usercode: string, fuzzy: false, songname: string, difficulty?: ArcaeaDifficulty): Promise<{ 330 | userBest: BotArcApiScore, 331 | songInfo: BotArcApiSonginfo, 332 | songAlias: Array 333 | }> 334 | public userBest(usercode: string, songname: string, difficulty?: ArcaeaDifficulty): Promise<{ 335 | userBest: BotArcApiScore, 336 | songInfo: BotArcApiSonginfo, 337 | songAlias: Array 338 | }> 339 | public userBest(usercode: string, fuzzy: boolean | string, songname?: string | ArcaeaDifficulty, difficulty?: ArcaeaDifficulty): Promise<{ 340 | userBest: BotArcApiScore, 341 | songInfo: BotArcApiSonginfo, 342 | songAlias: Array 343 | }> { 344 | const api = this.api; 345 | let userParam: string, songnameParam: string, difficultyParam: string; 346 | if (typeof fuzzy === "boolean") { 347 | if (fuzzy) userParam = `user=${usercode}`; 348 | else userParam = `usercode=${usercode}`; 349 | songnameParam = `songname=${songname}`; 350 | const _difficulty = (typeof difficulty === "number" && difficulty >= 0 && difficulty <= 3) ? difficulty : 2; 351 | difficultyParam = `difficulty=${_difficulty}`; 352 | } else { 353 | userParam = `usercode=${usercode}`; 354 | songnameParam = `songname=${fuzzy}`; 355 | const _difficulty = (typeof songname === "number" && songname >= 0 && songname <= 3) ? songname : 2; 356 | difficultyParam = `difficulty=${_difficulty}`; 357 | } 358 | 359 | return new Promise((resolve, reject) => { 360 | api.batch([{ 361 | id: 0, 362 | bind: { 363 | "$sid": "song_id" 364 | }, 365 | endpoint: `user/best?${userParam}&${songnameParam}&${difficultyParam}` 366 | }, { 367 | id: 1, 368 | endpoint: `song/info?songname=$sid` 369 | }, { 370 | id: 2, 371 | endpoint: `song/alias?songid=$sid` 372 | }]).then(response => { 373 | const userBestResponse = response.filter(i => i.id === 0)[0]; 374 | const songInfoResponse = response.filter(i => i.id === 1)[0]; 375 | const songAliasResponse = response.filter(i => i.id === 2)[0]; 376 | if (userBestResponse.result.status < 0) reject(userBestResponse.result.message); 377 | else if (songInfoResponse.result.status < 0) reject(songInfoResponse.result.message); 378 | else if (songAliasResponse.result.status < 0) reject(songAliasResponse.result.message); 379 | else { 380 | const userBest = userBestResponse.result.content as BotArcApiScore; 381 | const songInfo = songInfoResponse.result.content as BotArcApiSonginfo; 382 | const songAlias = songAliasResponse.result.content as Array; 383 | resolve({userBest, songInfo, songAlias}); 384 | } 385 | }).catch(reject); 386 | }); 387 | } 388 | 389 | public userBest30(user: string, fuzzy: true, recent?: BotArcApiRecent): Promise<{ 390 | userInfo: BotArcApiUserInfoV4WithSongInfo, 391 | userBest30: BotArcApiUserBest30WithSongInfo 392 | }> 393 | public userBest30(usercode: string, fuzzy: false, recent?: BotArcApiRecent): Promise<{ 394 | userInfo: BotArcApiUserInfoV4WithSongInfo, 395 | userBest30: BotArcApiUserBest30WithSongInfo 396 | }> 397 | public userBest30(usercode: string, recent?: BotArcApiRecent): Promise<{ 398 | userInfo: BotArcApiUserInfoV4WithSongInfo, 399 | userBest30: BotArcApiUserBest30WithSongInfo 400 | }> 401 | public userBest30(usercode: string, fuzzy?: boolean | BotArcApiRecent, recent?: BotArcApiRecent): Promise<{ 402 | userInfo: BotArcApiUserInfoV4WithSongInfo, 403 | userBest30: BotArcApiUserBest30WithSongInfo 404 | }> { 405 | const api = this.api; 406 | let userParam: string, recentParam: string, _recent: number; 407 | if (typeof fuzzy === "boolean") { 408 | if (fuzzy) userParam = `user=${usercode}`; 409 | else userParam = `usercode=${usercode}`; 410 | _recent = (typeof recent === "number" && recent >= 0 && recent <= 7) ? recent : 0; 411 | if (_recent && _recent > 0) recentParam = `recent=${_recent}`; 412 | } else { 413 | userParam = `usercode=${usercode}`; 414 | _recent = (typeof fuzzy === "number" && fuzzy >= 0 && fuzzy <= 7) ? fuzzy : 0; 415 | if (_recent && _recent > 0) recentParam = `recent=${_recent}`; 416 | } 417 | return new Promise((resolve, reject) => { 418 | let batchCalls: Array = 419 | new Array({ 420 | id: 0, 421 | bind: {}, 422 | endpoint: `user/best30?${userParam}` 423 | }, { 424 | id: 1, 425 | bind: {}, 426 | endpoint: `user/info?${userParam}${_recent && _recent > 0 ? ("&" + recentParam) : ""}` 427 | }); 428 | for (let i = 0; i < 30; i++) { 429 | if (!batchCalls[0] || !batchCalls[0].bind) { 430 | reject("lib internal error occurred"); 431 | return; 432 | } 433 | batchCalls[0].bind[`\$${i + 1}`] = `best30_list[${i}].song_id`; 434 | batchCalls.push({ 435 | id: 2 + i, 436 | endpoint: `song/info?songname=\$${i + 1}` 437 | }); 438 | } 439 | for (let i = 0; i < _recent; i++) { 440 | if (!batchCalls[1] || !batchCalls[1].bind) { 441 | reject(); 442 | return; 443 | } 444 | batchCalls[1].bind[`\$${31 + i}`] = `recent_score[${i}].song_id`; 445 | batchCalls.push({ 446 | id: 32 + i, 447 | endpoint: `song/info?songname=\$${31 + i}` 448 | }); 449 | } 450 | api.batch(batchCalls).then(response => { 451 | const userBest30Response = response.filter(i => i.id === 0)[0]; 452 | const userInfoResponse = response.filter(i => i.id === 1)[0]; 453 | if (userBest30Response.result.status < 0) reject(userBest30Response.result.message); 454 | else if (userInfoResponse.result.status < 0) reject(userInfoResponse.result.message); 455 | else { 456 | const userInfo: BotArcApiUserInfoV4WithSongInfo = 457 | userInfoResponse.result.content as BotArcApiUserInfoV4WithSongInfo; 458 | const userBest30: BotArcApiUserBest30WithSongInfo = 459 | userBest30Response.result.content as BotArcApiUserBest30WithSongInfo; 460 | const songInfoList = response 461 | .filter(i => i.id > 1 && i.result.status === 0) 462 | .map(i => i.result.content) as Array; 463 | if (userInfo.recent_score) userInfo.recent_score.forEach((v, i, a) => { 464 | const songInfo = songInfoList.filter(s => s.id === v.song_id)[0]; 465 | a[i] = { 466 | ...a[i], 467 | songInfo 468 | } as BotArcApiScoreWithSongInfo; 469 | }); 470 | userBest30.best30_list.forEach((v, i, a) => { 471 | const songInfo = songInfoList.filter(s => s.id === v.song_id)[0]; 472 | a[i] = { 473 | ...a[i], 474 | songInfo 475 | } as BotArcApiScoreWithSongInfo; 476 | }); 477 | resolve({userInfo, userBest30}); 478 | } 479 | }).catch(reject); 480 | }); 481 | } 482 | } 483 | 484 | export class BotArcApiV4 { 485 | private axios: AxiosInstance; 486 | public readonly user: BotArcApiV4User; 487 | public readonly song: BotArcApiV4Song; 488 | public readonly forward: BotArcApiV4Forward; 489 | public readonly util: BotArcApiV4Util; 490 | 491 | constructor(axiosConfig?: AxiosRequestConfig) 492 | constructor(baseURL?: string, timeout?: number) 493 | constructor(baseURL?: string | AxiosRequestConfig, timeout?: number) { 494 | const createAxiosInstance = function (): AxiosInstance { 495 | if (typeof baseURL === "string") { 496 | return axios.create({ 497 | baseURL, 498 | timeout: timeout || 30000 499 | }); 500 | } 501 | return axios.create(baseURL); 502 | } 503 | const axiosInstance = createAxiosInstance(); 504 | this.axios = axiosInstance; 505 | 506 | this.user = new BotArcApiV4User(axiosInstance); 507 | this.song = new BotArcApiV4Song(axiosInstance); 508 | this.forward = new BotArcApiV4Forward(axiosInstance); 509 | this.util = new BotArcApiV4Util(axiosInstance, this); 510 | 511 | return this; 512 | } 513 | 514 | /** 515 | * This is the advance function for developer. You have to know all params and returns 516 | * to make a series of requests be in one. Of course, you may use encapsulated 517 | * functions in "util". These are really nice. 518 | */ 519 | public batch( 520 | calls: string | Array 521 | ): Promise>> { 522 | const _calls = typeof calls === "string" ? JSON.parse(calls) : calls; 523 | return new Promise>>((resolve, reject) => { 524 | this.axios({ 525 | method: "POST", 526 | url: "/v4/batch", 527 | params: { 528 | calls: JSON.stringify(_calls) 529 | } 530 | }).then((response: AxiosResponse) => { 531 | const data = response.data as BotArcApiResponseV4>>; 532 | if (data.status === 0 && data.content) resolve(data.content); 533 | else reject(data.message || "undefined error occurred"); 534 | }).catch(reject); 535 | }); 536 | } 537 | 538 | /** 539 | * The code in https://lowest.world/ 540 | */ 541 | public connect(): Promise<{key: string}> { 542 | return new Promise<{key: string}>((resolve, reject) => { 543 | axios({ 544 | method: "GET", 545 | url: "v4/connect" 546 | }).then((response: AxiosResponse) => { 547 | const data = response.data as BotArcApiResponseV4<{key: string}>; 548 | if (data.status === 0 && data.content) resolve(data.content); 549 | else reject(data.message || "undefined error occurred"); 550 | }); 551 | }); 552 | } 553 | 554 | /** 555 | * Latest version and download link of Arcaea(China ver.) 556 | */ 557 | public update(): Promise<{url: string, version: string}> { 558 | return new Promise<{url: string, version: string}>((resolve, reject) => { 559 | axios({ 560 | method: "GET", 561 | url: "v4/update" 562 | }).then((response: AxiosResponse) => { 563 | const data = response.data as BotArcApiResponseV4<{url: string, version: string}>; 564 | if (data.status === 0 && data.content) resolve(data.content); 565 | else reject(data.message || "undefined error occurred"); 566 | }); 567 | }); 568 | } 569 | } 570 | -------------------------------------------------------------------------------- /src/v5/index.ts: -------------------------------------------------------------------------------- 1 | import axios, {AxiosInstance, AxiosRequestConfig, AxiosResponse} from "axios"; 2 | import { 3 | ArcaeaDifficulty, 4 | BotArcApiResponseV4, 5 | BotArcApiRecent, 6 | BotArcApiDifficultyRange, 7 | BotArcApiContentV5, 8 | BotArcApiDifficultyInfoV5, 9 | } from "../types"; 10 | 11 | class BotArcApiV5User { 12 | private readonly axios: AxiosInstance; 13 | 14 | constructor(axios: AxiosInstance) { 15 | this.axios = axios; 16 | } 17 | 18 | /** 19 | * Search and return user info. 20 | * Use "recent" argument to get 0~7 recent score(s) of user. 21 | */ 22 | 23 | /** 24 | * @function info - Search and return user info. 25 | * @param {string} user - Arcaea Friend ID or username. 26 | * @param {boolean} [fuzzy=true] - Should query be username as well as friend ID? null or false means "no and only friend ID". 27 | * @param {boolean | BotArcApiRecent | number} [recent] - How many recent scores should the API includes? If null, false or 0, then no recent. 28 | * @param {boolean} [withSongInfo=false] - Returns Songinfos if true (Only available if fuzzy is true and BotArcApiRecent is higher than 0) 29 | * @returns {Promise} - Returns the info 30 | */ 31 | public info(user: string, fuzzy: true, recent?: BotArcApiRecent, withSongInfo?: boolean): Promise 32 | /** 33 | * @function info - Search and return user info. 34 | * @param {string} usercode - Arcaea Friend ID 35 | * @param {boolean} [fuzzy=false] - Should query be username as well as friend ID? null or false means "no and only friend ID". 36 | * @param {boolean | BotArcApiRecent | number} [recent] - How many recent scores should the API includes? If null, false or 0, then no recent. 37 | * @param {boolean} [withSongInfo=false] - Returns Songinfos if true (Only available if fuzzy is true and BotArcApiRecent is higher than 0) 38 | * @returns {Promise} - Returns the info 39 | */ 40 | public info(usercode: string, fuzzy: false, recent?: BotArcApiRecent, withSongInfo?: boolean): Promise 41 | /** 42 | * @function info - Search and return user info. 43 | * @param {string} usercode - Arcaea Friend ID 44 | * @param {boolean | BotArcApiRecent | number} [recent] - How many recent scores should the API includes? If null, false or 0, then no recent. 45 | * @param {boolean} [withSongInfo=false] - Returns Songinfos if true (Only available if fuzzy is true and BotArcApiRecent is higher than 0) 46 | * @returns {Promise} - Returns the info 47 | */ 48 | public info(usercode: string, recent?: BotArcApiRecent, withSongInfo?: boolean): Promise 49 | /** 50 | * @function info - Search and return user info. 51 | * @param {string} usercode - Arcaea Friend ID 52 | * @param {boolean} [fuzzy] - Should query be username as well as friend ID? null or false means "no and only friend ID". 53 | * @param {boolean | BotArcApiRecent | number } [recent] - How many recent scores should the API includes? If null, false or 0, then no recent. 54 | * @param {boolean} [withSongInfo=false] - Returns Songinfos if true (Only available if fuzzy is true and BotArcApiRecent is higher than 0) 55 | * @returns {Promise} - Returns the info 56 | */ 57 | public info(usercode: string, fuzzy?: boolean | BotArcApiRecent, recent?: boolean | BotArcApiRecent, withSongInfo?: boolean): Promise { 58 | const axiosInstance = this.axios; 59 | let params: Record = {}; 60 | if (typeof fuzzy === "boolean") { 61 | if (fuzzy) params.user = usercode; 62 | else params.usercode = usercode; 63 | const _recent = (typeof recent === "number" && recent >= 0 && recent <= 7) ? recent : 0; 64 | if (_recent && _recent > 0) params.recent = _recent; 65 | if (withSongInfo) params.withSongInfo = true; 66 | } else { 67 | params.usercode = usercode; 68 | const _recent = (typeof fuzzy === "number" && fuzzy >= 0 && fuzzy <= 7) ? fuzzy : 0; 69 | if (_recent && _recent > 0) params.recent = _recent; 70 | if (recent) params.withSongInfo = true; 71 | } 72 | return new Promise((resolve, reject) => { 73 | axiosInstance({ 74 | method: "GET", 75 | url: "/user/info", 76 | params: params 77 | }).then((response: AxiosResponse) => { 78 | const data = response.data as BotArcApiResponseV4; 79 | if (data.status === 0 && data.content) resolve(data.content); 80 | else { 81 | reject(data.message || "undefined error occurred"); 82 | } 83 | }).catch(reject); 84 | }); 85 | } 86 | 87 | /** 88 | * Get one of user's best scores by user code, song name and difficulty. 89 | */ 90 | public best(user: string, fuzzy: true, songname: string, difficulty?: ArcaeaDifficulty, withSongInfo?: boolean): Promise 91 | public best(usercode: string, fuzzy: false, songname: string, difficulty?: ArcaeaDifficulty, withSongInfo?: boolean): Promise 92 | public best(usercode: string, songname: string, difficulty?: ArcaeaDifficulty, withSongInfo?: boolean): Promise 93 | public best(usercode: string, fuzzy: boolean | string, songname?: string | ArcaeaDifficulty, difficulty?: boolean | ArcaeaDifficulty, withSongInfo?: boolean): Promise { 94 | const axiosInstance = this.axios; 95 | let params: Record = {}; 96 | if (typeof fuzzy === "boolean") { 97 | if (fuzzy) params.user = usercode; 98 | else params.usercode = usercode; 99 | params.songname = songname; 100 | params.difficulty = (typeof difficulty === "number" && difficulty >= 0 && difficulty <= 3) ? difficulty : 2; 101 | if (withSongInfo) params.withsonginfo = true; 102 | } else { 103 | params.usercode = usercode; 104 | params.songname = fuzzy; 105 | params.difficulty = (typeof songname === "number" && songname >= 0 && songname <= 3) ? songname : 2; 106 | if (difficulty) params.withsonginfo = true; 107 | } 108 | return new Promise((resolve, reject) => { 109 | axiosInstance({ 110 | method: "GET", 111 | url: "/user/best", 112 | params 113 | }).then((response: AxiosResponse) => { 114 | const data = response.data as BotArcApiResponseV4; 115 | if (data.status === 0 && data.content) resolve(data.content); 116 | else reject(data.message || "undefined error occurred"); 117 | }).catch(reject); 118 | }); 119 | } 120 | 121 | /** 122 | * Get user's 30 best scores. 123 | */ 124 | public best30(user: string, fuzzy: true, withSongInfo?: boolean, overflow?: number): Promise 125 | public best30(usercode: string, fuzzy: false, withSongInfo?: boolean, overflow?: number): Promise 126 | public best30(usercode: string): Promise 127 | public best30(usercode: string, fuzzy?: boolean, withSongInfo?: boolean, overflow?: number): Promise { 128 | const axiosInstance = this.axios; 129 | let params: Record = {}; 130 | if (fuzzy) params.user = usercode; 131 | else params.usercode = usercode; 132 | if (withSongInfo) params.withsonginfo = true; 133 | if (overflow) params.overflow = overflow; 134 | return new Promise((resolve, reject) => { 135 | axiosInstance({ 136 | method: "GET", 137 | url: "/user/best30", 138 | params 139 | }).then((response: AxiosResponse) => { 140 | const data = response.data as BotArcApiResponseV4; 141 | if (data.status === 0 && data.content) resolve(data.content); 142 | else reject(data.message || "undefined error occurred"); 143 | }).catch(reject); 144 | }); 145 | } 146 | } 147 | 148 | class BotArcApiV5Song { 149 | private readonly axios: AxiosInstance; 150 | 151 | constructor(axios: AxiosInstance) { 152 | this.axios = axios; 153 | } 154 | 155 | public info(songname: string, fuzzy: true): Promise 156 | public info(songid: string, fuzzy: false): Promise 157 | public info(str: string, fuzzy: boolean = true): Promise { 158 | const axiosInstance = this.axios; 159 | return new Promise((resolve, reject) => { 160 | axiosInstance({ 161 | method: "GET", 162 | url: "/song/info", 163 | params: fuzzy ? { songname: str } : { songid: str } 164 | }).then((response: AxiosResponse) => { 165 | const data = response.data as BotArcApiResponseV4; 166 | if (data.status === 0 && data.content) resolve(data.content); 167 | else { 168 | reject(data.message || "undefined error occurred"); 169 | } 170 | }).catch(reject); 171 | }); 172 | } 173 | 174 | public alias(songname: string, fuzzy: true): Promise 175 | public alias(songid: string, fuzzy: false): Promise 176 | public alias(str: string, fuzzy: boolean = true): Promise { 177 | const axiosInstance = this.axios; 178 | return new Promise((resolve, reject) => { 179 | axiosInstance({ 180 | method: "GET", 181 | url: "/song/alias", 182 | params: fuzzy ? { songname: str } : { songid: str } 183 | }).then((response: AxiosResponse) => { 184 | const data = response.data as BotArcApiResponseV4; 185 | if (data.status === 0 && data.content) resolve(data.content); 186 | else { 187 | reject(data.message || "undefined error occurred"); 188 | } 189 | }).catch(reject); 190 | }); 191 | } 192 | 193 | public random(withSongInfo?: boolean): Promise 194 | public random(start?: BotArcApiDifficultyRange, withSongInfo?: boolean): Promise 195 | public random(start?: BotArcApiDifficultyRange, end?: BotArcApiDifficultyRange): Promise 196 | public random(start?: BotArcApiDifficultyRange, end?: BotArcApiDifficultyRange, withSongInfo?: boolean): Promise 197 | public random(start?: BotArcApiDifficultyRange | boolean, end?: BotArcApiDifficultyRange | boolean, withSongInfo?: boolean): Promise { 198 | const axiosInstance = this.axios; 199 | const params: Record = {}; 200 | if ((typeof start === 'boolean' && start) || (typeof end === 'boolean' && end) || withSongInfo) params.withsonginfo = true; 201 | if (typeof start === 'string') params.start = start; 202 | if (typeof end === 'string') params.end = end; 203 | return new Promise((resolve, reject) => { 204 | axiosInstance({ 205 | method: "GET", 206 | url: "/song/random", 207 | params 208 | }).then((response: AxiosResponse) => { 209 | const data = response.data as BotArcApiResponseV4; 210 | if (data.status === 0 && data.content) resolve(data.content); 211 | else { 212 | reject(data.message || "undefined error occurred"); 213 | } 214 | }).catch(reject); 215 | }); 216 | } 217 | } 218 | 219 | class BotArcApiV5Assets { 220 | private readonly axios: AxiosInstance; 221 | 222 | constructor(axios: AxiosInstance) { 223 | this.axios = axios; 224 | } 225 | 226 | public char(partner: number, awakened?: boolean): Promise { 227 | const axiosInstance = this.axios; 228 | return new Promise((resolve, reject) => { 229 | axiosInstance({ 230 | method: "GET", 231 | url: "/assets/char", 232 | responseType: "arraybuffer", 233 | params: { partner, awakened } 234 | }).then((response: AxiosResponse) => { 235 | const data = response.data; 236 | if (response.headers["content-type"].includes("application/json")) { 237 | const responseJSON = JSON.parse(Buffer.from(data).toString("utf-8")) as BotArcApiResponseV4; 238 | reject(responseJSON.message || "undefined error occurred"); 239 | } else resolve(data); 240 | }).catch(reject); 241 | }); 242 | } 243 | 244 | public icon(partner: number, awakened?: boolean): Promise { 245 | const axiosInstance = this.axios; 246 | return new Promise((resolve, reject) => { 247 | axiosInstance({ 248 | method: "GET", 249 | url: "/assets/icon", 250 | responseType: "arraybuffer", 251 | params: { partner, awakened } 252 | }).then((response: AxiosResponse) => { 253 | const data = response.data; 254 | if (response.headers["content-type"].includes("application/json")) { 255 | const responseJSON = JSON.parse(Buffer.from(data).toString("utf-8")) as BotArcApiResponseV4; 256 | reject(responseJSON.message || "undefined error occurred"); 257 | } else resolve(data); 258 | }).catch(reject); 259 | }); 260 | } 261 | 262 | public song(songname: string, fuzzy: true, beyond?: boolean): Promise 263 | public song(songid: string, fuzzy: false, beyond?: boolean): Promise 264 | public song(str: string, fuzzy: boolean = true, beyond: boolean = false): Promise { 265 | const axiosInstance = this.axios; 266 | let params: Record = {}; 267 | if (fuzzy) params.songname = str; 268 | else params.songid = str; 269 | if (beyond) params.difficulty = 3; 270 | return new Promise((resolve, reject) => { 271 | axiosInstance({ 272 | method: "GET", 273 | url: "/assets/song", 274 | responseType: "arraybuffer", 275 | params 276 | }).then((response: AxiosResponse) => { 277 | const data = response.data; 278 | if (response.headers["content-type"].includes("application/json")) { 279 | const responseJSON = JSON.parse(Buffer.from(data).toString("utf-8")) as BotArcApiResponseV4; 280 | reject(responseJSON.message || "undefined error occurred"); 281 | } else resolve(data); 282 | }).catch(reject); 283 | }); 284 | } 285 | } 286 | 287 | export class BotArcApiV5 { 288 | private axios: AxiosInstance; 289 | public readonly user: BotArcApiV5User; 290 | public readonly song: BotArcApiV5Song; 291 | public readonly assets: BotArcApiV5Assets; 292 | 293 | constructor(axiosConfig?: AxiosRequestConfig) 294 | constructor(baseURL?: string, timeout?: number) 295 | constructor(baseURL?: string | AxiosRequestConfig, timeout?: number) { 296 | const createAxiosInstance = function (): AxiosInstance { 297 | if (typeof baseURL === "string") { 298 | return axios.create({ 299 | baseURL, 300 | timeout: timeout || 30000 301 | }); 302 | } 303 | return axios.create(baseURL); 304 | } 305 | const axiosInstance = createAxiosInstance(); 306 | this.axios = axiosInstance; 307 | 308 | this.user = new BotArcApiV5User(axiosInstance); 309 | this.song = new BotArcApiV5Song(axiosInstance); 310 | this.assets = new BotArcApiV5Assets(axiosInstance); 311 | 312 | return this; 313 | } 314 | 315 | /** 316 | * The code in https://lowest.world/ 317 | * @deprecated 318 | */ 319 | public connect(): Promise { 320 | return new Promise((resolve, reject) => { 321 | this.axios({ 322 | method: "GET", 323 | url: "/connect" 324 | }).then((response: AxiosResponse) => { 325 | const data = response.data as BotArcApiResponseV4; 326 | if (data.status === 0 && data.content) resolve(data.content); 327 | else reject(data.message || "undefined error occurred"); 328 | }); 329 | }); 330 | } 331 | 332 | /** 333 | * Latest version and download link of Arcaea(China ver.) 334 | */ 335 | public update(): Promise<{url: string, version: string}> { 336 | return new Promise<{url: string, version: string}>((resolve, reject) => { 337 | this.axios({ 338 | method: "GET", 339 | url: "/update" 340 | }).then((response: AxiosResponse) => { 341 | const data = response.data as BotArcApiResponseV4<{url: string, version: string}>; 342 | if (data.status === 0 && data.content) resolve(data.content); 343 | else reject(data.message || "undefined error occurred"); 344 | }); 345 | }); 346 | } 347 | } 348 | -------------------------------------------------------------------------------- /src/v5/utils.ts: -------------------------------------------------------------------------------- 1 | export enum ErrorStatus { 2 | INVALID_USERNAME_OR_USER_CODE = -1, 3 | INVALID_USER_CODE = -2, 4 | USER_NOT_FOUND = -3, 5 | TOO_MANY_USERS = -4, 6 | INVALID_SONG_NAME_OR_SONG_ID = -5, 7 | INVALID_SONG_ID = -6, 8 | SONG_NOT_RECORDED = -7, 9 | TOO_MANY_RECORDS = -8, 10 | INVALID_DIFFICULTY = -9, 11 | INVALID_RECENT_NUMBER = -10, 12 | ALLOCATE_AN_ARC_ACCOUNT_FAILED = -11, 13 | CLEAR_FRIEND_FAILED = -12, 14 | ADD_FRIEND_FAILED = -13, 15 | THIS_SONG_HAS_NO_BEYOND_LEVEL = -14, 16 | NOT_PLAYED_YET = -15, 17 | USER_GOT_SHADOWBANNED = -16, 18 | QUERYING_BEST30_FAILED = -17, 19 | UPDATE_SERVICE_UNAVAILABLE = -18, 20 | INVALID_PARTNER = -19, 21 | FILE_UNAVAILABLE = -20, 22 | POTENTIAL_BELOW_THE_THRESHOLD = -23, 23 | NEED_TO_UPDATE_ARCAEA = -24, 24 | INTERNAL_ERROR_OCCURRED = -233 25 | } 26 | 27 | const errorStatusString = { 28 | "en": { 29 | "-1": "Invalid username or user code", 30 | "-2": "Invalid user code", 31 | "-3": "User not found", 32 | "-4": "Too many users", 33 | "-5": "Invalid song name or song id", 34 | "-6": "Invalid song id", 35 | "-7": "Song not recorded", 36 | "-8": "Too many records", 37 | "-9": "Invalid difficulty", 38 | "-10": "Invalid recent number", 39 | "-11": "Allocate an Arcaea account failed", 40 | "-12": "Clear friend list failed", 41 | "-13": "Add friend failed", 42 | "-14": "This song has no beyond level", 43 | "-15": "Not played yet", 44 | "-16": "User got shadowbanned", 45 | "-17": "Querying best30 failed", 46 | "-18": "Update service unavailable", 47 | "-19": "Invalid partner", 48 | "-20": "File unavailable", 49 | "-21": "Invalid range", 50 | "-22": "Range of rating end smaller than its start", 51 | "-23": "Potential is below the threshold of querying best30 (7.0)", 52 | "-24": "Need to update arcaea, please contact maintainer", 53 | "-233": "Internal error occurred" 54 | }, 55 | "zh-cn": { 56 | "-1": "无效的用户名或用户代码", 57 | "-2": "无效的用户代码", 58 | "-3": "未找到用户", 59 | "-4": "匹配到了多个用户", 60 | "-5": "无效的歌曲名称或歌曲ID", 61 | "-6": "无效的歌曲ID", 62 | "-7": "歌曲未记录", 63 | "-8": "匹配到了多个歌曲条目", 64 | "-9": "无效的难度", 65 | "-10": "无效的最近成绩数", 66 | "-11": "分配Arcaea账号失败", 67 | "-12": "清空好友列表失败", 68 | "-13": "添加好友失败", 69 | "-14": "此歌曲没有Beyond难度", 70 | "-15": "尚未游玩过", 71 | "-16": "该用户排行榜已被封禁", 72 | "-17": "查询Best 30失败", 73 | "-18": "升级服务不可用", 74 | "-19": "无效的搭档", 75 | "-20": "文件不可用", 76 | "-21": "无效的区间", 77 | "-22": "评级区间的上界值小于下界值", 78 | "-23": "查询的Best 30低于潜力值阈值(7.0)", 79 | "-24": "服务端Arcaea需要升级,请联系维护人员", 80 | "-233": "发生了内部错误" 81 | }, 82 | } 83 | 84 | export function errorStatusToString(errorStatus: ErrorStatus, lang: "en" | "zh-cn" = "en") { 85 | return errorStatusString[lang][errorStatus]; 86 | } 87 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Visit https://aka.ms/tsconfig.json to read more about this file */ 4 | 5 | /* Basic Options */ 6 | // "incremental": true, /* Enable incremental compilation */ 7 | "target": "es6", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 8 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 9 | // "lib": [], /* Specify library files to be included in the compilation. */ 10 | // "allowJs": true, /* Allow javascript files to be compiled. */ 11 | // "checkJs": true, /* Report errors in .js files. */ 12 | // "jsx": "preserve", /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */ 13 | // "declaration": true, /* Generates corresponding '.d.ts' file. */ 14 | // "declarationMap": true, /* Generates a sourcemap for each corresponding '.d.ts' file. */ 15 | // "sourceMap": true, /* Generates corresponding '.map' file. */ 16 | // "outFile": "./", /* Concatenate and emit output to single file. */ 17 | // "outDir": "./", /* Redirect output structure to the directory. */ 18 | // "rootDir": "./", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 19 | // "composite": true, /* Enable project compilation */ 20 | // "tsBuildInfoFile": "./", /* Specify file to store incremental compilation information */ 21 | // "removeComments": true, /* Do not emit comments to output. */ 22 | // "noEmit": true, /* Do not emit outputs. */ 23 | // "importHelpers": true, /* Import emit helpers from 'tslib'. */ 24 | // "downlevelIteration": true, /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */ 25 | // "isolatedModules": true, /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */ 26 | 27 | /* Strict Type-Checking Options */ 28 | "strict": true, /* Enable all strict type-checking options. */ 29 | // "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 30 | // "strictNullChecks": true, /* Enable strict null checks. */ 31 | // "strictFunctionTypes": true, /* Enable strict checking of function types. */ 32 | // "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 33 | // "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 34 | // "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 35 | // "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 36 | 37 | /* Additional Checks */ 38 | // "noUnusedLocals": true, /* Report errors on unused locals. */ 39 | // "noUnusedParameters": true, /* Report errors on unused parameters. */ 40 | // "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 41 | // "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 42 | 43 | /* Module Resolution Options */ 44 | // "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 45 | // "baseUrl": "./", /* Base directory to resolve non-absolute module names. */ 46 | // "paths": {}, /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */ 47 | // "rootDirs": [], /* List of root folders whose combined content represents the structure of the project at runtime. */ 48 | // "typeRoots": [], /* List of folders to include type definitions from. */ 49 | // "types": [], /* Type declaration files to be included in compilation. */ 50 | // "allowSyntheticDefaultImports": true, /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */ 51 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 52 | // "preserveSymlinks": true, /* Do not resolve the real path of symlinks. */ 53 | // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ 54 | 55 | /* Source Map Options */ 56 | // "sourceRoot": "", /* Specify the location where debugger should locate TypeScript files instead of source locations. */ 57 | // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ 58 | // "inlineSourceMap": true, /* Emit a single file with source maps instead of having a separate file. */ 59 | // "inlineSources": true, /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */ 60 | 61 | /* Experimental Options */ 62 | // "experimentalDecorators": true, /* Enables experimental support for ES7 decorators. */ 63 | // "emitDecoratorMetadata": true, /* Enables experimental support for emitting type metadata for decorators. */ 64 | 65 | /* Advanced Options */ 66 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 67 | "forceConsistentCasingInFileNames": true, /* Disallow inconsistently-cased references to the same file. */ 68 | 69 | "outDir": "./build", 70 | "rootDir": "./", 71 | 72 | "declaration": true 73 | } 74 | } 75 | --------------------------------------------------------------------------------