exaroton 1.7.0 → 1.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,19 @@
1
+ name: Node.js Package
2
+
3
+ on:
4
+ release:
5
+ types: [created]
6
+
7
+ jobs:
8
+ publish-npm:
9
+ runs-on: ubuntu-latest
10
+ steps:
11
+ - uses: actions/checkout@v3
12
+ - uses: actions/setup-node@v3
13
+ with:
14
+ node-version: 16
15
+ registry-url: https://registry.npmjs.org/
16
+ - run: npm ci
17
+ - run: npm publish
18
+ env:
19
+ NODE_AUTH_TOKEN: ${{secrets.npm_token}}
package/README.md CHANGED
@@ -218,6 +218,79 @@ try {
218
218
  }
219
219
  ```
220
220
 
221
+ #### Files
222
+
223
+ You can request information about files, download and upload files.
224
+
225
+ ##### Get a file object
226
+ This just creates the file object but doesn't request any information or content
227
+ ```js
228
+ let file = server.getFile("server.properties");
229
+ ```
230
+
231
+ ##### Get file information
232
+ If a file doesn't exist you will get a 404 error.
233
+ ```js
234
+ try {
235
+ await file.getInfo();
236
+ console.log(file);
237
+ } catch (e) {
238
+ console.error(e.message);
239
+ }
240
+ ```
241
+
242
+ ##### Get the content of a file / download a file
243
+ ```js
244
+ try {
245
+ // get the content of the file in a variable
246
+ // large files will cause high memory usage
247
+ let content = await file.getContent();
248
+ console.log(content);
249
+
250
+ // or download the file to a local file
251
+ await file.download("test.txt");
252
+
253
+ // or download the file to a stream
254
+ let stream = await file.downloadToStream(createWriteStream("test.txt"));
255
+ } catch (e) {
256
+ console.error(e.message);
257
+ }
258
+ ```
259
+
260
+ ##### Change the content of a file / upload a file
261
+ ```js
262
+ try {
263
+ // change the content of the file
264
+ await file.setContent("Hello world!");
265
+
266
+ // or upload a local file
267
+ await file.upload("test.txt");
268
+
269
+ // or upload from a stream
270
+ await file.uploadFromStream(createReadStream("test.txt"));
271
+ } catch (e) {
272
+ console.error(e.message);
273
+ }
274
+ ```
275
+
276
+ ##### Delete a file
277
+ ```js
278
+ try {
279
+ await file.delete();
280
+ } catch (e) {
281
+ console.error(e.message);
282
+ }
283
+ ```
284
+
285
+ ##### Create a directory
286
+ ```js
287
+ try {
288
+ await file.createAsDirectory();
289
+ } catch (e) {
290
+ console.error(e.message);
291
+ }
292
+ ```
293
+
221
294
  ### Websocket API
222
295
  The websocket API allows a constant connection to our websocket service to receive
223
296
  events in real time without polling (e.g. trying to get the server status every few seconds).
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "exaroton",
3
- "version": "1.7.0",
3
+ "version": "1.8.0",
4
4
  "description": "exaroton API client",
5
5
  "homepage": "https://exaroton.com",
6
6
  "main": "index.js",
package/src/Client.js CHANGED
@@ -1,4 +1,5 @@
1
1
  const got = require('got');
2
+ const fs = require("fs").promises;
2
3
 
3
4
  const Server = require('./Server/Server');
4
5
  const Account = require('./Account/Account');
@@ -29,7 +30,9 @@ class Client {
29
30
  *
30
31
  * @type {string}
31
32
  */
32
- get baseURL() { return this.protocol + "://" + this.host + this.basePath }
33
+ get baseURL() {
34
+ return this.protocol + "://" + this.host + this.basePath
35
+ }
33
36
 
34
37
  /**
35
38
  * API token used for authentication
@@ -112,7 +115,7 @@ class Client {
112
115
  method: request.method,
113
116
  retry: 0,
114
117
  headers: headers,
115
- responseType: "json"
118
+ responseType: request.responseType
116
119
  };
117
120
 
118
121
  if (request.hasBody()) {
@@ -121,22 +124,51 @@ class Client {
121
124
 
122
125
  let response;
123
126
  try {
124
- response = await got(url, gotOptions);
127
+ if (request.hasOutputStream()) {
128
+ await this.streamResponse(url, gotOptions, request.getOutputStream());
129
+ return request.createResponse();
130
+ } else {
131
+ response = await got(url, gotOptions);
132
+ }
125
133
  } catch (e) {
134
+ if (request.outputPath !== null) {
135
+ try {
136
+ await fs.unlink(request.outputPath);
137
+ } catch (e) {
138
+ // ignore
139
+ }
140
+ }
126
141
  throw new RequestStatusError(e);
127
142
  }
128
143
 
129
- if (response.body.success) {
144
+ if (!request.expectsJsonResponse() || response.body.success) {
130
145
  return request.createResponse(response.body);
131
146
  } else {
132
147
  throw new RequestBodyError(response);
133
148
  }
134
149
  }
135
150
 
151
+ /**
152
+ * @param {string} url
153
+ * @param {{}} gotOptions
154
+ * @param {stream.Writable} outputStream
155
+ * @return {Promise<unknown>}
156
+ */
157
+ streamResponse(url, gotOptions, outputStream) {
158
+ return new Promise((resolve, reject) => {
159
+ let stream = got.stream(url, gotOptions);
160
+ stream.pipe(outputStream);
161
+ stream.on("error", async (error) => {
162
+ reject(error);
163
+ });
164
+ stream.on("end", resolve);
165
+ });
166
+ }
167
+
136
168
  /**
137
169
  * Get a list of all servers
138
170
  *
139
- * @return {Response}
171
+ * @return {Promise<Server[]>}
140
172
  * @throws {RequestError}
141
173
  */
142
174
  async getServers() {
@@ -1,4 +1,6 @@
1
1
  const FormData = require('form-data');
2
+ const {createReadStream} = require('fs');
3
+ const {createWriteStream} = require("fs");
2
4
 
3
5
  const Response = require('../Response/Response');
4
6
 
@@ -32,7 +34,7 @@ class Request {
32
34
  /**
33
35
  * Post body data
34
36
  *
35
- * @type {null|{}}
37
+ * @type {null|{}|string}
36
38
  */
37
39
  data = null;
38
40
 
@@ -43,6 +45,43 @@ class Request {
43
45
  */
44
46
  responseClass = Response;
45
47
 
48
+ /**
49
+ * Response type (text|json|buffer)
50
+ *
51
+ * https://github.com/sindresorhus/got/blob/main/documentation/2-options.md#responsetype
52
+ *
53
+ * @type {string}
54
+ */
55
+ responseType = "json";
56
+
57
+ /**
58
+ * Optional path to write the response body to
59
+ *
60
+ * @type {string|null}
61
+ */
62
+ outputPath = null;
63
+
64
+ /**
65
+ * Optional stream to stream the response body to
66
+ *
67
+ * @type {stream.Writable|null}
68
+ */
69
+ outputStream = null;
70
+
71
+ /**
72
+ * Optional path to read the request body from
73
+ *
74
+ * @type {string|null}
75
+ */
76
+ inputPath = null;
77
+
78
+ /**
79
+ * Optional stream to read the request body from
80
+ *
81
+ * @type {stream.Readable|null}
82
+ */
83
+ inputStream = null;
84
+
46
85
  /**
47
86
  * Client that has executed this request
48
87
  *
@@ -93,15 +132,23 @@ class Request {
93
132
  * @return {boolean}
94
133
  */
95
134
  hasBody() {
96
- return ["POST", "PUT", "DELETE"].includes(this.method) && this.data !== null;
135
+ return ["POST", "PUT", "DELETE"].includes(this.method) && (this.data !== null || this.inputPath !== null || this.inputStream !== null);
97
136
  }
98
137
 
99
138
  /**
100
139
  * Get body for request
101
140
  *
102
- * @return {FormData|string}
141
+ * @return {FormData|string|ReadStream}
103
142
  */
104
143
  getBody() {
144
+ if (this.hasInputStream()) {
145
+ return this.getInputStream();
146
+ }
147
+
148
+ if (typeof this.data === "string") {
149
+ return this.data;
150
+ }
151
+
105
152
  let body = new FormData();
106
153
  for (let key in this.data) {
107
154
  if (!this.data.hasOwnProperty(key)) {
@@ -121,14 +168,116 @@ class Request {
121
168
  /**
122
169
  * Create a response object for this request
123
170
  *
124
- * @param {{}} body
171
+ * @param {{}|string|null} body
125
172
  * @return {Response}
126
173
  */
127
- createResponse(body) {
174
+ createResponse(body = null) {
128
175
  let response = new (this.responseClass)(this);
129
176
  response.setBody(body);
130
177
  return response;
131
178
  }
179
+
180
+ /**
181
+ * @return {boolean}
182
+ */
183
+ expectsJsonResponse() {
184
+ return this.responseType === "json";
185
+ }
186
+
187
+ /**
188
+ * @return {null|stream.Writable}
189
+ */
190
+ getOutputStream() {
191
+ if (this.outputStream !== null) {
192
+ return this.outputStream;
193
+ }
194
+ if (this.outputPath !== null) {
195
+ return createWriteStream(this.outputPath);
196
+ }
197
+ return null;
198
+ }
199
+
200
+ /**
201
+ * @return {boolean}
202
+ */
203
+ hasOutputStream() {
204
+ return this.outputStream !== null || this.outputPath !== null;
205
+ }
206
+
207
+ /**
208
+ * @return {null|stream.Readable}
209
+ */
210
+ getInputStream() {
211
+ if (this.inputStream !== null) {
212
+ return this.inputStream;
213
+ }
214
+ if (this.inputPath !== null) {
215
+ return createReadStream(this.inputPath);
216
+ }
217
+ return null;
218
+ }
219
+
220
+ /**
221
+ * @return {boolean}
222
+ */
223
+ hasInputStream() {
224
+ return this.inputStream !== null || this.inputPath !== null;
225
+ }
226
+
227
+ /**
228
+ * Set the data to put as string
229
+ *
230
+ * @param {string|{}} data
231
+ * @return {this}
232
+ */
233
+ setData(data) {
234
+ this.data = data;
235
+ return this;
236
+ }
237
+
238
+ /**
239
+ * Set a file as input file for the request body
240
+ *
241
+ * @param {string} inputPath
242
+ * @return {this}
243
+ */
244
+ setInputPath(inputPath) {
245
+ this.inputPath = inputPath;
246
+ return this;
247
+ }
248
+
249
+ /**
250
+ * Set a file as output file for the response body
251
+ *
252
+ * @param {string} outputPath
253
+ * @return {this}
254
+ */
255
+ setOutputPath(outputPath) {
256
+ this.outputPath = outputPath;
257
+ return this;
258
+ }
259
+
260
+ /**
261
+ * Set a stream as input stream for the request body
262
+ *
263
+ * @param {stream.Readable} inputStream
264
+ * @return {this}
265
+ */
266
+ setInputStream(inputStream) {
267
+ this.inputStream = inputStream;
268
+ return this;
269
+ }
270
+
271
+ /**
272
+ * Set a stream as output stream for the response body
273
+ *
274
+ * @param {stream.Writable} outputStream
275
+ * @return {this}
276
+ */
277
+ setOutputStream(outputStream) {
278
+ this.outputStream = outputStream;
279
+ return this;
280
+ }
132
281
  }
133
282
 
134
283
  module.exports = Request;
@@ -0,0 +1,9 @@
1
+ const PutFileDataRequest = require("./PutFileDataRequest");
2
+
3
+ class CreateDirectoryRequest extends PutFileDataRequest {
4
+ headers = {
5
+ "Content-Type": "inode/directory"
6
+ }
7
+ }
8
+
9
+ module.exports = CreateDirectoryRequest;
@@ -0,0 +1,7 @@
1
+ const FileDataRequest = require("./FileDataRequest");
2
+
3
+ class DeleteFileDataRequest extends FileDataRequest {
4
+ method = "DELETE";
5
+ }
6
+
7
+ module.exports = DeleteFileDataRequest;
@@ -0,0 +1,7 @@
1
+ const FileRequest = require("./FileRequest");
2
+
3
+ class FileDataRequest extends FileRequest {
4
+ endpoint = "servers/{id}/files/data/{path}";
5
+ }
6
+
7
+ module.exports = FileDataRequest;
@@ -0,0 +1,26 @@
1
+ const ServerRequest = require('../ServerRequest');
2
+
3
+ class FileRequest extends ServerRequest {
4
+ /**
5
+ * FileRequest constructor
6
+ *
7
+ * @param {string} id
8
+ * @param {string} path
9
+ */
10
+ constructor(id, path) {
11
+ super(id);
12
+ this.setPath(path);
13
+ }
14
+
15
+ /**
16
+ * Set the path parameter and url encode all characters except slashes
17
+ *
18
+ * @return {this}
19
+ */
20
+ setPath(path) {
21
+ this.setParameter("path", path.replace(/[^\/]+/g, encodeURIComponent));
22
+ return this;
23
+ }
24
+ }
25
+
26
+ module.exports = FileRequest;
@@ -0,0 +1,7 @@
1
+ const FileDataRequest = require("./FileDataRequest");
2
+
3
+ class GetFileDataRequest extends FileDataRequest {
4
+ responseType = "text";
5
+ }
6
+
7
+ module.exports = GetFileDataRequest;
@@ -0,0 +1,7 @@
1
+ const FileRequest = require('./FileRequest');
2
+
3
+ class GetFileInformationRequest extends FileRequest {
4
+ endpoint = "servers/{id}/files/info/{path}";
5
+ }
6
+
7
+ module.exports = GetFileInformationRequest;
@@ -0,0 +1,7 @@
1
+ const FileDataRequest = require("./FileDataRequest");
2
+
3
+ class PutFileDataRequest extends FileDataRequest {
4
+ method = "PUT";
5
+ }
6
+
7
+ module.exports = PutFileDataRequest;
@@ -7,7 +7,7 @@ class Response {
7
7
  /**
8
8
  * (raw/parsed) response body
9
9
  *
10
- * @type {{}}
10
+ * @type {{}|string}
11
11
  */
12
12
  body;
13
13
 
@@ -27,13 +27,21 @@ class Response {
27
27
  * @return {*|null}
28
28
  */
29
29
  getData() {
30
+ if (typeof this.body === "undefined" || this.body === null) {
31
+ return null;
32
+ }
33
+
34
+ if (typeof this.body === "string") {
35
+ return this.body;
36
+ }
37
+
30
38
  return typeof this.body.data !== "undefined" ? this.body.data : null;
31
39
  }
32
40
 
33
41
  /**
34
42
  * Set the body to this.body and maybe parse content
35
43
  *
36
- * @param {{}} body
44
+ * @param {{}|string} body
37
45
  */
38
46
  setBody(body) {
39
47
  this.body = body;
@@ -0,0 +1,256 @@
1
+ const GetFileInformationRequest = require("../Request/Server/Files/GetFileInformationRequest");
2
+ const GetFileDataRequest = require("../Request/Server/Files/GetFileDataRequest");
3
+ const PutFileDataRequest = require("../Request/Server/Files/PutFileDataRequest");
4
+ const DeleteFileDataRequest = require("../Request/Server/Files/DeleteFileDataRequest");
5
+ const CreateDirectoryRequest = require("../Request/Server/Files/CreateDirectoryRequest");
6
+
7
+ class File {
8
+ /**
9
+ * File path relative to server root
10
+ *
11
+ * @type {string}
12
+ */
13
+ path;
14
+
15
+ /**
16
+ * File name
17
+ *
18
+ * @type {string}
19
+ */
20
+ name;
21
+
22
+ /**
23
+ * @type {boolean}
24
+ */
25
+ isTextFile;
26
+
27
+ /**
28
+ * @type {boolean}
29
+ */
30
+ isConfigFile;
31
+
32
+ /**
33
+ * @type {boolean}
34
+ */
35
+ isDirectory;
36
+
37
+ /**
38
+ * @type {boolean}
39
+ */
40
+ isLog;
41
+
42
+ /**
43
+ * @type {boolean}
44
+ */
45
+ isReadable;
46
+
47
+ /**
48
+ * @type {boolean}
49
+ */
50
+ isWritable;
51
+
52
+ /**
53
+ * @type {number}
54
+ */
55
+ size;
56
+
57
+ /**
58
+ * @type {[File]|null}
59
+ */
60
+ children = null;
61
+
62
+ /**
63
+ * @type {{Server}}
64
+ */
65
+ #server;
66
+
67
+ /**
68
+ * @type {{Client}}
69
+ */
70
+ #client;
71
+
72
+ /**
73
+ * @param {string|null} path
74
+ */
75
+ constructor(path = null) {
76
+ if (path) {
77
+ this.setPath(path);
78
+ }
79
+ }
80
+
81
+ /**
82
+ * @param {string} path
83
+ */
84
+ setPath(path) {
85
+ if (path.startsWith("/")) {
86
+ path = path.substring(1);
87
+ }
88
+ this.path = path;
89
+ }
90
+
91
+ /**
92
+ * Apply data from the API response
93
+ *
94
+ * @param {object} object
95
+ * @return {File}
96
+ */
97
+ applyData(object) {
98
+ if (typeof object.path !== "undefined") {
99
+ this.setPath(object.path);
100
+ }
101
+ this.name = typeof object.name !== "undefined" ? object.name : null;
102
+ this.isTextFile = typeof object.isTextFile !== "undefined" ? object.isTextFile : null;
103
+ this.isConfigFile = typeof object.isConfigFile !== "undefined" ? object.isConfigFile : null;
104
+ this.isDirectory = typeof object.isDirectory !== "undefined" ? object.isDirectory : null;
105
+ this.isLog = typeof object.isLog !== "undefined" ? object.isLog : null;
106
+ this.isReadable = typeof object.isReadable !== "undefined" ? object.isReadable : null;
107
+ this.isWritable = typeof object.isWritable !== "undefined" ? object.isWritable : null;
108
+ this.size = typeof object.size !== "undefined" ? object.size : null;
109
+ this.children = Array.isArray(object.children) ? object.children.map((child) => new File().applyData(child).setServer(this.#server).setClient(this.#client)) : null;
110
+ return this;
111
+ }
112
+
113
+ /**
114
+ * Set the server
115
+ *
116
+ * @param server
117
+ * @returns {this}
118
+ */
119
+ setServer(server) {
120
+ this.#server = server;
121
+ if (Array.isArray(this.children)) {
122
+ for (let child of this.children) {
123
+ child.setServer(server);
124
+ }
125
+ }
126
+ return this;
127
+ }
128
+
129
+ /**
130
+ * Set the API client
131
+ *
132
+ * @param client
133
+ * @returns {this}
134
+ */
135
+ setClient(client) {
136
+ this.#client = client;
137
+ if (Array.isArray(this.children)) {
138
+ for (let child of this.children) {
139
+ child.setClient(client);
140
+ }
141
+ }
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * Get file information from the API
147
+ *
148
+ * @returns {Promise<File>}
149
+ */
150
+ async getInfo() {
151
+ const response = await this.#client.request(new GetFileInformationRequest(this.#server.id, this.path));
152
+ this.applyData(response.getData());
153
+ return this;
154
+ }
155
+
156
+ /**
157
+ * Get the data/content of a file
158
+ *
159
+ * If you want to download the file to a local file use File.download() instead
160
+ *
161
+ * @return {Promise<string>}
162
+ */
163
+ async getContent() {
164
+ const response = await this.#client.request(new GetFileDataRequest(this.#server.id, this.path));
165
+ return response.getData();
166
+ }
167
+
168
+ /**
169
+ * Download the data/content of a file to a local file
170
+ *
171
+ * If you want to use the content of the file directly use File.getContent() instead
172
+ *
173
+ * @param {string} outputPath
174
+ * @return {Promise<Response>}
175
+ */
176
+ async download(outputPath) {
177
+ return await this.#client.request(new GetFileDataRequest(this.#server.id, this.path).setOutputPath(outputPath));
178
+ }
179
+
180
+ /**
181
+ * Download the data/content of a file into a writable stream
182
+ *
183
+ * @param {stream.Writable} outputStream
184
+ * @return {Promise<Response>}
185
+ */
186
+ async downloadToStream(outputStream) {
187
+ return await this.#client.request(new GetFileDataRequest(this.#server.id, this.path).setOutputStream(outputStream));
188
+ }
189
+
190
+ /**
191
+ * Put the content of a file
192
+ *
193
+ * If you want to upload a local file use File.upload() instead
194
+ *
195
+ * @param {string} content
196
+ * @return {Promise<Response>}
197
+ */
198
+ async putContent(content) {
199
+ return await this.#client.request(new PutFileDataRequest(this.#server.id, this.path).setData(content));
200
+ }
201
+
202
+ /**
203
+ * Upload a local file
204
+ *
205
+ * If you want to upload the content of the file directly as a string use File.putContent() instead
206
+ *
207
+ * @param {string} inputPath
208
+ * @return {Promise<Response>}
209
+ */
210
+ async upload(inputPath) {
211
+ return await this.#client.request(new PutFileDataRequest(this.#server.id, this.path).setInputPath(inputPath));
212
+ }
213
+
214
+ /**
215
+ * Upload from a readable stream
216
+ *
217
+ * @param {stream.Readable} inputStream
218
+ * @return {Promise<Response>}
219
+ */
220
+ async uploadFromStream(inputStream) {
221
+ return this.#client.request(new PutFileDataRequest(this.#server.id, this.path).setInputStream(inputStream));
222
+ }
223
+
224
+ /**
225
+ * Delete the file
226
+ *
227
+ * @return {Promise<Response>}
228
+ */
229
+ async delete() {
230
+ return await this.#client.request(new DeleteFileDataRequest(this.#server.id, this.path));
231
+ }
232
+
233
+ /**
234
+ * Create a directory
235
+ *
236
+ * @return {Promise<Response>}
237
+ */
238
+ async createAsDirectory() {
239
+ return await this.#client.request(new CreateDirectoryRequest(this.#server.id, this.path));
240
+ }
241
+
242
+ /**
243
+ * Get the children of a directory
244
+ *
245
+ * @return {Promise<[File]|null>}
246
+ */
247
+ async getChildren() {
248
+ if (this.children === null && this.isDirectory) {
249
+ await this.getInfo();
250
+ }
251
+
252
+ return this.children;
253
+ }
254
+ }
255
+
256
+ module.exports = File;
@@ -5,6 +5,7 @@ const Software = require('./Software');
5
5
  const Players = require('./Players');
6
6
  const ServerStatus = require('./ServerStatus');
7
7
  const PlayerList = require('./PlayerList');
8
+ const File = require('./File');
8
9
  const GetServerRequest = require('../Request/Server/GetServerRequest');
9
10
  const StartServerRequest = require('../Request/Server/StartServerRequest');
10
11
  const StopServerRequest = require('../Request/Server/StopServerRequest');
@@ -306,6 +307,19 @@ class Server extends EventEmitter {
306
307
  return this.#playerLists[name];
307
308
  }
308
309
 
310
+ /**
311
+ * Get a file object for a server file
312
+ *
313
+ * This doesn't request file info or content yet.
314
+ * Use the File.getInfo() and File.getContent() functions for that
315
+ *
316
+ * @param {string} path The path of the file relative to the server root
317
+ * @return {File}
318
+ */
319
+ getFile(path) {
320
+ return new File(path).setServer(this).setClient(this.#client);
321
+ }
322
+
309
323
  /**
310
324
  * Check if the server has one or one of multiple status codes
311
325
  *
@@ -344,7 +358,7 @@ class Server extends EventEmitter {
344
358
  * Subscribe to one or multiple streams
345
359
  *
346
360
  * @return {boolean}
347
- * @param {string[]|string} streams
361
+ * @param {string[]|string} [streams]
348
362
  */
349
363
  subscribe(streams) {
350
364
  let websocketClient = this.getWebsocketClient();
@@ -372,7 +386,7 @@ class Server extends EventEmitter {
372
386
  /**
373
387
  * Unsubscribe from one, multiple or all streams
374
388
  *
375
- * @param {string[]|string} streams
389
+ * @param {string[]|string} [streams]
376
390
  */
377
391
  unsubscribe(streams) {
378
392
  let websocketClient = this.getWebsocketClient();
@@ -421,6 +435,28 @@ class Server extends EventEmitter {
421
435
  }
422
436
  return this;
423
437
  }
438
+
439
+ /**
440
+ * Only return intended public fields for JSON serialization
441
+ *
442
+ * Otherwise, fields inherited from EventEmitter would be serialized as well
443
+ *
444
+ * @returns {{}}
445
+ */
446
+ toJSON() {
447
+ return {
448
+ id: this.id,
449
+ name: this.name,
450
+ address: this.address,
451
+ motd: this.motd,
452
+ status: this.status,
453
+ host: this.host,
454
+ port: this.port,
455
+ shared: this.shared,
456
+ software: this.software,
457
+ players: this.players
458
+ }
459
+ }
424
460
  }
425
461
 
426
462
  module.exports = Server;