├── .gitignore ├── .prettierignore ├── .prettierrc ├── CHANGELOG.md ├── LICENSE ├── README.md ├── docs ├── .nojekyll ├── assets │ ├── highlight.css │ ├── icons.css │ ├── icons.png │ ├── icons@2x.png │ ├── main.js │ ├── search.js │ ├── style.css │ ├── widgets.png │ └── widgets@2x.png ├── classes │ ├── Account.html │ ├── ApplicationDatabaseManager.html │ ├── ApplicationServer.html │ ├── ApplicationServerManager.html │ ├── BackupManager.html │ ├── BaseManager.html │ ├── BaseUser.html │ ├── Builder.html │ ├── ClientDatabaseManager.html │ ├── ClientServer.html │ ├── ClientServerManager.html │ ├── Dict.html │ ├── FileManager.html │ ├── NestEggsManager.html │ ├── NestManager.html │ ├── NetworkManager.html │ ├── Node.html │ ├── NodeAllocationManager.html │ ├── NodeBuilder.html │ ├── NodeLocationManager.html │ ├── NodeManager.html │ ├── Permissions.html │ ├── PteroAPIError.html │ ├── PteroApp.html │ ├── PteroClient.html │ ├── RequestError.html │ ├── RequestManager.html │ ├── Schedule.html │ ├── ScheduleManager.html │ ├── ServerBuilder.html │ ├── Shard.html │ ├── SubUser.html │ ├── SubUserManager.html │ ├── User.html │ ├── UserBuilder.html │ ├── UserManager.html │ ├── ValidationError.html │ ├── WebSocketError.html │ └── WebSocketManager.html ├── enums │ ├── Flags.html │ ├── ServerStatus.html │ └── ShardStatus.html ├── index.html ├── interfaces │ ├── APIErrorResponse.html │ ├── APIKey.html │ ├── Activity.html │ ├── Allocation.html │ ├── AllowedQueryOptions.html │ ├── ApplicationDatabase.html │ ├── Backup.html │ ├── ClientDatabase.html │ ├── ClientMeta.html │ ├── ClientResources.html │ ├── ConvertOptions.html │ ├── CreateBackupOptions.html │ ├── CreateNodeOptions.html │ ├── CreateScheduleOptions.html │ ├── CreateServerOptions.html │ ├── CreateUserOptions.html │ ├── Cron.html │ ├── DaemonData.html │ ├── DictConstructor.html │ ├── Egg.html │ ├── EggVariable.html │ ├── FeatureLimits.html │ ├── FetchOptions.html │ ├── File.html │ ├── FileChmodData.html │ ├── FileConfig.html │ ├── Limits.html │ ├── Nest.html │ ├── NetworkAllocation.html │ ├── NodeConfiguration.html │ ├── NodeDeploymentOptions.html │ ├── NodeLocation.html │ ├── OptionSpec.html │ ├── PaginationMeta.html │ ├── PermissionDescriptor.html │ ├── RequestEvents.html │ ├── SSHKey.html │ ├── ScheduleTask.html │ ├── StartupData.html │ ├── UpdateBuildOptions.html │ ├── UpdateDetailsOptions.html │ ├── UpdateStartupOptions.html │ ├── UpdateUserOptions.html │ ├── WebSocketAuth.html │ ├── WebSocketEvents.html │ └── WebSocketPayload.html └── modules.html ├── examples ├── create_server.js └── get_all_logs.js ├── migrations └── v2-0-1.md ├── package.json ├── pterojs.json ├── src ├── application │ ├── ApplicationDatabaseManager.ts │ ├── ApplicationServerManager.ts │ ├── NestEggsManager.ts │ ├── NestManager.ts │ ├── NodeAllocationManager.ts │ ├── NodeLocationManager.ts │ ├── NodeManager.ts │ ├── UserManager.ts │ ├── endpoints.ts │ └── index.ts ├── builders │ ├── Node.ts │ ├── Server.ts │ ├── User.ts │ └── base.ts ├── client │ ├── BackupManager.ts │ ├── ClientDatabaseManager.ts │ ├── ClientServerManager.ts │ ├── FileManager.ts │ ├── NetworkManager.ts │ ├── ScheduleManager.ts │ ├── SubUserManager.ts │ ├── endpoints.ts │ ├── index.ts │ └── ws │ │ ├── Shard.ts │ │ ├── WebSocketManager.ts │ │ └── packetHandler.ts ├── common │ ├── app.ts │ ├── client.ts │ └── index.ts ├── http │ └── RequestManager.ts ├── index.ts ├── structures │ ├── ApplicationServer.ts │ ├── BaseManager.ts │ ├── ClientServer.ts │ ├── Dict.ts │ ├── Errors.ts │ ├── Node.ts │ ├── Permissions.ts │ ├── Schedule.ts │ └── User.ts ├── types │ └── index.d.ts └── util │ ├── caseConv.ts │ ├── config.ts │ ├── escape.ts │ └── query.ts ├── tsconfig.json └── tsup.config.ts /.gitignore: -------------------------------------------------------------------------------- 1 | # General directories 2 | /.vscode 3 | /node_modules 4 | /dist 5 | 6 | # General files 7 | package-lock.json 8 | /tests/auth.ts 9 | 10 | # Test files/directories 11 | /test 12 | /src/remote 13 | *.cmd 14 | *.sh 15 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /package-lock.json 2 | /node_modules 3 | /LICENSE 4 | /docs 5 | /dist 6 | 7 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "singleQuote": true, 3 | "quoteProps": "as-needed", 4 | "trailingComma": "all", 5 | "bracketSpacing": true, 6 | "arrowParens": "avoid", 7 | "semi": true, 8 | "useTabs": false, 9 | "tabWidth": 4 10 | } -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | Tracking changes for PteroJS and extensions from v2 onwards (using [SemVer 2](http://semver.org/)). 4 | 5 | ## [2.2.0] - 08-02-2023 6 | 7 | ### Added 8 | 9 | - `WebSocketManager#getAuth()` method for getting websocket auth data 10 | - `type` query parameter for `ClientServerManager#fetch()` (takes "admin", "admin-all", or "owner") 11 | 12 | ### Fixed 13 | 14 | - Switch `Shard` class to use `WebSocketManager#getAuth()` 15 | - Fix & ensure `NodeAllocationManager#fetchAvailable()` fetches all allocations 16 | 17 | ## [2.1.0] - 08-10-2022 18 | 19 | A lot of bug fixes and some new useful QOL features for the library and developers. 20 | 21 | ### Added 22 | 23 | - `Dict#update()` updates the dict instance in place with another dict 24 | - `Shard#request()` method for making sendable requests to the server 25 | - `WebSocketManager#broadcast()` method to broadcast events to all shards and collect the responses 26 | - `ApplicationServer#container` property 27 | - `ClientServer#internalId` property 28 | - `ClientServer#eggFeatures` optional property 29 | - `ClientServer#invocation` property 30 | - `ClientServer#transferring` boolean property 31 | - `UserBuilder` class 32 | - `ServerBuilder` class 33 | - `NodeBuilder` class 34 | - Activity logs support (`Account#fetchActivities()`) 35 | - SSH keys support (`Account#fetchSSHKeys()`, `Account#createSSHKey()`, `Account#removeSSHKey()`) 36 | - Jest testing instead of custom testing 37 | - Additional documentation and examples for application API 38 | - Support including servers for `UserManager` 39 | - Documentation with examples for the application and client API 40 | - Include `token` field from metadata when creating API keys 41 | - Cache metadata from all `fetch()` methods in the application API 42 | - Support `origin` header for websocket connections 43 | 44 | ### Changed 45 | 46 | - All managers with caches now uses the `Dict#update()` method 47 | - `ClientServer#state` -> `ClientServer#status` matches API data 48 | - Overloaded `PteroClient#addSocketServer()` to not return an array if only one ID is present 49 | 50 | ### Fixed 51 | 52 | - `Node#daemon` now shows the actual daemon data object 53 | - `caseConv` functions handling arrays incorrectly 54 | - Node creation method now uses the correct endpoint 55 | - `NodeCreationOptions` is now updated to use actual creation options in the API 56 | - `ApplicationServerManager#updateBuild()` applies missing limits and feature limits 57 | - `UserManager#query()` now uses the correct endpoint (previously servers) 58 | - Export missing type/interface members for documentation 59 | - `UserUpdateOptions#externalId` now accepts `null` to remove the external ID 60 | - `UserManager#update()` now checks if `externalId` is set before defaulting 61 | 62 | ## [2.0.0] - 26-05-2022 63 | 64 | A huge turning point for the PteroJS library, having a new TypeScript look, updated classes/methods, and proper documentation. Thanks to everyone that contributed! :D 65 | 66 | ### Added 67 | 68 | - Global types and interfaces to `/src/common` 69 | - Expandable query option types 70 | - "page" and "perPage" query options support 71 | - `BaseManager` with abstract query properties 72 | - Additional parse options for `caseConv` util 73 | - Support sometimes "meta" property for API errors 74 | - `FileManager#getDownloadURL()` replaces old `download()` method 75 | - `FileManager#getUploadURL()` replaces old `upload()` method 76 | - `FileManager#chmod()` method 77 | - `FileChmodData` type for chmod requests 78 | - `NodeManager#fetchDeployable()` method with types 79 | - `BackupManager#getDownloadURL()` replaces old `download()` method 80 | - `WebSocketManager#active` for checking active created shards 81 | - `ClientServerManager#fetchResources()` method 82 | - `ClientServer#fetchResources()` method 83 | - Support for `skipScripts`, `oomDisabled`, `allocation.additional`, `deploy`, and `startOnCompletion` with server creation 84 | - Added warning doc for `oomDisabled` broken behaviour 85 | - `ClientServerManager#setDockerImage()` method 86 | - `ClientServer#setDockerImage()` method 87 | - `ClientServerManager#rename()` method 88 | - `ClientServer#rename()` method 89 | - `ClientServerManager#reinstall()` method 90 | - `ClientServer#reinstall()` method 91 | - `ValidationError` class (implemented in managers), will come with additional uses in future versions 92 | - Guard API requests from unexpected `null_resource` response objects 93 | - `Dict#clone()` method for deep cloning the existing dict 94 | - Typings for `RequestManager` events 95 | - Static getters for grouped permissions 96 | - Support startup viewing and modification endpoints 97 | - `ClientServerManager#fetchStartup()` method 98 | - `ClientServer#fetchStartup()` method 99 | - `ClientServerManager#setVariable()` method 100 | - `ClientServer#setVariable()` method 101 | - Support `external` option for fetching application servers by external ID 102 | - `PteroClient#fetchPermissions()` method to return the raw permission group descriptors (separate from the `Permissions` utility class). 103 | 104 | ### Changed 105 | 106 | - All application and client managers now extend `BaseManager` 107 | - Renamed user classes to reflect the API 108 | - `PteroUser` is now `User` 109 | - `PteroSubUser` is now `SubUser` 110 | - `ClientUser` is now `Account` 111 | - Renamed `ClientServerManager#pageData` to `#meta` 112 | - `PteroFile` -> `File` (typing change) 113 | - `FileManager#download()` now downloads the file 114 | - `BackupManager#download()` now downloads the backup 115 | - `RequestManager` uses axios with internal parsing 116 | - Nost structures now use its manager for API requests in its group 117 | - Changeed `env` to `environment` for server creation 118 | - Changed `image` to `dockerImage` for server creation 119 | - `Permissions#has()` is now split into `hasAny()` and `hasAll()` 120 | - Refactored `Permissions` and `Flags` to only use the API's string-based permissions. Numbers wont be parsed anymore 121 | - `Permissions#raw` -> `Permissions#value` 122 | - `Account#get2faCode()` -> `Account#get2FACode()` 123 | - `Account#enable2fa()` -> `Account#enable2FA()` 124 | - `Account#disable2fa()` -> `Account#disable2FA()` 125 | 126 | ### Deprecated 127 | 128 | - `UserManager#fetchExternal()`: use `UserManager#fetch()` with the `external` option instead 129 | 130 | ### Removed 131 | 132 | - Useless value return types (e.g. `Promise` which should be `Promise`) 133 | - Most union string/number & instance type parameters 134 | - `ClientServer#addWebsocket()`: use the client directly instead 135 | - `File#isEditable`: never existed in the API, issue from the docs 136 | - `WebSocketManager#readyAt`: no longer centralised, replaced by `active` 137 | - `NestEggsManager#for()`: use cache methods instead 138 | - `ApplicationServer#delete()`: conflicts with cache; use manager instead 139 | - `Node#delete()`: conflicts with cache; use manager instead 140 | - `Schedule#delete()`: conflicts with cache; use manager instead 141 | - `PermissionResolvable` type: all permissions are strings now making it redundant 142 | - `Permissions#toArray()`: redundant; use `Permissions#value` instead 143 | - `Permissions#toStrings()`: redundant; all permission values are now strings 144 | - `Permissions#fromStrings():` redundant; ditto 145 | - `Permissions#DEFAULT`: control permissions are now grouped under `Permissions#CONTROL` 146 | 147 | ### Fixed 148 | 149 | - Export all endpoints properly 150 | - `Dict#join()` now actually joins the other dicts to the existing dict 151 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021-present PteroPackages 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |

PteroJS

2 |

A verbose API library for Pterodactyl

3 |

4 | 5 | ## About 6 | 7 | PteroJS is a verbose API library for the [Pterodactyl Game Panel](https://pterodactyl.io) designed to give developers full access and control over the API without having to compromise on code quality or efficiency. 8 | 9 | ## Installing 10 | 11 | If you are using Node.js (v14.x and above): 12 | 13 | ``` 14 | npm install @devnote-dev/pterojs 15 | yarn add @devnote-dev/pterojs 16 | ``` 17 | 18 | or if you are using Deno: 19 | ```js 20 | import pterojs from 'https://cdn.skypack.dev/@devnote-dev/pterojs'; 21 | ``` 22 | 23 | Please join the [support server](https://discord.com/invite/dwcfTjgn7S) if you experience any issues with installing the package. 24 | 25 | ## Compatibility 26 | 27 | Note that you can use older versions of PteroJS with newer versions of Pterodactyl and Wings, but they will not be up-to-date with the latest features and fixes. 28 | 29 | | PteroJS | Panel | Wings | 30 | | ------- | ---------------- | -------- | 31 | | ❌ | `<= 0.7` | `<= 1.5` | 32 | | `1.3.0` | `1.6.5 >= 1.7.0` | `~1.6.0` | 33 | | `1.4.2` | `1.7.0 >= 1.8.1` | `~1.6.0` | 34 | | `2.0.1` | `^1.9.0` | `^1.7.0` | 35 | | `2.1.0` | `^1.10.0` | `^1.7.0` | 36 | 37 | ## Setting Up 38 | 39 | PteroJS uses separate classes for the client and application sides of the Pterodactyl API. 40 | 41 | ### Using the application API 42 | 43 | ```js 44 | const { PteroApp } = require('@devnote-dev/pterojs'); 45 | 46 | // Initialising the application 47 | const app = new PteroApp('your.domain.name', 'pterodactyl_api_key'); 48 | 49 | // Accessing information 50 | app.servers.fetch(4).then(console.log); 51 | ``` 52 | 53 | ### Using the client API 54 | 55 | ```js 56 | const { PteroClient } = require('@devnote-dev/pterojs'); 57 | 58 | // Initialising the client 59 | const client = new PteroClient('your.domain.name', 'pterodactyl_api_key'); 60 | 61 | // Adding the server to listen for 62 | const shard = client.addSocketServer('f7eca02e'); 63 | 64 | // Listening to events 65 | shard.on('statusUpdate', (status) => { 66 | console.log(`server ${shard.id} status: ${status}`); 67 | }); 68 | 69 | // Connecting to the server 70 | shard.connect(); 71 | ``` 72 | 73 | ## Migrations 74 | 75 | Checkout the new [migrations guide](./migrations/v2-0-1.md) to PteroJS v2! 76 | 77 | ## Contributing 78 | 79 | Please see the [issues](https://github.com/PteroPackages/PteroJS/issues) section for contributing ideas. New ideas/features are also welcome. 80 | 81 | 1. [Fork this repo](https://github.com/PteroPackages/PteroJS/fork)! 82 | 2. Make a branch from `main` (`git branch -b `) 83 | 3. Commit your changes (`git commit -am "..."`) 84 | 4. Open a PR here (`git push origin `) 85 | 86 | ## Contributors 87 | 88 | - [Devonte](https://github.com/devnote-dev) - Owner, maintainer 89 | - [Chelog](https://github.com/chelog) - Code contributor 90 | - [Cain](https://github.com/cainthebest) - Code contributor 91 | 92 | This repository is managed under the MIT license. 93 | 94 | © 2021-present PteroPackages 95 | -------------------------------------------------------------------------------- /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | TypeDoc added this file to prevent GitHub Pages from using Jekyll. You can turn off this behavior by setting the `githubPages` option to false. -------------------------------------------------------------------------------- /docs/assets/highlight.css: -------------------------------------------------------------------------------- 1 | :root { 2 | --light-hl-0: #001080; 3 | --dark-hl-0: #9CDCFE; 4 | --light-hl-1: #000000; 5 | --dark-hl-1: #D4D4D4; 6 | --light-hl-2: #AF00DB; 7 | --dark-hl-2: #C586C0; 8 | --light-hl-3: #A31515; 9 | --dark-hl-3: #CE9178; 10 | --light-hl-4: #0000FF; 11 | --dark-hl-4: #569CD6; 12 | --light-hl-5: #0070C1; 13 | --dark-hl-5: #4FC1FF; 14 | --light-hl-6: #795E26; 15 | --dark-hl-6: #DCDCAA; 16 | --light-hl-7: #008000; 17 | --dark-hl-7: #6A9955; 18 | --light-hl-8: #098658; 19 | --dark-hl-8: #B5CEA8; 20 | --light-hl-9: #000000FF; 21 | --dark-hl-9: #D4D4D4; 22 | --light-hl-10: #EE0000; 23 | --dark-hl-10: #D7BA7D; 24 | --light-hl-11: #267F99; 25 | --dark-hl-11: #4EC9B0; 26 | --light-hl-12: #000000; 27 | --dark-hl-12: #C8C8C8; 28 | --light-code-background: #F5F5F5; 29 | --dark-code-background: #1E1E1E; 30 | } 31 | 32 | @media (prefers-color-scheme: light) { :root { 33 | --hl-0: var(--light-hl-0); 34 | --hl-1: var(--light-hl-1); 35 | --hl-2: var(--light-hl-2); 36 | --hl-3: var(--light-hl-3); 37 | --hl-4: var(--light-hl-4); 38 | --hl-5: var(--light-hl-5); 39 | --hl-6: var(--light-hl-6); 40 | --hl-7: var(--light-hl-7); 41 | --hl-8: var(--light-hl-8); 42 | --hl-9: var(--light-hl-9); 43 | --hl-10: var(--light-hl-10); 44 | --hl-11: var(--light-hl-11); 45 | --hl-12: var(--light-hl-12); 46 | --code-background: var(--light-code-background); 47 | } } 48 | 49 | @media (prefers-color-scheme: dark) { :root { 50 | --hl-0: var(--dark-hl-0); 51 | --hl-1: var(--dark-hl-1); 52 | --hl-2: var(--dark-hl-2); 53 | --hl-3: var(--dark-hl-3); 54 | --hl-4: var(--dark-hl-4); 55 | --hl-5: var(--dark-hl-5); 56 | --hl-6: var(--dark-hl-6); 57 | --hl-7: var(--dark-hl-7); 58 | --hl-8: var(--dark-hl-8); 59 | --hl-9: var(--dark-hl-9); 60 | --hl-10: var(--dark-hl-10); 61 | --hl-11: var(--dark-hl-11); 62 | --hl-12: var(--dark-hl-12); 63 | --code-background: var(--dark-code-background); 64 | } } 65 | 66 | body.light { 67 | --hl-0: var(--light-hl-0); 68 | --hl-1: var(--light-hl-1); 69 | --hl-2: var(--light-hl-2); 70 | --hl-3: var(--light-hl-3); 71 | --hl-4: var(--light-hl-4); 72 | --hl-5: var(--light-hl-5); 73 | --hl-6: var(--light-hl-6); 74 | --hl-7: var(--light-hl-7); 75 | --hl-8: var(--light-hl-8); 76 | --hl-9: var(--light-hl-9); 77 | --hl-10: var(--light-hl-10); 78 | --hl-11: var(--light-hl-11); 79 | --hl-12: var(--light-hl-12); 80 | --code-background: var(--light-code-background); 81 | } 82 | 83 | body.dark { 84 | --hl-0: var(--dark-hl-0); 85 | --hl-1: var(--dark-hl-1); 86 | --hl-2: var(--dark-hl-2); 87 | --hl-3: var(--dark-hl-3); 88 | --hl-4: var(--dark-hl-4); 89 | --hl-5: var(--dark-hl-5); 90 | --hl-6: var(--dark-hl-6); 91 | --hl-7: var(--dark-hl-7); 92 | --hl-8: var(--dark-hl-8); 93 | --hl-9: var(--dark-hl-9); 94 | --hl-10: var(--dark-hl-10); 95 | --hl-11: var(--dark-hl-11); 96 | --hl-12: var(--dark-hl-12); 97 | --code-background: var(--dark-code-background); 98 | } 99 | 100 | .hl-0 { color: var(--hl-0); } 101 | .hl-1 { color: var(--hl-1); } 102 | .hl-2 { color: var(--hl-2); } 103 | .hl-3 { color: var(--hl-3); } 104 | .hl-4 { color: var(--hl-4); } 105 | .hl-5 { color: var(--hl-5); } 106 | .hl-6 { color: var(--hl-6); } 107 | .hl-7 { color: var(--hl-7); } 108 | .hl-8 { color: var(--hl-8); } 109 | .hl-9 { color: var(--hl-9); } 110 | .hl-10 { color: var(--hl-10); } 111 | .hl-11 { color: var(--hl-11); } 112 | .hl-12 { color: var(--hl-12); } 113 | pre, code { background: var(--code-background); } 114 | -------------------------------------------------------------------------------- /docs/assets/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PteroPackages/PteroJS/68c7050e682b545ab73fca7915f61e18be8b9ad9/docs/assets/icons.png -------------------------------------------------------------------------------- /docs/assets/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PteroPackages/PteroJS/68c7050e682b545ab73fca7915f61e18be8b9ad9/docs/assets/icons@2x.png -------------------------------------------------------------------------------- /docs/assets/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PteroPackages/PteroJS/68c7050e682b545ab73fca7915f61e18be8b9ad9/docs/assets/widgets.png -------------------------------------------------------------------------------- /docs/assets/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/PteroPackages/PteroJS/68c7050e682b545ab73fca7915f61e18be8b9ad9/docs/assets/widgets@2x.png -------------------------------------------------------------------------------- /docs/classes/Builder.html: -------------------------------------------------------------------------------- 1 | Builder | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Class Builder Abstract

