├── .github └── workflows │ └── publish.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package-lock.json ├── package.json └── src ├── Account └── Account.js ├── Billing └── Pool │ ├── Pool.js │ └── PoolMember.js ├── Client.js ├── Error ├── RequestBodyError.js ├── RequestError.js └── RequestStatusError.js ├── Request ├── Account │ └── GetAccountRequest.js ├── Billing │ └── Pool │ │ ├── GetPoolMembersRequest.js │ │ ├── GetPoolRequest.js │ │ ├── GetPoolServersRequest.js │ │ ├── GetPoolsRequest.js │ │ └── PoolRequest.js ├── GetServersRequest.js ├── Request.js └── Server │ ├── ExecuteServerCommandRequest.js │ ├── Files │ ├── Config │ │ ├── GetConfigFileOptionsRequest.js │ │ └── UpdateConfigFileOptionsRequest.js │ ├── CreateDirectoryRequest.js │ ├── DeleteFileDataRequest.js │ ├── FileDataRequest.js │ ├── FileRequest.js │ ├── GetFileDataRequest.js │ ├── GetFileInformationRequest.js │ └── PutFileDataRequest.js │ ├── GetServerLogsRequest.js │ ├── GetServerOptionRequest.js │ ├── GetServerRequest.js │ ├── PlayerLists │ ├── DeletePlayerListEntriesRequest.js │ ├── GetPlayerListEntriesRequest.js │ ├── GetPlayerListsRequest.js │ ├── PlayerListRequest.js │ └── PutPlayerListEntriesRequest.js │ ├── RestartServerRequest.js │ ├── ServerRequest.js │ ├── SetServerOptionRequest.js │ ├── ShareServerLogsRequest.js │ ├── StartServerRequest.js │ └── StopServerRequest.js ├── Response ├── ArrayResponse.js ├── PlayerListsResponse.js ├── PoolMembersResponse.js ├── PoolsResponse.js ├── Response.js └── ServersResponse.js ├── Server ├── Config │ ├── Config.js │ ├── ConfigOption.js │ └── ConfigOptionType.js ├── File.js ├── PlayerList.js ├── Players.js ├── Server.js ├── ServerStatus.js └── Software.js └── Websocket ├── ConsoleStream.js ├── HeapStream.js ├── StatsStream.js ├── Stream.js ├── TickStream.js └── WebsocketClient.js /.github/workflows/publish.yml: -------------------------------------------------------------------------------- 1 | name: Node.js Package 2 | 3 | on: 4 | release: 5 | types: [created] 6 | 7 | jobs: 8 | publish-npm: 9 | runs-on: ubuntu-latest 10 | permissions: 11 | contents: read 12 | id-token: write 13 | steps: 14 | - uses: actions/checkout@v3 15 | - uses: actions/setup-node@v3 16 | with: 17 | node-version: 20 18 | registry-url: https://registry.npmjs.org/ 19 | - run: npm install -g npm 20 | - run: npm ci 21 | - run: npm publish --provenance --access public 22 | env: 23 | NODE_AUTH_TOKEN: ${{secrets.npm_token}} -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .idea 3 | *.log 4 | test.js -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2020-2023 Aternos GmbH 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Node.js exaroton API client 2 | 3 | ## About 4 | The exaroton API allows automated access to some basic functionalities of your game servers, such as starting or stopping 5 | the server. You can read the API documentation here: https://developers.exaroton.com 6 | 7 | This is the official Node.js implementation of this API. 8 | 9 | ### Installation 10 | 11 | ``` 12 | npm install exaroton 13 | ``` 14 | 15 | ## Usage 16 | To use the API and this client you have to get your API key, which you can generate in your exaroton account settings: https://exaroton.com/account 17 | 18 | 19 | ### Create a client object 20 | ```js 21 | const {Client} = require('exaroton'); 22 | 23 | const client = new Client(token); 24 | ``` 25 | *Remember to keep your token secret and don't add it to any private or public code repositories.* 26 | 27 | ### REST API 28 | 29 | #### Get account info 30 | ````js 31 | let account = await client.getAccount(); 32 | console.log("My account is " + account.name + " and I have " + account.credits + " credits."); 33 | ```` 34 | 35 | The account object contains the fields and information as listed in the [documentation](https://developers.exaroton.com/#account-get). 36 | 37 | #### List servers 38 | ```js 39 | let servers = await client.getServers(); 40 | 41 | for(let server of servers) { 42 | console.log(server.name + ": " + server.id); 43 | } 44 | ``` 45 | 46 | Each server object contains the fields and information as listed in the [documentation](https://developers.exaroton.com/#servers-get). 47 | 48 | #### Create a server object by ID 49 | ```js 50 | let server = client.server(id); 51 | ``` 52 | 53 | #### Get server information 54 | ```js 55 | await server.get(); 56 | console.log(server.name + ": " + server.id); 57 | ``` 58 | 59 | #### Get/check the server status 60 | ```js 61 | console.log(server.status); 62 | 63 | if (server.hasStatus(server.STATUS.ONLINE)) { 64 | console.log("Server is online."); 65 | } else if (server.hasStatus([server.STATUS.PREPARING, server.STATUS.LOADING, server.STATUS.STARTING])) { 66 | console.log("Server is online soon."); 67 | } else { 68 | console.log("Server is offline."); 69 | } 70 | ``` 71 | The server status is an `integer` as described in the [documentation](https://developers.exaroton.com/#header-server-status). You can use 72 | the [ServerStatus](./src/Server/ServerStatus.js) object, which you can require on its own `const {ServerStatus} = require('exaroton')` or via the 73 | shorthand `server.STATUS` property. 74 | 75 | #### Start/stop/restart the server 76 | ```js 77 | try { 78 | await server.start(); 79 | await server.stop(); 80 | await server.restart(); 81 | } catch (e) { 82 | console.error(e.message); 83 | } 84 | ``` 85 | *It's important to catch errors, because incorrect calls, e.g. a `server.stop()` when the server is offline will result in an error.* 86 | 87 | #### Execute a server command 88 | ```js 89 | try { 90 | await server.executeCommand("say Hello world!"); 91 | } catch (e) { 92 | console.error(e.message); 93 | } 94 | ``` 95 | 96 | #### Get the server logs 97 | ```js 98 | try { 99 | let logs = await server.getLogs(); 100 | console.log(logs); 101 | } catch (e) { 102 | console.error(e.message); 103 | } 104 | ``` 105 | *This is cached and will not return the latest updates immediately. It's also not possible to get the server logs while the server is loading, stopping or saving.* 106 | 107 | #### Share the server logs via mclo.gs 108 | ```js 109 | try { 110 | let url = await server.shareLogs(); 111 | console.log(url); 112 | } catch (e) { 113 | console.error(e.message); 114 | } 115 | ``` 116 | *This is cached and will not return the latest updates immediately. It's also not possible to share the server logs while the server is loading, stopping or saving.* 117 | 118 | #### Get the server RAM 119 | ```js 120 | try { 121 | let ram = await server.getRAM(); 122 | console.log("This server has " + ram + " GB RAM."); 123 | } catch (e) { 124 | console.error(e.message); 125 | } 126 | ``` 127 | The amount of RAM is returned in full GiB. 128 | 129 | #### Set the server RAM 130 | ```js 131 | try { 132 | await server.setRAM(8); 133 | } catch (e) { 134 | console.error(e.message); 135 | } 136 | ``` 137 | The RAM is set in full GiB and has to be between 2 and 16. 138 | 139 | #### Get the server MOTD 140 | ```js 141 | try { 142 | let motd = await server.getMOTD(); 143 | console.log(motd); 144 | } catch (e) { 145 | console.error(e.message); 146 | } 147 | ``` 148 | 149 | #### Set the server MOTD 150 | ```js 151 | try { 152 | await server.setMOTD("Hello world!"); 153 | } catch (e) { 154 | console.error(e.message); 155 | } 156 | ``` 157 | 158 | #### Player lists 159 | A player list is a list of players such as the whitelist, ops or bans. 160 | Player list entries are usually usernames, but might be something else, e.g. IPs in the banned-ips list. 161 | All player list operations are storage operations that might take a while, so try to reduce the amount of requests and combine actions when possible (e.g. adding/deleting multiple entries at once). 162 | Player lists are also cached any might not immediately return new results when changed through other methods e.g. in-game. 163 | 164 | ##### Get a player list object 165 | You can list all available player lists... 166 | ```js 167 | try { 168 | let lists = await server.getPlayerLists(); 169 | console.log(lists); 170 | } catch (e) { 171 | console.error(e.message); 172 | } 173 | ``` 174 | 175 | ... or if you already know the name (e.g. "whitelist") you can directly create a player list object: 176 | ```js 177 | try { 178 | let list = server.getPlayerList("whitelist"); 179 | console.log(list); 180 | } catch (e) { 181 | console.error(e.message); 182 | } 183 | ``` 184 | 185 | ##### Get all player list entries 186 | ```js 187 | try { 188 | let list = server.getPlayerList("whitelist"); 189 | let entries = await list.getEntries(); 190 | console.log(entries); 191 | } catch (e) { 192 | console.error(e.message); 193 | } 194 | ``` 195 | 196 | ##### Add player list entries 197 | We handle all the heavy work of adding player list entries for you, e.g. automatically adding UUIDs depending on the online mode or executing the necessary commands while the server is running. 198 | ```js 199 | try { 200 | let list = server.getPlayerList("whitelist"); 201 | await list.addEntry("Steve"); // add just one entry 202 | await list.addEntries(["Steve", "Alex"]); // add multiple entries at once 203 | console.log(await list.getEntries()); 204 | } catch (e) { 205 | console.error(e.message); 206 | } 207 | ``` 208 | 209 | ##### Delete player list entries 210 | ```js 211 | try { 212 | let list = server.getPlayerList("whitelist"); 213 | await list.deleteEntry("Steve"); // delete just one entry 214 | await list.deleteEntries(["Steve", "Alex"]); // delete multiple entries at once 215 | console.log(await list.getEntries()); 216 | } catch (e) { 217 | console.error(e.message); 218 | } 219 | ``` 220 | 221 | #### Files 222 | 223 | You can request information about files, download and upload files. 224 | 225 | ##### Get a file object 226 | This just creates the file object but doesn't request any information or content 227 | ```js 228 | let file = server.getFile("server.properties"); 229 | ``` 230 | 231 | ##### Get file information 232 | If a file doesn't exist you will get a 404 error. 233 | ```js 234 | try { 235 | await file.getInfo(); 236 | console.log(file); 237 | } catch (e) { 238 | console.error(e.message); 239 | } 240 | ``` 241 | 242 | ##### List files in a directory 243 | ```js 244 | try { 245 | // get the children of the directory 246 | let children = await file.getChildren(); 247 | console.log(children); 248 | 249 | // iterate over the children 250 | // each child is a file object 251 | for (let child of children) { 252 | console.log(child); 253 | } 254 | } catch (e) { 255 | console.error(e.message); 256 | } 257 | ``` 258 | 259 | ##### Get the content of a file / download a file 260 | ```js 261 | try { 262 | // get the content of the file in a variable 263 | // large files will cause high memory usage 264 | let content = await file.getContent(); 265 | console.log(content); 266 | 267 | // or download the file to a local file 268 | await file.download("test.txt"); 269 | 270 | // or download the file to a stream 271 | let stream = await file.downloadToStream(createWriteStream("test.txt")); 272 | } catch (e) { 273 | console.error(e.message); 274 | } 275 | ``` 276 | 277 | ##### Change the content of a file / upload a file 278 | ```js 279 | try { 280 | // change the content of the file 281 | await file.setContent("Hello world!"); 282 | 283 | // or upload a local file 284 | await file.upload("test.txt"); 285 | 286 | // or upload from a stream 287 | await file.uploadFromStream(createReadStream("test.txt")); 288 | } catch (e) { 289 | console.error(e.message); 290 | } 291 | ``` 292 | 293 | ##### Delete a file 294 | ```js 295 | try { 296 | await file.delete(); 297 | } catch (e) { 298 | console.error(e.message); 299 | } 300 | ``` 301 | 302 | ##### Create a directory 303 | ```js 304 | try { 305 | await file.createAsDirectory(); 306 | } catch (e) { 307 | console.error(e.message); 308 | } 309 | ``` 310 | 311 | #### Config files 312 | Config files are files that contain server configuration and are shown as forms rather than plain text files in the exaroton panel. 313 | You can get a config file object by using the `getConfig()` method on a file object. 314 | Whether a file is a config file can be checked using the `isConfigFile` property. 315 | 316 | ```js 317 | let file = server.getFile("server.properties"); 318 | let config = file.getConfig(); 319 | ``` 320 | 321 | ##### Get config file options 322 | ```js 323 | let options = await config.getOptions(); 324 | for(let [key, option] of options) { 325 | console.log(key, option.getValue()); 326 | } 327 | ``` 328 | 329 | ##### Update config file options 330 | ```js 331 | let options = await config.getOptions(); 332 | 333 | options.get("max-players").setValue(26); 334 | options.get("pvp").setValue(false); 335 | 336 | await config.save(); 337 | ``` 338 | 339 | Options of type `select` or `multiselect` have a list of possible values. 340 | ```js 341 | let options = await config.getOptions(); 342 | console.log(options.get("difficulty").getOptions()); 343 | ``` 344 | 345 | #### Credit pools 346 | Credit pools allow sharing the costs of a server between multiple users. 347 | 348 | ##### List credit pools 349 | ```js 350 | let pools = await client.getPools(); 351 | console.log(pools); 352 | ``` 353 | 354 | ##### Create a pool object by ID 355 | ```js 356 | let server = client.pool(id); 357 | ``` 358 | 359 | ##### Get pool information 360 | ```js 361 | await pool.get(); 362 | console.log(pool.name + ": " + pool.credits); 363 | ``` 364 | 365 | ##### Get pool members 366 | ```js 367 | let members = await pool.getMembers(); 368 | console.log(members); 369 | ``` 370 | 371 | ##### Get pool servers 372 | ```js 373 | let servers = await pool.getServers(); 374 | console.log(servers); 375 | ``` 376 | 377 | ### Websocket API 378 | The websocket API allows a constant connection to our websocket service to receive 379 | events in real time without polling (e.g. trying to get the server status every few seconds). 380 | 381 | #### Server status events 382 | You can simply connect to the websocket API for a server by running the `subscribe()` function. 383 | ```js 384 | server.subscribe(); 385 | ``` 386 | 387 | By default, you are always subscribed to server status update events, you can 388 | react to server status changes by adding a listener: 389 | ```js 390 | server.subscribe(); 391 | server.on("status", function(server) { 392 | console.log(server.status); 393 | }); 394 | ``` 395 | This event is not only triggered when the status itself changes but also when other 396 | events happen, e.g. a player joins the server. 397 | 398 | #### Console 399 | There are several optional streams that you can subscribe to, e.g. 400 | the console. 401 | ```js 402 | server.subscribe("console"); 403 | ``` 404 | The console stream emits an event for every new console line. 405 | ```js 406 | server.subscribe("console"); 407 | server.on("console:line", function(data) { 408 | console.log(data.line); 409 | }); 410 | ``` 411 | The `data.line` property is already cleaned up for easier use in this client library, you can use `data.rawLine` if you want the raw 412 | data with all formatting codes etc. 413 | 414 | The console stream also allows you to send commands directly over the websocket. This is faster because the connection is already 415 | established and no further authorization etc. is necessary. This library already checks if you are subscribed to the console stream 416 | and sends the command through that stream instead, so you can just use it the same way as before: 417 | ```js 418 | try { 419 | await server.executeCommand("say Hello world!"); 420 | } catch (e) { 421 | console.error(e.message); 422 | } 423 | ``` 424 | 425 | #### Tick times 426 | On Minecraft Java edition servers with version 1.16 and higher it is possible to get the tick times, and the TPS (ticks per second) of your server. 427 | This information is also available as an optional stream. 428 | ```js 429 | server.subscribe("tick"); 430 | server.on("tick:tick", function(data) { 431 | console.log("Tick time: " + data.averageTickTime + "ms"); 432 | console.log("TPS: " + data.tps); 433 | }); 434 | ``` 435 | 436 | #### RAM usage 437 | There are two different optional streams to get RAM usage, the general `stats` stream and the Java specific `heap` stream. 438 | It is recommended to use the `heap` stream if you are running a server software that is based on Java. It is not recommended using both. 439 | 440 | You can subscribe to multiple streams at once by passing an array to the subscribe function. 441 | ```js 442 | server.subscribe(["stats", "heap"]); 443 | server.on("stats:stats", function(data) { 444 | console.log(data.memory.usage); 445 | }); 446 | server.on("heap:heap", function(data) { 447 | console.log(data.usage); 448 | }); 449 | ``` 450 | 451 | #### Unsubscribe 452 | You can unsubscribe from one, multiple or all streams using the `server.unsubscribe()` function. 453 | 454 | ```js 455 | server.unsubscribe("console"); 456 | server.unsubscribe(["tick", "heap"]); 457 | server.unsubscribe(); // this disconnects the websocket connection 458 | ``` 459 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | Client: require('./src/Client'), 3 | Server: require('./src/Server/Server'), 4 | Software: require('./src/Server/Software'), 5 | ServerStatus: require('./src/Server/ServerStatus'), 6 | Request: require('./src/Request/Request'), 7 | Response: require('./src/Response/Response'), 8 | ConfigOptionType: require('./src/Server/Config/ConfigOptionType'), 9 | Pool: require('./src/Billing/Pool/Pool'), 10 | PoolMember: require('./src/Billing/Pool/PoolMember') 11 | } 12 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exaroton", 3 | "version": "1.11.3", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "exaroton", 9 | "version": "1.11.3", 10 | "license": "MIT", 11 | "dependencies": { 12 | "got": "^11.8.2", 13 | "ws": "^7.4.5" 14 | }, 15 | "engines": { 16 | "node": ">=12.0.0" 17 | } 18 | }, 19 | "node_modules/@sindresorhus/is": { 20 | "version": "4.2.0", 21 | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", 22 | "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==", 23 | "engines": { 24 | "node": ">=10" 25 | }, 26 | "funding": { 27 | "url": "https://github.com/sindresorhus/is?sponsor=1" 28 | } 29 | }, 30 | "node_modules/@szmarczak/http-timer": { 31 | "version": "4.0.6", 32 | "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", 33 | "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", 34 | "dependencies": { 35 | "defer-to-connect": "^2.0.0" 36 | }, 37 | "engines": { 38 | "node": ">=10" 39 | } 40 | }, 41 | "node_modules/@types/cacheable-request": { 42 | "version": "6.0.2", 43 | "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", 44 | "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", 45 | "dependencies": { 46 | "@types/http-cache-semantics": "*", 47 | "@types/keyv": "*", 48 | "@types/node": "*", 49 | "@types/responselike": "*" 50 | } 51 | }, 52 | "node_modules/@types/http-cache-semantics": { 53 | "version": "4.0.1", 54 | "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", 55 | "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" 56 | }, 57 | "node_modules/@types/keyv": { 58 | "version": "3.1.3", 59 | "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", 60 | "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", 61 | "dependencies": { 62 | "@types/node": "*" 63 | } 64 | }, 65 | "node_modules/@types/node": { 66 | "version": "16.11.12", 67 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", 68 | "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" 69 | }, 70 | "node_modules/@types/responselike": { 71 | "version": "1.0.0", 72 | "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", 73 | "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", 74 | "dependencies": { 75 | "@types/node": "*" 76 | } 77 | }, 78 | "node_modules/cacheable-lookup": { 79 | "version": "5.0.4", 80 | "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", 81 | "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==", 82 | "engines": { 83 | "node": ">=10.6.0" 84 | } 85 | }, 86 | "node_modules/cacheable-request": { 87 | "version": "7.0.2", 88 | "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", 89 | "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", 90 | "dependencies": { 91 | "clone-response": "^1.0.2", 92 | "get-stream": "^5.1.0", 93 | "http-cache-semantics": "^4.0.0", 94 | "keyv": "^4.0.0", 95 | "lowercase-keys": "^2.0.0", 96 | "normalize-url": "^6.0.1", 97 | "responselike": "^2.0.0" 98 | }, 99 | "engines": { 100 | "node": ">=8" 101 | } 102 | }, 103 | "node_modules/clone-response": { 104 | "version": "1.0.2", 105 | "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", 106 | "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", 107 | "dependencies": { 108 | "mimic-response": "^1.0.0" 109 | } 110 | }, 111 | "node_modules/decompress-response": { 112 | "version": "6.0.0", 113 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 114 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 115 | "dependencies": { 116 | "mimic-response": "^3.1.0" 117 | }, 118 | "engines": { 119 | "node": ">=10" 120 | }, 121 | "funding": { 122 | "url": "https://github.com/sponsors/sindresorhus" 123 | } 124 | }, 125 | "node_modules/decompress-response/node_modules/mimic-response": { 126 | "version": "3.1.0", 127 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 128 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", 129 | "engines": { 130 | "node": ">=10" 131 | }, 132 | "funding": { 133 | "url": "https://github.com/sponsors/sindresorhus" 134 | } 135 | }, 136 | "node_modules/defer-to-connect": { 137 | "version": "2.0.1", 138 | "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", 139 | "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==", 140 | "engines": { 141 | "node": ">=10" 142 | } 143 | }, 144 | "node_modules/end-of-stream": { 145 | "version": "1.4.4", 146 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 147 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 148 | "dependencies": { 149 | "once": "^1.4.0" 150 | } 151 | }, 152 | "node_modules/get-stream": { 153 | "version": "5.2.0", 154 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 155 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 156 | "dependencies": { 157 | "pump": "^3.0.0" 158 | }, 159 | "engines": { 160 | "node": ">=8" 161 | }, 162 | "funding": { 163 | "url": "https://github.com/sponsors/sindresorhus" 164 | } 165 | }, 166 | "node_modules/got": { 167 | "version": "11.8.6", 168 | "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", 169 | "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", 170 | "dependencies": { 171 | "@sindresorhus/is": "^4.0.0", 172 | "@szmarczak/http-timer": "^4.0.5", 173 | "@types/cacheable-request": "^6.0.1", 174 | "@types/responselike": "^1.0.0", 175 | "cacheable-lookup": "^5.0.3", 176 | "cacheable-request": "^7.0.2", 177 | "decompress-response": "^6.0.0", 178 | "http2-wrapper": "^1.0.0-beta.5.2", 179 | "lowercase-keys": "^2.0.0", 180 | "p-cancelable": "^2.0.0", 181 | "responselike": "^2.0.0" 182 | }, 183 | "engines": { 184 | "node": ">=10.19.0" 185 | }, 186 | "funding": { 187 | "url": "https://github.com/sindresorhus/got?sponsor=1" 188 | } 189 | }, 190 | "node_modules/http-cache-semantics": { 191 | "version": "4.1.1", 192 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", 193 | "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" 194 | }, 195 | "node_modules/http2-wrapper": { 196 | "version": "1.0.3", 197 | "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", 198 | "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", 199 | "dependencies": { 200 | "quick-lru": "^5.1.1", 201 | "resolve-alpn": "^1.0.0" 202 | }, 203 | "engines": { 204 | "node": ">=10.19.0" 205 | } 206 | }, 207 | "node_modules/json-buffer": { 208 | "version": "3.0.1", 209 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 210 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" 211 | }, 212 | "node_modules/keyv": { 213 | "version": "4.0.4", 214 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", 215 | "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==", 216 | "dependencies": { 217 | "json-buffer": "3.0.1" 218 | } 219 | }, 220 | "node_modules/lowercase-keys": { 221 | "version": "2.0.0", 222 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", 223 | "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", 224 | "engines": { 225 | "node": ">=8" 226 | } 227 | }, 228 | "node_modules/mimic-response": { 229 | "version": "1.0.1", 230 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", 231 | "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==", 232 | "engines": { 233 | "node": ">=4" 234 | } 235 | }, 236 | "node_modules/normalize-url": { 237 | "version": "6.1.0", 238 | "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", 239 | "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", 240 | "engines": { 241 | "node": ">=10" 242 | }, 243 | "funding": { 244 | "url": "https://github.com/sponsors/sindresorhus" 245 | } 246 | }, 247 | "node_modules/once": { 248 | "version": "1.4.0", 249 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 250 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 251 | "dependencies": { 252 | "wrappy": "1" 253 | } 254 | }, 255 | "node_modules/p-cancelable": { 256 | "version": "2.1.1", 257 | "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", 258 | "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", 259 | "engines": { 260 | "node": ">=8" 261 | } 262 | }, 263 | "node_modules/pump": { 264 | "version": "3.0.0", 265 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 266 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 267 | "dependencies": { 268 | "end-of-stream": "^1.1.0", 269 | "once": "^1.3.1" 270 | } 271 | }, 272 | "node_modules/quick-lru": { 273 | "version": "5.1.1", 274 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", 275 | "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==", 276 | "engines": { 277 | "node": ">=10" 278 | }, 279 | "funding": { 280 | "url": "https://github.com/sponsors/sindresorhus" 281 | } 282 | }, 283 | "node_modules/resolve-alpn": { 284 | "version": "1.2.1", 285 | "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", 286 | "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" 287 | }, 288 | "node_modules/responselike": { 289 | "version": "2.0.0", 290 | "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", 291 | "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", 292 | "dependencies": { 293 | "lowercase-keys": "^2.0.0" 294 | } 295 | }, 296 | "node_modules/wrappy": { 297 | "version": "1.0.2", 298 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 299 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 300 | }, 301 | "node_modules/ws": { 302 | "version": "7.5.6", 303 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", 304 | "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", 305 | "engines": { 306 | "node": ">=8.3.0" 307 | }, 308 | "peerDependencies": { 309 | "bufferutil": "^4.0.1", 310 | "utf-8-validate": "^5.0.2" 311 | }, 312 | "peerDependenciesMeta": { 313 | "bufferutil": { 314 | "optional": true 315 | }, 316 | "utf-8-validate": { 317 | "optional": true 318 | } 319 | } 320 | } 321 | }, 322 | "dependencies": { 323 | "@sindresorhus/is": { 324 | "version": "4.2.0", 325 | "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-4.2.0.tgz", 326 | "integrity": "sha512-VkE3KLBmJwcCaVARtQpfuKcKv8gcBmUubrfHGF84dXuuW6jgsRYxPtzcIhPyK9WAPpRt2/xY6zkD9MnRaJzSyw==" 327 | }, 328 | "@szmarczak/http-timer": { 329 | "version": "4.0.6", 330 | "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", 331 | "integrity": "sha512-4BAffykYOgO+5nzBWYwE3W90sBgLJoUPRWWcL8wlyiM8IB8ipJz3UMJ9KXQd1RKQXpKp8Tutn80HZtWsu2u76w==", 332 | "requires": { 333 | "defer-to-connect": "^2.0.0" 334 | } 335 | }, 336 | "@types/cacheable-request": { 337 | "version": "6.0.2", 338 | "resolved": "https://registry.npmjs.org/@types/cacheable-request/-/cacheable-request-6.0.2.tgz", 339 | "integrity": "sha512-B3xVo+dlKM6nnKTcmm5ZtY/OL8bOAOd2Olee9M1zft65ox50OzjEHW91sDiU9j6cvW8Ejg1/Qkf4xd2kugApUA==", 340 | "requires": { 341 | "@types/http-cache-semantics": "*", 342 | "@types/keyv": "*", 343 | "@types/node": "*", 344 | "@types/responselike": "*" 345 | } 346 | }, 347 | "@types/http-cache-semantics": { 348 | "version": "4.0.1", 349 | "resolved": "https://registry.npmjs.org/@types/http-cache-semantics/-/http-cache-semantics-4.0.1.tgz", 350 | "integrity": "sha512-SZs7ekbP8CN0txVG2xVRH6EgKmEm31BOxA07vkFaETzZz1xh+cbt8BcI0slpymvwhx5dlFnQG2rTlPVQn+iRPQ==" 351 | }, 352 | "@types/keyv": { 353 | "version": "3.1.3", 354 | "resolved": "https://registry.npmjs.org/@types/keyv/-/keyv-3.1.3.tgz", 355 | "integrity": "sha512-FXCJgyyN3ivVgRoml4h94G/p3kY+u/B86La+QptcqJaWtBWtmc6TtkNfS40n9bIvyLteHh7zXOtgbobORKPbDg==", 356 | "requires": { 357 | "@types/node": "*" 358 | } 359 | }, 360 | "@types/node": { 361 | "version": "16.11.12", 362 | "resolved": "https://registry.npmjs.org/@types/node/-/node-16.11.12.tgz", 363 | "integrity": "sha512-+2Iggwg7PxoO5Kyhvsq9VarmPbIelXP070HMImEpbtGCoyWNINQj4wzjbQCXzdHTRXnqufutJb5KAURZANNBAw==" 364 | }, 365 | "@types/responselike": { 366 | "version": "1.0.0", 367 | "resolved": "https://registry.npmjs.org/@types/responselike/-/responselike-1.0.0.tgz", 368 | "integrity": "sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==", 369 | "requires": { 370 | "@types/node": "*" 371 | } 372 | }, 373 | "cacheable-lookup": { 374 | "version": "5.0.4", 375 | "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-5.0.4.tgz", 376 | "integrity": "sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==" 377 | }, 378 | "cacheable-request": { 379 | "version": "7.0.2", 380 | "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.2.tgz", 381 | "integrity": "sha512-pouW8/FmiPQbuGpkXQ9BAPv/Mo5xDGANgSNXzTzJ8DrKGuXOssM4wIQRjfanNRh3Yu5cfYPvcorqbhg2KIJtew==", 382 | "requires": { 383 | "clone-response": "^1.0.2", 384 | "get-stream": "^5.1.0", 385 | "http-cache-semantics": "^4.0.0", 386 | "keyv": "^4.0.0", 387 | "lowercase-keys": "^2.0.0", 388 | "normalize-url": "^6.0.1", 389 | "responselike": "^2.0.0" 390 | } 391 | }, 392 | "clone-response": { 393 | "version": "1.0.2", 394 | "resolved": "https://registry.npmjs.org/clone-response/-/clone-response-1.0.2.tgz", 395 | "integrity": "sha1-0dyXOSAxTfZ/vrlCI7TuNQI56Ws=", 396 | "requires": { 397 | "mimic-response": "^1.0.0" 398 | } 399 | }, 400 | "decompress-response": { 401 | "version": "6.0.0", 402 | "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", 403 | "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", 404 | "requires": { 405 | "mimic-response": "^3.1.0" 406 | }, 407 | "dependencies": { 408 | "mimic-response": { 409 | "version": "3.1.0", 410 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", 411 | "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==" 412 | } 413 | } 414 | }, 415 | "defer-to-connect": { 416 | "version": "2.0.1", 417 | "resolved": "https://registry.npmjs.org/defer-to-connect/-/defer-to-connect-2.0.1.tgz", 418 | "integrity": "sha512-4tvttepXG1VaYGrRibk5EwJd1t4udunSOVMdLSAL6mId1ix438oPwPZMALY41FCijukO1L0twNcGsdzS7dHgDg==" 419 | }, 420 | "end-of-stream": { 421 | "version": "1.4.4", 422 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", 423 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", 424 | "requires": { 425 | "once": "^1.4.0" 426 | } 427 | }, 428 | "get-stream": { 429 | "version": "5.2.0", 430 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", 431 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", 432 | "requires": { 433 | "pump": "^3.0.0" 434 | } 435 | }, 436 | "got": { 437 | "version": "11.8.6", 438 | "resolved": "https://registry.npmjs.org/got/-/got-11.8.6.tgz", 439 | "integrity": "sha512-6tfZ91bOr7bOXnK7PRDCGBLa1H4U080YHNaAQ2KsMGlLEzRbk44nsZF2E1IeRc3vtJHPVbKCYgdFbaGO2ljd8g==", 440 | "requires": { 441 | "@sindresorhus/is": "^4.0.0", 442 | "@szmarczak/http-timer": "^4.0.5", 443 | "@types/cacheable-request": "^6.0.1", 444 | "@types/responselike": "^1.0.0", 445 | "cacheable-lookup": "^5.0.3", 446 | "cacheable-request": "^7.0.2", 447 | "decompress-response": "^6.0.0", 448 | "http2-wrapper": "^1.0.0-beta.5.2", 449 | "lowercase-keys": "^2.0.0", 450 | "p-cancelable": "^2.0.0", 451 | "responselike": "^2.0.0" 452 | } 453 | }, 454 | "http-cache-semantics": { 455 | "version": "4.1.1", 456 | "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", 457 | "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" 458 | }, 459 | "http2-wrapper": { 460 | "version": "1.0.3", 461 | "resolved": "https://registry.npmjs.org/http2-wrapper/-/http2-wrapper-1.0.3.tgz", 462 | "integrity": "sha512-V+23sDMr12Wnz7iTcDeJr3O6AIxlnvT/bmaAAAP/Xda35C90p9599p0F1eHR/N1KILWSoWVAiOMFjBBXaXSMxg==", 463 | "requires": { 464 | "quick-lru": "^5.1.1", 465 | "resolve-alpn": "^1.0.0" 466 | } 467 | }, 468 | "json-buffer": { 469 | "version": "3.0.1", 470 | "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 471 | "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" 472 | }, 473 | "keyv": { 474 | "version": "4.0.4", 475 | "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.0.4.tgz", 476 | "integrity": "sha512-vqNHbAc8BBsxk+7QBYLW0Y219rWcClspR6WSeoHYKG5mnsSoOH+BL1pWq02DDCVdvvuUny5rkBlzMRzoqc+GIg==", 477 | "requires": { 478 | "json-buffer": "3.0.1" 479 | } 480 | }, 481 | "lowercase-keys": { 482 | "version": "2.0.0", 483 | "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", 484 | "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==" 485 | }, 486 | "mimic-response": { 487 | "version": "1.0.1", 488 | "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-1.0.1.tgz", 489 | "integrity": "sha512-j5EctnkH7amfV/q5Hgmoal1g2QHFJRraOtmx0JpIqkxhBhI/lJSl1nMpQ45hVarwNETOoWEimndZ4QK0RHxuxQ==" 490 | }, 491 | "normalize-url": { 492 | "version": "6.1.0", 493 | "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", 494 | "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==" 495 | }, 496 | "once": { 497 | "version": "1.4.0", 498 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 499 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 500 | "requires": { 501 | "wrappy": "1" 502 | } 503 | }, 504 | "p-cancelable": { 505 | "version": "2.1.1", 506 | "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", 507 | "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==" 508 | }, 509 | "pump": { 510 | "version": "3.0.0", 511 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", 512 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", 513 | "requires": { 514 | "end-of-stream": "^1.1.0", 515 | "once": "^1.3.1" 516 | } 517 | }, 518 | "quick-lru": { 519 | "version": "5.1.1", 520 | "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-5.1.1.tgz", 521 | "integrity": "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA==" 522 | }, 523 | "resolve-alpn": { 524 | "version": "1.2.1", 525 | "resolved": "https://registry.npmjs.org/resolve-alpn/-/resolve-alpn-1.2.1.tgz", 526 | "integrity": "sha512-0a1F4l73/ZFZOakJnQ3FvkJ2+gSTQWz/r2KE5OdDY0TxPm5h4GkqkWWfM47T7HsbnOtcJVEF4epCVy6u7Q3K+g==" 527 | }, 528 | "responselike": { 529 | "version": "2.0.0", 530 | "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.0.tgz", 531 | "integrity": "sha512-xH48u3FTB9VsZw7R+vvgaKeLKzT6jOogbQhEe/jewwnZgzPcnyWui2Av6JpoYZF/91uueC+lqhWqeURw5/qhCw==", 532 | "requires": { 533 | "lowercase-keys": "^2.0.0" 534 | } 535 | }, 536 | "wrappy": { 537 | "version": "1.0.2", 538 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 539 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" 540 | }, 541 | "ws": { 542 | "version": "7.5.6", 543 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz", 544 | "integrity": "sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==", 545 | "requires": {} 546 | } 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "exaroton", 3 | "version": "1.11.3", 4 | "description": "exaroton API client", 5 | "homepage": "https://exaroton.com", 6 | "main": "index.js", 7 | "repository": "github:exaroton/node-exaroton-api", 8 | "author": "Matthias Neid", 9 | "license": "MIT", 10 | "engines": { 11 | "node": ">=12.0.0" 12 | }, 13 | "dependencies": { 14 | "got": "^11.8.2", 15 | "ws": "^7.4.5" 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /src/Account/Account.js: -------------------------------------------------------------------------------- 1 | const GetAccountRequest = require('../Request/Account/GetAccountRequest'); 2 | 3 | class Account { 4 | /** 5 | * @type {Client} 6 | * @private 7 | */ 8 | #client; 9 | 10 | /** 11 | * Username 12 | * 13 | * @type {string} 14 | */ 15 | name; 16 | 17 | /** 18 | * Email address 19 | * 20 | * @type {string} 21 | */ 22 | email; 23 | 24 | /** 25 | * Email address verification 26 | * 27 | * @type {boolean} 28 | */ 29 | verified; 30 | 31 | /** 32 | * The amount of credits currently available 33 | * 34 | * @type {int} 35 | */ 36 | credits; 37 | 38 | /** 39 | * Account constructor 40 | * 41 | * @param {Client} client 42 | */ 43 | constructor(client) { 44 | this.#client = client; 45 | } 46 | 47 | /** 48 | * Get/update the account info 49 | * 50 | * @return {this} 51 | * @throws {RequestError} 52 | */ 53 | async get() { 54 | let response = await this.#client.request(new GetAccountRequest()); 55 | this.setFromObject(response.getData()); 56 | return this; 57 | } 58 | 59 | /** 60 | * Map raw object to this instance 61 | * 62 | * @param {{}} account 63 | * @return {this} 64 | */ 65 | setFromObject(account) { 66 | this.name = typeof account.name !== "undefined" ? account.name : null; 67 | this.email = typeof account.email !== "undefined" ? account.email : null; 68 | this.verified = typeof account.verified !== "undefined" ? account.verified : null; 69 | this.credits = typeof account.credits !== "undefined" ? account.credits : null; 70 | return this; 71 | } 72 | } 73 | 74 | module.exports = Account; -------------------------------------------------------------------------------- /src/Billing/Pool/Pool.js: -------------------------------------------------------------------------------- 1 | const GetPoolRequest = require("../../Request/Billing/Pool/GetPoolRequest.js"); 2 | const GetPoolMembersRequest = require("../../Request/Billing/Pool/GetPoolMembersRequest.js"); 3 | const GetPoolServersRequest = require("../../Request/Billing/Pool/GetPoolServersRequest.js"); 4 | 5 | class Pool { 6 | /** 7 | * @type {Client} 8 | * @private 9 | */ 10 | #client; 11 | 12 | /** 13 | * Pool ID 14 | * 15 | * @type {string} 16 | */ 17 | id; 18 | 19 | /** 20 | * Pool name 21 | * 22 | * @type {string} 23 | */ 24 | name; 25 | 26 | /** 27 | * Pool credit balance 28 | * 29 | * @type {number} 30 | */ 31 | credits; 32 | 33 | /** 34 | * Pool server count 35 | * 36 | * @type {number} 37 | */ 38 | servers; 39 | 40 | /** 41 | * Pool owner ID 42 | * 43 | * @type {string} 44 | */ 45 | owner; 46 | 47 | /** 48 | * Is pool owner 49 | * 50 | * @type {boolean} 51 | */ 52 | isOwner; 53 | 54 | /** 55 | * Pool member count 56 | * 57 | * @type {number} 58 | */ 59 | members; 60 | 61 | /** 62 | * Share of this pool owned by the current account 63 | * 64 | * @type {number} 65 | */ 66 | ownShare; 67 | 68 | /** 69 | * Credits in this pool owned by the current account 70 | * 71 | * @type {number} 72 | */ 73 | ownCredits; 74 | 75 | /** 76 | * Pool constructor 77 | * 78 | * @param {Client} client 79 | * @param {string} id 80 | */ 81 | constructor(client, id) { 82 | this.#client = client; 83 | this.id = id; 84 | } 85 | 86 | /** 87 | * @param {{}} poolObject 88 | * @return {this} 89 | */ 90 | setFromObject(poolObject) { 91 | this.id = typeof poolObject.id !== "undefined" ? poolObject.id : null; 92 | this.name = typeof poolObject.name !== "undefined" ? poolObject.name : null; 93 | this.credits = typeof poolObject.credits !== "undefined" ? poolObject.credits : null; 94 | this.servers = typeof poolObject.servers !== "undefined" ? poolObject.servers : null; 95 | this.owner = typeof poolObject.owner !== "undefined" ? poolObject.owner : null; 96 | this.isOwner = typeof poolObject.isOwner !== "undefined" ? poolObject.isOwner : null; 97 | this.members = typeof poolObject.members !== "undefined" ? poolObject.members : null; 98 | this.ownShare = typeof poolObject.ownShare !== "undefined" ? poolObject.ownShare : null; 99 | this.ownCredits = typeof poolObject.ownCredits !== "undefined" ? poolObject.ownCredits : null; 100 | 101 | return this; 102 | } 103 | 104 | /** 105 | * Get credit pool info 106 | * 107 | * @return {this} 108 | * @throws {RequestError} 109 | */ 110 | async get() { 111 | let response = await this.#client.request(new GetPoolRequest(this.id)); 112 | this.setFromObject(response.getData()); 113 | return this; 114 | } 115 | 116 | /** 117 | * Get pool members 118 | * 119 | * @return {Promise} 120 | */ 121 | async getMembers() { 122 | let response = await this.#client.request(new GetPoolMembersRequest(this.id)); 123 | return response.getData(); 124 | } 125 | 126 | /** 127 | * Get pool servers 128 | * 129 | * @return {Promise} 130 | */ 131 | async getServers() { 132 | let response = await this.#client.request(new GetPoolServersRequest(this.id)); 133 | return response.getData(); 134 | } 135 | } 136 | 137 | module.exports = Pool; 138 | -------------------------------------------------------------------------------- /src/Billing/Pool/PoolMember.js: -------------------------------------------------------------------------------- 1 | 2 | class PoolMember { 3 | /** 4 | * Pool member account ID 5 | * 6 | * @type {string} 7 | */ 8 | account; 9 | 10 | /** 11 | * Pool member name 12 | * 13 | * @type {string} 14 | */ 15 | name; 16 | 17 | /** 18 | * Pool member share 19 | * 20 | * @type {number} 21 | */ 22 | share; 23 | 24 | /** 25 | * Pool member credits 26 | * 27 | * @type {number} 28 | */ 29 | credits; 30 | 31 | /** 32 | * Is pool owner 33 | * 34 | * @type {boolean} 35 | */ 36 | isOwner; 37 | 38 | /** 39 | * Pool member constructor 40 | * 41 | * @param {{}} poolMemberObject 42 | */ 43 | constructor(poolMemberObject) { 44 | this.account = typeof poolMemberObject.account !== "undefined" ? poolMemberObject.account : null; 45 | this.name = typeof poolMemberObject.name !== "undefined" ? poolMemberObject.name : null; 46 | this.share = typeof poolMemberObject.share !== "undefined" ? poolMemberObject.share : null; 47 | this.credits = typeof poolMemberObject.credits !== "undefined" ? poolMemberObject.credits : null; 48 | this.isOwner = typeof poolMemberObject.isOwner !== "undefined" ? poolMemberObject.isOwner : null; 49 | } 50 | } 51 | 52 | module.exports = PoolMember; 53 | -------------------------------------------------------------------------------- /src/Client.js: -------------------------------------------------------------------------------- 1 | const got = require('got'); 2 | const fs = require("fs").promises; 3 | 4 | const Server = require('./Server/Server'); 5 | const Account = require('./Account/Account'); 6 | const RequestStatusError = require('./Error/RequestStatusError'); 7 | const RequestBodyError = require('./Error/RequestBodyError'); 8 | const GetServersRequest = require('./Request/GetServersRequest'); 9 | const GetPoolsRequest = require('./Request/Billing/Pool/GetPoolsRequest'); 10 | 11 | const packageConfig = require('../package.json'); 12 | const Pool = require("./Billing/Pool/Pool.js"); 13 | 14 | class Client { 15 | /** 16 | * @type {string} 17 | */ 18 | protocol = "https"; 19 | 20 | /** 21 | * @type {string} 22 | */ 23 | host = "api.exaroton.com"; 24 | 25 | /** 26 | * @type {string} 27 | */ 28 | basePath = "/v1/"; 29 | 30 | /** 31 | * API base URL used for all requests 32 | * 33 | * @type {string} 34 | */ 35 | get baseURL() { 36 | return this.protocol + "://" + this.host + this.basePath 37 | } 38 | 39 | /** 40 | * API token used for authentication 41 | * 42 | * @type {string|null} 43 | * @private 44 | */ 45 | #apiToken = null; 46 | 47 | /** 48 | * User agent sent with all requests 49 | * 50 | * @type {string} 51 | * @private 52 | */ 53 | #userAgent = "node-exaroton-api@" + packageConfig.version; 54 | 55 | /** 56 | * Client constructor 57 | * 58 | * @param {string} apiToken string API token, create one here: https://exaroton.com/account/ 59 | */ 60 | constructor(apiToken) { 61 | this.setAPIToken(apiToken); 62 | } 63 | 64 | /** 65 | * Set the API token 66 | * 67 | * @param {string} apiToken 68 | * @return {this} 69 | */ 70 | setAPIToken(apiToken) { 71 | if (typeof apiToken !== "string") { 72 | throw new TypeError("Invalid API token, expected string, but got " + typeof apiToken); 73 | } 74 | 75 | this.#apiToken = apiToken; 76 | return this; 77 | } 78 | 79 | /** 80 | * @return {string} 81 | */ 82 | getAPIToken() { 83 | return this.#apiToken; 84 | } 85 | 86 | /** 87 | * Set the user agent 88 | * 89 | * @param {string} userAgent 90 | * @return {this} 91 | */ 92 | setUserAgent(userAgent) { 93 | if (typeof userAgent !== "string") { 94 | throw new TypeError("Invalid user agent, expected string, but got " + typeof userAgent); 95 | } 96 | 97 | this.#userAgent = userAgent; 98 | return this; 99 | } 100 | 101 | /** 102 | * Send a {Request} to the API and get a {Response} 103 | * 104 | * @param {Request} request 105 | * @return {Promise} 106 | * @throws {RequestError} 107 | */ 108 | async request(request) { 109 | request.client = this; 110 | const url = this.baseURL + request.getEndpoint(); 111 | 112 | let gotOptions = { 113 | method: request.method, 114 | retry: 0, 115 | responseType: request.responseType 116 | }; 117 | 118 | if (request.hasBody()) { 119 | gotOptions.body = request.getBody(); 120 | } 121 | 122 | gotOptions.headers = Object.assign({ 123 | "authorization": "Bearer " + this.#apiToken, 124 | "user-agent": this.#userAgent 125 | }, request.headers); 126 | 127 | let response; 128 | try { 129 | if (request.hasOutputStream()) { 130 | await this.streamResponse(url, gotOptions, request.getOutputStream()); 131 | return request.createResponse(); 132 | } else { 133 | response = await got(url, gotOptions); 134 | } 135 | } catch (e) { 136 | if (request.outputPath !== null) { 137 | try { 138 | await fs.unlink(request.outputPath); 139 | } catch (e) { 140 | // ignore 141 | } 142 | } 143 | throw new RequestStatusError(e); 144 | } 145 | 146 | if (!request.expectsJsonResponse() || response.body.success) { 147 | return request.createResponse(response.body); 148 | } else { 149 | throw new RequestBodyError(response); 150 | } 151 | } 152 | 153 | /** 154 | * @param {string} url 155 | * @param {{}} gotOptions 156 | * @param {stream.Writable} outputStream 157 | * @return {Promise} 158 | */ 159 | streamResponse(url, gotOptions, outputStream) { 160 | return new Promise((resolve, reject) => { 161 | let stream = got.stream(url, gotOptions); 162 | stream.pipe(outputStream); 163 | stream.on("error", async (error) => { 164 | reject(error); 165 | }); 166 | stream.on("end", resolve); 167 | }); 168 | } 169 | 170 | /** 171 | * Get a list of all servers 172 | * 173 | * @return {Promise} 174 | * @throws {RequestError} 175 | */ 176 | async getServers() { 177 | return (await this.request(new GetServersRequest)).getData(); 178 | } 179 | 180 | /** 181 | * Get a list of all credit pools 182 | * 183 | * @return {Promise} 184 | * @throws {RequestError} 185 | */ 186 | async getPools() { 187 | return (await this.request(new GetPoolsRequest)).getData(); 188 | } 189 | 190 | /** 191 | * Get account info for the current account 192 | * 193 | * @throws {RequestError} 194 | * @returns {Promise} 195 | */ 196 | async getAccount() { 197 | return (await new Account(this).get()); 198 | } 199 | 200 | /** 201 | * Initialize a new server object 202 | * 203 | * @param {string} id 204 | * @return {Server} 205 | */ 206 | server(id) { 207 | return new Server(this, id); 208 | } 209 | 210 | /** 211 | * Initialize a new pool object 212 | * 213 | * @param {string} id 214 | * @return {Pool} 215 | */ 216 | pool(id) { 217 | return new Pool(this, id); 218 | } 219 | } 220 | 221 | module.exports = Client; 222 | -------------------------------------------------------------------------------- /src/Error/RequestBodyError.js: -------------------------------------------------------------------------------- 1 | const RequestError = require('./RequestError'); 2 | 3 | class RequestBodyError extends RequestError { 4 | constructor(response) { 5 | super(); 6 | this.setErrorFromResponseBody(response); 7 | } 8 | } 9 | 10 | module.exports = RequestBodyError; -------------------------------------------------------------------------------- /src/Error/RequestError.js: -------------------------------------------------------------------------------- 1 | class RequestError extends Error { 2 | statusCode; 3 | error; 4 | response; 5 | 6 | /** 7 | * Set error and status code from response object 8 | * 9 | * Returns if an error message was found 10 | * 11 | * @param {{}} response 12 | * @return {boolean} 13 | */ 14 | setErrorFromResponseBody(response) { 15 | this.response = response; 16 | this.statusCode = typeof response.statusCode === "number" ? response.statusCode : 0; 17 | if (typeof response.body === "object" && typeof response.body.success === "boolean" && !response.body.success && typeof response.body.error === "string") { 18 | this.message = response.body.error; 19 | return true; 20 | } 21 | return false; 22 | } 23 | } 24 | 25 | module.exports = RequestError; -------------------------------------------------------------------------------- /src/Error/RequestStatusError.js: -------------------------------------------------------------------------------- 1 | const RequestError = require('./RequestError'); 2 | 3 | class RequestStatusError extends RequestError { 4 | constructor(error) { 5 | super(); 6 | if (!error.response || !this.setErrorFromResponseBody(error.response)) { 7 | this.message = error.toString(); 8 | } 9 | } 10 | } 11 | 12 | module.exports = RequestStatusError; -------------------------------------------------------------------------------- /src/Request/Account/GetAccountRequest.js: -------------------------------------------------------------------------------- 1 | const Request = require('../Request'); 2 | 3 | class GetAccountRequest extends Request { 4 | endpoint = "account/"; 5 | } 6 | 7 | module.exports = GetAccountRequest; -------------------------------------------------------------------------------- /src/Request/Billing/Pool/GetPoolMembersRequest.js: -------------------------------------------------------------------------------- 1 | const PoolRequest = require("./PoolRequest.js"); 2 | const PoolMembersResponse = require("../../../Response/PoolMembersResponse.js"); 3 | 4 | class GetPoolMembersRequest extends PoolRequest { 5 | endpoint = "billing/pools/{id}/members"; 6 | responseClass = PoolMembersResponse; 7 | } 8 | 9 | module.exports = GetPoolMembersRequest; 10 | -------------------------------------------------------------------------------- /src/Request/Billing/Pool/GetPoolRequest.js: -------------------------------------------------------------------------------- 1 | const PoolRequest = require("./PoolRequest.js"); 2 | 3 | class GetPoolRequest extends PoolRequest { 4 | endpoint = "billing/pools/{id}"; 5 | } 6 | 7 | module.exports = GetPoolRequest; 8 | -------------------------------------------------------------------------------- /src/Request/Billing/Pool/GetPoolServersRequest.js: -------------------------------------------------------------------------------- 1 | const PoolRequest = require("./PoolRequest.js"); 2 | const ServersResponse = require("../../../Response/ServersResponse.js"); 3 | 4 | class GetPoolServersRequest extends PoolRequest { 5 | endpoint = "billing/pools/{id}/servers"; 6 | responseClass = ServersResponse; 7 | } 8 | 9 | module.exports = GetPoolServersRequest; 10 | -------------------------------------------------------------------------------- /src/Request/Billing/Pool/GetPoolsRequest.js: -------------------------------------------------------------------------------- 1 | const Request = require('../../Request'); 2 | const PoolsResponse = require("../../../Response/PoolsResponse.js"); 3 | 4 | class GetPoolsRequest extends Request { 5 | endpoint = "billing/pools"; 6 | responseClass = PoolsResponse; 7 | } 8 | 9 | module.exports = GetPoolsRequest; 10 | -------------------------------------------------------------------------------- /src/Request/Billing/Pool/PoolRequest.js: -------------------------------------------------------------------------------- 1 | const Request = require('../../Request.js'); 2 | 3 | class PoolRequest extends Request { 4 | /** 5 | * Pool request constructor 6 | * 7 | * @param {string} id 8 | */ 9 | constructor(id) { 10 | super(); 11 | this.setParameter("id", id); 12 | } 13 | } 14 | 15 | module.exports = PoolRequest; 16 | -------------------------------------------------------------------------------- /src/Request/GetServersRequest.js: -------------------------------------------------------------------------------- 1 | const Request = require('./Request'); 2 | const ServersResponse = require('../Response/ServersResponse'); 3 | 4 | class GetServersRequest extends Request { 5 | endpoint = "servers"; 6 | responseClass = ServersResponse; 7 | 8 | } 9 | 10 | module.exports = GetServersRequest; -------------------------------------------------------------------------------- /src/Request/Request.js: -------------------------------------------------------------------------------- 1 | const {createReadStream} = require('fs'); 2 | const {createWriteStream} = require("fs"); 3 | 4 | const Response = require('../Response/Response'); 5 | 6 | class Request { 7 | /** 8 | * Request method, e.g. "GET" or "POST" 9 | * 10 | * @type {string} 11 | */ 12 | method = "GET"; 13 | 14 | /** 15 | * Endpoint URL, without base, version or starting / 16 | */ 17 | endpoint; 18 | 19 | /** 20 | * URL parameters, which are replaced in the endpoint string 21 | * 22 | * @type {{}} 23 | */ 24 | parameters = {}; 25 | 26 | /** 27 | * HTTP request headers 28 | * 29 | * @type {{}} 30 | */ 31 | headers = {}; 32 | 33 | /** 34 | * Post body data 35 | * 36 | * @type {null|{}|string} 37 | */ 38 | data = null; 39 | 40 | /** 41 | * Response class used to create/parse responses to this request 42 | * 43 | * @type {Response} 44 | */ 45 | responseClass = Response; 46 | 47 | /** 48 | * Response type (text|json|buffer) 49 | * 50 | * https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#responsetype 51 | * 52 | * @type {string} 53 | */ 54 | responseType = "json"; 55 | 56 | /** 57 | * Optional path to write the response body to 58 | * 59 | * @type {string|null} 60 | */ 61 | outputPath = null; 62 | 63 | /** 64 | * Optional stream to stream the response body to 65 | * 66 | * @type {stream.Writable|null} 67 | */ 68 | outputStream = null; 69 | 70 | /** 71 | * Optional path to read the request body from 72 | * 73 | * @type {string|null} 74 | */ 75 | inputPath = null; 76 | 77 | /** 78 | * Optional stream to read the request body from 79 | * 80 | * @type {stream.Readable|null} 81 | */ 82 | inputStream = null; 83 | 84 | /** 85 | * Client that has executed this request 86 | * 87 | * @type {Client} 88 | */ 89 | client; 90 | 91 | /** 92 | * Set a URL parameter 93 | * 94 | * URL parameters replace {key} variables in the endpoint URL 95 | * 96 | * @param {string} key 97 | * @param {string} value 98 | */ 99 | setParameter(key, value) { 100 | this.parameters[key] = value; 101 | } 102 | 103 | /** 104 | * 105 | * @param {string} key 106 | * @param {string} value 107 | */ 108 | setHeader(key, value) { 109 | this.headers[key.toLowerCase()] = value; 110 | } 111 | 112 | /** 113 | * Get endpoint with replaced parameters 114 | * 115 | * @return {string} 116 | */ 117 | getEndpoint() { 118 | let endpoint = this.endpoint; 119 | for (let key in this.parameters) { 120 | if (!this.parameters.hasOwnProperty(key)) { 121 | continue; 122 | } 123 | endpoint = endpoint.replace("{" + key + "}", this.parameters[key]); 124 | } 125 | return endpoint; 126 | } 127 | 128 | /** 129 | * Check if the request has a body 130 | * 131 | * @return {boolean} 132 | */ 133 | hasBody() { 134 | return ["POST", "PUT", "DELETE"].includes(this.method) && (this.data !== null || this.inputPath !== null || this.inputStream !== null); 135 | } 136 | 137 | /** 138 | * Get body for request 139 | * 140 | * @return {FormData|string|ReadStream} 141 | */ 142 | getBody() { 143 | if (this.hasInputStream()) { 144 | return this.getInputStream(); 145 | } 146 | 147 | if (typeof this.data === "string") { 148 | return this.data; 149 | } 150 | 151 | this.setHeader("content-type", "application/json"); 152 | return JSON.stringify(this.data); 153 | } 154 | 155 | /** 156 | * Create a response object for this request 157 | * 158 | * @param {{}|string|null} body 159 | * @return {Response} 160 | */ 161 | createResponse(body = null) { 162 | let response = new (this.responseClass)(this); 163 | response.setBody(body); 164 | return response; 165 | } 166 | 167 | /** 168 | * @return {boolean} 169 | */ 170 | expectsJsonResponse() { 171 | return this.responseType === "json"; 172 | } 173 | 174 | /** 175 | * @return {null|stream.Writable} 176 | */ 177 | getOutputStream() { 178 | if (this.outputStream !== null) { 179 | return this.outputStream; 180 | } 181 | if (this.outputPath !== null) { 182 | return createWriteStream(this.outputPath); 183 | } 184 | return null; 185 | } 186 | 187 | /** 188 | * @return {boolean} 189 | */ 190 | hasOutputStream() { 191 | return this.outputStream !== null || this.outputPath !== null; 192 | } 193 | 194 | /** 195 | * @return {null|stream.Readable} 196 | */ 197 | getInputStream() { 198 | if (this.inputStream !== null) { 199 | return this.inputStream; 200 | } 201 | if (this.inputPath !== null) { 202 | return createReadStream(this.inputPath); 203 | } 204 | return null; 205 | } 206 | 207 | /** 208 | * @return {boolean} 209 | */ 210 | hasInputStream() { 211 | return this.inputStream !== null || this.inputPath !== null; 212 | } 213 | 214 | /** 215 | * Set the data to put as string 216 | * 217 | * @param {string|{}} data 218 | * @return {this} 219 | */ 220 | setData(data) { 221 | this.data = data; 222 | return this; 223 | } 224 | 225 | /** 226 | * Set a file as input file for the request body 227 | * 228 | * @param {string} inputPath 229 | * @return {this} 230 | */ 231 | setInputPath(inputPath) { 232 | this.inputPath = inputPath; 233 | return this; 234 | } 235 | 236 | /** 237 | * Set a file as output file for the response body 238 | * 239 | * @param {string} outputPath 240 | * @return {this} 241 | */ 242 | setOutputPath(outputPath) { 243 | this.outputPath = outputPath; 244 | return this; 245 | } 246 | 247 | /** 248 | * Set a stream as input stream for the request body 249 | * 250 | * @param {stream.Readable} inputStream 251 | * @return {this} 252 | */ 253 | setInputStream(inputStream) { 254 | this.inputStream = inputStream; 255 | return this; 256 | } 257 | 258 | /** 259 | * Set a stream as output stream for the response body 260 | * 261 | * @param {stream.Writable} outputStream 262 | * @return {this} 263 | */ 264 | setOutputStream(outputStream) { 265 | this.outputStream = outputStream; 266 | return this; 267 | } 268 | } 269 | 270 | module.exports = Request; -------------------------------------------------------------------------------- /src/Request/Server/ExecuteServerCommandRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class ExecuteServerCommandRequest extends ServerRequest { 4 | endpoint = "servers/{id}/command"; 5 | method = "POST"; 6 | 7 | /** 8 | * Server request constructor 9 | * 10 | * @param {string} id 11 | * @param {string} command 12 | */ 13 | constructor(id, command) { 14 | super(id); 15 | 16 | this.data = {command: command}; 17 | } 18 | } 19 | 20 | module.exports = ExecuteServerCommandRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/Config/GetConfigFileOptionsRequest.js: -------------------------------------------------------------------------------- 1 | const FileRequest = require("../FileRequest"); 2 | 3 | class GetConfigFileOptionsRequest extends FileRequest { 4 | endpoint = "servers/{id}/files/config/{path}"; 5 | } 6 | 7 | module.exports = GetConfigFileOptionsRequest; 8 | -------------------------------------------------------------------------------- /src/Request/Server/Files/Config/UpdateConfigFileOptionsRequest.js: -------------------------------------------------------------------------------- 1 | const FileRequest = require("../FileRequest"); 2 | 3 | class UpdateConfigFileOptionsRequest extends FileRequest { 4 | endpoint = "servers/{id}/files/config/{path}"; 5 | method = "POST"; 6 | 7 | /** 8 | * UpdateConfigFileOptionsRequest constructor 9 | * 10 | * @param {string} id 11 | * @param {string} path 12 | * @param {Object.} options 13 | */ 14 | constructor(id, path, options) { 15 | super(id, path); 16 | 17 | this.data = options; 18 | } 19 | } 20 | 21 | module.exports = UpdateConfigFileOptionsRequest; 22 | -------------------------------------------------------------------------------- /src/Request/Server/Files/CreateDirectoryRequest.js: -------------------------------------------------------------------------------- 1 | const PutFileDataRequest = require("./PutFileDataRequest"); 2 | 3 | class CreateDirectoryRequest extends PutFileDataRequest { 4 | headers = { 5 | "Content-Type": "inode/directory" 6 | } 7 | } 8 | 9 | module.exports = CreateDirectoryRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/DeleteFileDataRequest.js: -------------------------------------------------------------------------------- 1 | const FileDataRequest = require("./FileDataRequest"); 2 | 3 | class DeleteFileDataRequest extends FileDataRequest { 4 | method = "DELETE"; 5 | } 6 | 7 | module.exports = DeleteFileDataRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/FileDataRequest.js: -------------------------------------------------------------------------------- 1 | const FileRequest = require("./FileRequest"); 2 | 3 | class FileDataRequest extends FileRequest { 4 | endpoint = "servers/{id}/files/data/{path}"; 5 | } 6 | 7 | module.exports = FileDataRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/FileRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('../ServerRequest'); 2 | 3 | class FileRequest extends ServerRequest { 4 | /** 5 | * FileRequest constructor 6 | * 7 | * @param {string} id 8 | * @param {string} path 9 | */ 10 | constructor(id, path) { 11 | super(id); 12 | this.setPath(path); 13 | } 14 | 15 | /** 16 | * Set the path parameter and url encode all characters except slashes 17 | * 18 | * @return {this} 19 | */ 20 | setPath(path) { 21 | this.setParameter("path", path.replace(/[^\/]+/g, encodeURIComponent)); 22 | return this; 23 | } 24 | } 25 | 26 | module.exports = FileRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/GetFileDataRequest.js: -------------------------------------------------------------------------------- 1 | const FileDataRequest = require("./FileDataRequest"); 2 | 3 | class GetFileDataRequest extends FileDataRequest { 4 | responseType = "text"; 5 | } 6 | 7 | module.exports = GetFileDataRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/GetFileInformationRequest.js: -------------------------------------------------------------------------------- 1 | const FileRequest = require('./FileRequest'); 2 | 3 | class GetFileInformationRequest extends FileRequest { 4 | endpoint = "servers/{id}/files/info/{path}"; 5 | } 6 | 7 | module.exports = GetFileInformationRequest; -------------------------------------------------------------------------------- /src/Request/Server/Files/PutFileDataRequest.js: -------------------------------------------------------------------------------- 1 | const FileDataRequest = require("./FileDataRequest"); 2 | 3 | class PutFileDataRequest extends FileDataRequest { 4 | method = "PUT"; 5 | } 6 | 7 | module.exports = PutFileDataRequest; -------------------------------------------------------------------------------- /src/Request/Server/GetServerLogsRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class GetServerLogsRequest extends ServerRequest { 4 | endpoint = "servers/{id}/logs"; 5 | } 6 | 7 | module.exports = GetServerLogsRequest; -------------------------------------------------------------------------------- /src/Request/Server/GetServerOptionRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class GetServerOptionRequest extends ServerRequest { 4 | endpoint = "servers/{id}/options/{option}/"; 5 | 6 | /** 7 | * GetServerOptionRequest constructor 8 | * 9 | * @param {string} id 10 | * @param {string} option 11 | */ 12 | constructor(id, option) { 13 | super(id); 14 | this.setOption(option); 15 | } 16 | 17 | /** 18 | * Set the option name 19 | * 20 | * @param option 21 | */ 22 | setOption(option) { 23 | this.setParameter("option", option); 24 | } 25 | } 26 | 27 | module.exports = GetServerOptionRequest; -------------------------------------------------------------------------------- /src/Request/Server/GetServerRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class GetServerRequest extends ServerRequest { 4 | endpoint = "servers/{id}"; 5 | } 6 | 7 | module.exports = GetServerRequest; -------------------------------------------------------------------------------- /src/Request/Server/PlayerLists/DeletePlayerListEntriesRequest.js: -------------------------------------------------------------------------------- 1 | const PlayerListRequest = require('./PlayerListRequest'); 2 | 3 | class DeletePlayerListEntriesRequest extends PlayerListRequest { 4 | method = "DELETE"; 5 | constructor(id, name, entries) { 6 | super(id, name); 7 | this.data = {entries: entries}; 8 | } 9 | } 10 | 11 | module.exports = DeletePlayerListEntriesRequest; -------------------------------------------------------------------------------- /src/Request/Server/PlayerLists/GetPlayerListEntriesRequest.js: -------------------------------------------------------------------------------- 1 | const PlayerListRequest = require('./PlayerListRequest'); 2 | 3 | class GetPlayerListEntriesRequest extends PlayerListRequest { 4 | 5 | } 6 | 7 | module.exports = GetPlayerListEntriesRequest; -------------------------------------------------------------------------------- /src/Request/Server/PlayerLists/GetPlayerListsRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('../ServerRequest'); 2 | const PlayerListResponse = require('../../../Response/PlayerListsResponse'); 3 | 4 | class GetPlayerListsRequest extends ServerRequest { 5 | endpoint = "servers/{id}/playerlists"; 6 | responseClass = PlayerListResponse; 7 | } 8 | 9 | module.exports = GetPlayerListsRequest; -------------------------------------------------------------------------------- /src/Request/Server/PlayerLists/PlayerListRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('../ServerRequest'); 2 | 3 | class PlayerListRequest extends ServerRequest { 4 | endpoint = "servers/{id}/playerlists/{name}/"; 5 | constructor(id, name) { 6 | super(id); 7 | this.setParameter("name", name); 8 | } 9 | } 10 | 11 | module.exports = PlayerListRequest; -------------------------------------------------------------------------------- /src/Request/Server/PlayerLists/PutPlayerListEntriesRequest.js: -------------------------------------------------------------------------------- 1 | const PlayerListRequest = require('./PlayerListRequest'); 2 | 3 | class PutPlayerListEntriesRequest extends PlayerListRequest { 4 | method = "PUT"; 5 | constructor(id, name, entries) { 6 | super(id, name); 7 | this.data = {entries: entries}; 8 | } 9 | } 10 | 11 | module.exports = PutPlayerListEntriesRequest; -------------------------------------------------------------------------------- /src/Request/Server/RestartServerRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class RestartServerRequest extends ServerRequest { 4 | endpoint = "servers/{id}/restart"; 5 | } 6 | 7 | module.exports = RestartServerRequest; -------------------------------------------------------------------------------- /src/Request/Server/ServerRequest.js: -------------------------------------------------------------------------------- 1 | const Request = require('../Request'); 2 | 3 | class ServerRequest extends Request { 4 | /** 5 | * Server request constructor 6 | * 7 | * @param {string} id 8 | */ 9 | constructor(id) { 10 | super(); 11 | this.setParameter("id", id); 12 | } 13 | } 14 | 15 | module.exports = ServerRequest; -------------------------------------------------------------------------------- /src/Request/Server/SetServerOptionRequest.js: -------------------------------------------------------------------------------- 1 | const GetServerOptionsRequest = require('./GetServerOptionRequest'); 2 | 3 | class SetServerOptionRequest extends GetServerOptionsRequest { 4 | method = "POST"; 5 | 6 | /** 7 | * SetServerOptionRequest constructor 8 | * 9 | * @param {string} id 10 | * @param {string} option 11 | * @param value 12 | */ 13 | constructor(id, option, value) { 14 | super(id, option); 15 | this.data = {}; 16 | this.data[option] = value; 17 | } 18 | } 19 | 20 | module.exports = SetServerOptionRequest; -------------------------------------------------------------------------------- /src/Request/Server/ShareServerLogsRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class ShareServerLogsRequest extends ServerRequest { 4 | endpoint = "servers/{id}/logs/share"; 5 | } 6 | 7 | module.exports = ShareServerLogsRequest; -------------------------------------------------------------------------------- /src/Request/Server/StartServerRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class StartServerRequest extends ServerRequest { 4 | endpoint = "servers/{id}/start"; 5 | method = "POST"; 6 | 7 | /** 8 | * StartServerRequest constructor 9 | * 10 | * @param {string} id 11 | * @param {boolean} useOwnCredits 12 | */ 13 | constructor(id, useOwnCredits = false) { 14 | super(id); 15 | this.data = { 16 | useOwnCredits: useOwnCredits 17 | }; 18 | } 19 | } 20 | 21 | module.exports = StartServerRequest; -------------------------------------------------------------------------------- /src/Request/Server/StopServerRequest.js: -------------------------------------------------------------------------------- 1 | const ServerRequest = require('./ServerRequest'); 2 | 3 | class StopServerRequest extends ServerRequest { 4 | endpoint = "servers/{id}/stop"; 5 | } 6 | 7 | module.exports = StopServerRequest; -------------------------------------------------------------------------------- /src/Response/ArrayResponse.js: -------------------------------------------------------------------------------- 1 | const Response = require('./Response'); 2 | 3 | /** 4 | * @abstract 5 | */ 6 | class ArrayResponse extends Response { 7 | /** 8 | * @type {*[]} 9 | */ 10 | items = []; 11 | 12 | /** 13 | * @inheritDoc 14 | */ 15 | setBody(body) { 16 | super.setBody(body); 17 | 18 | if (!Array.isArray(body.data)) { 19 | return; 20 | } 21 | 22 | for (let object of body.data) { 23 | this.items.push(this.handleItem(object)); 24 | } 25 | } 26 | 27 | /** 28 | * @param {*} item 29 | * @return {*} 30 | * @abstract 31 | */ 32 | handleItem(item) { 33 | 34 | } 35 | 36 | /** 37 | * @inheritDoc 38 | */ 39 | getData() { 40 | return this.items; 41 | } 42 | } 43 | 44 | module.exports = ArrayResponse; 45 | -------------------------------------------------------------------------------- /src/Response/PlayerListsResponse.js: -------------------------------------------------------------------------------- 1 | const PlayerList = require('../Server/PlayerList'); 2 | const ArrayResponse = require("./ArrayResponse.js"); 3 | 4 | class PlayerListsResponse extends ArrayResponse { 5 | /** 6 | * @inheritDoc 7 | */ 8 | handleItem(item) { 9 | return new PlayerList(item).setClient(this.request.client); 10 | } 11 | } 12 | module.exports = PlayerListsResponse; 13 | -------------------------------------------------------------------------------- /src/Response/PoolMembersResponse.js: -------------------------------------------------------------------------------- 1 | const ArrayResponse = require("./ArrayResponse.js"); 2 | const PoolMember = require("../Billing/Pool/PoolMember.js"); 3 | 4 | class PoolMembersResponse extends ArrayResponse { 5 | /** 6 | * @inheritDoc 7 | */ 8 | handleItem(item) { 9 | return new PoolMember(item); 10 | } 11 | } 12 | 13 | module.exports = PoolMembersResponse; 14 | -------------------------------------------------------------------------------- /src/Response/PoolsResponse.js: -------------------------------------------------------------------------------- 1 | const Pool = require('../Billing/Pool/Pool.js'); 2 | const ArrayResponse = require("./ArrayResponse.js"); 3 | 4 | class PoolsResponse extends ArrayResponse { 5 | /** 6 | * @inheritDoc 7 | */ 8 | handleItem(item) { 9 | return new Pool(this.request.client, item.id).setFromObject(item); 10 | } 11 | } 12 | 13 | module.exports = PoolsResponse; 14 | -------------------------------------------------------------------------------- /src/Response/Response.js: -------------------------------------------------------------------------------- 1 | class Response { 2 | /** 3 | * @type {Request} 4 | */ 5 | request; 6 | 7 | /** 8 | * (raw/parsed) response body 9 | * 10 | * @type {{}|string} 11 | */ 12 | body; 13 | 14 | /** 15 | * Response constructor 16 | * 17 | * @param {Request} request 18 | * @constructor 19 | */ 20 | constructor(request) { 21 | this.request = request; 22 | } 23 | 24 | /** 25 | * Get the data from the response 26 | * 27 | * @return {*|null} 28 | */ 29 | getData() { 30 | if (typeof this.body === "undefined" || this.body === null) { 31 | return null; 32 | } 33 | 34 | if (typeof this.body === "string") { 35 | return this.body; 36 | } 37 | 38 | return typeof this.body.data !== "undefined" ? this.body.data : null; 39 | } 40 | 41 | /** 42 | * Set the body to this.body and maybe parse content 43 | * 44 | * @param {{}|string} body 45 | */ 46 | setBody(body) { 47 | this.body = body; 48 | } 49 | } 50 | 51 | module.exports = Response; -------------------------------------------------------------------------------- /src/Response/ServersResponse.js: -------------------------------------------------------------------------------- 1 | const Server = require('../Server/Server'); 2 | const ArrayResponse = require("./ArrayResponse.js"); 3 | 4 | class ServersResponse extends ArrayResponse { 5 | /** 6 | * @inheritDoc 7 | */ 8 | handleItem(item) { 9 | return new Server(this.request.client, item.id).setFromObject(item); 10 | } 11 | } 12 | 13 | module.exports = ServersResponse; 14 | -------------------------------------------------------------------------------- /src/Server/Config/Config.js: -------------------------------------------------------------------------------- 1 | const GetConfigFileOptionsRequest = require("../../Request/Server/Files/Config/GetConfigFileOptionsRequest"); 2 | const UpdateConfigFileOptionsRequest = require("../../Request/Server/Files/Config/UpdateConfigFileOptionsRequest"); 3 | const ConfigOption = require("./ConfigOption"); 4 | 5 | class Config { 6 | /** 7 | * @type {File} 8 | */ 9 | #file; 10 | 11 | /** 12 | * @type {null|Map} 13 | */ 14 | #options = null; 15 | 16 | /** 17 | * @type {null|Map} 18 | */ 19 | #originalValues = null; 20 | 21 | /** 22 | * @param {File} file 23 | */ 24 | constructor(file) { 25 | this.#file = file; 26 | } 27 | 28 | /** 29 | * @param {Object} object 30 | * @return {this} 31 | */ 32 | applyData(object) { 33 | this.#options = new Map(); 34 | this.#originalValues = new Map(); 35 | 36 | if (!Array.isArray(object)) { 37 | return this; 38 | } 39 | 40 | for (const option of object) { 41 | if (typeof option !== "object") { 42 | continue; 43 | } 44 | 45 | let {key, label, type, value, options} = option; 46 | if (typeof key !== "string" || typeof label !== "string" || typeof type !== "string" || options && !Array.isArray(options)) { 47 | continue; 48 | } 49 | 50 | this.#options.set(key, new ConfigOption(key, label, type, value, options)); 51 | this.#originalValues.set(key, value); 52 | } 53 | return this; 54 | } 55 | 56 | /** 57 | * @return {Promise} 58 | */ 59 | async #loadOptions() { 60 | const response = await this.#file.getClient().request(new GetConfigFileOptionsRequest(this.#file.getServer().id, this.#file.path)); 61 | this.applyData(response.getData()); 62 | return this; 63 | } 64 | 65 | /** 66 | * @param {boolean} update 67 | * @return {Promise>} 68 | */ 69 | async getOptions(update = false) { 70 | if (update || this.#options === null) { 71 | await this.#loadOptions(); 72 | } 73 | return this.#options; 74 | } 75 | 76 | /** 77 | * @param {string} key 78 | * @return {Promise} 79 | */ 80 | async getOption(key) { 81 | const options = await this.getOptions(); 82 | return options.get(key) ?? null; 83 | } 84 | 85 | /** 86 | * Save all changes made to this config file 87 | * 88 | * @return {Promise} null if no changes were made 89 | */ 90 | async save() { 91 | let updated = false; 92 | let changed = {}; 93 | for (let [key, option] of this.#options) { 94 | if (option.getValue() !== this.#originalValues.get(key)) { 95 | updated = true; 96 | changed[key] = option.getValue(); 97 | } 98 | } 99 | 100 | if (!updated) { 101 | return null; 102 | } 103 | 104 | let response = await this.#file.getClient().request( 105 | new UpdateConfigFileOptionsRequest(this.#file.getServer().id, this.#file.path, changed)); 106 | 107 | for (let [key, value] of Object.entries(changed)) { 108 | this.#originalValues.set(key, value); 109 | } 110 | 111 | return response; 112 | } 113 | } 114 | 115 | module.exports = Config; 116 | -------------------------------------------------------------------------------- /src/Server/Config/ConfigOption.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @typedef {string|number|boolean|string[]} ConfigOptionValue 3 | */ 4 | 5 | class ConfigOption { 6 | /** 7 | * Key of this config option 8 | * 9 | * @type {string} 10 | */ 11 | #key; 12 | 13 | /** 14 | * Label of this config option 15 | * 16 | * @type {string} 17 | */ 18 | #label; 19 | 20 | /** 21 | * Option type 22 | * 23 | * @type {string} 24 | */ 25 | #type; 26 | 27 | /** 28 | * Current option value 29 | * 30 | * @type {ConfigOptionValue} 31 | */ 32 | #value; 33 | 34 | /** 35 | * Available options for select/multiselect 36 | * 37 | * @type {string[]|null} 38 | */ 39 | #options; 40 | 41 | /** 42 | * ConfigOption constructor 43 | * 44 | * @param {string} key 45 | * @param {string} label 46 | * @param {string} type 47 | * @param {ConfigOptionValue} value 48 | * @param {string[]|null} options 49 | */ 50 | constructor(key, label, type, value, options = null) { 51 | this.#key = key; 52 | this.#label = label; 53 | this.#type = type; 54 | this.#value = value; 55 | this.#options = options; 56 | } 57 | 58 | /** 59 | * @return {string} 60 | */ 61 | getKey() { 62 | return this.#key; 63 | } 64 | 65 | /** 66 | * @return {string} 67 | */ 68 | getLabel() { 69 | return this.#label; 70 | } 71 | 72 | /** 73 | * @return {string} 74 | */ 75 | getType() { 76 | return this.#type; 77 | } 78 | 79 | /** 80 | * @return {ConfigOptionValue} 81 | */ 82 | getValue() { 83 | return this.#value; 84 | } 85 | 86 | /** 87 | * @param {ConfigOptionValue} value 88 | * @return {ConfigOption} 89 | */ 90 | setValue(value) { 91 | this.#value = value; 92 | return this; 93 | } 94 | 95 | /** 96 | * @return {string[]|null} 97 | */ 98 | getOptions() { 99 | return this.#options; 100 | } 101 | } 102 | 103 | module.exports = ConfigOption; 104 | -------------------------------------------------------------------------------- /src/Server/Config/ConfigOptionType.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @enum {string} 3 | */ 4 | class ConfigOptionType { 5 | static STRING = "string"; 6 | static INTEGER = "number"; 7 | static FLOAT = "float"; 8 | static BOOLEAN = "boolean"; 9 | static MULTI_SELECT = "multiselect"; 10 | static SELECT = "select"; 11 | } 12 | 13 | module.exports = ConfigOptionType; 14 | -------------------------------------------------------------------------------- /src/Server/File.js: -------------------------------------------------------------------------------- 1 | const GetFileInformationRequest = require("../Request/Server/Files/GetFileInformationRequest"); 2 | const GetFileDataRequest = require("../Request/Server/Files/GetFileDataRequest"); 3 | const PutFileDataRequest = require("../Request/Server/Files/PutFileDataRequest"); 4 | const DeleteFileDataRequest = require("../Request/Server/Files/DeleteFileDataRequest"); 5 | const CreateDirectoryRequest = require("../Request/Server/Files/CreateDirectoryRequest"); 6 | const Config = require("./Config/Config"); 7 | 8 | class File { 9 | /** 10 | * File path relative to server root 11 | * 12 | * @type {string} 13 | */ 14 | path; 15 | 16 | /** 17 | * File name 18 | * 19 | * @type {string} 20 | */ 21 | name; 22 | 23 | /** 24 | * @type {boolean} 25 | */ 26 | isTextFile; 27 | 28 | /** 29 | * @type {boolean} 30 | */ 31 | isConfigFile; 32 | 33 | /** 34 | * @type {boolean} 35 | */ 36 | isDirectory; 37 | 38 | /** 39 | * @type {boolean} 40 | */ 41 | isLog; 42 | 43 | /** 44 | * @type {boolean} 45 | */ 46 | isReadable; 47 | 48 | /** 49 | * @type {boolean} 50 | */ 51 | isWritable; 52 | 53 | /** 54 | * @type {number} 55 | */ 56 | size; 57 | 58 | /** 59 | * @type {[File]|null} 60 | */ 61 | children = null; 62 | 63 | /** 64 | * @type {Server} 65 | */ 66 | #server; 67 | 68 | /** 69 | * @type {Client} 70 | */ 71 | #client; 72 | 73 | /** 74 | * @type {Config|null} 75 | */ 76 | #config = null; 77 | 78 | /** 79 | * @param {string|null} path 80 | */ 81 | constructor(path = null) { 82 | if (path !== null) { 83 | this.setPath(path); 84 | } 85 | } 86 | 87 | /** 88 | * @param {string} path 89 | */ 90 | setPath(path) { 91 | if (path.startsWith("/")) { 92 | path = path.substring(1); 93 | } 94 | this.path = path; 95 | } 96 | 97 | /** 98 | * Apply data from the API response 99 | * 100 | * @param {object} object 101 | * @return {File} 102 | */ 103 | applyData(object) { 104 | if (typeof object.path !== "undefined") { 105 | this.setPath(object.path); 106 | } 107 | this.name = typeof object.name !== "undefined" ? object.name : null; 108 | this.isTextFile = typeof object.isTextFile !== "undefined" ? object.isTextFile : null; 109 | this.isConfigFile = typeof object.isConfigFile !== "undefined" ? object.isConfigFile : null; 110 | this.isDirectory = typeof object.isDirectory !== "undefined" ? object.isDirectory : null; 111 | this.isLog = typeof object.isLog !== "undefined" ? object.isLog : null; 112 | this.isReadable = typeof object.isReadable !== "undefined" ? object.isReadable : null; 113 | this.isWritable = typeof object.isWritable !== "undefined" ? object.isWritable : null; 114 | this.size = typeof object.size !== "undefined" ? object.size : null; 115 | this.children = Array.isArray(object.children) ? object.children.map((child) => new File().applyData(child).setServer(this.#server).setClient(this.#client)) : null; 116 | return this; 117 | } 118 | 119 | /** 120 | * Set the server 121 | * 122 | * @param {Server} server 123 | * @returns {this} 124 | */ 125 | setServer(server) { 126 | this.#server = server; 127 | if (Array.isArray(this.children)) { 128 | for (let child of this.children) { 129 | child.setServer(server); 130 | } 131 | } 132 | return this; 133 | } 134 | 135 | /** 136 | * Set the API client 137 | * 138 | * @param {Client} client 139 | * @returns {this} 140 | */ 141 | setClient(client) { 142 | this.#client = client; 143 | if (Array.isArray(this.children)) { 144 | for (let child of this.children) { 145 | child.setClient(client); 146 | } 147 | } 148 | return this; 149 | } 150 | 151 | /** 152 | * @return {Client} 153 | */ 154 | getClient() { 155 | return this.#client; 156 | } 157 | 158 | /** 159 | * @return {Server} 160 | */ 161 | getServer() { 162 | return this.#server; 163 | } 164 | 165 | /** 166 | * Get file information from the API 167 | * 168 | * @returns {Promise} 169 | */ 170 | async getInfo() { 171 | const response = await this.#client.request(new GetFileInformationRequest(this.#server.id, this.path)); 172 | this.applyData(response.getData()); 173 | return this; 174 | } 175 | 176 | /** 177 | * Get the data/content of a file 178 | * 179 | * If you want to download the file to a local file use File.download() instead 180 | * 181 | * @return {Promise} 182 | */ 183 | async getContent() { 184 | const response = await this.#client.request(new GetFileDataRequest(this.#server.id, this.path)); 185 | return response.getData(); 186 | } 187 | 188 | /** 189 | * Download the data/content of a file to a local file 190 | * 191 | * If you want to use the content of the file directly use File.getContent() instead 192 | * 193 | * @param {string} outputPath 194 | * @return {Promise} 195 | */ 196 | async download(outputPath) { 197 | return await this.#client.request(new GetFileDataRequest(this.#server.id, this.path).setOutputPath(outputPath)); 198 | } 199 | 200 | /** 201 | * Download the data/content of a file into a writable stream 202 | * 203 | * @param {stream.Writable} outputStream 204 | * @return {Promise} 205 | */ 206 | async downloadToStream(outputStream) { 207 | return await this.#client.request(new GetFileDataRequest(this.#server.id, this.path).setOutputStream(outputStream)); 208 | } 209 | 210 | /** 211 | * Put the content of a file 212 | * 213 | * If you want to upload a local file use File.upload() instead 214 | * 215 | * @param {string} content 216 | * @return {Promise} 217 | */ 218 | async putContent(content) { 219 | return await this.#client.request(new PutFileDataRequest(this.#server.id, this.path).setData(content)); 220 | } 221 | 222 | /** 223 | * Upload a local file 224 | * 225 | * If you want to upload the content of the file directly as a string use File.putContent() instead 226 | * 227 | * @param {string} inputPath 228 | * @return {Promise} 229 | */ 230 | async upload(inputPath) { 231 | return await this.#client.request(new PutFileDataRequest(this.#server.id, this.path).setInputPath(inputPath)); 232 | } 233 | 234 | /** 235 | * Upload from a readable stream 236 | * 237 | * @param {stream.Readable} inputStream 238 | * @return {Promise} 239 | */ 240 | async uploadFromStream(inputStream) { 241 | return this.#client.request(new PutFileDataRequest(this.#server.id, this.path).setInputStream(inputStream)); 242 | } 243 | 244 | /** 245 | * Delete the file 246 | * 247 | * @return {Promise} 248 | */ 249 | async delete() { 250 | return await this.#client.request(new DeleteFileDataRequest(this.#server.id, this.path)); 251 | } 252 | 253 | /** 254 | * Create a directory 255 | * 256 | * @return {Promise} 257 | */ 258 | async createAsDirectory() { 259 | return await this.#client.request(new CreateDirectoryRequest(this.#server.id, this.path)); 260 | } 261 | 262 | /** 263 | * Get the children of a directory 264 | * 265 | * @return {Promise<[File]|null>} 266 | */ 267 | async getChildren() { 268 | if (this.children === null && (this.isDirectory || this.isDirectory === undefined)) { 269 | await this.getInfo(); 270 | } 271 | 272 | return this.children; 273 | } 274 | 275 | /** 276 | * Get Config object for this file 277 | * Only available if the file is a config file 278 | * 279 | * @return {Config} 280 | */ 281 | getConfig() { 282 | if (this.#config === null) { 283 | this.#config = new Config(this); 284 | } 285 | return this.#config; 286 | } 287 | } 288 | 289 | module.exports = File; 290 | -------------------------------------------------------------------------------- /src/Server/PlayerList.js: -------------------------------------------------------------------------------- 1 | const GetPlayerListEntriesRequest = require('../Request/Server/PlayerLists/GetPlayerListEntriesRequest'); 2 | const PutPlayerListEntriesRequest = require('../Request/Server/PlayerLists/PutPlayerListEntriesRequest'); 3 | const DeletePlayerListEntriesRequest = require('../Request/Server/PlayerLists/DeletePlayerListEntriesRequest'); 4 | 5 | class PlayerList { 6 | /** 7 | * List name / identifier 8 | * 9 | * @type {{string}} 10 | */ 11 | name; 12 | 13 | /** 14 | * @type {{Server}} 15 | */ 16 | #server; 17 | 18 | /** 19 | * @type {{Client}} 20 | */ 21 | #client; 22 | 23 | /** 24 | * @param name 25 | */ 26 | 27 | constructor(name) { 28 | this.name = name; 29 | } 30 | 31 | /** 32 | * Set the server for this list 33 | * 34 | * @param server 35 | * @returns {PlayerList} 36 | */ 37 | setServer(server) { 38 | this.#server = server; 39 | return this; 40 | } 41 | 42 | /** 43 | * Set the API client 44 | * 45 | * @param client 46 | * @returns {PlayerList} 47 | */ 48 | setClient(client) { 49 | this.#client = client; 50 | return this; 51 | } 52 | 53 | /** 54 | * Get the list name 55 | * 56 | * @returns {{string}} 57 | */ 58 | getName() { 59 | return this.name; 60 | } 61 | 62 | /** 63 | * @returns {Promise} 64 | */ 65 | async getEntries() { 66 | return (await this.#client.request(new GetPlayerListEntriesRequest(this.#server.id, this.name))).getData(); 67 | } 68 | 69 | /** 70 | * Add multiple entries 71 | * 72 | * @param entries 73 | * @returns {Promise<*>} 74 | */ 75 | async addEntries(entries) { 76 | return this.#client.request(new PutPlayerListEntriesRequest(this.#server.id, this.name, entries)); 77 | } 78 | 79 | /** 80 | * Add a single entry 81 | * 82 | * @param entry 83 | * @returns {Promise<*>} 84 | */ 85 | async addEntry(entry) { 86 | return this.addEntries([entry]); 87 | } 88 | 89 | /** 90 | * Delete multiple entries 91 | * 92 | * @param {{string[]}} entries 93 | * @returns {Promise<*>} 94 | */ 95 | async deleteEntries(entries) { 96 | return this.#client.request(new DeletePlayerListEntriesRequest(this.#server.id, this.name, entries)); 97 | } 98 | 99 | /** 100 | * Delete a single entry 101 | * 102 | * @param {{string}} entry 103 | * @returns {Promise<*>} 104 | */ 105 | async deleteEntry(entry) { 106 | return this.deleteEntries([entry]); 107 | } 108 | } 109 | 110 | module.exports = PlayerList; -------------------------------------------------------------------------------- /src/Server/Players.js: -------------------------------------------------------------------------------- 1 | 2 | class Players { 3 | /** 4 | * Max amount of players / slots 5 | * 6 | * @type {int} 7 | */ 8 | max; 9 | 10 | /** 11 | * Current amount of players 12 | * 13 | * @type {number} 14 | */ 15 | count = 0; 16 | 17 | /** 18 | * List of player names 19 | * 20 | * @type {[string]} 21 | */ 22 | list = []; 23 | 24 | /** 25 | * Players constructor 26 | * 27 | * @param {{}} playersObject 28 | */ 29 | constructor(playersObject) { 30 | this.max = typeof playersObject.max !== "undefined" ? playersObject.max : null; 31 | this.count = typeof playersObject.count === "number" ? playersObject.count : 0; 32 | this.list = typeof playersObject.list === "object" ? playersObject.list : []; 33 | } 34 | } 35 | 36 | module.exports = Players; -------------------------------------------------------------------------------- /src/Server/Server.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | const WebsocketClient = require("../Websocket/WebsocketClient"); 4 | const Software = require('./Software'); 5 | const Players = require('./Players'); 6 | const ServerStatus = require('./ServerStatus'); 7 | const PlayerList = require('./PlayerList'); 8 | const File = require('./File'); 9 | const GetServerRequest = require('../Request/Server/GetServerRequest'); 10 | const StartServerRequest = require('../Request/Server/StartServerRequest'); 11 | const StopServerRequest = require('../Request/Server/StopServerRequest'); 12 | const RestartServerRequest = require('../Request/Server/RestartServerRequest'); 13 | const ExecuteServerCommandRequest = require('../Request/Server/ExecuteServerCommandRequest'); 14 | const GetServerLogsRequest = require('../Request/Server/GetServerLogsRequest'); 15 | const ShareServerLogsRequest = require('../Request/Server/ShareServerLogsRequest'); 16 | const GetServerOptionRequest = require('../Request/Server/GetServerOptionRequest'); 17 | const SetServerOptionRequest = require('../Request/Server/SetServerOptionRequest'); 18 | const GetPlayerListsRequest = require('../Request/Server/PlayerLists/GetPlayerListsRequest'); 19 | 20 | class Server extends EventEmitter { 21 | /** 22 | * Shorthand to get server status constants 23 | * 24 | * @return {{LOADING: number, STARTING: number, SAVING: number, RESTARTING: number, PENDING: number, PREPARING: number, STOPPING: number, OFFLINE: number, ONLINE: number, CRASHED: number}} 25 | */ 26 | get STATUS() { 27 | return ServerStatus 28 | }; 29 | 30 | /** 31 | * @type {Client} 32 | * @private 33 | */ 34 | #client; 35 | 36 | /** 37 | * @type {WebsocketClient} 38 | */ 39 | #websocketClient; 40 | 41 | /** 42 | * Unique server ID 43 | * 44 | * @type {string} 45 | */ 46 | id; 47 | 48 | /** 49 | * Server name 50 | * 51 | * @type {string} 52 | */ 53 | name; 54 | 55 | /** 56 | * Full server address (e.g. example.exaroton.me) 57 | * 58 | * @type {string} 59 | */ 60 | address; 61 | 62 | /** 63 | * MOTD 64 | * 65 | * @type {string} 66 | */ 67 | motd; 68 | 69 | /** 70 | * Server status (see ./ServerStatus.js) 71 | * 72 | * @type {int} 73 | */ 74 | status; 75 | 76 | /** 77 | * Host address, only available if the server is online 78 | * 79 | * @type {string|null} 80 | */ 81 | host = null; 82 | 83 | /** 84 | * Server port, only available if the server is online 85 | * 86 | * @type {null} 87 | */ 88 | port = null; 89 | 90 | /** 91 | * Check if this an owned or a shared server 92 | * 93 | * @type {boolean} 94 | */ 95 | shared = false; 96 | 97 | /** 98 | * Server software 99 | * 100 | * @type {Software} 101 | */ 102 | software; 103 | 104 | /** 105 | * Player data 106 | * 107 | * @type {Players} 108 | */ 109 | players; 110 | 111 | /** 112 | * Player lists 113 | * 114 | * @type {{{string}: PlayerList}} 115 | */ 116 | #playerLists = {}; 117 | 118 | /** 119 | * Server constructor 120 | * 121 | * @param {Client} client 122 | * @param {string} id 123 | */ 124 | constructor(client, id) { 125 | super(); 126 | this.#client = client; 127 | this.id = id; 128 | } 129 | 130 | /** 131 | * @return {Client} 132 | */ 133 | getClient() { 134 | return this.#client; 135 | } 136 | 137 | /** 138 | * Get/update the server info 139 | * 140 | * @return {this} 141 | * @throws {RequestError} 142 | */ 143 | async get() { 144 | let response = await this.#client.request(new GetServerRequest(this.id)); 145 | this.setFromObject(response.getData()); 146 | return this; 147 | } 148 | 149 | /** 150 | * Start the server 151 | * 152 | * @param {boolean} useOwnCredits 153 | * @return {Promise} 154 | * @throws {RequestError} 155 | */ 156 | async start(useOwnCredits = false) { 157 | return this.#client.request(new StartServerRequest(this.id, useOwnCredits)); 158 | } 159 | 160 | /** 161 | * Stop the server 162 | * 163 | * @return {Promise} 164 | * @throws {RequestError} 165 | */ 166 | async stop() { 167 | return this.#client.request(new StopServerRequest(this.id)); 168 | } 169 | 170 | /** 171 | * Restart the server 172 | * 173 | * @return {Promise} 174 | * @throws {RequestError} 175 | */ 176 | async restart() { 177 | return this.#client.request(new RestartServerRequest(this.id)); 178 | } 179 | 180 | /** 181 | * Execute a command in the server console 182 | * 183 | * @param {string} command 184 | * @return {Promise} 185 | */ 186 | async executeCommand(command) { 187 | if (this.#websocketClient && this.#websocketClient.hasStream("console")) { 188 | /** @type {ConsoleStream} stream **/ 189 | let stream = this.#websocketClient.getStream("console"); 190 | if (stream.isStarted()) { 191 | stream.sendCommand(command); 192 | return true; 193 | } 194 | } 195 | return this.#client.request(new ExecuteServerCommandRequest(this.id, command)); 196 | } 197 | 198 | /** 199 | * Get the content of the server logs 200 | * 201 | * This is cached and will not return the latest updates immediately. 202 | * 203 | * @returns {Promise} 204 | */ 205 | async getLogs() { 206 | let response = await this.#client.request(new GetServerLogsRequest(this.id)); 207 | return response.getData().content; 208 | } 209 | 210 | /** 211 | * Upload the content of the server logs to mclo.gs 212 | * 213 | * Returns the URL of the logs on mclo.gs 214 | * 215 | * @returns {Promise} 216 | */ 217 | async shareLogs() { 218 | let response = await this.#client.request(new ShareServerLogsRequest(this.id)); 219 | return response.getData().url; 220 | } 221 | 222 | /** 223 | * Get the assigned max server RAM in GB 224 | * 225 | * @return {Promise} 226 | */ 227 | getRAM() { 228 | return this.getOption("ram"); 229 | } 230 | 231 | /** 232 | * Set the assigned max server RAM in GB 233 | * 234 | * @param {int} ram 235 | * @return {Promise} 236 | */ 237 | setRAM(ram) { 238 | return this.setOption("ram", ram); 239 | } 240 | 241 | /** 242 | * Get the server MOTD 243 | * 244 | * @returns {Promise} 245 | */ 246 | getMOTD() { 247 | return this.getOption("motd"); 248 | } 249 | 250 | /** 251 | * Set the server MOTD 252 | * 253 | * @param {string} motd 254 | * @returns {Promise} 255 | */ 256 | setMOTD(motd) { 257 | return this.setOption("motd", motd); 258 | } 259 | 260 | /** 261 | * Get a server option 262 | * 263 | * @param option 264 | * @return {Promise<*>} 265 | */ 266 | async getOption(option) { 267 | let response = await this.#client.request(new GetServerOptionRequest(this.id, option)); 268 | return response.getData()[option]; 269 | } 270 | 271 | /** 272 | * Set a server option 273 | * 274 | * @param option 275 | * @param value 276 | * @return {Promise} 277 | */ 278 | setOption(option, value) { 279 | return this.#client.request(new SetServerOptionRequest(this.id, option, value)); 280 | } 281 | 282 | /** 283 | * Get all player lists available for the server 284 | * 285 | * @returns {Promise} 286 | */ 287 | async getPlayerLists() { 288 | let lists = (await this.#client.request(new GetPlayerListsRequest(this.id))).getData(); 289 | this.#playerLists = {}; 290 | for (let list of lists) { 291 | list.setServer(this); 292 | this.#playerLists[list.getName()] = list; 293 | } 294 | return lists; 295 | } 296 | 297 | /** 298 | * Get a player list by name 299 | * 300 | * @param name 301 | * @returns {PlayerList} 302 | */ 303 | getPlayerList(name) { 304 | if (typeof this.#playerLists[name] !== "undefined") { 305 | return this.#playerLists[name]; 306 | } 307 | this.#playerLists[name] = new PlayerList(name).setServer(this).setClient(this.#client); 308 | return this.#playerLists[name]; 309 | } 310 | 311 | /** 312 | * Get a file object for a server file 313 | * 314 | * This doesn't request file info or content yet. 315 | * Use the File.getInfo() and File.getContent() functions for that 316 | * 317 | * @param {string} path The path of the file relative to the server root 318 | * @return {File} 319 | */ 320 | getFile(path) { 321 | return new File(path).setServer(this).setClient(this.#client); 322 | } 323 | 324 | /** 325 | * Check if the server has one or one of multiple status codes 326 | * 327 | * Use this.STATUS. for status codes 328 | * 329 | * @param {int|int[]} status 330 | */ 331 | hasStatus(status) { 332 | if (typeof status === "number") { 333 | return this.status === status; 334 | } 335 | 336 | for (let statusCode of status) { 337 | if (this.status === statusCode) { 338 | return true; 339 | } 340 | } 341 | return false; 342 | } 343 | 344 | /** 345 | * Get a websocket client for this server 346 | * 347 | * @return {WebsocketClient} 348 | */ 349 | getWebsocketClient() { 350 | if (!this.#websocketClient) { 351 | this.#websocketClient = new WebsocketClient(this); 352 | this.#websocketClient.on("status", (server) => this.emit("status", server)); 353 | this.#websocketClient.on("event", (data) => this.emit(data.stream + ":" + data.type, data.data)); 354 | } 355 | return this.#websocketClient; 356 | } 357 | 358 | /** 359 | * Subscribe to one or multiple streams 360 | * 361 | * @return {boolean} 362 | * @param {string[]|string} [streams] 363 | */ 364 | subscribe(streams) { 365 | let websocketClient = this.getWebsocketClient(); 366 | if (!websocketClient.isConnected()) { 367 | websocketClient.connect(); 368 | } 369 | if (!streams) { 370 | return; 371 | } 372 | 373 | if (typeof streams === "string") { 374 | streams = [streams]; 375 | } 376 | 377 | for (let stream of streams) { 378 | let websocketStream = websocketClient.getStream(stream) 379 | if (!websocketStream) { 380 | return false; 381 | } 382 | websocketStream.start(); 383 | } 384 | return true; 385 | } 386 | 387 | /** 388 | * Unsubscribe from one, multiple or all streams 389 | * 390 | * @param {string[]|string} [streams] 391 | */ 392 | unsubscribe(streams) { 393 | let websocketClient = this.getWebsocketClient(); 394 | if (!streams) { 395 | websocketClient.disconnect(); 396 | return; 397 | } 398 | 399 | if (typeof streams === "string") { 400 | streams = [streams]; 401 | } 402 | 403 | for (let stream of streams) { 404 | let websocketStream = websocketClient.getStream(stream) 405 | if (websocketStream) { 406 | websocketStream.stop(); 407 | } 408 | } 409 | return true; 410 | } 411 | 412 | /** 413 | * Map raw object to this instance 414 | * 415 | * @param {{}} server 416 | * @return {this} 417 | */ 418 | setFromObject(server) { 419 | this.id = typeof server.id !== "undefined" ? server.id : null; 420 | this.name = typeof server.name !== "undefined" ? server.name : null; 421 | this.address = typeof server.address !== "undefined" ? server.address : null; 422 | this.motd = typeof server.motd !== "undefined" ? server.motd : null; 423 | this.status = typeof server.status !== "undefined" ? server.status : null; 424 | this.host = typeof server.host !== "undefined" ? server.host : null; 425 | this.port = typeof server.port !== "undefined" ? server.port : null; 426 | this.shared = typeof server.shared !== "undefined" ? server.shared : null; 427 | 428 | this.software = null; 429 | if (typeof server.software === "object") { 430 | this.software = new Software(server.software); 431 | } 432 | 433 | this.players = null; 434 | if (typeof server.players === "object") { 435 | this.players = new Players(server.players); 436 | } 437 | return this; 438 | } 439 | 440 | /** 441 | * Only return intended public fields for JSON serialization 442 | * 443 | * Otherwise, fields inherited from EventEmitter would be serialized as well 444 | * 445 | * @returns {{}} 446 | */ 447 | toJSON() { 448 | return { 449 | id: this.id, 450 | name: this.name, 451 | address: this.address, 452 | motd: this.motd, 453 | status: this.status, 454 | host: this.host, 455 | port: this.port, 456 | shared: this.shared, 457 | software: this.software, 458 | players: this.players 459 | } 460 | } 461 | } 462 | 463 | module.exports = Server; -------------------------------------------------------------------------------- /src/Server/ServerStatus.js: -------------------------------------------------------------------------------- 1 | 2 | const ServerStatus = { 3 | OFFLINE: 0, 4 | ONLINE: 1, 5 | STARTING: 2, 6 | STOPPING: 3, 7 | RESTARTING: 4, 8 | SAVING: 5, 9 | LOADING: 6, 10 | CRASHED: 7, 11 | PENDING: 8, 12 | TRANSFERRING: 9, 13 | PREPARING: 10 14 | }; 15 | 16 | module.exports = ServerStatus; -------------------------------------------------------------------------------- /src/Server/Software.js: -------------------------------------------------------------------------------- 1 | 2 | class Software { 3 | /** 4 | * Software ID 5 | * 6 | * @type {string} 7 | */ 8 | id; 9 | 10 | /** 11 | * Software name 12 | * 13 | * @type {string} 14 | */ 15 | name; 16 | 17 | /** 18 | * Software version 19 | * 20 | * @type {string} 21 | */ 22 | version; 23 | 24 | /** 25 | * Software constructor 26 | * 27 | * @param {{}} softwareObject 28 | */ 29 | constructor(softwareObject) { 30 | this.id = typeof softwareObject.id !== "undefined" ? softwareObject.id : null; 31 | this.name = typeof softwareObject.name !== "undefined" ? softwareObject.name : null; 32 | this.version = typeof softwareObject.version !== "undefined" ? softwareObject.version : null; 33 | } 34 | } 35 | 36 | module.exports = Software; -------------------------------------------------------------------------------- /src/Websocket/ConsoleStream.js: -------------------------------------------------------------------------------- 1 | const Stream = require("./Stream"); 2 | 3 | class ConsoleStream extends Stream { 4 | #ansiRegex = new RegExp('[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:[a-zA-Z\\d]*(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)|(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))', "g"); 5 | name = "console"; 6 | startData = {tail: 0}; 7 | 8 | onDataMessage(type, message) { 9 | switch (type) { 10 | case "line": 11 | this.emitEvent("line", {rawLine: message.data, line: this.parseLine(message.data)}); 12 | } 13 | } 14 | 15 | parseReturns(str) { 16 | str = str.replace(/^\r|\r$/, ''); 17 | let rIndex = str.lastIndexOf('\r'); 18 | if (rIndex !== -1) { 19 | str = str.substr(rIndex + 1); 20 | } 21 | return str; 22 | } 23 | 24 | parseLine(line) { 25 | return this.parseReturns(line).replace(this.#ansiRegex, ''); 26 | } 27 | 28 | sendCommand(command) { 29 | this.send("command", command); 30 | } 31 | } 32 | 33 | module.exports = ConsoleStream; -------------------------------------------------------------------------------- /src/Websocket/HeapStream.js: -------------------------------------------------------------------------------- 1 | const Stream = require("./Stream"); 2 | 3 | class HeapStream extends Stream { 4 | name = "heap"; 5 | startStatuses = [1]; 6 | } 7 | 8 | module.exports = HeapStream; -------------------------------------------------------------------------------- /src/Websocket/StatsStream.js: -------------------------------------------------------------------------------- 1 | const Stream = require("./Stream"); 2 | 3 | class StatsStream extends Stream { 4 | name = "stats"; 5 | startStatuses = [1]; 6 | } 7 | 8 | module.exports = StatsStream; -------------------------------------------------------------------------------- /src/Websocket/Stream.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | 3 | class Stream extends EventEmitter { 4 | /** 5 | * @type {WebsocketClient} 6 | */ 7 | #client; 8 | 9 | /** 10 | * @type {boolean} 11 | */ 12 | #started = false; 13 | 14 | /** 15 | * @type {boolean} 16 | */ 17 | #shouldStart = false; 18 | 19 | /** 20 | * @type {string} 21 | */ 22 | name; 23 | 24 | /** 25 | * @type {{}} 26 | */ 27 | startData; 28 | 29 | /** 30 | * @type {number[]} 31 | */ 32 | startStatuses = [1, 2, 3, 4]; 33 | 34 | /** 35 | * @param {WebsocketClient} client 36 | */ 37 | constructor(client) { 38 | super(); 39 | this.#client = client; 40 | this.#client.on('status', this.onStatusChange.bind(this)); 41 | this.#client.on('ready', this.onStatusChange.bind(this)); 42 | this.#client.on('disconnected', this.onDisconnected.bind(this)); 43 | this.#client.on('close', this.onDisconnected.bind(this)); 44 | } 45 | 46 | /** 47 | * @param type 48 | * @param data 49 | */ 50 | send(type, data) { 51 | this.#client.send(this.name, type, data); 52 | } 53 | 54 | /** 55 | * Status change event 56 | */ 57 | onStatusChange() { 58 | this.tryToStart(); 59 | this.tryToStop(); 60 | } 61 | 62 | /** 63 | * Message event listener 64 | * 65 | * @param message 66 | */ 67 | onMessage(message) { 68 | switch (message.type) { 69 | case "started": 70 | this.emit("started"); 71 | this.#started = true; 72 | break; 73 | case "stopped": 74 | this.emit("stopped"); 75 | this.#started = false; 76 | break; 77 | default: 78 | this.onDataMessage(message.type, message); 79 | } 80 | } 81 | 82 | onDataMessage(type, message) { 83 | this.emitEvent(type, message.data); 84 | } 85 | 86 | onDisconnected() { 87 | this.#started = false; 88 | } 89 | 90 | /** 91 | * Double event emitter for generic or specific event handling 92 | * 93 | * @param type 94 | * @param data 95 | */ 96 | emitEvent(type, data) { 97 | this.emit(type, data); 98 | this.emit("event", {stream: this.name, type: type, data: data}); 99 | } 100 | 101 | /** 102 | * Start this stream 103 | */ 104 | start(data) { 105 | if (data) { 106 | this.startData = data; 107 | } 108 | this.#shouldStart = true; 109 | this.tryToStart(); 110 | } 111 | 112 | /** 113 | * Should/can this stream be started 114 | * 115 | * @return {boolean} 116 | */ 117 | async shouldBeStarted() { 118 | return this.#shouldStart && this.startStatuses.includes(await this.#client.getServerStatus()); 119 | } 120 | 121 | /** 122 | * Try to start if possible 123 | * 124 | * @return {boolean} 125 | */ 126 | async tryToStart() { 127 | if (this.#started || !this.#client.isReady() || !await this.shouldBeStarted()) { 128 | return false; 129 | } 130 | 131 | this.send("start", this.startData); 132 | } 133 | 134 | /** 135 | * Stop this stream 136 | */ 137 | stop() { 138 | this.#shouldStart = false; 139 | this.tryToStop(); 140 | delete this.#client.removeStream(this.name); 141 | } 142 | 143 | /** 144 | * Try to stop this stream if possible 145 | * 146 | * @return {boolean} 147 | */ 148 | async tryToStop() { 149 | if (!this.#started || await this.shouldBeStarted()) { 150 | return false; 151 | } 152 | this.send("stop"); 153 | } 154 | 155 | /** 156 | * @return {boolean} 157 | */ 158 | isStarted() { 159 | return this.#started; 160 | } 161 | } 162 | 163 | module.exports = Stream; -------------------------------------------------------------------------------- /src/Websocket/TickStream.js: -------------------------------------------------------------------------------- 1 | const Stream = require("./Stream"); 2 | 3 | class TickStream extends Stream { 4 | name = "tick"; 5 | startStatuses = [1]; 6 | 7 | onDataMessage(type, message) { 8 | switch(type) { 9 | case "tick": 10 | message.data.tps = Math.round(Math.min(1000 / message.data.averageTickTime, 20) * 10) / 10; 11 | this.emitEvent("tick", message.data); 12 | } 13 | } 14 | } 15 | 16 | module.exports = TickStream; -------------------------------------------------------------------------------- /src/Websocket/WebsocketClient.js: -------------------------------------------------------------------------------- 1 | const EventEmitter = require('events'); 2 | const WebSocket = require('ws'); 3 | 4 | const ConsoleStream = require("./ConsoleStream"); 5 | const HeapStream = require("./HeapStream"); 6 | const StatsStream = require("./StatsStream"); 7 | const TickStream = require("./TickStream"); 8 | 9 | /** 10 | * Websocket client to connect to the websocket for this server 11 | */ 12 | class WebsocketClient extends EventEmitter { 13 | /** 14 | * @type {string} 15 | */ 16 | protocol = "wss"; 17 | 18 | /** 19 | * @type {Client} 20 | * @private 21 | */ 22 | #client; 23 | 24 | /** 25 | * @type {Server} 26 | * @private 27 | */ 28 | #server; 29 | 30 | /** 31 | * @type {WebSocket} 32 | */ 33 | #websocket; 34 | 35 | /** 36 | * Automatically reconnect in case of a disconnect 37 | * 38 | * @type {boolean} 39 | */ 40 | autoReconnect = true; 41 | 42 | /** 43 | * Time to wait to reconnect 44 | * 45 | * Only change this with caution. A time too low here can 46 | * cause a spam in requests which can get your application 47 | * rate limited or even blocked. 48 | * 49 | * @type {number} 50 | */ 51 | reconnectTimeout = 3000; 52 | 53 | #reconnectInterval; 54 | 55 | /** 56 | * @type {boolean} 57 | */ 58 | #connected = false; 59 | 60 | /** 61 | * @type {boolean} 62 | */ 63 | #shouldConnect = false; 64 | 65 | /** 66 | * @type {boolean} 67 | */ 68 | #serverConnected = false; 69 | 70 | /** 71 | * @type {boolean} 72 | */ 73 | #ready = false; 74 | 75 | /** 76 | * @type {{{string}: Stream}} 77 | */ 78 | #streams = {}; 79 | 80 | #availableStreams = { 81 | "console": ConsoleStream, 82 | "heap": HeapStream, 83 | "stats": StatsStream, 84 | "tick": TickStream 85 | }; 86 | 87 | /** 88 | * @param {Server} server 89 | */ 90 | constructor(server) { 91 | super(); 92 | this.#server = server; 93 | this.#client = server.getClient(); 94 | this.protocol = this.#client.protocol === "https" ? "wss" : "ws"; 95 | this.url = this.protocol + "://" + this.#client.host + this.#client.basePath + "servers/" + this.#server.id + "/websocket"; 96 | } 97 | 98 | /** 99 | * Connect to websocket 100 | */ 101 | connect() { 102 | this.#shouldConnect = true; 103 | this.#websocket = new WebSocket(this.url, {headers: {authorization: "Bearer " + this.#client.getAPIToken()}}); 104 | this.#websocket.on('open', this.onOpen.bind(this)); 105 | this.#websocket.on('close', this.onClose.bind(this)); 106 | this.#websocket.on('error', this.onError.bind(this)); 107 | this.#websocket.on('message', this.onMessage.bind(this)); 108 | if (!this.streamRetryInterval) { 109 | this.streamRetryInterval = setInterval(this.tryToStartStreams.bind(this), 15000); 110 | } 111 | } 112 | 113 | /** 114 | * Disconnect from the websocket and all streams 115 | */ 116 | disconnect() { 117 | this.#shouldConnect = false; 118 | this.#websocket.close(); 119 | this.#streams = {}; 120 | clearInterval(this.#reconnectInterval); 121 | clearInterval(this.streamRetryInterval); 122 | this.streamRetryInterval = null; 123 | } 124 | 125 | onOpen() { 126 | this.#connected = true; 127 | clearInterval(this.#reconnectInterval); 128 | this.emit('open'); 129 | } 130 | 131 | onClose() { 132 | this.emit('close'); 133 | this.#ready = false; 134 | if (this.autoReconnect && this.#shouldConnect) { 135 | this.#reconnectInterval = setInterval(this.connect.bind(this), this.reconnectTimeout); 136 | } else { 137 | this.#connected = false; 138 | } 139 | } 140 | 141 | onError(error) { 142 | this.emit('error', error); 143 | } 144 | 145 | onMessage(rawMessage) { 146 | let message = JSON.parse(rawMessage); 147 | 148 | // noinspection FallThroughInSwitchStatementJS 149 | switch (message.type) { 150 | case "keep-alive": 151 | break; 152 | case "ready": 153 | this.#ready = true; 154 | this.emit('ready'); 155 | break; 156 | case "connected": 157 | this.#serverConnected = true; 158 | this.emit('connected'); 159 | break; 160 | case "disconnected": 161 | this.#serverConnected = false; 162 | this.emit('disconnected'); 163 | if (this.autoReconnect) { 164 | setTimeout(this.tryToStartStreams.bind(this), this.reconnectTimeout); 165 | } 166 | break; 167 | case "status": 168 | if (message.stream === "status") { 169 | this.#server.setFromObject(message.data); 170 | this.emit('status', this.#server); 171 | break; 172 | } 173 | default: 174 | if (message.stream && this.#streams[message.stream]) { 175 | this.#streams[message.stream].onMessage(message); 176 | } 177 | } 178 | 179 | } 180 | 181 | /** 182 | * @return {boolean} 183 | */ 184 | isConnected() { 185 | return this.#connected; 186 | } 187 | 188 | /** 189 | * @return {boolean} 190 | */ 191 | isReady() { 192 | return this.#ready; 193 | } 194 | 195 | /** 196 | * @return {Server} 197 | */ 198 | getServer() { 199 | return this.#server; 200 | } 201 | 202 | /** 203 | * @return {int|number} 204 | */ 205 | async getServerStatus() { 206 | if (!Number.isInteger(this.#server.status)) { 207 | await this.#server.get(); 208 | } 209 | return this.#server.status; 210 | } 211 | 212 | /** 213 | * Get a stream by name 214 | * 215 | * @param {string} stream 216 | * @return {boolean|Stream} 217 | */ 218 | getStream(stream) { 219 | if (!this.#availableStreams[stream]) { 220 | return false; 221 | } 222 | 223 | if (this.#streams[stream]) { 224 | return this.#streams[stream]; 225 | } 226 | 227 | this.#streams[stream] = new this.#availableStreams[stream](this); 228 | this.#streams[stream].on('event', (data) => this.emit('event', data)); 229 | return this.#streams[stream]; 230 | } 231 | 232 | /** 233 | * @param stream 234 | * @return {boolean} 235 | */ 236 | hasStream(stream) { 237 | return !!this.#streams[stream]; 238 | } 239 | 240 | tryToStartStreams() { 241 | for (let stream of Object.keys(this.#streams)) { 242 | this.#streams[stream].tryToStart(); 243 | } 244 | } 245 | 246 | removeStream(stream) { 247 | delete this.#streams[stream]; 248 | } 249 | 250 | /** 251 | * @param stream 252 | * @param type 253 | * @param data 254 | * @return {boolean} 255 | */ 256 | send(stream, type, data) { 257 | if (this.#websocket.readyState !== 1 || !this.isReady()) { 258 | return false; 259 | } 260 | 261 | let message = {stream: stream, type: type}; 262 | if (typeof data !== "undefined") { 263 | message.data = data; 264 | } 265 | this.#websocket.send(JSON.stringify(message)); 266 | } 267 | } 268 | 269 | module.exports = WebsocketClient; --------------------------------------------------------------------------------