Hierarchy

Index

Constructors

Methods

Constructors

Methods

  • build(): any

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/enums/ShardStatus.html: -------------------------------------------------------------------------------- 1 | ShardStatus | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Enumeration ShardStatus

Index

Enumeration Members

Enumeration Members

CLOSED: 0
CONNECTED: 2
CONNECTING: 1

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/APIErrorResponse.html: -------------------------------------------------------------------------------- 1 | APIErrorResponse | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface APIErrorResponse

2 |

Represents an API error response object.

3 |

Hierarchy

  • APIErrorResponse

Index

Properties

Properties

errors: { code: string; detail: string; meta?: unknown; status: string }[]

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/ClientMeta.html: -------------------------------------------------------------------------------- 1 | ClientMeta | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface ClientMeta

2 |

Represents the client metedata from a client servers request.

3 |

Hierarchy

  • ClientMeta

Index

Properties

isServerOwner?: boolean
userPermissions?: Record<string, string>

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/FileChmodData.html: -------------------------------------------------------------------------------- 1 | FileChmodData | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FileChmodData

2 |

Options for changing file permissions.

3 |

Hierarchy

  • FileChmodData

Index

Properties

Properties

file: string
mode: number

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/FileConfig.html: -------------------------------------------------------------------------------- 1 | FileConfig | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface FileConfig

2 |

Represents the configuration for the pterojs.json file.

3 |

Hierarchy

  • FileConfig

Index

Properties

application?: Record<string, OptionSpec>
client?: Record<string, OptionSpec>

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/PermissionDescriptor.html: -------------------------------------------------------------------------------- 1 | PermissionDescriptor | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface PermissionDescriptor

2 |

Represents a permission descriptor for grouped permissions. 3 | Available permission groups:

4 |
    5 |
  • websocket
  • 6 |
  • control
  • 7 |
  • user
  • 8 |
  • file
  • 9 |
  • backup
  • 10 |
  • allocation
  • 11 |
  • startup
  • 12 |
  • database
  • 13 |
  • schedule
  • 14 |
  • settings
  • 15 |
16 |

Hierarchy

  • PermissionDescriptor

Index

Properties

Properties

description: string
keys: Record<string, string>

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/WebSocketAuth.html: -------------------------------------------------------------------------------- 1 | WebSocketAuth | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface WebSocketAuth

Hierarchy

  • WebSocketAuth

Index

Properties

Properties

data: { socket: string; token: string }

Type declaration

  • socket: string
  • token: string

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /docs/interfaces/WebSocketPayload.html: -------------------------------------------------------------------------------- 1 | WebSocketPayload | @devnote-dev/pterojs
Options
All
  • Public
  • Public/Protected
  • All
Menu

Interface WebSocketPayload

Hierarchy

  • WebSocketPayload

Index

Properties

Properties

args?: string[]
event: string

Legend

  • Constructor
  • Property
  • Method
  • Inherited constructor
  • Inherited property
  • Inherited method
  • Property
  • Static method

Settings

Theme

Generated using TypeDoc

-------------------------------------------------------------------------------- /examples/create_server.js: -------------------------------------------------------------------------------- 1 | const { PteroApp } = require('@devnote-dev/pterojs'); 2 | 3 | const app = new PteroApp( 4 | 'https://pterodactyl.test', 5 | 'ptlc_nkan3orij9fjewfio4fni34nf4', 6 | ); 7 | 8 | (async () => { 9 | const alloc = await app.allocations.fetchAvailable(2, true); 10 | const server = await app.servers.create({ 11 | name: 'server create test', 12 | user: 4, 13 | egg: 16, 14 | dockerImage: 'ghcr.io/parkervcp/yolks:nodejs_16', 15 | startup: 16 | 'if [[ -d .git ]] && [[ {{AUTO_UPDATE}} == "1" ]]; then git pull; fi; if [[ ! -z ${NODE_PACKAGES} ]]; then /usr/local/bin/npm install ${NODE_PACKAGES}; fi; if [[ ! -z ${UNNODE_PACKAGES} ]]; then /usr/local/bin/npm uninstall ${UNNODE_PACKAGES}; fi; if [ -f /home/container/package.json ]; then /usr/local/bin/npm install; fi; /usr/local/bin/node /home/container/{{BOT_JS_FILE}}', 17 | environment: { 18 | USER_UPLOAD: false, 19 | AUTO_UPDATE: false, 20 | BOT_JS_FILE: 'index.js', 21 | }, 22 | limits: { 23 | io: 200, 24 | }, 25 | featureLimits: { 26 | allocations: 0, 27 | backups: 0, 28 | databases: 0, 29 | }, 30 | allocation: { 31 | default: alloc.id, 32 | }, 33 | }); 34 | 35 | console.log(server); 36 | })(); 37 | -------------------------------------------------------------------------------- /examples/get_all_logs.js: -------------------------------------------------------------------------------- 1 | const { PteroClient } = require('@devnote-dev/pterojs'); 2 | 3 | const client = new PteroClient( 4 | 'https://pterodactyl.test', 5 | 'ptlc_nkan3orij9fjewfio4fni34nf4', 6 | ); 7 | const shard = client.addSocketServer('f0e206ca'); 8 | 9 | // Sends a request for logs after authorising 10 | shard.on('authSuccess', () => shard.send('send logs')); 11 | 12 | // Writes all the incoming logs to the console 13 | shard.on('consoleOutput', console.log); 14 | 15 | // Connects the shard to the server websocket 16 | shard.connect(); 17 | -------------------------------------------------------------------------------- /migrations/v2-0-1.md: -------------------------------------------------------------------------------- 1 | # Migrating to v2 2 | 3 | If you've already used PteroJS then this guide is for you! If not then read it anyway, it may be useful! 4 | 5 | First and foremost, make sure you've updated to v2, you can check by running `npm info @devnote-dev/pterojs`. This package requires NodeJS v14 or above to work, you can check this with `npm -v`. 6 | 7 | ### Type Expansion 8 | 9 | Now that the package is in TypeScript, full TS typings and interfaces support has been made use of, this can be found mainly in the [commons](../src/common/) directory which includes typings for the whole package, as well as group-specific typings for the application API and client API. 10 | 11 | ### User Structures 12 | 13 | The `PteroUser` and `PteroSubUser` classes have been renamed to `User` and `SubUser` respectively. `ClientUser` has also been renamed to `Account`. This is to reflect the models used by Pterodactyl. 14 | 15 | ### Meta 16 | 17 | The `ClientServerManager#pageData` property has been renamed to `meta` for API consistency. 18 | 19 | ### Parameter Resolution 20 | 21 | Parameters that used to accept objects or numbers/strings have been changed to only accept the base type (number or string). This mainly applies to methods such as `UserManager#update` which accepted a user object in place of the user ID. 22 | 23 | ```diff 24 | const user = await app.users.fetch(7); 25 | - await app.users.update(user, { ... }); 26 | + await app.users.update(user.id, { ... }); 27 | ``` 28 | 29 | This change was made because of the very little validation that was in place for verifying the provided object. It also allowed for outdated information to be passed along as supplementary values to API requests, leading to bigger issues. These methods have been changed to fetch the updated object resource from the API before continuing with operations. 30 | 31 | ### Permissions 32 | 33 | Permission flags have been changed from numbers to strings for consistency with the API. In other words, permission resolution from numeric values is no longer possible. 34 | 35 | ```diff 36 | - const perms = new Permissions(2, 3, 4, 5); 37 | + const perms = new Permissions( 38 | + 'control.console', 39 | + 'control.start', 40 | + 'control.stop', 41 | + 'control.restart' 42 | + ); 43 | ``` 44 | 45 | Additionally, you can specify permission groups as method arguments: 46 | 47 | ```ts 48 | const perms = new Permissions(...Permissions.CONTROL); 49 | ``` 50 | 51 | Available permission groups on the Permissions class are: `CONTROL`, `USERS`, `FILES`, `BACKUPS`, `ALLOCATIONS`, `STARTUPS`, `DATABASES`, `SCHEDULES`, `SETTINGS`, and `ADMIN`. 52 | 53 | The `Permissions#has` method has been split into `hasAny` and `hasAll`. Asterisks (`*`) now properly work for resolution and represents all permissions. The `Permissions#raw` property has been renamed to `value`. The `Permissions#toStrings` method has been removed as it can be accessed through the `Permissions#value` property. The `Permissions#fromStrings` method has been removed as the `resolve` class method already does this. 54 | 55 | ### Model Properties 56 | 57 | The `env` property for the server creation object has been renamed to `environment`, and the `image` property to `dockerImage`. Previously PteroJS changed these properties internally but it was confusing and was not consistent with the API. 58 | 59 | #### Previous Usage 60 | 61 | ```ts 62 | // don't do this 63 | const server = await app.servers.create({ 64 | name: 'my mc server', 65 | user: 7, 66 | egg: 2, 67 | image: 'ghcr.io/pterodactyl/yolks:java_17', 68 | startup: 69 | 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}', 70 | env: { 71 | MINECRAFT_VERSION: '1.18.2', 72 | SERVER_JARFILE: 'server.jar', 73 | BUILD_NUMBER: 'latest', 74 | }, 75 | featureLimits: { 76 | allocations: 2, 77 | backups: 3, 78 | databases: 0, 79 | }, 80 | allocation: { 81 | default: 12, 82 | }, 83 | }); 84 | ``` 85 | 86 | #### New Usage 87 | 88 | ```ts 89 | // do this! 90 | const server = await app.servers.create({ 91 | name: 'my mc server', 92 | user: 7, 93 | egg: 2, 94 | dockerImage: 'ghcr.io/pterodactyl/yolks:java_17', 95 | startup: 96 | 'java -Xms128M -Xmx{{SERVER_MEMORY}}M -Dterminal.jline=false -Dterminal.ansi=true -jar {{SERVER_JARFILE}}', 97 | environment: { 98 | MINECRAFT_VERSION: '1.18.2', 99 | SERVER_JARFILE: 'server.jar', 100 | BUILD_NUMBER: 'latest', 101 | }, 102 | featureLimits: { 103 | allocations: 2, 104 | backups: 3, 105 | }, 106 | allocation: { 107 | default: 12, 108 | }, 109 | }); 110 | ``` 111 | 112 | ### Two Factor 113 | 114 | The two-factor methods on the `Account` class have been renamed to follow proper casing convetions (e.g. `get2faCode` -> `get2FACode`). In most cases you only have to capitalize "FA" in your code. 115 | 116 | ### File Actions 117 | 118 | The `FileManager#download` method's functionality has been changed to actually download files now, it's previous functionality has been reimplemented as `FileManager#getDownloadURL`. This makes downloading files through PteroJS finally possible, and only requires calling the new method (which calls the old method internally). 119 | 120 | #### Example 121 | 122 | ```ts 123 | const server = await client.servers.fetch(4); 124 | // returns the download url, not the download 125 | const url = await server.files.getDownloadURL('config.yml'); 126 | console.log(url); 127 | 128 | // downloads the 'server.jar' file from the server and writes it to the path 129 | await server.files.download('server.jar', 'jars/server.jar'); 130 | ``` 131 | 132 | PteroJS will assume the destination path is a relative file path from your current working directory unless an absolute path is specified. 133 | 134 | The `FileManager#upload` method has been replaced with the `FileManager#getUploadURL` method, however, file uploading through PteroJS has not been implemented yet. 135 | 136 | The `File#isEditable` property has been removed; it was never part of the API and doesn't need to be. 137 | 138 | ### Validation Errors 139 | 140 | Internal validation handling is starting to be implemented within the package to save time before interacting with the API. These errors are designed to be more specific and useful than other package errors and will be improved as more features come. 141 | 142 | ### Manager Centralisation 143 | 144 | The `delete` method for most structures has been removed, this is because of conflicts with the manager's cache when using the instance instead of the manager. 145 | 146 | #### Example 147 | 148 | ```diff 149 | const user = await app.users.fetch(6); 150 | - await user.delete(); 151 | + await app.users.delete(user.id); 152 | ``` 153 | 154 | ## Other Notes 155 | 156 | You can view the whole list of changes in the [changelog](../CHANGELOG.md). Happy coding! 157 | 158 | devnote-dev - 21/06/2022 159 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@devnote-dev/pterojs", 3 | "version": "2.2.2", 4 | "description": "A verbose API library for Pterodactyl", 5 | "author": "Devonte ", 6 | "contributors": [ 7 | "Chelog ", 8 | "Cain " 9 | ], 10 | "license": "MIT", 11 | "bugs": { 12 | "url": "https://github.com/PteroPackages/PteroJS/issues" 13 | }, 14 | "homepage": "https://pteropackages.github.io/PteroJS/", 15 | "repository": { 16 | "type": "git", 17 | "url": "git+https://github.com/PteroPackages/PteroJS.git" 18 | }, 19 | "main": "./dist/index.js", 20 | "types": "./dist/index.d.ts", 21 | "exports": { 22 | "require": "./dist/index.js", 23 | "import": "./dist/index.mjs", 24 | "default": "./dist/index.mjs", 25 | "types": "./dist/index.d.ts", 26 | "node": "./dist/index.js" 27 | }, 28 | "files": [ 29 | "dist", 30 | "CHANGELOG.md", 31 | "LICENSE", 32 | "pterojs.json", 33 | "README.md" 34 | ], 35 | "engines": { 36 | "node": ">=14.16" 37 | }, 38 | "scripts": { 39 | "prepublishOnly": "tsup", 40 | "prepublish": "npm run build", 41 | "build": "tsup", 42 | "docs": "typedoc --out docs --excludePrivate src/index.ts", 43 | "format": "prettier --write **/**.{js,ts}" 44 | }, 45 | "keywords": [ 46 | "api", 47 | "nodejs", 48 | "library", 49 | "wrapper", 50 | "javascript", 51 | "typescript", 52 | "pterodactyl", 53 | "pterodactyl-api" 54 | ], 55 | "dependencies": { 56 | "axios": "^0.27.2", 57 | "form-data": "^4.0.0", 58 | "ws": "^8.8.1" 59 | }, 60 | "devDependencies": { 61 | "@types/node": "^17.0.45", 62 | "@types/ws": "^8.5.3", 63 | "prettier": "^2.7.1", 64 | "ts-node": "^10.9.1", 65 | "tsup": "^6.3.0", 66 | "typedoc": "^0.22.18" 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /pterojs.json: -------------------------------------------------------------------------------- 1 | { 2 | "application": { 3 | "users": { 4 | "fetch": true, 5 | "cache": true, 6 | "max": -1 7 | }, 8 | "nodes": { 9 | "fetch": true, 10 | "cache": true, 11 | "max": -1 12 | }, 13 | "nests": { 14 | "fetch": false, 15 | "cache": false, 16 | "max": 0 17 | }, 18 | "servers": { 19 | "fetch": false, 20 | "cache": true, 21 | "max": 10 22 | }, 23 | "locations": { 24 | "fetch": false, 25 | "cache": false, 26 | "max": 0 27 | } 28 | }, 29 | "client": { 30 | "ws": true, 31 | "fetchClient": true, 32 | "servers": { 33 | "fetch": true, 34 | "cache": true, 35 | "max": -1 36 | }, 37 | "subUsers": { 38 | "fetch": false, 39 | "cache": false, 40 | "max": 0 41 | }, 42 | "disableEvents": ["debug"] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/application/ApplicationDatabaseManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroApp } from '.'; 2 | import { BaseManager } from '../structures/BaseManager'; 3 | import { Dict } from '../structures/Dict'; 4 | import { ApplicationDatabase } from '../common/app'; 5 | import { FetchOptions, Include } from '../common'; 6 | import { ValidationError } from '../structures/Errors'; 7 | import caseConv from '../util/caseConv'; 8 | import endpoints from './endpoints'; 9 | 10 | export class ApplicationDatabaseManager extends BaseManager { 11 | public client: PteroApp; 12 | public cache: Dict; 13 | public serverId: number; 14 | 15 | /** Allowed filter arguments for server databases. */ 16 | get FILTERS() { 17 | return Object.freeze([]); 18 | } 19 | 20 | /** Allowed include arguments for server databases. */ 21 | get INCLUDES() { 22 | return Object.freeze(['host', 'password']); 23 | } 24 | 25 | /** Allowed sort arguments for server databases. */ 26 | get SORTS() { 27 | return Object.freeze([]); 28 | } 29 | 30 | constructor(client: PteroApp, serverId: number) { 31 | super(); 32 | this.client = client; 33 | this.cache = new Dict(); 34 | this.serverId = serverId; 35 | } 36 | 37 | _patch(data: any): any { 38 | if (data.data) { 39 | const res = new Dict(); 40 | for (let o of data.data) { 41 | const d = caseConv.toCamelCase( 42 | o.attributes, 43 | ); 44 | } 45 | this.cache.update(res); 46 | return res; 47 | } 48 | 49 | const d = caseConv.toCamelCase(data.attributes); 50 | this.cache.set(d.id, d); 51 | return d; 52 | } 53 | 54 | async fetch( 55 | id: number, 56 | options?: Include, 57 | ): Promise; 58 | async fetch( 59 | options?: Include, 60 | ): Promise>; 61 | async fetch( 62 | op?: number | Include, 63 | ops: Include = {}, 64 | ): Promise { 65 | let path: string; 66 | switch (typeof op) { 67 | case 'number': { 68 | if (!ops.force && this.cache.has(op)) return this.cache.get(op); 69 | 70 | path = endpoints.servers.databases.get(this.serverId, op); 71 | break; 72 | } 73 | case 'undefined': 74 | case 'object': { 75 | path = endpoints.servers.databases.main(this.serverId); 76 | if (op) ops = op; 77 | break; 78 | } 79 | default: 80 | throw new ValidationError( 81 | `expected database id or fetch options; got ${typeof op}`, 82 | ); 83 | } 84 | 85 | const data = await this.client.requests.get(path, ops, null, this); 86 | return this._patch(data); 87 | } 88 | 89 | async create( 90 | database: string, 91 | remote: string, 92 | host: number, 93 | ): Promise { 94 | if (!/^[0-9%.]{1,15}$/.test(remote)) 95 | throw new ValidationError( 96 | 'remote did not pass the required validation: /^[0-9%.]{1,15}$/', 97 | ); 98 | 99 | const data = await this.client.requests.post( 100 | endpoints.servers.databases.main(this.serverId), 101 | { database, remote, host }, 102 | ); 103 | return this._patch(data); 104 | } 105 | 106 | async resetPasword(id: number): Promise { 107 | await this.client.requests.post( 108 | endpoints.servers.databases.reset(this.serverId, id), 109 | ); 110 | } 111 | 112 | async delete(id: number): Promise { 113 | await this.client.requests.delete( 114 | endpoints.servers.databases.get(this.serverId, id), 115 | ); 116 | this.cache.delete(id); 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /src/application/NestEggsManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroApp } from '.'; 2 | import { BaseManager } from '../structures/BaseManager'; 3 | import { Dict } from '../structures/Dict'; 4 | import { FetchOptions, Include } from '../common'; 5 | import { Egg } from '../common/app'; 6 | import caseConv from '../util/caseConv'; 7 | import endpoints from './endpoints'; 8 | 9 | export class NestEggsManager extends BaseManager { 10 | public client: PteroApp; 11 | public cache: Dict; 12 | 13 | /** Allowed filter arguments for eggs (none). */ 14 | get FILTERS() { 15 | return Object.freeze([]); 16 | } 17 | 18 | /** 19 | * Allowed include arguments for eggs: 20 | * * nest 21 | * * servers 22 | * * config 23 | * * script 24 | * * variables 25 | */ 26 | get INCLUDES() { 27 | return Object.freeze([ 28 | 'nest', 29 | 'servers', 30 | 'config', 31 | 'script', 32 | 'variables', 33 | ]); 34 | } 35 | 36 | /** Allowed sort arguments for eggs (none). */ 37 | get SORTS() { 38 | return Object.freeze([]); 39 | } 40 | 41 | constructor(client: PteroApp) { 42 | super(); 43 | this.client = client; 44 | this.cache = new Dict(); 45 | } 46 | 47 | /** 48 | * Transforms the raw egg object(s) into typed objects. 49 | * @param data The resolvable egg object(s). 50 | * @returns The resolved egg object(s). 51 | */ 52 | _patch(data: any): any { 53 | if (data?.data) { 54 | const res = new Dict(); 55 | for (let o of data.data) { 56 | let e = caseConv.toCamelCase(o.attributes); 57 | e.createdAt = new Date(e.createdAt); 58 | e.updatedAt &&= new Date(e.updatedAt); 59 | res.set(e.id, e); 60 | } 61 | 62 | this.cache.update(res); 63 | return res; 64 | } 65 | 66 | let e = caseConv.toCamelCase(data.attributes); 67 | e.createdAt = new Date(e.createdAt); 68 | e.updatedAt &&= new Date(e.updatedAt); 69 | this.cache.set(e.id, e); 70 | return e; 71 | } 72 | 73 | /** 74 | * @param id The ID of the egg. 75 | * @returns The formatted URL to the egg in the admin panel. 76 | */ 77 | adminURLFor(id: number): string { 78 | return `${this.client.domain}/admin/nests/egg/${id}`; 79 | } 80 | 81 | /** 82 | * Fetches an egg from the API by its ID. This will check the cache first unless the force 83 | * option is specified. 84 | * 85 | * @param nest The ID of the nest. 86 | * @param id The ID of the egg. 87 | * @param [options] Additional fetch options. 88 | * @returns The fetched egg. 89 | * @example 90 | * ``` 91 | * app.nests.eggs.fetch(1, 16).then(console.log).catch(console.error); 92 | * ``` 93 | */ 94 | async fetch( 95 | nest: number, 96 | id: number, 97 | options?: Include, 98 | ): Promise; 99 | /** 100 | * Fetches a list of eggs from the API by its ID. This will check the cache first unless the 101 | * force option is specified. 102 | * 103 | * @param nest The ID of the nest. 104 | * @param [options] Additional fetch options. 105 | * @returns The fetched eggs. 106 | * @example 107 | * ``` 108 | * app.nests.eggs.fetch(1) 109 | * .then(eggs => eggs.forEach(e => console.log(e))) 110 | * .catch(console.error); 111 | * ``` 112 | */ 113 | async fetch( 114 | nest: number, 115 | options?: Include, 116 | ): Promise>; 117 | async fetch( 118 | nest: number, 119 | op1?: number | Include, 120 | op2: Include = {}, 121 | ): Promise { 122 | let path = endpoints.nests.eggs.main(nest); 123 | 124 | if (typeof op1 === 'number') { 125 | if (!op2.force && this.cache.has(op1)) return this.cache.get(op1); 126 | 127 | path = endpoints.nests.eggs.get(nest, op1); 128 | } else { 129 | if (op1) op2 = op1; 130 | } 131 | 132 | const data = await this.client.requests.get(path, op2, null, this); 133 | return this._patch(data); 134 | } 135 | } 136 | -------------------------------------------------------------------------------- /src/application/NestManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroApp } from '.'; 2 | import { Dict } from '../structures/Dict'; 3 | import { FetchOptions, Include, PaginationMeta } from '../common'; 4 | import { Nest } from '../common/app'; 5 | import { NestEggsManager } from './NestEggsManager'; 6 | import caseConv from '../util/caseConv'; 7 | import endpoints from './endpoints'; 8 | import { BaseManager } from '../structures/BaseManager'; 9 | 10 | export class NestManager extends BaseManager { 11 | public client: PteroApp; 12 | public cache: Dict; 13 | public meta: PaginationMeta; 14 | public eggs: NestEggsManager; 15 | 16 | /** Allowed filter arguments for nests (none). */ 17 | get FILTERS() { 18 | return Object.freeze([]); 19 | } 20 | 21 | /** 22 | * Allowed include arguments for nests: 23 | * * eggs 24 | * * servers 25 | */ 26 | get INCLUDES() { 27 | return Object.freeze(['eggs', 'servers']); 28 | } 29 | 30 | /** Allowed sort arguments for nests (none). */ 31 | get SORTS() { 32 | return Object.freeze([]); 33 | } 34 | 35 | constructor(client: PteroApp) { 36 | super(); 37 | this.client = client; 38 | this.cache = new Dict(); 39 | this.eggs = new NestEggsManager(client); 40 | this.meta = { 41 | current: 0, 42 | total: 0, 43 | count: 0, 44 | perPage: 0, 45 | totalPages: 0, 46 | }; 47 | } 48 | 49 | /** 50 | * Transforms the raw nest object(s) into typed objects. 51 | * @param data The resolvable nest object(s). 52 | * @returns The resolved nest object(s). 53 | */ 54 | _patch(data: any): any { 55 | if (data?.meta?.pagination) { 56 | this.meta = caseConv.toCamelCase(data.meta.pagination, { 57 | ignore: ['current_page'], 58 | }); 59 | this.meta.current = data.meta.pagination.current_page; 60 | } 61 | 62 | if (data?.data) { 63 | const res = new Dict(); 64 | for (let o of data.data) { 65 | const n = caseConv.toCamelCase(o.attributes); 66 | n.createdAt = new Date(n.createdAt); 67 | n.updatedAt &&= new Date(n.updatedAt); 68 | res.set(n.id, n); 69 | } 70 | 71 | if (this.client.options.nests.cache) this.cache.update(res); 72 | return res; 73 | } 74 | 75 | const n = caseConv.toCamelCase(data.attributes); 76 | n.createdAt = new Date(n.createdAt); 77 | n.updatedAt &&= new Date(n.updatedAt); 78 | if (this.client.options.nodes.cache) this.cache.set(n.id, n); 79 | return n; 80 | } 81 | 82 | /** 83 | * @param id The ID of the nest. 84 | * @returns The formatted URL to the nest in the admin panel. 85 | */ 86 | adminURLFor(id: number): string { 87 | return `${this.client.domain}/admin/nests/view/${id}`; 88 | } 89 | 90 | /** 91 | * Fetches a nest from the API with the given options (default is undefined). 92 | * @param id The ID of the nest. 93 | * @param [include] Optional include arguments. 94 | * @returns The fetched nest. 95 | * @example 96 | * ``` 97 | * app.nests.fetch(1).then(console.log).catch(console.error); 98 | * ``` 99 | */ 100 | async fetch(id: number, include?: string[]): Promise; 101 | /** 102 | * Fetches a list of nests from the API with the given options (default is undefined). 103 | * @param [include] Optional include arguments. 104 | * @returns The fetched nest. 105 | * @example 106 | * ``` 107 | * app.nests.fetch() 108 | * .then(nests => nests.forEach(n => console.log(n))) 109 | * .catch(console.error); 110 | * ``` 111 | */ 112 | async fetch(include?: string[]): Promise>; 113 | async fetch(op?: number | string[], include: string[] = []): Promise { 114 | let path = endpoints.nests.main; 115 | if (typeof op === 'number') { 116 | path = endpoints.nests.get(op); 117 | } else { 118 | include.push(...(op || [])); 119 | } 120 | 121 | const data = await this.client.requests.get( 122 | path, 123 | { include } as Include, 124 | null, 125 | this, 126 | ); 127 | return this._patch(data); 128 | } 129 | } 130 | -------------------------------------------------------------------------------- /src/application/NodeAllocationManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroApp } from '.'; 2 | import { BaseManager } from '../structures/BaseManager'; 3 | import { Allocation } from '../common/app'; 4 | import { Dict } from '../structures/Dict'; 5 | import { FetchOptions, Include, PaginationMeta } from '../common'; 6 | import caseConv from '../util/caseConv'; 7 | import endpoints from './endpoints'; 8 | 9 | export class NodeAllocationManager extends BaseManager { 10 | public client: PteroApp; 11 | public cache: Dict>; 12 | public meta: PaginationMeta; 13 | 14 | /** Allowed filter arguments for allocations. */ 15 | get FILTERS() { 16 | return Object.freeze([]); 17 | } 18 | 19 | /** Allowed include arguments for allocations. */ 20 | get INCLUDES() { 21 | return Object.freeze(['node', 'server']); 22 | } 23 | 24 | /** Allowed sort arguments for allocations. */ 25 | get SORTS() { 26 | return Object.freeze([]); 27 | } 28 | 29 | constructor(client: PteroApp) { 30 | super(); 31 | this.client = client; 32 | this.cache = new Dict(); 33 | this.meta = { 34 | current: 0, 35 | total: 0, 36 | count: 0, 37 | perPage: 0, 38 | totalPages: 0, 39 | }; 40 | } 41 | 42 | _patch(node: number, data: any): any { 43 | if (data?.meta?.pagination) { 44 | this.meta = caseConv.toCamelCase(data.meta.pagination, { 45 | ignore: ['current_page'], 46 | }); 47 | this.meta.current = data.meta.pagination.current_page; 48 | } 49 | 50 | const res = new Dict(); 51 | for (let o of data.data) { 52 | const a = caseConv.toCamelCase(o.attributes); 53 | res.set(a.id, a); 54 | } 55 | 56 | const all = (this.cache.get(node) || new Dict()).join(res); 57 | this.cache.set(node, all); 58 | return res; 59 | } 60 | 61 | /** 62 | * @param id The ID of the allocation. 63 | * @returns The formatted URL to the allocation in the admin panel. 64 | */ 65 | adminURLFor(id: number): string { 66 | return `${this.client.domain}/admin/nodes/view/${id}/allocation`; 67 | } 68 | 69 | /** 70 | * Fetches a list of allocations on a specific node from the API with the given options 71 | * (default is undefined). 72 | * @see {@link Include} and {@link FetchOptions}. 73 | * 74 | * @param [node] Node id 75 | * @param [options] Additional fetch options. 76 | * @returns The fetched allocations. 77 | * @example 78 | * ``` 79 | * app.allocations.fetch(4, { page: 3 }) 80 | * .then(console.log) 81 | * .catch(console.error); 82 | * ``` 83 | */ 84 | async fetch( 85 | node: number, 86 | options: Include = {}, 87 | ): Promise> { 88 | if (!options.force) { 89 | const a = this.cache.get(node); 90 | if (a) return Promise.resolve(a); 91 | } 92 | 93 | const data = await this.client.requests.get( 94 | endpoints.nodes.allocations.main(node), 95 | options, 96 | null, 97 | this, 98 | ); 99 | return this._patch(node, data); 100 | } 101 | 102 | /** 103 | * Fetches all allocations on a specific node from the API with the given options (default is undefined). 104 | * @see {@link Include} and {@link FetchOptions}. 105 | * 106 | * @param [node] Node id 107 | * @param [options] Additional fetch options. 108 | * @returns The fetched allocations. 109 | * @example 110 | * ``` 111 | * app.allocations.fetchAll().then(console.log).catch(console.error); 112 | * ``` 113 | */ 114 | 115 | fetchAll( 116 | node: number, 117 | options?: Include>, 118 | ): Promise> { 119 | return this.getFetchAll(node, options); 120 | } 121 | 122 | /** 123 | * Fetches the available allocations on a node and returns a single one. 124 | * @param node The ID of the node. 125 | * @param single Whether to return a single allocation. 126 | * @returns The available allocation(s). 127 | * @example 128 | * ``` 129 | * app.allocations.fetchAvailable(4, true) 130 | * .then(console.log) 131 | * .catch(console.error); 132 | * ``` 133 | */ 134 | async fetchAvailable( 135 | node: number, 136 | single: true, 137 | ): Promise; 138 | /** 139 | * Fetches the available allocations on a node. 140 | * @param node The ID of the node. 141 | * @param single Whether to return a single allocation. 142 | * @returns The available allocation(s). 143 | * @example 144 | * ``` 145 | * app.allocations.fetchAvailable(4, false) 146 | * .then(all => all.forEach(a => console.log(a))) 147 | * .catch(console.error); 148 | * ``` 149 | */ 150 | async fetchAvailable( 151 | node: number, 152 | single: false, 153 | ): Promise>; 154 | async fetchAvailable(node: number, single: boolean): Promise { 155 | const all = await this.fetchAll(node, { force: true }); 156 | return single 157 | ? all.filter(a => !a.assigned).first() 158 | : all.filter(a => !a.assigned); 159 | } 160 | 161 | /** 162 | * Creates a number of allocations based on the ports specified. Note that the created 163 | * allocations will not be returned due to the number that can be created in a single request, 164 | * which can cause unwanted issues. 165 | * @param node The ID of the node. 166 | * @param ip The IP for the allocation. 167 | * @param ports A list of ports or port ranges for the allocation. 168 | * @example 169 | * ``` 170 | * app.allocations.create(4, '10.0.0.1', ['8000-9000']) 171 | * .catch(console.error); 172 | * ``` 173 | */ 174 | async create(node: number, ip: string, ports: string[]): Promise { 175 | if (!ports.every(p => typeof p === 'string')) 176 | throw new TypeError( 177 | 'Allocation ports must be a string integer or string range.', 178 | ); 179 | 180 | for (const port of ports) { 181 | if (!port.includes('-')) continue; 182 | const [_start, _stop] = port.split('-'); 183 | const start = Number(_start), 184 | stop = Number(_stop); 185 | 186 | if (start > stop) 187 | throw new RangeError('Start cannot be greater than stop.'); 188 | 189 | if (start <= 1024 || stop > 65535) 190 | throw new RangeError( 191 | 'Port range must be between 1024 and 65535.', 192 | ); 193 | 194 | if (stop - start > 1000) 195 | throw new RangeError('Maximum port range exceeded (1000).'); 196 | } 197 | 198 | await this.client.requests.post( 199 | endpoints.nodes.allocations.main(node), 200 | { ip, ports }, 201 | ); 202 | } 203 | 204 | /** 205 | * Deletes an allocation from a node. 206 | * @param node The ID of the node. 207 | * @param id The ID of the allocation. 208 | * @example 209 | * ``` 210 | * app.allocations.delete(4, 92).catch(console.error); 211 | * ``` 212 | */ 213 | async delete(node: number, id: number): Promise { 214 | await this.client.requests.delete( 215 | endpoints.nodes.allocations.get(node, id), 216 | ); 217 | this.cache.get(node)?.delete(id); 218 | } 219 | } 220 | -------------------------------------------------------------------------------- /src/application/endpoints.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | users: { 3 | main: '/users', 4 | get: (u: number) => `/users/${u}`, 5 | ext: (u: string) => `/users/external/${u}`, 6 | }, 7 | nodes: { 8 | main: '/nodes', 9 | get: (n: number) => `/nodes/${n}`, 10 | deploy: '/nodes/deployable', 11 | config: (n: number) => `/nodes/${n}/configuration`, 12 | allocations: { 13 | main: (n: number) => `/nodes/${n}/allocations`, 14 | get: (n: number, a: number) => `/nodes/${n}/allocations/${a}`, 15 | }, 16 | }, 17 | servers: { 18 | main: '/servers', 19 | get: (s: number) => `/servers/${s}`, 20 | ext: (s: string) => `/servers/external/${s}`, 21 | details: (s: number) => `/servers/${s}/details`, 22 | build: (s: number) => `/servers/${s}/build`, 23 | startup: (s: number) => `/servers/${s}/startup`, 24 | suspend: (s: number) => `/servers/${s}/suspend`, 25 | unsuspend: (s: number) => `/servers/${s}/unsuspend`, 26 | reinstall: (s: number) => `/servers/${s}/reinstall`, 27 | databases: { 28 | main: (s: number) => `/servers/${s}/databases`, 29 | get: (s: number, id: number) => `/servers/${s}/databases/${id}`, 30 | reset: (s: number, id: number) => 31 | `/servers/${s}/databases/${id}/reset-password`, 32 | }, 33 | }, 34 | locations: { 35 | main: '/locations', 36 | get: (l: number) => `/locations/${l}`, 37 | }, 38 | nests: { 39 | main: '/nests', 40 | get: (n: number) => `/nests/${n}`, 41 | eggs: { 42 | main: (n: number) => `/nests/${n}/eggs`, 43 | get: (n: number, e: number) => `/nests/${n}/eggs/${e}`, 44 | }, 45 | }, 46 | }; 47 | -------------------------------------------------------------------------------- /src/application/index.ts: -------------------------------------------------------------------------------- 1 | import { ApplicationServerManager } from './ApplicationServerManager'; 2 | import { NestManager } from './NestManager'; 3 | import { NodeAllocationManager } from './NodeAllocationManager'; 4 | import { NodeLocationManager } from './NodeLocationManager'; 5 | import { NodeManager } from './NodeManager'; 6 | import { RequestManager } from '../http/RequestManager'; 7 | import { UserManager } from './UserManager'; 8 | import { OptionSpec } from '../common'; 9 | import { ValidationError } from '../structures/Errors'; 10 | import loader from '../util/config'; 11 | 12 | /** 13 | * The base class for the Pterodactyl application API. 14 | * This operates using a Pterodactyl API key which can be found at 15 | * or if you are using a client API key. 16 | * 17 | * **Warning:** Keep your API key private at all times. Exposing this can lead 18 | * to your servers, nodes, configurations and more being corrupted and/or deleted. 19 | */ 20 | export class PteroApp { 21 | /** 22 | * The domain for your Pterodactyl panel. This should be the main URL only 23 | * (not "/api"). Any additional paths will count as the API path. 24 | */ 25 | public domain: string; 26 | 27 | /** 28 | * The API key for your Pterodactyl panel. This should be kept private at 29 | * all times. Full access must be granted in the panel for the whole library 30 | * to be accessible. 31 | */ 32 | public auth: string; 33 | 34 | /** @deprecated To be replaced with a better system. */ 35 | public options: Record; 36 | 37 | public allocations: NodeAllocationManager; 38 | public locations: NodeLocationManager; 39 | public nests: NestManager; 40 | public nodes: NodeManager; 41 | public servers: ApplicationServerManager; 42 | public users: UserManager; 43 | public requests: RequestManager; 44 | 45 | constructor( 46 | domain: string, 47 | auth: string, 48 | options: Record = {}, 49 | ) { 50 | if (!/https?\:\/\/(?:localhost\:\d{4}|[\w\.\-]{3,256})/gi.test(domain)) 51 | throw new ValidationError( 52 | "Domain URL must start with 'http://' or 'https://' and " + 53 | 'must be bound to a port if using localhost.', 54 | ); 55 | 56 | if (domain.endsWith('/')) domain = domain.slice(0, -1); 57 | this.domain = domain; 58 | this.auth = auth; 59 | this.options = loader.appConfig({ application: options }); 60 | 61 | this.allocations = new NodeAllocationManager(this); 62 | this.locations = new NodeLocationManager(this); 63 | this.nests = new NestManager(this); 64 | this.nodes = new NodeManager(this); 65 | this.servers = new ApplicationServerManager(this); 66 | this.users = new UserManager(this); 67 | this.requests = new RequestManager('Application', domain, auth); 68 | } 69 | 70 | get ping(): number { 71 | return this.requests._ping; 72 | } 73 | } 74 | -------------------------------------------------------------------------------- /src/builders/Node.ts: -------------------------------------------------------------------------------- 1 | import { Builder } from './base'; 2 | import { CreateNodeOptions } from '../common/app'; 3 | import { ValidationError } from '../structures/Errors'; 4 | 5 | export class NodeBuilder extends Builder { 6 | private name: string; 7 | private description: string | undefined; 8 | private locationId: number; 9 | private public: boolean; // ironic 10 | private fqdn: string; 11 | private scheme: string; 12 | private behindProxy: boolean; 13 | private memory: number; 14 | private memoryOverallocate: number; 15 | private disk: number; 16 | private diskOverallocate: number; 17 | private daemonBase: string; 18 | private daemonSftp: number; 19 | private daemonListen: number; 20 | private maintenanceMode: boolean; 21 | private uploadSize: number; 22 | 23 | constructor() { 24 | super(); 25 | 26 | this.name = ''; 27 | this.description = undefined; 28 | this.locationId = 0; 29 | this.public = false; 30 | this.fqdn = ''; 31 | this.scheme = ''; 32 | this.behindProxy = false; 33 | this.memory = 0; 34 | this.memoryOverallocate = -1; 35 | this.disk = 0; 36 | this.diskOverallocate = -1; 37 | this.daemonBase = '/var/lib/pterodactyl/volumes'; 38 | this.daemonSftp = 2022; 39 | this.daemonListen = 8080; 40 | this.maintenanceMode = false; 41 | this.uploadSize = 0; 42 | } 43 | 44 | setName(name: string): this { 45 | this.name = name; 46 | return this; 47 | } 48 | 49 | setDescription(description: string): this { 50 | this.description = description; 51 | return this; 52 | } 53 | 54 | setLocationId(id: number): this { 55 | this.locationId = id; 56 | return this; 57 | } 58 | 59 | setPublic(value: boolean): this { 60 | this.public = value; 61 | return this; 62 | } 63 | 64 | setFQDN(fqdn: string): this { 65 | this.fqdn = fqdn; 66 | return this; 67 | } 68 | 69 | setScheme(scheme: string): this { 70 | this.scheme = scheme; 71 | return this; 72 | } 73 | 74 | setBehindProxy(value: boolean): this { 75 | this.behindProxy = value; 76 | return this; 77 | } 78 | 79 | setMemory(memory: number, overallocate?: number): this { 80 | this.memory = memory; 81 | if (overallocate) this.memoryOverallocate = overallocate; 82 | return this; 83 | } 84 | 85 | setDisk(disk: number, overallocate?: number): this { 86 | this.disk = disk; 87 | if (overallocate) this.diskOverallocate = overallocate; 88 | return this; 89 | } 90 | 91 | setDaemonBase(base: string): this { 92 | this.daemonBase = base; 93 | return this; 94 | } 95 | 96 | setDaemonSFTP(port: number): this { 97 | this.daemonSftp = port; 98 | return this; 99 | } 100 | 101 | setDaemonListen(port: number): this { 102 | this.daemonListen = port; 103 | return this; 104 | } 105 | 106 | setMaintenance(mode: boolean): this { 107 | this.maintenanceMode = mode; 108 | return this; 109 | } 110 | 111 | setUploadSize(size: number): this { 112 | this.uploadSize = size; 113 | return this; 114 | } 115 | 116 | build(): CreateNodeOptions { 117 | if (!this.name) throw new ValidationError('A node name is required'); 118 | if (!this.locationId) 119 | throw new ValidationError('A location id is required'); 120 | 121 | if (!this.fqdn) throw new ValidationError('An FQDN is required'); 122 | if (!this.scheme) 123 | throw new ValidationError('A HTTP scheme is required'); 124 | 125 | if (!this.memory) 126 | throw new ValidationError('A total memory limit is required'); 127 | 128 | if (this.memory < 1) 129 | throw new ValidationError('Memory cannot be less than 1'); 130 | 131 | if (this.memoryOverallocate < -1) 132 | throw new ValidationError( 133 | 'Overallocated memory cannot be less than -1', 134 | ); 135 | 136 | if (!this.disk) 137 | throw new ValidationError('A total disk limit is required'); 138 | 139 | if (this.disk < 1) 140 | throw new ValidationError('Disk cannot be less than 1'); 141 | 142 | if (this.diskOverallocate < -1) 143 | throw new ValidationError( 144 | 'Overallocated disk cannot be less than -1', 145 | ); 146 | 147 | if (this.uploadSize < 1 || this.uploadSize > 1024) 148 | throw new ValidationError( 149 | 'The upload size must be between 1 and 1024', 150 | ); 151 | 152 | return super.build(); 153 | } 154 | } 155 | -------------------------------------------------------------------------------- /src/builders/Server.ts: -------------------------------------------------------------------------------- 1 | import type { User } from '../structures/User'; 2 | import { Builder } from './base'; 3 | import { CreateServerOptions, Egg } from '../common/app'; 4 | import { ValidationError } from '../structures/Errors'; 5 | import { FeatureLimits, Limits } from '../common'; 6 | 7 | export class ServerBuilder extends Builder { 8 | private externalId: string | undefined; 9 | private name: string; 10 | private description: string | undefined; 11 | private user: number; 12 | private egg: number; 13 | private dockerImage: string; 14 | private startup: string; 15 | private environment: Record; 16 | private skipScripts?: boolean; 17 | private oomDisabled?: boolean; 18 | private limits: Limits; 19 | private featureLimits: FeatureLimits; 20 | private allocation?: { 21 | default: number; 22 | additional?: number[]; 23 | }; 24 | private deploy?: { 25 | locations: number[]; 26 | dedicatedIp: boolean; 27 | portRange: string[]; 28 | }; 29 | private startOnCompletion?: boolean; 30 | 31 | constructor() { 32 | super(); 33 | 34 | this.environment = {}; 35 | this.limits = { 36 | memory: 128, 37 | swap: 0, 38 | disk: 512, 39 | io: 500, 40 | cpu: 100, 41 | threads: null, 42 | }; 43 | this.featureLimits = { 44 | allocations: 1, 45 | databases: 1, 46 | backups: 1, 47 | }; 48 | this.allocation = { default: 0 }; 49 | this.deploy = { 50 | locations: [], 51 | dedicatedIp: false, 52 | portRange: [], 53 | }; 54 | } 55 | 56 | setExternalId(id: string | undefined): this { 57 | this.externalId = id; 58 | return this; 59 | } 60 | 61 | setName(name: string): this { 62 | this.name = name; 63 | return this; 64 | } 65 | 66 | setDescription(description: string): this { 67 | this.description = description; 68 | return this; 69 | } 70 | 71 | setUser(user: number | User): this { 72 | this.user = typeof user === 'number' ? user : user.id; 73 | return this; 74 | } 75 | 76 | setEgg(egg: number | Egg): this { 77 | if (typeof egg === 'number') { 78 | this.egg = egg; 79 | } else { 80 | this.egg = egg.id; 81 | this.dockerImage = Object.values(egg.dockerImages)[0]; 82 | this.startup = egg.startup; 83 | } 84 | 85 | return this; 86 | } 87 | 88 | setDockerImage(image: string): this { 89 | this.dockerImage = image; 90 | return this; 91 | } 92 | 93 | setStartup(command: string): this { 94 | this.startup = command; 95 | return this; 96 | } 97 | 98 | setVariable(key: string, value: string | number | boolean): this { 99 | this.environment[key] = value; 100 | return this; 101 | } 102 | 103 | setEnvironment(variables: Record): this { 104 | this.environment = variables; 105 | return this; 106 | } 107 | 108 | setSkipScripts(value: boolean): this { 109 | this.skipScripts = value; 110 | return this; 111 | } 112 | 113 | setOOMDisabled(value: boolean): this { 114 | this.oomDisabled = value; 115 | return this; 116 | } 117 | 118 | setLimits(limits: Partial): this { 119 | this.limits = Object.assign(this.limits, limits); 120 | return this; 121 | } 122 | 123 | setFeatureLimits(featureLimits: Partial): this { 124 | this.featureLimits = Object.assign(this.featureLimits, featureLimits); 125 | return this; 126 | } 127 | 128 | setAllocation(options: { default?: number; additional?: number[] }): this { 129 | if (options.default) this.allocation!.default = options.default; 130 | if (options.additional?.length) 131 | this.allocation!.additional = ( 132 | this.allocation!.additional || [] 133 | ).concat(options.additional); 134 | 135 | return this; 136 | } 137 | 138 | setDeployment(options: { 139 | locations?: number[]; 140 | dedicatedIp?: boolean; 141 | portRange?: string[]; 142 | }): this { 143 | if (options.locations?.length) 144 | this.deploy!.locations = (this.deploy!.locations || []).concat( 145 | options.locations, 146 | ); 147 | 148 | if (options.dedicatedIp != undefined) 149 | this.deploy!.dedicatedIp = options.dedicatedIp; 150 | 151 | if (options.portRange?.length) 152 | this.deploy!.portRange = (this.deploy!.portRange || []).concat( 153 | options.portRange, 154 | ); 155 | 156 | return this; 157 | } 158 | 159 | setStartOnCompletion(value: boolean): this { 160 | this.startOnCompletion = value; 161 | return this; 162 | } 163 | 164 | build(): CreateServerOptions { 165 | if (!this.name) throw new ValidationError('A server name is required'); 166 | if (!this.user) 167 | throw new ValidationError('A server owner (user) is required'); 168 | 169 | if (!this.egg) throw new ValidationError('An egg is required'); 170 | if (!this.dockerImage) 171 | throw new ValidationError('A docker image is required'); 172 | 173 | if (!this.startup) 174 | throw new ValidationError('A startup command is required'); 175 | 176 | if (!this.deploy!.locations.length || !this.deploy!.portRange.length) { 177 | if (!this.allocation!.default) 178 | throw new ValidationError( 179 | 'A default allocation or deployment options is required', 180 | ); 181 | } 182 | 183 | return super.build(); 184 | } 185 | } 186 | -------------------------------------------------------------------------------- /src/builders/User.ts: -------------------------------------------------------------------------------- 1 | import type { User } from '../structures/User'; 2 | import { Builder } from './base'; 3 | import { CreateUserOptions } from '../common/app'; 4 | import { ValidationError } from '../structures/Errors'; 5 | 6 | export class UserBuilder extends Builder { 7 | private externalId: string | undefined; 8 | private username: string; 9 | private email: string; 10 | private firstname: string; 11 | private lastname: string; 12 | private password: string | undefined; 13 | private isAdmin: boolean; 14 | 15 | constructor() { 16 | super(); 17 | 18 | this.externalId = undefined; 19 | this.username = ''; 20 | this.email = ''; 21 | this.firstname = ''; 22 | this.lastname = ''; 23 | this.password = undefined; 24 | this.isAdmin = false; 25 | } 26 | 27 | static fromUser(user: User): UserBuilder { 28 | let b = new this(); 29 | 30 | if (user.externalId) b.setExternalId(user.externalId); 31 | if (user.username) b.setUsername(user.username); 32 | if (user.email) b.setEmail(user.email); 33 | if (user.firstname) b.setFirstname(user.firstname); 34 | if (user.lastname) b.setLastname(user.lastname); 35 | if (user.isAdmin) b.setAdmin(user.isAdmin); 36 | 37 | return b; 38 | } 39 | 40 | setExternalId(id: string | undefined): this { 41 | this.externalId = id; 42 | return this; 43 | } 44 | 45 | setUsername(username: string): this { 46 | this.username = username; 47 | return this; 48 | } 49 | 50 | setEmail(email: string): this { 51 | this.email = email; 52 | return this; 53 | } 54 | 55 | setFirstname(name: string): this { 56 | this.firstname = name; 57 | return this; 58 | } 59 | 60 | setLastname(name: string): this { 61 | this.lastname = name; 62 | return this; 63 | } 64 | 65 | setPassword(password: string | undefined): this { 66 | this.password = password; 67 | return this; 68 | } 69 | 70 | setAdmin(state: boolean): this { 71 | this.isAdmin = state; 72 | return this; 73 | } 74 | 75 | build(): CreateUserOptions { 76 | if (!this.username) throw new ValidationError('a username is required'); 77 | if (!this.email) throw new ValidationError('an email is required'); 78 | if (!this.firstname) 79 | throw new ValidationError('a first name is required'); 80 | 81 | if (!this.lastname) 82 | throw new ValidationError('a last name is required'); 83 | 84 | return super.build(); 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /src/builders/base.ts: -------------------------------------------------------------------------------- 1 | export abstract class Builder { 2 | public build() { 3 | return Object.entries(this).reduce((a, b) => { 4 | a[b[0]] = b[1]; 5 | return a; 6 | }, {}); 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /src/client/ClientDatabaseManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroClient } from '.'; 2 | import { BaseManager } from '../structures/BaseManager'; 3 | import { ClientDatabase } from '../common/client'; 4 | import { Dict } from '../structures/Dict'; 5 | import { FetchOptions, Include } from '../common'; 6 | import caseConv from '../util/caseConv'; 7 | import endpoints from './endpoints'; 8 | 9 | export class ClientDatabaseManager extends BaseManager { 10 | public client: PteroClient; 11 | public cache: Dict; 12 | public serverId: string; 13 | 14 | /** Allowed filter arguments for databases (none). */ 15 | get FILTERS() { 16 | return Object.freeze([]); 17 | } 18 | 19 | /** 20 | * Allowed include arguments for databases: 21 | * * password 22 | */ 23 | get INCLUDES() { 24 | return Object.freeze(['password']); 25 | } 26 | 27 | /** Allowed sort arguments for databases (none). */ 28 | get SORTS() { 29 | return Object.freeze([]); 30 | } 31 | 32 | constructor(client: PteroClient, serverId: string) { 33 | super(); 34 | this.client = client; 35 | this.cache = new Dict(); 36 | this.serverId = serverId; 37 | } 38 | 39 | /** 40 | * Transforms the raw database object(s) into typed objects. 41 | * @param data The resolvable database object(s). 42 | * @returns The resolved database object(s). 43 | */ 44 | _patch(data: any): any { 45 | if (data.data) { 46 | const res = new Dict(); 47 | for (let o of data.data) { 48 | const d = caseConv.toCamelCase(o.attributes); 49 | res.set(d.id, d); 50 | } 51 | this.cache.update(res); 52 | return res; 53 | } 54 | 55 | const d = caseConv.toCamelCase(data.attributes); 56 | this.cache.set(d.id, d); 57 | return d; 58 | } 59 | 60 | /** 61 | * Fetches a list of databases from the API with the given options (default is undefined). 62 | * 63 | * @param [options] Additional fetch options. 64 | * @returns The fetched databases. 65 | * @example 66 | * ``` 67 | * const server = await client.servers.fetch('1c639a86'); 68 | * await server.databases.fetch({ page: 2 }) 69 | * .then(console.log) 70 | * .catch(console.error); 71 | * ``` 72 | */ 73 | async fetch( 74 | options: Include = {}, 75 | ): Promise> { 76 | const data = await this.client.requests.get( 77 | endpoints.servers.databases.main(this.serverId), 78 | options, 79 | null, 80 | this, 81 | ); 82 | return this._patch(data); 83 | } 84 | 85 | /** 86 | * Creates a database on the server. 87 | * @param database The name of the database. 88 | * @param remote The connections allowed to the database. 89 | * @returns The new database. 90 | */ 91 | async create(database: string, remote: string): Promise { 92 | const data = await this.client.requests.post( 93 | endpoints.servers.databases.main(this.serverId), 94 | { database, remote }, 95 | ); 96 | return this._patch(data); 97 | } 98 | 99 | /** 100 | * Rotates the password of a specified database. 101 | * @param id The ID of the database. 102 | * @returns The updated database. 103 | * @example 104 | * ``` 105 | * const server = await client.servers.fetch('1c639a86'); 106 | * await server.databases.rotate(1) 107 | * .then(console.log) 108 | * .catch(console.error); 109 | * ``` 110 | */ 111 | async rotate(id: number): Promise { 112 | const data = await this.client.requests.post( 113 | endpoints.servers.databases.rotate(this.serverId, id), 114 | ); 115 | return this._patch(data); 116 | } 117 | 118 | /** 119 | * Deletes a database from the server. 120 | * @param id The ID of the database. 121 | * @example 122 | * ``` 123 | * const server = await client.servers.fetch('1c639a86'); 124 | * await server.databases.delete(2).catch(console.error); 125 | * ``` 126 | */ 127 | async delete(id: number): Promise { 128 | await this.client.requests.delete( 129 | endpoints.servers.databases.get(this.serverId, id), 130 | ); 131 | this.cache.delete(id); 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /src/client/NetworkManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroClient } from '.'; 2 | import { Dict } from '../structures/Dict'; 3 | import { NetworkAllocation } from '../common/client'; 4 | import caseConv from '../util/caseConv'; 5 | import endpoints from './endpoints'; 6 | 7 | export class NetworkManager { 8 | public client: PteroClient; 9 | public cache: Dict; 10 | public serverId: string; 11 | 12 | constructor(client: PteroClient, serverId: string) { 13 | this.client = client; 14 | this.cache = new Dict(); 15 | this.serverId = serverId; 16 | } 17 | 18 | /** 19 | * Transforms the raw allocation object(s) into typed objects. 20 | * @param data The resolvable allocation object(s). 21 | * @returns The resolved allocation object(s). 22 | */ 23 | _patch(data: any): any { 24 | if (data.data) { 25 | const res = new Dict(); 26 | for (let o of data.data) { 27 | const a = caseConv.toCamelCase(o.attributes); 28 | a.notes ||= null; 29 | res.set(a.id, a); 30 | } 31 | this.cache.update(res); 32 | return res; 33 | } 34 | 35 | const a = caseConv.toCamelCase(data); 36 | a.notes ||= null; 37 | this.cache.set(a.id, a); 38 | return a; 39 | } 40 | 41 | /** 42 | * Fetches the network allocations on the server. 43 | * @returns The fetched network allocations. 44 | * @example 45 | * ``` 46 | * const server = await client.servers.fetch('1c639a86'); 47 | * await server.network.fetch() 48 | * .then(console.log) 49 | * .catch(console.error); 50 | * ``` 51 | */ 52 | async fetch(): Promise> { 53 | const data = await this.client.requests.get( 54 | endpoints.servers.network.main(this.serverId), 55 | ); 56 | return this._patch(data); 57 | } 58 | 59 | /** 60 | * Sets the notes of a specified network allocation. 61 | * @param id The ID of the network allocation. 62 | * @param notes The notes to set. 63 | * @returns The updated network allocation. 64 | * @example 65 | * ``` 66 | * const server = await client.servers.fetch('1c639a86'); 67 | * await server.network.setNote(14, 'bungee') 68 | * .then(console.log) 69 | * .catch(console.error); 70 | * ``` 71 | */ 72 | async setNote(id: number, notes: string): Promise { 73 | const data = await this.client.requests.post( 74 | endpoints.servers.network.get(this.serverId, id), 75 | { notes }, 76 | ); 77 | return this._patch(data); 78 | } 79 | 80 | /** 81 | * Sets the primary allocation of the server. 82 | * @param id The ID of the network allocation. 83 | * @returns The updated network allocation. 84 | * @example 85 | * ``` 86 | * const server = await client.servers.fetch('1c639a86'); 87 | * await server.network.setPrimary(14) 88 | * .then(console.log) 89 | * .catch(console.error); 90 | * ``` 91 | */ 92 | async setPrimary(id: number): Promise { 93 | const data = await this.client.requests.post( 94 | endpoints.servers.network.primary(this.serverId, id), 95 | ); 96 | return this._patch(data); 97 | } 98 | 99 | /** 100 | * Unassigns the specified network allocation form the server. 101 | * @param id The ID of the network allocation. 102 | * @example 103 | * ``` 104 | * const server = await client.servers.fetch('1c639a86'); 105 | * await server.network.unassign(12).catch(console.error); 106 | * ``` 107 | */ 108 | async unassign(id: number): Promise { 109 | await this.client.requests.delete( 110 | endpoints.servers.network.get(this.serverId, id), 111 | ); 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/client/ScheduleManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroClient } from '.'; 2 | import { CreateScheduleOptions } from '../common/client'; 3 | import { Dict } from '../structures/Dict'; 4 | import { FetchOptions } from '../common'; 5 | import { Schedule } from '../structures/Schedule'; 6 | import { ValidationError } from '../structures/Errors'; 7 | import caseConv from '../util/caseConv'; 8 | import endpoints from './endpoints'; 9 | 10 | export class ScheduleManager { 11 | public cache: Dict>; 12 | 13 | constructor(public client: PteroClient) { 14 | this.cache = new Dict(); 15 | } 16 | 17 | /** 18 | * Transforms the raw schedule object(s) into class objects. 19 | * @param data The resolvable schedule object(s). 20 | * @returns The resolved schedule object(s). 21 | */ 22 | _patch(id: string, data: any): any { 23 | if (data.data) { 24 | const res = new Dict(); 25 | for (let o of data.data) { 26 | const s = new Schedule(this.client, id, o.attributes); 27 | res.set(s.id, s); 28 | } 29 | const hold = (this.cache.get(id) || new Dict()).join(res); 30 | this.cache.set(id, hold); 31 | return res; 32 | } 33 | 34 | const s = new Schedule(this.client, id, data.attributes); 35 | const hold = (this.cache.get(id) || new Dict()).set(s.id, s); 36 | this.cache.set(id, hold); 37 | return s; 38 | } 39 | 40 | /** 41 | * Fetches a schedule from the API by its ID. This will check the cache first unless the force 42 | * option is specified. 43 | * 44 | * @param server The identifier of the server. 45 | * @param id The ID of the schedule. 46 | * @param [options] Additional fetch options. 47 | * @returns The fetched schedule. 48 | * @example 49 | * ``` 50 | * client.schedules.fetch('411d2eb9', 12) 51 | * .then(console.log) 52 | * .catch(console.error); 53 | * ``` 54 | */ 55 | async fetch( 56 | server: string, 57 | id: number, 58 | options?: FetchOptions, 59 | ): Promise; 60 | /** 61 | * Fetches a list of schedules from the API with the given options (default is undefined). 62 | * 63 | * @param server The identifier of the server. 64 | * @param [options] Additional fetch options. 65 | * @returns The fetched schedule. 66 | * @example 67 | * ``` 68 | * client.schedules.fetch('411d2eb9', { perPage: 10 }) 69 | * .then(console.log) 70 | * .catch(console.error); 71 | * ``` 72 | */ 73 | async fetch( 74 | server: string, 75 | options?: FetchOptions, 76 | ): Promise>; 77 | async fetch( 78 | server: string, 79 | op1?: number | FetchOptions, 80 | op2: FetchOptions = {}, 81 | ): Promise { 82 | let path = endpoints.servers.schedules.main(server); 83 | if (typeof op1 === 'number') { 84 | if (!op2.force && this.cache.get(server)?.has(op1)) 85 | return this.cache.get(server)!.get(op1); 86 | 87 | path = endpoints.servers.schedules.get(server, op1); 88 | } else { 89 | if (op1) op2 = op1; 90 | } 91 | 92 | const data = await this.client.requests.get(path, op2); 93 | return this._patch(server, data); 94 | } 95 | 96 | /** 97 | * Creates a schedule for a specified server. 98 | * @see {@link CreateScheduleOptions}. 99 | * 100 | * @param server The identifier of the server. 101 | * @param options Create schedule options. 102 | * @returns The new schedule. 103 | * @example 104 | * ``` 105 | * client.schedules.create( 106 | * '411d2eb9', 107 | * { 108 | * name: 'Weekly backup', 109 | * active: false, 110 | * month: '1', 111 | * hour: '*', 112 | * minute: '*' 113 | * } 114 | * ) 115 | * .then(console.log) 116 | * .catch(console.error); 117 | * ``` 118 | */ 119 | async create( 120 | server: string, 121 | options: CreateScheduleOptions, 122 | ): Promise { 123 | options.dayOfWeek ||= '*'; 124 | options.dayOfMonth ||= '*'; 125 | options.onlyWhenOnline ??= false; 126 | 127 | const payload = caseConv.toSnakeCase(options, { 128 | map: { active: 'is_active' }, 129 | }); 130 | 131 | const data = await this.client.requests.post( 132 | endpoints.servers.schedules.main(server), 133 | payload, 134 | ); 135 | return this._patch(server, data); 136 | } 137 | 138 | /** 139 | * Updates a schedule on the specified server. 140 | * @see {@link CreateScheduleOptions}. 141 | * 142 | * @param server The identifier of the server. 143 | * @param id The ID of the schedule. 144 | * @param options Update schedule options. 145 | * @returns The updated schedule. 146 | * @example 147 | * ``` 148 | * client.schedules.update('411d2eb9', 5, { onlyWhenOnline: true }) 149 | * .then(console.log) 150 | * .catch(console.error); 151 | * ``` 152 | */ 153 | async update( 154 | server: string, 155 | id: number, 156 | options: Partial, 157 | ): Promise { 158 | if (!Object.keys(options).length) 159 | throw new ValidationError('Too few options to update schedule.'); 160 | 161 | const s = await this.fetch(server, id); 162 | options.name ||= s.name; 163 | options.active ||= s.active; 164 | options.hour ||= s.cron.hour; 165 | options.minute ||= s.cron.minute; 166 | options.dayOfWeek ||= s.cron.dayOfWeek; 167 | options.dayOfMonth ||= s.cron.dayOfMonth; 168 | options.onlyWhenOnline ??= false; 169 | 170 | const data = await this.client.requests.patch( 171 | endpoints.servers.schedules.get(server, id), 172 | caseConv.toSnakeCase(options), 173 | ); 174 | return this._patch(server, data); 175 | } 176 | 177 | /** 178 | * Deletes a schedule from a specified server. 179 | * @param server The identifier of the server. 180 | * @param id The ID of the schedule. 181 | * @example 182 | * ``` 183 | * client.schedules.delete('411d2eb9', 3).catch(console.error); 184 | * ``` 185 | */ 186 | async delete(server: string, id: number): Promise { 187 | await this.client.requests.delete( 188 | endpoints.servers.schedules.get(server, id), 189 | ); 190 | this.cache.get(server)?.delete(id); 191 | } 192 | } 193 | -------------------------------------------------------------------------------- /src/client/SubUserManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroClient } from '.'; 2 | import { Dict } from '../structures/Dict'; 3 | import { FetchOptions, Resolvable } from '../common'; 4 | import { Permissions } from '../structures/Permissions'; 5 | import { SubUser } from '../structures/User'; 6 | import { ValidationError } from '../structures/Errors'; 7 | import endpoints from './endpoints'; 8 | 9 | export class SubUserManager { 10 | cache: Dict; 11 | 12 | constructor(public client: PteroClient, public serverId: string) { 13 | this.cache = new Dict(); 14 | } 15 | 16 | /** 17 | * Transforms the raw subuser object(s) into class objects. 18 | * @param data The resolvable subuser object(s). 19 | * @returns The resolved subuser object(s). 20 | */ 21 | _patch(data: any): any { 22 | if (data?.data) { 23 | const res = new Dict(); 24 | for (const o of data.data) { 25 | const s = new SubUser(this.client, this.serverId, o.attributes); 26 | res.set(s.uuid, s); 27 | } 28 | if (this.client.options.subUsers.cache) this.cache.update(res); 29 | return res; 30 | } 31 | 32 | const u = new SubUser(this.client, this.serverId, data.attributes); 33 | if (this.client.options.subUsers.cache) this.cache.set(u.uuid, u); 34 | return u; 35 | } 36 | 37 | /** 38 | * Resolves a subuser from an object. This can be: 39 | * * a string 40 | * * a number 41 | * * an object 42 | * 43 | * @param obj The object to resolve from. 44 | * @returns The resolved user or undefined if not found. 45 | */ 46 | resolve(obj: Resolvable): SubUser | undefined { 47 | if (obj instanceof SubUser) return obj; 48 | // needed for typing resolution 49 | if (typeof obj === 'number') return undefined; 50 | if (typeof obj === 'string') return this.cache.get(obj); 51 | if (obj.relationships?.users) 52 | return this._patch(obj.relationships.users); 53 | return undefined; 54 | } 55 | 56 | /** 57 | * Returns a formatted URL to the subuser. 58 | * @returns The formatted URL. 59 | */ 60 | get panelURL(): string { 61 | return `${this.client.domain}/server/${this.serverId}/users`; 62 | } 63 | 64 | /** 65 | * Fetches a subuser from the API by its UUID. This will check the cache first unless the 66 | * force option is specified. 67 | * 68 | * @param uuid The UUID of the subuser. 69 | * @param [options] Additional fetch options. 70 | * @returns The fetched subuser. 71 | * @example 72 | * ``` 73 | * const server = await client.servers.fetch('1c639a86'); 74 | * await server.users.fetch('36de5ed4-8c37-4bde-a1da-4203115a3e9d') 75 | * .then(console.log) 76 | * .catch(console.error); 77 | * ``` 78 | */ 79 | async fetch(uuid: string, options?: FetchOptions): Promise; 80 | /** 81 | * Fetches a list of subusers from the API with the given options (default is undefined). 82 | * 83 | * @param [options] Additional fetch options. 84 | * @returns The fetched subusers. 85 | * @example 86 | * ``` 87 | * const server = await client.servers.fetch('1c639a86'); 88 | * await server.users.fetch({ perPage: 10 }) 89 | * .then(console.log) 90 | * .catch(console.error); 91 | * ``` 92 | */ 93 | async fetch(options?: FetchOptions): Promise>; 94 | async fetch( 95 | op?: string | FetchOptions, 96 | ops: FetchOptions = {}, 97 | ): Promise { 98 | let path = endpoints.servers.users.main(this.serverId); 99 | if (typeof op === 'string') { 100 | if (!ops.force && this.cache.has(op)) return this.cache.get(op); 101 | 102 | path = endpoints.servers.users.get(this.serverId, op); 103 | } else { 104 | if (op) ops = op; 105 | } 106 | 107 | const data = await this.client.requests.get(path, ops); 108 | return this._patch(data); 109 | } 110 | 111 | /** 112 | * Adds a user as a subuser to the server. 113 | * @param email The email of the account to add. 114 | * @param permissions Permissions for the account. 115 | * @returns The new subuser. 116 | * @example 117 | * ``` 118 | * const perms = new Permissions(...Permissions.CONTROL, ...Permissions.FILES); 119 | * const server = await client.servers.fetch('1c639a86'); 120 | * await server.users.add('user@example.com', perms.value) 121 | * .then(console.log) 122 | * .catch(console.error); 123 | * ``` 124 | */ 125 | async add( 126 | email: string, 127 | permissions: string[], // TODO: change to permissions 128 | ): Promise { 129 | const perms = Permissions.resolve(...permissions); 130 | if (!perms.length) 131 | throw new ValidationError( 132 | 'Need at least 1 permission for the subuser.', 133 | ); 134 | 135 | const data = await this.client.requests.post( 136 | endpoints.servers.users.main(this.serverId), 137 | { email, permissions: perms }, 138 | ); 139 | return this._patch(data); 140 | } 141 | 142 | /** 143 | * Updates the permissions of a specified subuser. 144 | * @param id The UUID of the subuser. 145 | * @param permissions The permissions to set. 146 | * @returns The updated subuser account. 147 | * @example 148 | * ``` 149 | * const perms = new Permissions(...Permissions.FILES, ...Permissions.BACKUPS); 150 | * const server = await client.servers.fetch('1c639a86'); 151 | * await server.users.setPermissions( 152 | * '36de5ed4-8c37-4bde-a1da-4203115a3e9d', 153 | * perms.value 154 | * ) 155 | * .then(console.log) 156 | * .catch(console.error); 157 | * ``` 158 | */ 159 | async setPermissions(id: string, permissions: string[]): Promise { 160 | const perms = Permissions.resolve(...permissions); 161 | if (!perms.length) 162 | throw new ValidationError( 163 | 'No permissions specified for the subuser.', 164 | ); 165 | 166 | const data = await this.client.requests.post( 167 | endpoints.servers.users.get(this.serverId, id), 168 | { permissions: perms }, 169 | ); 170 | return this._patch(data); 171 | } 172 | 173 | /** 174 | * Removes a subuser's access to the server. 175 | * @param id The UUID of the subuser. 176 | * @example 177 | * ``` 178 | * const server = await client.servers.fetch('1c639a86'); 179 | * await server.users.remove('9d7b1d20-6e34-4a3a-abcd-c26ae79dc2bd') 180 | * .catch(console.error); 181 | * ``` 182 | */ 183 | async remove(id: string): Promise { 184 | await this.client.requests.delete( 185 | endpoints.servers.users.get(this.serverId, id), 186 | ); 187 | this.cache.delete(id); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/client/endpoints.ts: -------------------------------------------------------------------------------- 1 | export default { 2 | account: { 3 | main: '/account', 4 | tfa: '/account/two-factor', 5 | email: '/account/email', 6 | password: '/account/password', 7 | apikeys: { 8 | main: '/account/api-keys', 9 | get: (k: string) => `/account/api-keys/${k}`, 10 | }, 11 | activity: '/account/activity', 12 | sshkeys: { 13 | main: '/account/ssh-keys', 14 | remove: '/account/ssh-keys/remove', 15 | }, 16 | }, 17 | servers: { 18 | main: '', 19 | get: (s: string) => `/servers/${s}`, 20 | databases: { 21 | main: (s: string) => `/servers/${s}/databases`, 22 | get: (s: string, id: number) => `/servers/${s}/databases/${id}`, 23 | rotate: (s: string, id: number) => 24 | `/servers/${s}/databases/${id}/rotate-password`, 25 | }, 26 | files: { 27 | main: (s: string) => `/servers/${s}/files/list`, 28 | contents: (s: string, f: string) => 29 | `/servers/${s}/files/contents?file=${f}`, 30 | download: (s: string, f: string) => 31 | `/servers/${s}/files/download?file=${f}`, 32 | rename: (s: string) => `/servers/${s}/files/rename`, 33 | copy: (s: string) => `/servers/${s}/files/copy`, 34 | write: (s: string, f: string) => 35 | `/servers/${s}/files/write?file=${f}`, 36 | compress: (s: string) => `/servers/${s}/files/compress`, 37 | decompress: (s: string) => `/servers/${s}/files/decompress`, 38 | delete: (s: string) => `/servers/${s}/files/delete`, 39 | create: (s: string) => `/servers/${s}/files/create-folder`, 40 | upload: (s: string) => `/servers/${s}/files/upload`, 41 | chmod: (s: string) => `/servers/${s}/files/chmod`, 42 | }, 43 | schedules: { 44 | main: (s: string) => `/servers/${s}/schedules`, 45 | get: (s: string, id: number) => `/servers/${s}/schedules/${id}`, 46 | exec: (s: string, id: number) => 47 | `/servers/${s}/schedules/${id}/execute`, 48 | tasks: { 49 | main: (s: string, id: number) => 50 | `/servers/${s}/schedules/${id}/tasks`, 51 | get: (s: string, id: number, t: number) => 52 | `/servers/${s}/schedules/${id}/tasks/${t}`, 53 | }, 54 | }, 55 | network: { 56 | main: (s: string) => `/servers/${s}/network/allocations`, 57 | get: (s: string, id: number) => 58 | `/servers/${s}/network/allocations/${id}`, 59 | primary: (s: string, id: number) => 60 | `/servers/${s}/network/allocations/${id}/primary`, 61 | }, 62 | users: { 63 | main: (s: string) => `/servers/${s}/users`, 64 | get: (s: string, id: string) => `/servers/${s}/users/${id}`, 65 | }, 66 | backups: { 67 | main: (s: string) => `/servers/${s}/backups`, 68 | get: (s: string, id: string) => `/servers/${s}/backups/${id}`, 69 | lock: (s: string, id: string) => `/servers/${s}/backups/${id}/lock`, 70 | download: (s: string, id: string) => 71 | `/servers/${s}/backups/${id}/download`, 72 | restore: (s: string, id: string) => 73 | `/servers/${s}/backups/${id}/restore`, 74 | }, 75 | startup: { 76 | get: (s: string) => `/servers/${s}/startup`, 77 | var: (s: string) => `/servers/${s}/startup/variable`, 78 | }, 79 | settings: { 80 | image: (s: string) => `/servers/${s}/settings/docker-image`, 81 | rename: (s: string) => `/servers/${s}/settings/rename`, 82 | reinstall: (s: string) => `/servers/${s}/settings/reinstall`, 83 | }, 84 | ws: (s: string) => `/servers/${s}/websocket`, 85 | resources: (s: string) => `/servers/${s}/resources`, 86 | command: (s: string) => `/servers/${s}/command`, 87 | power: (s: string) => `/servers/${s}/power`, 88 | }, 89 | permissions: '/permissions', 90 | }; 91 | -------------------------------------------------------------------------------- /src/client/index.ts: -------------------------------------------------------------------------------- 1 | import type { Shard } from './ws/Shard'; 2 | import { Account } from '../structures/User'; 3 | import { ClientServerManager } from './ClientServerManager'; 4 | import { OptionSpec } from '../common'; 5 | import { PermissionDescriptor } from '../common/client'; 6 | import { RequestManager } from '../http/RequestManager'; 7 | import { ScheduleManager } from './ScheduleManager'; 8 | import { ValidationError } from '../structures/Errors'; 9 | import { WebSocketManager } from './ws/WebSocketManager'; 10 | import endpoints from './endpoints'; 11 | import loader from '../util/config'; 12 | 13 | /** 14 | * The base class for the Pterodactyl client API. 15 | * This operates using a Pterodactyl client API key which can be found 16 | * at . 17 | * 18 | * **Warning:** Keep your API key private at all times. Exposing this can lead 19 | * to your account and servers being corrupted, exposed and/or deleted. 20 | */ 21 | export class PteroClient { 22 | /** 23 | * The domain for your Pterodactyl panel. This should be the main URL only 24 | * (not "/api"). Any additional paths will count as the API path. 25 | */ 26 | public domain: string; 27 | 28 | /** 29 | * The API key for your Pterodactyl account. This should be kept private at 30 | * all times. 31 | */ 32 | public auth: string; 33 | 34 | /** @deprecated To be replaced with a better system. */ 35 | public options: Record; 36 | 37 | /** 38 | * The account class for controlling your panel account, including the email, 39 | * password, API keys and SSH keys. 40 | */ 41 | public account: Account; 42 | 43 | public schedules: ScheduleManager; 44 | public servers: ClientServerManager; 45 | public requests: RequestManager; 46 | public ws: WebSocketManager; 47 | 48 | constructor( 49 | domain: string, 50 | auth: string, 51 | options: Record = {}, 52 | ) { 53 | if (!/https?\:\/\/(?:localhost\:\d{4}|[\w\.\-]{3,256})/gi.test(domain)) 54 | throw new ValidationError( 55 | "Domain URL must start with 'http://' or 'https://' and " + 56 | 'must be bound to a port if using localhost.', 57 | ); 58 | 59 | if (domain.endsWith('/')) domain = domain.slice(0, -1); 60 | this.domain = domain; 61 | this.auth = auth; 62 | this.options = loader.clientConfig({ client: options }); 63 | this.account = new Account(this); 64 | 65 | this.schedules = new ScheduleManager(this); 66 | this.servers = new ClientServerManager(this); 67 | this.requests = new RequestManager('Client', domain, auth); 68 | this.ws = new WebSocketManager(this); 69 | } 70 | 71 | get ping(): number { 72 | return this.requests._ping; 73 | } 74 | 75 | /** 76 | * Fetches the raw permissions from the API. 77 | * @see {@link PermissionDescriptor}. 78 | * @returns The raw permission descriptors. 79 | */ 80 | async fetchPermissions(): Promise> { 81 | const data = await this.requests.get(endpoints.permissions); 82 | return data.attributes.permissions; 83 | } 84 | 85 | /** Performs preload requests to Pterodactyl. */ 86 | async connect(): Promise { 87 | if (this.options.fetchClient) await this.account.fetch(); 88 | if (this.options.servers.fetch && this.options.servers.cache) 89 | await this.servers.fetch(); 90 | } 91 | 92 | /** 93 | * Creates a websocket shard for a specified server. 94 | * @param id The identifier of the server. 95 | * @returns The server websocket shard. 96 | */ 97 | addSocketServer(id: string): Shard; 98 | /** 99 | * Creates websocket shards for the specified servers. 100 | * @param ids The identifiers of the servers. 101 | * @returns An array of the server websocket shards. 102 | */ 103 | addSocketServer(...ids: string[]): Shard[]; 104 | addSocketServer(...args: any[]): any { 105 | if (args.length === 1) return this.ws.createShard(args[0]); 106 | return args.map(i => this.ws.createShard(i)); 107 | } 108 | 109 | /** 110 | * Removes a server from websocket connections. 111 | * @param id The identifier of the server. 112 | * @returns Whether the shard was removed. 113 | */ 114 | removeSocketServer(id: string): boolean { 115 | return this.ws.deleteShard(id); 116 | } 117 | 118 | /** Closes any existing websocket connections. */ 119 | disconnect(): void { 120 | if (this.ws.active) this.ws.destroy(); 121 | } 122 | } 123 | -------------------------------------------------------------------------------- /src/client/ws/WebSocketManager.ts: -------------------------------------------------------------------------------- 1 | import type { PteroClient } from '..'; 2 | import { WebSocketAuth } from '../../common/client'; 3 | import { Shard } from './Shard'; 4 | import endpoints from '../endpoints'; 5 | 6 | export class WebSocketManager { 7 | public client: PteroClient; 8 | public shards: Map; 9 | public active: boolean; 10 | public useOrigin: boolean; 11 | 12 | constructor(client: PteroClient) { 13 | this.client = client; 14 | this.shards = new Map(); 15 | this.active = false; 16 | this.useOrigin = false; 17 | } 18 | 19 | /** 20 | * Creates a websocket shard instance for a specified server. 21 | * @param id The identifier of the server. 22 | * @returns The server websocket shard. 23 | */ 24 | createShard(id: string): Shard { 25 | if (this.shards.has(id)) return this.shards.get(id)!; 26 | const shard = new Shard(this.client, id, this.useOrigin); 27 | this.shards.set(id, shard); 28 | this.active = true; 29 | return shard; 30 | } 31 | 32 | /** 33 | * Disconnects a server shard's websocket connection and removes it. 34 | * If some shards do not return a value, `undefined` will be set in place. 35 | * @param id The identifier of the server. 36 | * @returns Whether the websocket shard was disconnected and/or removed. 37 | */ 38 | deleteShard(id: string): boolean { 39 | if (!this.shards.has(id)) return false; 40 | this.shards.get(id)!.disconnect(); 41 | this.active = !!this.shards.size; 42 | return this.shards.delete(id); 43 | } 44 | 45 | get ping(): number { 46 | if (!this.shards.size) return -1; 47 | let sum = 0; 48 | for (let s of this.shards.values()) sum += s.ping; 49 | return sum / this.shards.size; 50 | } 51 | 52 | /** 53 | * Fetches the websocket authentication data for a specified server. 54 | * @param id The identifier of the server. 55 | * @returns The websocket authentication data. 56 | */ 57 | getAuth(id: string): Promise { 58 | return this.client.requests.get(endpoints.servers.ws(id)); 59 | } 60 | 61 | /** 62 | * Broadcasts an event to all shards and waits for the responses. 63 | * @param event The event to broadcast. 64 | * @param args Arguments to send with the event. 65 | * @returns A list of the returned values, if any. 66 | * @example 67 | * ``` 68 | * const values = await client.ws.broadcast('sendStats'); 69 | * console.log(values.map(s => s.resources.uptime)); 70 | * ``` 71 | */ 72 | async broadcast(event: string, args?: string): Promise { 73 | const res = [] as T[]; 74 | for (const shard of this.shards.values()) { 75 | let data = await shard.request(event, args); 76 | res.push(data); 77 | } 78 | return res; 79 | } 80 | 81 | /** Disconnects all active websocket shards and removes them. */ 82 | destroy(): void { 83 | for (let s of this.shards.values()) s.disconnect(); 84 | this.shards.clear(); 85 | this.active = false; 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/client/ws/packetHandler.ts: -------------------------------------------------------------------------------- 1 | import type { Shard } from './Shard'; 2 | import type { WebSocketManager } from './WebSocketManager'; 3 | import { WebSocketPayload } from '../../common/client'; 4 | import caseConv from '../../util/caseConv'; 5 | 6 | /** 7 | * Handles the event dispatching of websocket events for the shard. 8 | * This is used interally by the {@link WebSocketManager} and should not be 9 | * accessed externally. 10 | * @param shard The websocket shard. 11 | * @param payload The websocket event payload. 12 | * @internal 13 | */ 14 | export default function (shard: Shard, payload: WebSocketPayload): void { 15 | const args = payload.args || []; 16 | 17 | switch (payload.event) { 18 | case 'status': 19 | shard.emit('statusUpdate', args.join()); 20 | break; 21 | case 'console output': 22 | shard.emit('serverOutput', args.join()); 23 | break; 24 | case 'daemon message': 25 | shard.emit('daemonMessage', args.join()); 26 | break; 27 | case 'install started': 28 | shard.emit('installStart'); 29 | break; 30 | case 'install output': 31 | shard.emit('installOutput', args.join()); 32 | break; 33 | case 'install completed': 34 | shard.emit('installComplete'); 35 | break; 36 | case 'stats': 37 | const stats = JSON.parse(args.join()); 38 | shard.emit('statsUpdate', caseConv.toCamelCase(stats)); 39 | break; 40 | case 'transfer logs': 41 | case 'transfer status': 42 | shard.emit('transferUpdate', args.join()); 43 | break; 44 | case 'backup completed': 45 | const backup = JSON.parse(args.join()); 46 | shard.emit('backupComplete', caseConv.toCamelCase(backup)); 47 | break; 48 | case 'daemon error': 49 | case 'jwt error': 50 | shard.emit('error', args.join()); 51 | break; 52 | default: 53 | shard.emit('error', `received unknown event '${payload.event}'`); 54 | break; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/common/app.ts: -------------------------------------------------------------------------------- 1 | import { FeatureLimits, Limits } from '../common'; 2 | 3 | /** Represents an allocation object. */ 4 | export interface Allocation { 5 | id: number; 6 | ip: string; 7 | alias: string | undefined; 8 | port: number; 9 | notes: string | undefined; 10 | assigned: boolean; 11 | } 12 | 13 | export interface ApplicationDatabase { 14 | id: number; 15 | serverId: number; 16 | hostId: number; 17 | database: unknown; 18 | username: string; 19 | remote: string; 20 | maxConnections: number; 21 | createdAt: Date; 22 | updatedAt: Date | undefined; 23 | } 24 | 25 | /** Options for creating a node. */ 26 | export interface CreateNodeOptions { 27 | name: string; 28 | description: string | undefined; 29 | /** @deprecated Broken, use `locationId`. */ 30 | location: string; 31 | locationId: number; 32 | public: boolean; 33 | fqdn: string; 34 | scheme: string; 35 | behindProxy: boolean; 36 | memory: number; 37 | memoryOverallocate?: number; 38 | disk: number; 39 | diskOverallocate?: number; 40 | /** @deprecated Use `daemonPort` and `daemonListen` instead. */ 41 | sftp: { 42 | port: number; 43 | listener: number; 44 | }; 45 | daemonBase: string; 46 | daemonSftp: number; 47 | daemonListen: number; 48 | maintenanceMode: boolean; 49 | uploadSize?: number; 50 | } 51 | 52 | /** Options for creating a user account. */ 53 | export interface CreateUserOptions { 54 | externalId?: string; 55 | email: string; 56 | username: string; 57 | firstname: string; 58 | lastname: string; 59 | password?: string; 60 | isAdmin?: boolean; 61 | } 62 | 63 | export type CreateServerOptions = 64 | | CreateServerWithAllocation 65 | | CreateServerWithDeploy; 66 | 67 | /** Options for creating a server. */ 68 | export interface CreateServerOptionsBase { 69 | /** The external identifier of the server. */ 70 | externalId?: string; 71 | /** The name of the server. */ 72 | name: string; 73 | /** 74 | * A description of the server. 75 | * @default undefined 76 | */ 77 | description?: string; 78 | /** The ID of the user that will own the server. */ 79 | user: number; 80 | /** The egg to use for the server. */ 81 | egg: number; 82 | /** The default docker image for the server. */ 83 | dockerImage: string; 84 | /** The server startup command. */ 85 | startup: string; 86 | /** An environment variables object. */ 87 | environment: Record; 88 | /** 89 | * Whether to skip the egg installation script. 90 | * @default false 91 | */ 92 | skipScripts?: boolean; 93 | /** Doesn't work, don't use this. */ 94 | oomDisabled?: boolean; 95 | /** The server limits. */ 96 | limits?: Partial; 97 | /** The server's feature limits. */ 98 | featureLimits?: Partial; 99 | /** 100 | * Whether to start the server after the installation process is complete. 101 | * @default false 102 | */ 103 | startOnCompletion?: boolean; 104 | } 105 | 106 | export interface CreateServerWithAllocation extends CreateServerOptionsBase { 107 | /** The server allocation details. */ 108 | allocation: { 109 | /** The default server allocation. */ 110 | default: number; 111 | /** Additional allocations for the server. */ 112 | additional?: number[]; 113 | }; 114 | } 115 | 116 | export interface CreateServerWithDeploy extends CreateServerOptionsBase { 117 | /** 118 | * Node deployment options. This is for more control over where the 119 | * server is deployed within the location and port ranges specified. 120 | */ 121 | deploy: { 122 | locations: number[]; 123 | dedicatedIp: boolean; 124 | portRange: string[]; 125 | }; 126 | } 127 | 128 | /** Represents a nest egg object. */ 129 | export interface Egg { 130 | id: number; 131 | uuid: string; 132 | nest: number; 133 | name: string; 134 | description: string; 135 | author: string; 136 | /** 137 | * @deprecated Will be removed in Pterodactyl v2 in favour of 138 | * {@link dockerImages}. 139 | */ 140 | dockerImage: string; 141 | dockerImages: string[]; 142 | config: { 143 | files: Record; 144 | startup: Record; 145 | stop: string; 146 | logs: string[]; 147 | fileDenylist: string[]; 148 | extends: string | null; 149 | }; 150 | startup: string; 151 | script: { 152 | privileged: boolean; 153 | install: string; 154 | entry: string; 155 | container: string; 156 | extends: string | null; 157 | }; 158 | createdAt: Date; 159 | updatedAt: Date | undefined; 160 | } 161 | 162 | /** Represents a nest object. */ 163 | export interface Nest { 164 | id: number; 165 | uuid: string; 166 | author: string; 167 | name: string; 168 | description: string; 169 | createdAt: Date; 170 | updatedAt: Date | undefined; 171 | } 172 | 173 | /** Represents a node configuration object (from Wings). */ 174 | export interface NodeConfiguration { 175 | uuid: string; 176 | tokenId: string; 177 | token: string; 178 | debug: boolean; 179 | api: { 180 | host: string; 181 | port: number; 182 | ssl: { 183 | enabled: boolean; 184 | cert: string; 185 | key: string; 186 | }; 187 | uploadLimit: number; 188 | }; 189 | system: { 190 | data: string; 191 | sftp: { 192 | bindPort: number; 193 | }; 194 | }; 195 | allowedMounts: string[]; 196 | remote: string; 197 | } 198 | 199 | /** Query options for fetching deployable nodes. */ 200 | export interface NodeDeploymentOptions { 201 | memory: number; 202 | disk: number; 203 | locationIds?: number[]; 204 | } 205 | 206 | /** Represents a server status. If the server has no status, `NONE` is used. */ 207 | export enum ServerStatus { 208 | INSTALLING = 'installing', 209 | INSTALL_FAILED = 'install_failed', 210 | SUSPENDED = 'suspended', 211 | RESTORING = 'restoring_backup', 212 | NONE = '', 213 | } 214 | 215 | export interface UpdateBuildOptions { 216 | limits?: Partial; 217 | featureLimits?: Partial; 218 | allocation?: number; 219 | oomDisabled?: boolean; 220 | addAllocations?: number[]; 221 | removeAllocations?: number[]; 222 | } 223 | 224 | export interface UpdateDetailsOptions { 225 | name?: string; 226 | owner?: number; 227 | externalId?: string; 228 | description?: string; 229 | } 230 | 231 | export interface UpdateStartupOptions { 232 | startup?: string; 233 | environment?: Record; 234 | egg?: number; 235 | image?: string; 236 | skipScripts?: boolean; 237 | } 238 | 239 | export interface UpdateUserOptions 240 | extends Omit { 241 | externalId?: string | null; 242 | } 243 | -------------------------------------------------------------------------------- /src/common/client.ts: -------------------------------------------------------------------------------- 1 | export interface Activity { 2 | id: string; 3 | batch: unknown; 4 | event: string; 5 | isApi: boolean; 6 | ip: string | null; 7 | description: string | null; 8 | properties: Record; 9 | hasAdditionalMetadata: boolean; 10 | timestamp: Date; 11 | } 12 | 13 | /** Represents a client API key. */ 14 | export interface APIKey { 15 | identifier: string; 16 | description: string; 17 | allowedIps: string[]; 18 | createdAt: Date; 19 | lastUsedAt: Date | undefined; 20 | token?: string; 21 | } 22 | 23 | /** Represents a server backup object. */ 24 | export interface Backup { 25 | uuid: string; 26 | name: string; 27 | ignoredFiles: string[]; 28 | hash: string | undefined; 29 | bytes: number; 30 | checksum: string | undefined; 31 | successful: boolean; 32 | locked: boolean; 33 | createdAt: Date; 34 | completedAt: Date | undefined; 35 | } 36 | 37 | /** Represents the client metedata from a client servers request. */ 38 | export interface ClientMeta { 39 | isServerOwner?: boolean; 40 | userPermissions?: Record; 41 | } 42 | 43 | /** Represents the currently used resources of a server. */ 44 | export interface ClientResources { 45 | currentState: string; 46 | isSuspended: boolean; 47 | resources: { 48 | memoryBytes: number; 49 | cpuAbsolute: number; 50 | diskBytes: number; 51 | networkRxBytes: number; 52 | networkTxBytes: number; 53 | uptime: number; 54 | }; 55 | } 56 | 57 | /** Options for creating a server backup. */ 58 | export interface CreateBackupOptions { 59 | name?: string; 60 | isLocked?: boolean; 61 | ignored?: string; 62 | } 63 | 64 | /** Options for creating a server schedule. */ 65 | export interface CreateScheduleOptions { 66 | name: string; 67 | active: boolean; 68 | dayOfWeek?: string; 69 | dayOfMonth?: string; 70 | month: string; 71 | hour: string; 72 | minute: string; 73 | onlyWhenOnline?: boolean; 74 | } 75 | 76 | /** Represents a schedule cronjob object. */ 77 | export interface Cron { 78 | dayOfWeek: string; 79 | dayOfMonth: string; 80 | month: string; 81 | hour: string; 82 | minute: string; 83 | } 84 | 85 | /** Represents a server database object. */ 86 | export interface ClientDatabase { 87 | id: number; 88 | name: string; 89 | username: string; 90 | host: { 91 | address: string; 92 | port: number; 93 | }; 94 | connectionsFrom: string; 95 | maxConnections: string; 96 | password?: string; 97 | } 98 | 99 | /** Represents an egg variable. */ 100 | export interface EggVariable { 101 | name: string; 102 | description: string; 103 | envVariable: string; 104 | defaultValue: string | number; 105 | serverValue: string | number; 106 | isEditable: boolean; 107 | rules: string; 108 | } 109 | 110 | /** Represents a file or file-like object on the server. */ 111 | export interface File { 112 | name: string; 113 | mode: string; 114 | modeBits: bigint; 115 | size: number; 116 | isFile: boolean; 117 | isSymlink: boolean; 118 | mimetype: string; 119 | createdAt: Date; 120 | modifiedAt: Date | undefined; 121 | } 122 | 123 | /** Options for changing file permissions. */ 124 | export interface FileChmodData { 125 | file: string; 126 | mode: number; 127 | } 128 | 129 | /** Represents a network allocation object for a server. */ 130 | export interface NetworkAllocation { 131 | id: number; 132 | ip: string; 133 | ipAlias: string; 134 | port: number; 135 | notes: string | null; 136 | isDefault: boolean; 137 | } 138 | 139 | /** 140 | * Represents a permission descriptor for grouped permissions. 141 | * Available permission groups: 142 | * * websocket 143 | * * control 144 | * * user 145 | * * file 146 | * * backup 147 | * * allocation 148 | * * startup 149 | * * database 150 | * * schedule 151 | * * settings 152 | */ 153 | export interface PermissionDescriptor { 154 | description: string; 155 | keys: Record; 156 | } 157 | 158 | /** Represents a task for a schedule. */ 159 | export interface ScheduleTask { 160 | id: number; 161 | sequenceId: number; 162 | action: string; 163 | payload: string; 164 | offset: number; 165 | queued: boolean; 166 | createdAt: Date; 167 | updatedAt: Date | undefined; 168 | } 169 | 170 | export type ScheduleTaskAction = 'backup' | 'command' | 'power'; 171 | 172 | export enum ShardStatus { 173 | CLOSED, 174 | CONNECTING, 175 | CONNECTED, 176 | } 177 | 178 | export interface SSHKey { 179 | name: string; 180 | fingerprint: string; 181 | publicKey: string; 182 | createdAt: Date; 183 | } 184 | 185 | export interface StartupData { 186 | variables: EggVariable[]; 187 | startupCommand: string; 188 | dockerImages?: string[]; 189 | rawStartupCommand: string; 190 | } 191 | 192 | export interface WebSocketAuth { 193 | data: { 194 | socket: string; 195 | token: string; 196 | }; 197 | } 198 | 199 | export interface WebSocketEvents { 200 | debug: [message: string]; 201 | error: [message: string]; 202 | rawPayload: [data: any]; 203 | 204 | authSuccess: []; 205 | serverConnect: [id: string]; 206 | serverOutput: [output: string]; 207 | daemonMessage: [output: string]; 208 | serverDisconnect: []; 209 | 210 | statsUpdate: [stats: ClientResources]; 211 | statusUpdate: [status: string]; 212 | transferUpdate: [data: any]; 213 | 214 | installStart: []; 215 | installOutput: [output: string]; 216 | installComplete: []; 217 | 218 | backupComplete: [backup: Partial]; 219 | } 220 | 221 | export interface WebSocketPayload { 222 | event: string; 223 | args?: string[]; 224 | } 225 | -------------------------------------------------------------------------------- /src/common/index.ts: -------------------------------------------------------------------------------- 1 | /** Applies an external option to the request. */ 2 | export type External = { external?: boolean } & T; 3 | 4 | /** Applies a filter option to the request. */ 5 | export type Filter = { filter?: string } & T; 6 | 7 | /** An internal form of the filter arguments. */ 8 | export type FilterArray = { filter?: string[] } & T; 9 | 10 | /** Applies an include option on the request. */ 11 | export type Include = { include?: string[] } & T; 12 | 13 | /** Applies a sort option on the request. */ 14 | export type Sort = { sort?: string } & T; 15 | 16 | /** The types object `T` can be resolved from, including itself. */ 17 | export type Resolvable = string | number | Record | T; 18 | 19 | /** Represents the daemon information on a node. */ 20 | export interface DaemonData { 21 | listening: number; 22 | sftp: number; 23 | base: string; 24 | } 25 | 26 | /** Represents the feature limits of a server. */ 27 | export interface FeatureLimits { 28 | /** The total number of allocations for the server. */ 29 | allocations: number; 30 | /** The total number of backups allowed on the server. */ 31 | backups: number; 32 | /** The total number of databases on the server. */ 33 | databases: number; 34 | } 35 | 36 | /** General fetch options for requests. */ 37 | export interface FetchOptions { 38 | /** 39 | * Whether to skip cache checks and go straight to the request. 40 | * This does not apply to all managers that use FetchOptions, 41 | * check the specific method docs for more information. 42 | * @default false 43 | */ 44 | force?: boolean; 45 | /** 46 | * The page number to get results from. 47 | * @default 1 48 | */ 49 | page?: number; 50 | /** 51 | * The number of results to return per-page. 52 | * @default 50 53 | */ 54 | perPage?: number; 55 | } 56 | 57 | /** Represents the configuration for the pterojs.json file. */ 58 | export interface FileConfig { 59 | application?: Record; 60 | client?: Record; 61 | } 62 | 63 | /** Represents the limits of a server. */ 64 | export interface Limits { 65 | /** The amount of memory allocated to the server. */ 66 | memory: number; 67 | /** The amount of swap space allocated to the server. */ 68 | swap: number; 69 | /** The amount of disk allocated to the server. */ 70 | disk: number; 71 | /** The amount of block IO bandwidth allowed for the server. */ 72 | io: number; 73 | /** 74 | * The number of threads (or specific threads) the server can use. 75 | * `null` means unlimited. 76 | */ 77 | threads: string | null; 78 | /** The amount of CPU allocated to the server. */ 79 | cpu: number; 80 | } 81 | 82 | /** Represents a location object. */ 83 | export interface NodeLocation { 84 | id: number; 85 | long: string; 86 | short: string; 87 | createdAt: Date; 88 | updatedAt: Date | null; 89 | } 90 | 91 | /** 92 | * Option specification attributes for the {@link FileConfig}. 93 | * PteroJS currently supports the following config options: 94 | * * users 95 | * * nodes 96 | * * nests 97 | * * servers 98 | * * locations 99 | * * subUsers 100 | */ 101 | export interface OptionSpec { 102 | /** 103 | * Whether to call the option manager's `fetch()` method 104 | * (used by the main class' `connect()` method). 105 | */ 106 | fetch?: boolean; 107 | /** Whether to cache the option manager's values. */ 108 | cache?: boolean; 109 | /** 110 | * The maximum amount of entries to allow for the option manager's cache. 111 | * @experimental 112 | */ 113 | max?: number; 114 | } 115 | 116 | /** 117 | * Represents the metadata received from endpoints with 118 | * paginated responses. 119 | */ 120 | export interface PaginationMeta { 121 | current: number; 122 | total: number; 123 | count: number; 124 | perPage: number; 125 | totalPages: number; 126 | links?: string[]; 127 | } 128 | 129 | export interface RequestEvents { 130 | debug: [message: string]; 131 | preRequest: [data: any]; 132 | postRequest: [data: any]; 133 | } 134 | -------------------------------------------------------------------------------- /src/http/RequestManager.ts: -------------------------------------------------------------------------------- 1 | import axios, { Axios, AxiosError, AxiosResponse } from 'axios'; 2 | import { EventEmitter } from 'events'; 3 | import type { BaseManager } from '../structures/BaseManager'; 4 | import { 5 | APIErrorResponse, 6 | PteroAPIError, 7 | RequestError, 8 | } from '../structures/Errors'; 9 | import { FetchOptions, RequestEvents } from '../common'; 10 | import { buildQuery } from '../util/query'; 11 | import { version } from '../../package.json'; 12 | 13 | export type Method = 'GET' | 'POST' | 'PATCH' | 'PUT' | 'DELETE'; 14 | 15 | export class RequestManager extends EventEmitter { 16 | public instance: Axios; 17 | public _ping: number; 18 | private _start: number; 19 | 20 | constructor( 21 | private _type: string, 22 | public _domain: string, 23 | public _auth: string, 24 | ) { 25 | super(); 26 | this.instance = axios.create({ 27 | baseURL: `${this._domain}/api/${this._type.toLowerCase()}`, 28 | transformResponse: (data, headers) => { 29 | if (headers && headers['content-type'].startsWith('text/plain')) 30 | return data; 31 | 32 | return JSON.parse(data); 33 | }, 34 | }); 35 | this._ping = -1; 36 | this._start = 0; 37 | } 38 | 39 | emit( 40 | event: E, 41 | ...args: RequestEvents[E] 42 | ): boolean { 43 | return super.emit(event, ...args); 44 | } 45 | 46 | on( 47 | event: E, 48 | listener: (...args: RequestEvents[E]) => void, 49 | ): this { 50 | super.on(event, listener); 51 | return this; 52 | } 53 | 54 | once( 55 | event: E, 56 | listener: (...args: RequestEvents[E]) => void, 57 | ): this { 58 | super.once(event, listener); 59 | return this; 60 | } 61 | 62 | off( 63 | event: E, 64 | listener: (...args: RequestEvents[E]) => void, 65 | ): this { 66 | super.off(event, listener); 67 | return this; 68 | } 69 | 70 | getHeaders(): Record { 71 | return { 72 | 'User-Agent': `PteroJS ${this._type} v${version}`, 73 | 'Content-Type': 'application/json', 74 | Accept: 'application/json', 75 | Authorization: `Bearer ${this._auth}`, 76 | }; 77 | } 78 | 79 | private debug(...data: string[]): void { 80 | data.map(d => `[HTTP] ${d}`).forEach(d => super.emit('debug', d)); 81 | } 82 | 83 | async _make(method: Method, path: string, body?: any) { 84 | const headers = this.getHeaders(); 85 | if (body !== null && body !== undefined) { 86 | if (typeof body === 'string') { 87 | headers['Content-Type'] = 'text/plain'; 88 | } else { 89 | body = JSON.stringify(body); 90 | } 91 | super.emit('preRequest', body); 92 | } 93 | 94 | this.debug( 95 | `requesting: ${method} ${path}`, 96 | `payload: ${body ? headers['Content-Type'] : 'none'}`, 97 | ); 98 | this._start = Date.now(); 99 | return await this.instance 100 | .request({ 101 | method, 102 | url: path, 103 | headers, 104 | data: body, 105 | }) 106 | .then(r => this.handleResponse(r)) 107 | .catch(e => this.handleError(e)); 108 | } 109 | 110 | async raw(method: Method, url: string, body?: any) { 111 | const headers = this.getHeaders(); 112 | if (body !== null && body !== undefined) { 113 | if (typeof body === 'string') { 114 | headers['Content-Type'] = 'text/plain'; 115 | } else { 116 | body = JSON.stringify(body); 117 | } 118 | super.emit('preRequest', body); 119 | } 120 | 121 | this.debug( 122 | `requesting: ${method} ${url}`, 123 | `payload: ${body ? headers['Content-Type'] : 'none'}`, 124 | ); 125 | this._start = Date.now(); 126 | return await axios 127 | .request({ 128 | url, 129 | method, 130 | headers, 131 | data: body, 132 | }) 133 | .then(r => this.handleResponse(r)) 134 | .catch(e => this.handleError(e)); 135 | } 136 | 137 | private handleResponse(res: AxiosResponse): any { 138 | this._ping = Date.now() - this._start; 139 | this.debug( 140 | `received status: ${res.status} (${this._ping}ms)`, 141 | `body: ${res.data ? res.headers['content-type'] : 'none'}`, 142 | ); 143 | 144 | if ([202, 204].includes(res.status)) return; 145 | super.emit('postRequest', res.data); 146 | 147 | if (res.data.object && res.data.object === 'null_resource') 148 | throw new RequestError('Request returned a null resource object'); 149 | 150 | return res.data; 151 | } 152 | 153 | private handleError(err: AxiosError): any { 154 | this._ping = Date.now() - this._start; 155 | this.debug( 156 | `received error: ${err.name} (${this._ping}ms)`, 157 | `message: ${err.message}`, 158 | ); 159 | 160 | if (err.response === undefined) 161 | throw new RequestError( 162 | `An unknown request error occurred: ${err.message}`, 163 | ); 164 | 165 | if (err.response!.status >= 500) 166 | throw new RequestError( 167 | `Received an unexpected response from the API ` + 168 | `(code ${err.response.status})`, 169 | ); 170 | 171 | throw new PteroAPIError(err.response.data as APIErrorResponse); 172 | } 173 | 174 | get(path: string, params?: FetchOptions, body?: any, cls?: BaseManager) { 175 | const query = 176 | params && cls ? buildQuery(params, cls.getQueryOptions()) : ''; 177 | 178 | return this._make('GET', path + query, body); 179 | } 180 | 181 | post(path: string, body?: any) { 182 | return this._make('POST', path, body); 183 | } 184 | 185 | patch(path: string, body?: any) { 186 | return this._make('PATCH', path, body); 187 | } 188 | 189 | put(path: string, body?: any) { 190 | return this._make('PUT', path, body); 191 | } 192 | 193 | delete(path: string, body?: any): Promise { 194 | return this._make('DELETE', path, body); 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { version as v } from '../package.json'; 2 | 3 | export const version = v; 4 | 5 | // Application API 6 | export { PteroApp } from './application'; 7 | export { ApplicationDatabaseManager } from './application/ApplicationDatabaseManager'; 8 | export { ApplicationServerManager } from './application/ApplicationServerManager'; 9 | export { NestEggsManager } from './application/NestEggsManager'; 10 | export { NestManager } from './application/NestManager'; 11 | export { NodeAllocationManager } from './application/NodeAllocationManager'; 12 | export { NodeLocationManager } from './application/NodeLocationManager'; 13 | export { NodeManager } from './application/NodeManager'; 14 | export { UserManager } from './application/UserManager'; 15 | 16 | // Client API 17 | export { PteroClient } from './client'; 18 | export { BackupManager } from './client/BackupManager'; 19 | export { ClientServerManager } from './client/ClientServerManager'; 20 | export { ClientDatabaseManager } from './client/ClientDatabaseManager'; 21 | export { FileManager } from './client/FileManager'; 22 | export { NetworkManager } from './client/NetworkManager'; 23 | export { ScheduleManager } from './client/ScheduleManager'; 24 | export { Shard } from './client/ws/Shard'; 25 | export { SubUserManager } from './client/SubUserManager'; 26 | export { WebSocketManager } from './client/ws/WebSocketManager'; 27 | 28 | // Commons 29 | export * from './common'; 30 | export * from './common/app'; 31 | export * from './common/client'; 32 | 33 | // HTTP 34 | export * from './http/RequestManager'; 35 | 36 | // Structures 37 | export { ApplicationServer } from './structures/ApplicationServer'; 38 | export { BaseManager } from './structures/BaseManager'; 39 | export { ClientServer } from './structures/ClientServer'; 40 | export * from './structures/Dict'; 41 | export * from './structures/Errors'; 42 | export { Node } from './structures/Node'; 43 | export * from './structures/Permissions'; 44 | export { Schedule } from './structures/Schedule'; 45 | export * from './structures/User'; 46 | 47 | // Builders 48 | export { Builder } from './builders/base'; 49 | export { NodeBuilder } from './builders/Node'; 50 | export { ServerBuilder } from './builders/Server'; 51 | export { UserBuilder } from './builders/User'; 52 | 53 | // Utilities 54 | export { default as escape } from './util/escape'; 55 | export { default as caseConv, ConvertOptions } from './util/caseConv'; 56 | export { default as configLoader } from './util/config'; 57 | export * from './util/query'; 58 | -------------------------------------------------------------------------------- /src/structures/BaseManager.ts: -------------------------------------------------------------------------------- 1 | import { FetchOptions, PaginationMeta } from '../common'; 2 | import { Dict } from './Dict'; 3 | 4 | export abstract class BaseManager { 5 | public meta: PaginationMeta = { 6 | current: 0, 7 | total: 0, 8 | count: 0, 9 | perPage: 0, 10 | totalPages: 0, 11 | }; 12 | 13 | abstract get FILTERS(): readonly string[]; 14 | abstract get SORTS(): readonly string[]; 15 | abstract get INCLUDES(): readonly string[]; 16 | 17 | abstract fetch(...args: unknown[]): Promise; 18 | 19 | /** 20 | * Gets the allowed query options from the inherited manager. 21 | * @returns The query options. 22 | * @internal 23 | */ 24 | getQueryOptions() { 25 | return { 26 | filters: this.FILTERS, 27 | sorts: this.SORTS, 28 | includes: this.INCLUDES, 29 | }; 30 | } 31 | 32 | /** 33 | * Fetches rach page and joins the results. 34 | * @returns Dictionary of the specified types 35 | * @internal 36 | */ 37 | protected async getFetchAll( 38 | ...options: unknown[] 39 | ): Promise> { 40 | // Last option should be FetchOptions 41 | const opts = (options[options.length - 1] || { 42 | page: 1, 43 | }) as FetchOptions; 44 | 45 | let data = (await this.fetch(...options)) as Dict; 46 | if (this.meta.totalPages > 1) { 47 | for (let i = 2; i <= this.meta.totalPages; i++) { 48 | opts.page = i; 49 | let page = (await this.fetch(opts)) as Dict; 50 | data.update(page); 51 | } 52 | } 53 | return data; 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/structures/Errors.ts: -------------------------------------------------------------------------------- 1 | /** Represents an API error response object. */ 2 | export interface APIErrorResponse { 3 | errors: { 4 | code: string; 5 | status: string; 6 | detail: string; 7 | meta?: unknown; 8 | }[]; 9 | } 10 | 11 | /** Thown when an API error is received (usually 4xx errors). */ 12 | export class PteroAPIError extends Error { 13 | public readonly codes: string[]; 14 | public readonly meta: unknown; 15 | 16 | constructor(data: APIErrorResponse) { 17 | const fmt = data.errors 18 | .map(e => `- ${e.status}: ${e.detail || 'No details provided'}`) 19 | .join('\n'); 20 | 21 | super('\n' + fmt); 22 | this.codes = data.errors.map(e => e.code); 23 | this.meta = data.errors.map(e => e.meta).filter(Boolean); 24 | } 25 | } 26 | 27 | /** Thrown when a non-API error is encountered. */ 28 | export class RequestError extends Error {} 29 | 30 | /** Thown when a validation a method, object or other fails to be validated. */ 31 | export class ValidationError extends Error { 32 | constructor(message: string); 33 | constructor(key: string, expected: any, got: any); 34 | constructor(...args: unknown[]) { 35 | switch (args.length) { 36 | case 3: 37 | super( 38 | `Failed to validate ${args[0]}: ` + 39 | `expected ${args[1]}; got ${args[2]}`, 40 | ); 41 | break; 42 | case 2: 43 | break; // not sure what to do with this yet. 44 | case 1: 45 | super(`Validation: ${args[0] as string}`); 46 | break; 47 | default: 48 | super('Validation check failed.'); 49 | break; 50 | } 51 | } 52 | } 53 | 54 | /** Thrown when a websocket error is encountered. */ 55 | export class WebSocketError extends Error {} 56 | -------------------------------------------------------------------------------- /src/structures/Node.ts: -------------------------------------------------------------------------------- 1 | import type { ApplicationServer } from './ApplicationServer'; 2 | import type { Dict } from './Dict'; 3 | import type { PteroApp } from '../application'; 4 | import { CreateNodeOptions, NodeConfiguration } from '../common/app'; 5 | import { DaemonData, NodeLocation } from '../common'; 6 | import caseConv from '../util/caseConv'; 7 | 8 | export class Node { 9 | public readonly client: PteroApp; 10 | 11 | /** The internal ID of the node (separate from UUID). */ 12 | public readonly id: number; 13 | 14 | /** The UUID of the node. */ 15 | public readonly uuid: string; 16 | 17 | /** The date the node was created. */ 18 | public readonly createdAt: Date; 19 | 20 | /** Whether the node is public. */ 21 | public public: boolean; 22 | 23 | /** The name of the node. */ 24 | public name: string; 25 | 26 | /** The description of the server (if set). */ 27 | public description: string | undefined; 28 | 29 | /** The ID of the location the node is on. */ 30 | public locationId: number; 31 | 32 | /** 33 | * The location object the node is on. This is not fetched by default 34 | * and must be retrieved by including 'location' in `NodeManager#fetch`. 35 | */ 36 | public location: NodeLocation | undefined; 37 | 38 | /** A dict of servers on the node. */ 39 | public servers: Dict; 40 | 41 | /** The FQDN of the node. */ 42 | public fqdn: string; 43 | 44 | /** The HTTP scheme of the node. */ 45 | public scheme: string; 46 | 47 | /** Whether the node is behind a proxy. */ 48 | public behindProxy: boolean; 49 | 50 | /** Whether the node is in maintenance mode. */ 51 | public maintenance: boolean; 52 | 53 | /** The amount of memory the node has. */ 54 | public memory: number; 55 | 56 | /** The amount of memory the node has overallocated. */ 57 | public overallocatedMemory: number; 58 | 59 | /** The amount of disk the node has. */ 60 | public disk: number; 61 | 62 | /** The amount of disk the node has overallocated. */ 63 | public overallocatedDisk: number; 64 | 65 | /** The maximum upload size for the node. */ 66 | public uploadSize: number; 67 | 68 | /** The Wings daemon information. */ 69 | public daemon: DaemonData; 70 | 71 | constructor(client: PteroApp, data: any) { 72 | this.client = client; 73 | this.id = data.id; 74 | this.uuid = data.uuid; 75 | this.createdAt = new Date(data.created_at); 76 | 77 | this._patch(data); 78 | } 79 | 80 | public _patch(data: any): void { 81 | if ('public' in data) this.public = data.public; 82 | if ('name' in data) this.name = data.name; 83 | if ('description' in data) 84 | this.description = data.description || undefined; 85 | 86 | if ('location_id' in data) this.locationId = data.location_id; 87 | if ('fqdn' in data) this.fqdn = data.fqdn; 88 | if ('scheme' in data) this.scheme = data.scheme; 89 | if ('behind_proxy' in data) this.behindProxy = data.behind_proxy; 90 | if ('maintenance_mode' in data) 91 | this.maintenance = data.maintenance_mode; 92 | 93 | if ('memory' in data) this.memory = data.memory; 94 | if ('memory_overallocate' in data) 95 | this.overallocatedMemory = data.memory_overallocate; 96 | 97 | if ('disk' in data) this.disk = data.disk; 98 | if ('disk_overallocate' in data) 99 | this.overallocatedDisk = data.disk_overallocate; 100 | 101 | if ('upload_size' in data) this.uploadSize = data.upload_size; 102 | if (!this.daemon) this.daemon = {} as DaemonData; 103 | if ('daemon_listen' in data) this.daemon.listening = data.daemon_listen; 104 | if ('daemon_sftp' in data) this.daemon.sftp = data.daemon_sftp; 105 | if ('daemon_base' in data) this.daemon.base = data.daemon_base; 106 | } 107 | 108 | /** 109 | * Returns a formatted URL to the node in the admin panel. 110 | * @returns The formatted URL. 111 | */ 112 | get adminURL(): string { 113 | return `${this.client.domain}/admin/nodes/view/${this.id}`; 114 | } 115 | 116 | /** 117 | * Fetches the configuration of the node. 118 | * @returns The node configuration. 119 | */ 120 | async getConfig(): Promise { 121 | return await this.client.nodes.getConfig(this.id); 122 | } 123 | 124 | /** 125 | * Updates the node with the specified options. 126 | * @param options Update node options. 127 | * @see {@link CreateNodeOptions UpdateNodeOptions}. 128 | * @returns The updated instance. 129 | */ 130 | async update(options: Partial): Promise { 131 | const data = await this.client.nodes.update(this.id, options); 132 | this._patch(data.toJSON()); 133 | return this; 134 | } 135 | 136 | /** 137 | * Converts the node into a JSON object, relative to the API 138 | * response object. 139 | * @returns The JSON object. 140 | */ 141 | toJSON(): object { 142 | return caseConv.toSnakeCase(this, { 143 | ignore: ['client', 'location', 'servers'], 144 | map: { 145 | maintainance: 'maintenance_mode', 146 | overallocatedMemory: 'memory_overallocate', 147 | overallocatedDisk: 'disk_overallocate', 148 | }, 149 | }); 150 | } 151 | 152 | /** @returns The string representation of the node. */ 153 | toString(): string { 154 | return this.name; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /src/structures/Schedule.ts: -------------------------------------------------------------------------------- 1 | import type { PteroClient } from '../client'; 2 | import { 3 | CreateScheduleOptions, 4 | Cron, 5 | ScheduleTask, 6 | ScheduleTaskAction, 7 | } from '../common/client'; 8 | import { Dict } from './Dict'; 9 | import { ValidationError } from '../structures/Errors'; 10 | import caseConv from '../util/caseConv'; 11 | import endpoints from '../client/endpoints'; 12 | 13 | export class Schedule { 14 | /** The ID of the schedule. */ 15 | public readonly id: number; 16 | 17 | /** The date the schedule was created. */ 18 | public readonly createdAt: Date; 19 | 20 | /** The name of the schedule. */ 21 | public name: string; 22 | 23 | /** The schedule cronjob data. */ 24 | public cron: Cron; 25 | 26 | /** Whether the schedule is active. */ 27 | public active: boolean; 28 | 29 | /** Whether the schedule is currently being processed. */ 30 | public processing: boolean; 31 | 32 | /** Whether the schedule should only run when the server is online. */ 33 | public onlyWhenOnline: boolean; 34 | 35 | /** The date the schedule was last updated. */ 36 | public updatedAt: Date | undefined; 37 | 38 | /** The date the schedule last ran. */ 39 | public lastRunAt: Date | undefined; 40 | 41 | /** The date the scheduls is supposed to run next. */ 42 | public nextRunAt: Date; 43 | 44 | /** A dict of tasks that will be executed when the schedule is running. */ 45 | public tasks: Dict; 46 | 47 | constructor( 48 | public client: PteroClient, 49 | public serverId: string, 50 | data: any, 51 | ) { 52 | this.id = data.id; 53 | this.createdAt = new Date(data.created_at); 54 | this.tasks = new Dict(); 55 | 56 | this._patch(data); 57 | } 58 | 59 | _patch(data: any): void { 60 | if ('name' in data) this.name = data.name; 61 | if ('cron' in data) this.cron = caseConv.toCamelCase(data.cron); 62 | if ('is_active' in data) this.active = data.is_active; 63 | if ('is_processing' in data) this.processing = data.is_processing; 64 | if ('only_when_online' in data) 65 | this.onlyWhenOnline = data.only_when_online; 66 | 67 | if ('updated_at' in data) this.updatedAt = new Date(data.updated_at); 68 | if ('last_run_at' in data) this.lastRunAt = new Date(data.last_run_at); 69 | if ('next_run_at' in data) this.nextRunAt = new Date(data.next_run_at); 70 | if ('relationships' in data) { 71 | if ('tasks' in data.relationships) 72 | data.relationships.tasks.data.forEach((t: any) => 73 | this._resolveTask(t), 74 | ); 75 | } 76 | } 77 | 78 | _resolveTask(data: any): ScheduleTask { 79 | const t = caseConv.toCamelCase(data.attributes, { 80 | map: { 81 | time_offset: 'offset', 82 | is_queued: 'queued', 83 | }, 84 | }); 85 | t.createdAt = new Date(t.createdAt); 86 | t.updatedAt &&= new Date(t.updatedAt); 87 | 88 | this.tasks.set(t.id, t); 89 | return t; 90 | } 91 | 92 | /** Executes the schedule immediately. */ 93 | async execute(): Promise { 94 | await this.client.requests.post( 95 | endpoints.servers.schedules.exec(this.serverId, this.id), 96 | ); 97 | this.processing = true; 98 | } 99 | 100 | /** 101 | * Updates the schedule with the specified options. 102 | * @param options Update schedule options. 103 | * @see {@link CreateScheduleOptions UpdateScheduleOptions}. 104 | * @returns The updated instance. 105 | */ 106 | async update(options: CreateScheduleOptions): Promise { 107 | const data = await this.client.schedules.update( 108 | this.serverId, 109 | this.id, 110 | options, 111 | ); 112 | this._patch(data.toJSON()); 113 | return this; 114 | } 115 | 116 | /** 117 | * Creates a task on the schedule. 118 | * @param action The action the task will perform. 119 | * @param payload The task payload. 120 | * @param offset The execution time offset. 121 | * @returns The new task. 122 | */ 123 | async createTask( 124 | action: ScheduleTaskAction, 125 | payload: string, 126 | offset: number, 127 | sequenceId?: number, 128 | ): Promise { 129 | const data = await this.client.requests.post( 130 | endpoints.servers.schedules.tasks.main(this.serverId, this.id), 131 | { action, payload, time_offset: offset, sequence_id: sequenceId }, 132 | ); 133 | return this._resolveTask(data); 134 | } 135 | 136 | /** 137 | * Updates a specified task in the schedule. 138 | * @param id The ID of the task. 139 | * @param options Update task options. 140 | * @returns The updated task. 141 | */ 142 | async updateTask( 143 | id: number, 144 | options: { 145 | action: ScheduleTaskAction; 146 | payload: string; 147 | offset: number; 148 | }, 149 | ): Promise { 150 | if (!Object.keys(options).length) 151 | throw new ValidationError( 152 | 'Too few options to update schedule task.', 153 | ); 154 | 155 | const payload = caseConv.toSnakeCase(options, { 156 | map: { offset: 'time_offset' }, 157 | }); 158 | const data = await this.client.requests.post( 159 | endpoints.servers.schedules.tasks.get(this.serverId, this.id, id), 160 | payload, 161 | ); 162 | return this._resolveTask(data); 163 | } 164 | 165 | /** 166 | * Deletes a task from the schedule. 167 | * @param id The ID of the task. 168 | */ 169 | async deleteTask(id: number): Promise { 170 | await this.client.requests.delete( 171 | endpoints.servers.schedules.tasks.get(this.serverId, this.id, id), 172 | ); 173 | this.tasks.delete(id); 174 | } 175 | 176 | /** 177 | * Converts the schedule into a JSON object, relative to the API 178 | * response object. 179 | * @returns The JSON object. 180 | */ 181 | toJSON(): object { 182 | const o = caseConv.toSnakeCase(this, { 183 | ignore: ['client', 'cron'], 184 | }); 185 | o.cron = caseConv.toSnakeCase(this.cron, { 186 | map: { 187 | week: 'day_of_week', 188 | month: 'day_of_month', 189 | }, 190 | }); 191 | return o; 192 | } 193 | 194 | /** @returns The string representation of the schedule. */ 195 | toString(): string { 196 | return this.name; 197 | } 198 | } 199 | -------------------------------------------------------------------------------- /src/types/index.d.ts: -------------------------------------------------------------------------------- 1 | // auto-generated by npm when installing - do not touch 2 | -------------------------------------------------------------------------------- /src/util/caseConv.ts: -------------------------------------------------------------------------------- 1 | export interface ConvertOptions { 2 | ignore?: string[]; 3 | map?: Record; 4 | cast?: Record; 5 | } 6 | 7 | function camelCase(str: string): string { 8 | let res = ''; 9 | let next = false; 10 | 11 | str.split('').forEach(c => { 12 | if (next) { 13 | next = false; 14 | res += c.toUpperCase(); 15 | } else if (c === '_') { 16 | next = true; 17 | } else { 18 | res += c; 19 | } 20 | }); 21 | 22 | return res; 23 | } 24 | 25 | function toCamelCase(obj: any, options: ConvertOptions = {}): T { 26 | if (typeof obj !== 'object') return obj; 27 | const parsed: Record = {}; 28 | 29 | if (Array.isArray(obj)) { 30 | return obj.map(i => toCamelCase(i)); 31 | } 32 | 33 | for (let [k, v] of Object.entries(obj)) { 34 | if (options.ignore?.includes(k)) continue; 35 | if (options.map?.[k]) k = options.map[k]; 36 | if (options.cast?.[k]) { 37 | try { 38 | const cls = options.cast[k]; 39 | // @ts-ignore 40 | v = new cls(v); 41 | } catch { 42 | v = String(v); 43 | } 44 | } 45 | if (Array.isArray(v)) { 46 | v = v.map(i => toCamelCase(i)); 47 | } else if (typeof v === 'object' && !!v) { 48 | v = toCamelCase(v); 49 | } 50 | parsed[camelCase(k)] = v; 51 | } 52 | 53 | return parsed as T; 54 | } 55 | 56 | function snakeCase(str: string): string { 57 | let res = ''; 58 | const isUpper = (c: string) => 59 | 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'.split('').includes(c); 60 | 61 | str.split('').forEach(c => { 62 | if (isUpper(c)) res += '_'; 63 | res += c.toLowerCase(); 64 | }); 65 | 66 | return res; 67 | } 68 | 69 | function toSnakeCase(obj: any, options: ConvertOptions = {}): T { 70 | if (typeof obj !== 'object') return obj; 71 | const parsed: Record = {}; 72 | 73 | if (Array.isArray(obj)) { 74 | return obj.map(i => toSnakeCase(i)); 75 | } 76 | 77 | for (let [k, v] of Object.entries(obj)) { 78 | if (options.ignore?.includes(k)) continue; 79 | if (options.map?.[k]) k = options.map[k]; 80 | if (options.cast?.[k]) { 81 | try { 82 | const cls = options.cast[k]; 83 | // @ts-ignore 84 | v = new cls(v); 85 | } catch { 86 | v = String(v); 87 | } 88 | } 89 | if (Array.isArray(v)) { 90 | v = v.map(i => toSnakeCase(i)); 91 | } else if (typeof v === 'object' && !!v) { 92 | v = toSnakeCase(v); 93 | } 94 | parsed[snakeCase(k)] = v; 95 | } 96 | 97 | return parsed as T; 98 | } 99 | 100 | export default { 101 | toCamelCase, 102 | toSnakeCase, 103 | }; 104 | -------------------------------------------------------------------------------- /src/util/config.ts: -------------------------------------------------------------------------------- 1 | import { join } from 'path'; 2 | import { FileConfig, OptionSpec } from '../common'; 3 | 4 | /** @deprecated To be replaced with a better system. */ 5 | const DEFAULT = { 6 | APPLICATION: { 7 | users: { 8 | fetch: false, 9 | cache: true, 10 | max: -1, 11 | }, 12 | nodes: { 13 | fetch: false, 14 | cache: true, 15 | max: -1, 16 | }, 17 | nests: { 18 | fetch: false, 19 | cache: true, 20 | max: -1, 21 | }, 22 | servers: { 23 | fetch: false, 24 | cache: true, 25 | max: -1, 26 | }, 27 | locations: { 28 | fetch: false, 29 | cache: true, 30 | max: -1, 31 | }, 32 | }, 33 | 34 | CLIENT: { 35 | ws: false, 36 | fetchClient: true, 37 | servers: { 38 | fetch: false, 39 | cache: true, 40 | max: -1, 41 | }, 42 | subUsers: { 43 | fetch: false, 44 | cache: true, 45 | max: -1, 46 | }, 47 | disableEvents: [], 48 | }, 49 | }; 50 | 51 | /** @deprecated To be replaced with a better system. */ 52 | function parseAs( 53 | from: Record, 54 | to: Record, 55 | ): Record { 56 | const res: { [key: string]: any } = {}; 57 | for (const [k, v] of Object.entries(to)) res[k] = k in from ? from[k] : v; 58 | for (const [k, v] of Object.entries(res)) 59 | if (v.max === -1) res[k].max = Infinity; 60 | return res; 61 | } 62 | 63 | /** @deprecated To be replaced with a better system. */ 64 | function appConfig(options?: FileConfig): Record { 65 | if ( 66 | options !== undefined && 67 | typeof options === 'object' && 68 | Object.keys(options).length 69 | ) 70 | return parseAs(options, DEFAULT.APPLICATION); 71 | try { 72 | options = require(join(process.cwd(), 'pterojs.json')) as FileConfig; 73 | return parseAs(options.application ?? {}, DEFAULT.APPLICATION); 74 | } catch { 75 | return DEFAULT.APPLICATION; 76 | } 77 | } 78 | 79 | /** @deprecated To be replaced with a better system. */ 80 | function clientConfig(options?: FileConfig): Record { 81 | if ( 82 | options !== undefined && 83 | typeof options === 'object' && 84 | Object.keys(options).length 85 | ) 86 | return parseAs(options, DEFAULT.CLIENT); 87 | try { 88 | options = require(join(process.cwd(), 'pterojs.json')) as FileConfig; 89 | return parseAs(options.client ?? {}, DEFAULT.CLIENT); 90 | } catch { 91 | return DEFAULT.CLIENT; 92 | } 93 | } 94 | 95 | export default { 96 | DEFAULT, 97 | parseAs, 98 | appConfig, 99 | clientConfig, 100 | }; 101 | -------------------------------------------------------------------------------- /src/util/escape.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Escapes (or removes) all the ASCII color and control codes from the given string. This is useful 3 | * for cleaning logs from a server webssocket output. 4 | * @param input The string to escape. 5 | * @returns The escaped string. 6 | */ 7 | export default function (input: string): string { 8 | return input.replace( 9 | /[\u001b\u009b][[()#;?]*(?:[0-9]{1,4}(?:;[0-9]{0,4})*)?[0-9A-ORZcf-nqry=><]/g, 10 | '', 11 | ); 12 | } 13 | -------------------------------------------------------------------------------- /src/util/query.ts: -------------------------------------------------------------------------------- 1 | import { FetchOptions, FilterArray, Include, Sort } from '../common'; 2 | import { ValidationError } from '../structures/Errors'; 3 | export interface AllowedQueryOptions { 4 | filters: readonly string[]; 5 | includes: readonly string[]; 6 | sorts: readonly string[]; 7 | } 8 | 9 | /** 10 | * Parses an array of arguments into a Pterodactyl query string. 11 | * @param args The args to parse into the query. 12 | * @param allowed Allowed argument guards. 13 | * @returns The parsed query. 14 | */ 15 | export const buildQuery = ( 16 | args: FilterArray>>, 17 | allowed: AllowedQueryOptions, 18 | ): string => { 19 | const parsed: string[] = []; 20 | 21 | if (args.page) parsed.push(`page=${args.page}`); 22 | 23 | if (args.perPage && args.perPage > 0) { 24 | if (args.perPage > 100) args.perPage = 100; 25 | parsed.push(`per_page=${args.perPage}`); 26 | } 27 | 28 | if (args.filter) { 29 | if (!allowed.filters?.includes(args.filter[0])) 30 | throw new ValidationError( 31 | `Invalid filter argument '${args.filter[0]}'.`, 32 | ); 33 | 34 | parsed.push(`filter[${args.filter[0]}]=${args.filter[1]}`); 35 | } 36 | 37 | if (args.include) { 38 | for (const arg of args.include) { 39 | if (!allowed.includes.includes(arg)) 40 | throw new ValidationError(`Invalid include argument '${arg}'.`); 41 | } 42 | if (args.include?.length) parsed.push(`include=${args.include}`); 43 | } 44 | 45 | if (args.sort) { 46 | if (!allowed.sorts.includes(args.sort)) 47 | throw new ValidationError(`Invalid sort argument '${args.sort}'.`); 48 | 49 | parsed.push(`sort=${args.sort}`); 50 | } 51 | 52 | if (!parsed.length) return ''; 53 | return '?' + parsed.join('&'); 54 | }; 55 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ESNext", 4 | "module": "CommonJS", 5 | "outDir": "./dist", 6 | "lib": ["ES2021"], 7 | "declaration": true, 8 | "noImplicitAny": true, 9 | "removeComments": false, 10 | "strictNullChecks": true, 11 | "resolveJsonModule": true, 12 | "moduleResolution": "Node", 13 | "preserveConstEnums": true, 14 | "useDefineForClassFields": false 15 | }, 16 | "include": ["src/**.ts"] 17 | } 18 | -------------------------------------------------------------------------------- /tsup.config.ts: -------------------------------------------------------------------------------- 1 | import type { Options } from 'tsup'; 2 | 3 | export const tsup: Options = { 4 | splitting: false, 5 | sourcemap: false, 6 | clean: true, 7 | bundle: true, 8 | dts: true, 9 | keepNames: true, 10 | target: 'esnext', 11 | format: ['esm', 'cjs'], 12 | entryPoints: ['src/index.ts'], 13 | }; 14 | --------------------------------------------------------------------------------