@screepts/screeps-api 1.0.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.
- package/LICENSE +13 -0
- package/README.md +141 -0
- package/bin/screeps-api.js +2 -0
- package/dist/cli.d.mts +1 -0
- package/dist/cli.mjs +978 -0
- package/dist/index.d.mts +1147 -0
- package/dist/index.mjs +859 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +62 -0
package/dist/cli.mjs
ADDED
|
@@ -0,0 +1,978 @@
|
|
|
1
|
+
import { Command } from "commander";
|
|
2
|
+
import { EventEmitter } from "eventemitter3";
|
|
3
|
+
import Debug from "debug";
|
|
4
|
+
import { inflate } from "pako";
|
|
5
|
+
import yaml from "js-yaml";
|
|
6
|
+
import { readFile, writeFile } from "fs/promises";
|
|
7
|
+
import { join, parse } from "path";
|
|
8
|
+
//#region src/Socket.ts
|
|
9
|
+
const debug = Debug("screepsapi:socket");
|
|
10
|
+
const DEFAULTS$1 = {
|
|
11
|
+
reconnect: true,
|
|
12
|
+
resubscribe: true,
|
|
13
|
+
keepAlive: true,
|
|
14
|
+
maxRetries: 10,
|
|
15
|
+
maxRetryDelay: 60 * 1e3
|
|
16
|
+
};
|
|
17
|
+
var Socket = class extends EventEmitter {
|
|
18
|
+
ws;
|
|
19
|
+
api;
|
|
20
|
+
opts;
|
|
21
|
+
authed = false;
|
|
22
|
+
connected = false;
|
|
23
|
+
reconnecting = false;
|
|
24
|
+
keepAliveInter = 0;
|
|
25
|
+
__queue = [];
|
|
26
|
+
__subQueue = [];
|
|
27
|
+
__subs = {};
|
|
28
|
+
constructor(api) {
|
|
29
|
+
super();
|
|
30
|
+
this.api = api;
|
|
31
|
+
this.opts = Object.assign({}, DEFAULTS$1);
|
|
32
|
+
this.on("error", () => {});
|
|
33
|
+
this.reset();
|
|
34
|
+
this.on("auth", (ev) => {
|
|
35
|
+
if (ev.data.status === "ok") {
|
|
36
|
+
while (this.__queue.length) this.ws?.send(this.__queue.shift());
|
|
37
|
+
clearInterval(this.keepAliveInter);
|
|
38
|
+
if (this.opts.keepAlive) this.keepAliveInter = setInterval(() => this.ws && this.ws.send(""), 1e4);
|
|
39
|
+
}
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
reset() {
|
|
43
|
+
this.authed = false;
|
|
44
|
+
this.connected = false;
|
|
45
|
+
this.reconnecting = false;
|
|
46
|
+
clearInterval(this.keepAliveInter);
|
|
47
|
+
this.keepAliveInter = 0;
|
|
48
|
+
this.__queue = [];
|
|
49
|
+
this.__subQueue = [];
|
|
50
|
+
this.__subs = {};
|
|
51
|
+
}
|
|
52
|
+
async connect(opts = {}) {
|
|
53
|
+
Object.assign(this.opts, opts);
|
|
54
|
+
if (!this.api.token) throw new Error("No token! Call api.auth() before connecting the socket!");
|
|
55
|
+
return new Promise((resolve, reject) => {
|
|
56
|
+
const baseURL = this.api.opts.url.replace("http", "ws");
|
|
57
|
+
const wsurl = new URL("socket/websocket", baseURL);
|
|
58
|
+
this.ws = new WebSocket(wsurl);
|
|
59
|
+
this.ws.addEventListener("open", () => {
|
|
60
|
+
this.connected = true;
|
|
61
|
+
this.reconnecting = false;
|
|
62
|
+
if (this.opts.resubscribe) this.__subQueue.push(...Object.keys(this.__subs));
|
|
63
|
+
debug("connected");
|
|
64
|
+
this.emit("connected");
|
|
65
|
+
resolve(this.auth(this.api.token));
|
|
66
|
+
});
|
|
67
|
+
this.ws.onclose = () => {
|
|
68
|
+
clearInterval(this.keepAliveInter);
|
|
69
|
+
this.authed = false;
|
|
70
|
+
this.connected = false;
|
|
71
|
+
debug("disconnected");
|
|
72
|
+
this.emit("disconnected");
|
|
73
|
+
if (this.opts.reconnect) this.reconnect().catch(() => {});
|
|
74
|
+
};
|
|
75
|
+
this.ws.addEventListener("error", (err) => {
|
|
76
|
+
this.ws?.close();
|
|
77
|
+
this.emit("error", err);
|
|
78
|
+
debug(`error ${err.error || err}`);
|
|
79
|
+
if (!this.connected) reject(err);
|
|
80
|
+
});
|
|
81
|
+
this.ws.addEventListener("message", (data) => this.handleMessage(data));
|
|
82
|
+
});
|
|
83
|
+
}
|
|
84
|
+
async reconnect() {
|
|
85
|
+
if (this.reconnecting) return;
|
|
86
|
+
this.reconnecting = true;
|
|
87
|
+
let retries = 0;
|
|
88
|
+
let retry;
|
|
89
|
+
do {
|
|
90
|
+
let time = Math.pow(2, retries) * 100;
|
|
91
|
+
if (time > this.opts.maxRetryDelay) time = this.opts.maxRetryDelay;
|
|
92
|
+
await this.sleep(time);
|
|
93
|
+
if (!this.reconnecting) return;
|
|
94
|
+
try {
|
|
95
|
+
await this.connect();
|
|
96
|
+
retry = false;
|
|
97
|
+
} catch {
|
|
98
|
+
retry = true;
|
|
99
|
+
}
|
|
100
|
+
retries++;
|
|
101
|
+
debug(`reconnect ${retries}/${this.opts.maxRetries}`);
|
|
102
|
+
} while (retry && retries < this.opts.maxRetries);
|
|
103
|
+
if (retry) {
|
|
104
|
+
const err = /* @__PURE__ */ new Error(`Reconnection failed after ${this.opts.maxRetries} retries`);
|
|
105
|
+
this.reconnecting = false;
|
|
106
|
+
debug("reconnect failed");
|
|
107
|
+
this.emit("error", err);
|
|
108
|
+
throw err;
|
|
109
|
+
} else Object.keys(this.__subs).forEach((sub) => this.subscribe(sub));
|
|
110
|
+
}
|
|
111
|
+
disconnect() {
|
|
112
|
+
debug("disconnect");
|
|
113
|
+
clearInterval(this.keepAliveInter);
|
|
114
|
+
if (this.ws) {
|
|
115
|
+
this.ws.onclose = null;
|
|
116
|
+
this.ws.close();
|
|
117
|
+
}
|
|
118
|
+
this.reset();
|
|
119
|
+
this.emit("disconnected");
|
|
120
|
+
}
|
|
121
|
+
sleep(time) {
|
|
122
|
+
return new Promise((resolve) => setTimeout(resolve, time));
|
|
123
|
+
}
|
|
124
|
+
handleMessage(ev) {
|
|
125
|
+
const msg = this.api.gz(ev.data || ev);
|
|
126
|
+
debug(`message ${msg}`);
|
|
127
|
+
if (msg[0] === "[") {
|
|
128
|
+
const arr = JSON.parse(msg);
|
|
129
|
+
let [, type, id, channel] = arr[0].match(/^(.+):(.+?)(?:\/(.+))?$/);
|
|
130
|
+
channel = channel || type;
|
|
131
|
+
const event = {
|
|
132
|
+
channel,
|
|
133
|
+
id,
|
|
134
|
+
type,
|
|
135
|
+
data: arr[1]
|
|
136
|
+
};
|
|
137
|
+
this.emit(msg[0], event);
|
|
138
|
+
this.emit(event.channel, event);
|
|
139
|
+
this.emit("message", event);
|
|
140
|
+
} else {
|
|
141
|
+
const [channel, ...data] = msg.split(" ");
|
|
142
|
+
const event = {
|
|
143
|
+
type: "server",
|
|
144
|
+
channel,
|
|
145
|
+
data
|
|
146
|
+
};
|
|
147
|
+
if (channel === "auth") event.data = {
|
|
148
|
+
status: data[0],
|
|
149
|
+
token: data[1]
|
|
150
|
+
};
|
|
151
|
+
if ([
|
|
152
|
+
"protocol",
|
|
153
|
+
"time",
|
|
154
|
+
"package"
|
|
155
|
+
].includes(channel)) event.data = { [channel]: data[0] };
|
|
156
|
+
this.emit(channel, event);
|
|
157
|
+
this.emit("message", event);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
async gzip(bool) {
|
|
161
|
+
await this.send(`gzip ${bool ? "on" : "off"}`);
|
|
162
|
+
}
|
|
163
|
+
async send(data) {
|
|
164
|
+
if (!this.connected) this.__queue.push(data);
|
|
165
|
+
else this.ws.send(data);
|
|
166
|
+
}
|
|
167
|
+
async auth(token) {
|
|
168
|
+
const waitAuth = new Promise((resolve) => this.once("auth", resolve));
|
|
169
|
+
await this.send(`auth ${token}`);
|
|
170
|
+
const { data } = await waitAuth;
|
|
171
|
+
if (data.status !== "ok") throw new Error("socket auth failed");
|
|
172
|
+
this.authed = true;
|
|
173
|
+
this.emit("token", data.token);
|
|
174
|
+
this.emit("authed");
|
|
175
|
+
while (this.__subQueue.length) await this.send(this.__subQueue.shift());
|
|
176
|
+
}
|
|
177
|
+
async subscribe(path, cb) {
|
|
178
|
+
if (!path) return;
|
|
179
|
+
const userID = await this.api.userID();
|
|
180
|
+
if (!path.match(/^(\w+):(.+?)$/)) path = `user:${userID}/${path}`;
|
|
181
|
+
if (this.authed) await this.send(`subscribe ${path}`);
|
|
182
|
+
else this.__subQueue.push(`subscribe ${path}`);
|
|
183
|
+
this.emit("subscribe", path);
|
|
184
|
+
this.__subs[path] = this.__subs[path] || 0;
|
|
185
|
+
this.__subs[path]++;
|
|
186
|
+
if (cb) this.on(path, cb);
|
|
187
|
+
}
|
|
188
|
+
async unsubscribe(path) {
|
|
189
|
+
if (!path) return;
|
|
190
|
+
const userID = await this.api.userID();
|
|
191
|
+
if (!path.match(/^(\w+):(.+?)$/)) path = `user:${userID}/${path}`;
|
|
192
|
+
await this.send(`unsubscribe ${path}`);
|
|
193
|
+
this.emit("unsubscribe", path);
|
|
194
|
+
if (this.__subs[path]) this.__subs[path]--;
|
|
195
|
+
}
|
|
196
|
+
};
|
|
197
|
+
//#endregion
|
|
198
|
+
//#region src/RawAPI.ts
|
|
199
|
+
const debugHttp = Debug("screepsapi:http");
|
|
200
|
+
const debugRateLimit = Debug("screepsapi:ratelimit");
|
|
201
|
+
const DEFAULT_SHARD = "shard0";
|
|
202
|
+
const OFFICIAL_HISTORY_INTERVAL = 100;
|
|
203
|
+
const PRIVATE_HISTORY_INTERVAL = 20;
|
|
204
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
205
|
+
const mapToShard = (res) => {
|
|
206
|
+
if (!res.shards) res.shards = { privSrv: res.list || res.rooms };
|
|
207
|
+
return res;
|
|
208
|
+
};
|
|
209
|
+
var RawAPI = class extends EventEmitter {
|
|
210
|
+
opts;
|
|
211
|
+
token;
|
|
212
|
+
__authed = false;
|
|
213
|
+
constructor(opts = {}) {
|
|
214
|
+
super();
|
|
215
|
+
this.opts = {};
|
|
216
|
+
this.setServer(opts);
|
|
217
|
+
}
|
|
218
|
+
raw = {
|
|
219
|
+
token: void 0,
|
|
220
|
+
version: () => this.req("GET", "/api/version"),
|
|
221
|
+
authmod: () => {
|
|
222
|
+
if (this.isOfficialServer()) return Promise.resolve({
|
|
223
|
+
ok: 1,
|
|
224
|
+
name: "official"
|
|
225
|
+
});
|
|
226
|
+
return this.req("GET", "/api/authmod");
|
|
227
|
+
},
|
|
228
|
+
history: (room, tick, shard = DEFAULT_SHARD) => {
|
|
229
|
+
if (this.isOfficialServer()) {
|
|
230
|
+
tick -= tick % OFFICIAL_HISTORY_INTERVAL;
|
|
231
|
+
return this.req("GET", `/room-history/${shard}/${room}/${tick}.json`);
|
|
232
|
+
} else {
|
|
233
|
+
tick -= tick % PRIVATE_HISTORY_INTERVAL;
|
|
234
|
+
return this.req("GET", "/room-history", {
|
|
235
|
+
room,
|
|
236
|
+
time: tick
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
servers: { list: () => this.req("POST", "/api/servers/list", {}) },
|
|
241
|
+
auth: {
|
|
242
|
+
signin: (email, password) => this.req("POST", "/api/auth/signin", {
|
|
243
|
+
email,
|
|
244
|
+
password
|
|
245
|
+
}),
|
|
246
|
+
steamTicket: (ticket, useNativeAuth = false) => this.req("POST", "/api/auth/steam-ticket", {
|
|
247
|
+
ticket,
|
|
248
|
+
useNativeAuth
|
|
249
|
+
}),
|
|
250
|
+
me: () => this.req("GET", "/api/auth/me"),
|
|
251
|
+
queryToken: (token) => this.req("GET", "/api/auth/query-token", { token })
|
|
252
|
+
},
|
|
253
|
+
register: {
|
|
254
|
+
checkEmail: (email) => this.req("GET", "/api/register/check-email", { email }),
|
|
255
|
+
checkUsername: (username) => this.req("GET", "/api/register/check-username", { username }),
|
|
256
|
+
setUsername: (username) => this.req("POST", "/api/register/set-username", { username }),
|
|
257
|
+
submit: (username, email, password, modules) => this.req("POST", "/api/register/submit", {
|
|
258
|
+
username,
|
|
259
|
+
email,
|
|
260
|
+
password,
|
|
261
|
+
modules
|
|
262
|
+
})
|
|
263
|
+
},
|
|
264
|
+
userMessages: {
|
|
265
|
+
list: (respondent) => this.req("GET", "/api/user/messages/list", { respondent }),
|
|
266
|
+
index: () => this.req("GET", "/api/user/messages/index"),
|
|
267
|
+
unreadCount: () => this.req("GET", "/api/user/messages/unread-count"),
|
|
268
|
+
send: (respondent, text) => this.req("POST", "/api/user/messages/send", {
|
|
269
|
+
respondent,
|
|
270
|
+
text
|
|
271
|
+
}),
|
|
272
|
+
markRead: (id) => this.req("POST", "/api/user/messages/mark-read", { id })
|
|
273
|
+
},
|
|
274
|
+
game: {
|
|
275
|
+
mapStats: (rooms, statName, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/map-stats", {
|
|
276
|
+
rooms,
|
|
277
|
+
statName,
|
|
278
|
+
shard
|
|
279
|
+
}),
|
|
280
|
+
genUniqueObjectName: (type, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/gen-unique-object-name", {
|
|
281
|
+
type,
|
|
282
|
+
shard
|
|
283
|
+
}),
|
|
284
|
+
checkUniqueObjectName: (type, name, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/check-unique-object-name", {
|
|
285
|
+
type,
|
|
286
|
+
name,
|
|
287
|
+
shard
|
|
288
|
+
}),
|
|
289
|
+
placeSpawn: (room, x, y, name, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/place-spawn", {
|
|
290
|
+
name,
|
|
291
|
+
room,
|
|
292
|
+
x,
|
|
293
|
+
y,
|
|
294
|
+
shard
|
|
295
|
+
}),
|
|
296
|
+
createFlag: (room, x, y, name, color = 1, secondaryColor = 1, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/create-flag", {
|
|
297
|
+
name,
|
|
298
|
+
room,
|
|
299
|
+
x,
|
|
300
|
+
y,
|
|
301
|
+
color,
|
|
302
|
+
secondaryColor,
|
|
303
|
+
shard
|
|
304
|
+
}),
|
|
305
|
+
genUniqueFlagName: (shard = DEFAULT_SHARD) => this.req("POST", "/api/game/gen-unique-flag-name", { shard }),
|
|
306
|
+
checkUniqueFlagName: (name, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/check-unique-flag-name", {
|
|
307
|
+
name,
|
|
308
|
+
shard
|
|
309
|
+
}),
|
|
310
|
+
changeFlagColor: (color = 1, secondaryColor = 1, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/change-flag-color", {
|
|
311
|
+
color,
|
|
312
|
+
secondaryColor,
|
|
313
|
+
shard
|
|
314
|
+
}),
|
|
315
|
+
removeFlag: (room, name, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/remove-flag", {
|
|
316
|
+
name,
|
|
317
|
+
room,
|
|
318
|
+
shard
|
|
319
|
+
}),
|
|
320
|
+
addObjectIntent: (room, name, intent, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/add-object-intent", {
|
|
321
|
+
room,
|
|
322
|
+
name,
|
|
323
|
+
intent,
|
|
324
|
+
shard
|
|
325
|
+
}),
|
|
326
|
+
createConstruction: (room, x, y, structureType, name, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/create-construction", {
|
|
327
|
+
room,
|
|
328
|
+
x,
|
|
329
|
+
y,
|
|
330
|
+
structureType,
|
|
331
|
+
name,
|
|
332
|
+
shard
|
|
333
|
+
}),
|
|
334
|
+
setNotifyWhenAttacked: (_id, enabled = true, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/set-notify-when-attacked", {
|
|
335
|
+
_id,
|
|
336
|
+
enabled,
|
|
337
|
+
shard
|
|
338
|
+
}),
|
|
339
|
+
createInvader: (room, x, y, size, type, boosted = false, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/create-invader", {
|
|
340
|
+
room,
|
|
341
|
+
x,
|
|
342
|
+
y,
|
|
343
|
+
size,
|
|
344
|
+
type,
|
|
345
|
+
boosted,
|
|
346
|
+
shard
|
|
347
|
+
}),
|
|
348
|
+
removeInvader: (_id, shard = DEFAULT_SHARD) => this.req("POST", "/api/game/remove-invader", {
|
|
349
|
+
_id,
|
|
350
|
+
shard
|
|
351
|
+
}),
|
|
352
|
+
time: (shard = DEFAULT_SHARD) => this.req("GET", "/api/game/time", { shard }),
|
|
353
|
+
worldSize: (shard = DEFAULT_SHARD) => this.req("GET", "/api/game/world-size", { shard }),
|
|
354
|
+
roomDecorations: (room, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/room-decorations", {
|
|
355
|
+
room,
|
|
356
|
+
shard
|
|
357
|
+
}),
|
|
358
|
+
roomObjects: (room, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/room-objects", {
|
|
359
|
+
room,
|
|
360
|
+
shard
|
|
361
|
+
}),
|
|
362
|
+
roomTerrain: (room, encoded = 1, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/room-terrain", {
|
|
363
|
+
room,
|
|
364
|
+
encoded,
|
|
365
|
+
shard
|
|
366
|
+
}),
|
|
367
|
+
roomStatus: (room, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/room-status", {
|
|
368
|
+
room,
|
|
369
|
+
shard
|
|
370
|
+
}),
|
|
371
|
+
roomOverview: (room, interval = 8, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/room-overview", {
|
|
372
|
+
room,
|
|
373
|
+
interval,
|
|
374
|
+
shard
|
|
375
|
+
}),
|
|
376
|
+
market: {
|
|
377
|
+
ordersIndex: (shard = DEFAULT_SHARD) => this.req("GET", "/api/game/market/orders-index", { shard }),
|
|
378
|
+
myOrders: () => this.req("GET", "/api/game/market/my-orders").then(mapToShard),
|
|
379
|
+
orders: (resourceType, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/market/orders", {
|
|
380
|
+
resourceType,
|
|
381
|
+
shard
|
|
382
|
+
}),
|
|
383
|
+
stats: (resourceType, shard = DEFAULT_SHARD) => this.req("GET", "/api/game/market/stats", {
|
|
384
|
+
resourceType,
|
|
385
|
+
shard
|
|
386
|
+
})
|
|
387
|
+
},
|
|
388
|
+
shards: { info: () => this.req("GET", "/api/game/shards/info") }
|
|
389
|
+
},
|
|
390
|
+
leaderboard: {
|
|
391
|
+
list: (limit = 10, mode = "world", offset = 0, season) => {
|
|
392
|
+
if (mode !== "world" && mode !== "power") throw new Error("incorrect mode parameter");
|
|
393
|
+
if (!season) season = this.currentSeason();
|
|
394
|
+
return this.req("GET", "/api/leaderboard/list", {
|
|
395
|
+
limit,
|
|
396
|
+
mode,
|
|
397
|
+
offset,
|
|
398
|
+
season
|
|
399
|
+
});
|
|
400
|
+
},
|
|
401
|
+
find: (username, mode = "world", season = "") => this.req("GET", "/api/leaderboard/find", {
|
|
402
|
+
season,
|
|
403
|
+
mode,
|
|
404
|
+
username
|
|
405
|
+
}),
|
|
406
|
+
seasons: () => this.req("GET", "/api/leaderboard/seasons")
|
|
407
|
+
},
|
|
408
|
+
user: {
|
|
409
|
+
badge: (badge) => this.req("POST", "/api/user/badge", { badge }),
|
|
410
|
+
respawn: () => this.req("POST", "/api/user/respawn"),
|
|
411
|
+
setActiveBranch: (branch, activeName) => this.req("POST", "/api/user/set-active-branch", {
|
|
412
|
+
branch,
|
|
413
|
+
activeName
|
|
414
|
+
}),
|
|
415
|
+
cloneBranch: (branch = "", newName, defaultModules) => this.req("POST", "/api/user/clone-branch", {
|
|
416
|
+
branch,
|
|
417
|
+
newName,
|
|
418
|
+
defaultModules
|
|
419
|
+
}),
|
|
420
|
+
deleteBranch: (branch) => this.req("POST", "/api/user/delete-branch", { branch }),
|
|
421
|
+
notifyPrefs: (prefs) => this.req("POST", "/api/user/notify-prefs", prefs),
|
|
422
|
+
tutorialDone: () => this.req("POST", "/api/user/tutorial-done"),
|
|
423
|
+
email: (email) => this.req("POST", "/api/user/email", { email }),
|
|
424
|
+
worldStartRoom: (shard) => this.req("GET", "/api/user/world-start-room", { shard }),
|
|
425
|
+
worldStatus: () => this.req("GET", "/api/user/world-status"),
|
|
426
|
+
branches: () => this.req("GET", "/api/user/branches"),
|
|
427
|
+
code: {
|
|
428
|
+
get: (branch) => this.req("GET", "/api/user/code", { branch }),
|
|
429
|
+
set: (branch, modules, _hash) => {
|
|
430
|
+
if (!_hash) _hash = Date.now();
|
|
431
|
+
return this.req("POST", "/api/user/code", {
|
|
432
|
+
branch,
|
|
433
|
+
modules,
|
|
434
|
+
_hash
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
},
|
|
438
|
+
decorations: {
|
|
439
|
+
inventory: () => this.req("GET", "/api/user/decorations/inventory"),
|
|
440
|
+
themes: () => this.req("GET", "/api/user/decorations/themes"),
|
|
441
|
+
convert: (decorations) => this.req("POST", "/api/user/decorations/convert", { decorations }),
|
|
442
|
+
pixelize: (count, theme = "") => this.req("POST", "/api/user/decorations/pixelize", {
|
|
443
|
+
count,
|
|
444
|
+
theme
|
|
445
|
+
}),
|
|
446
|
+
activate: (_id, active) => this.req("POST", "/api/user/decorations/activate", {
|
|
447
|
+
_id,
|
|
448
|
+
active
|
|
449
|
+
}),
|
|
450
|
+
deactivate: (decorations) => this.req("POST", "/api/user/decorations/deactivate", { decorations })
|
|
451
|
+
},
|
|
452
|
+
respawnProhibitedRooms: () => this.req("GET", "/api/user/respawn-prohibited-rooms"),
|
|
453
|
+
memory: {
|
|
454
|
+
get: (path = "", shard = DEFAULT_SHARD) => this.req("GET", "/api/user/memory", {
|
|
455
|
+
path,
|
|
456
|
+
shard
|
|
457
|
+
}),
|
|
458
|
+
set: (path, value, shard = DEFAULT_SHARD) => this.req("POST", "/api/user/memory", {
|
|
459
|
+
path,
|
|
460
|
+
value,
|
|
461
|
+
shard
|
|
462
|
+
}),
|
|
463
|
+
segment: {
|
|
464
|
+
get: (segment, shard = DEFAULT_SHARD) => this.req("GET", "/api/user/memory-segment", {
|
|
465
|
+
segment,
|
|
466
|
+
shard
|
|
467
|
+
}),
|
|
468
|
+
set: (segment, data, shard = DEFAULT_SHARD) => this.req("POST", "/api/user/memory-segment", {
|
|
469
|
+
segment,
|
|
470
|
+
data,
|
|
471
|
+
shard
|
|
472
|
+
})
|
|
473
|
+
}
|
|
474
|
+
},
|
|
475
|
+
find: (username) => this.req("GET", "/api/user/find", { username }),
|
|
476
|
+
findById: (id) => this.req("GET", "/api/user/find", { id }),
|
|
477
|
+
stats: (interval) => this.req("GET", "/api/user/stats", { interval }),
|
|
478
|
+
rooms: (id) => this.req("GET", "/api/user/rooms", { id }).then(mapToShard),
|
|
479
|
+
overview: (interval, statName) => this.req("GET", "/api/user/overview", {
|
|
480
|
+
interval,
|
|
481
|
+
statName
|
|
482
|
+
}),
|
|
483
|
+
moneyHistory: (page = 0) => this.req("GET", "/api/user/money-history", { page }),
|
|
484
|
+
console: (expression, shard = DEFAULT_SHARD) => this.req("POST", "/api/user/console", {
|
|
485
|
+
expression,
|
|
486
|
+
shard
|
|
487
|
+
}),
|
|
488
|
+
name: () => this.req("GET", "/api/user/name")
|
|
489
|
+
},
|
|
490
|
+
experimental: {
|
|
491
|
+
pvp: (interval = 100) => this.req("GET", "/api/experimental/pvp", { interval }).then(mapToShard),
|
|
492
|
+
nukes: () => this.req("GET", "/api/experimental/nukes").then(mapToShard)
|
|
493
|
+
},
|
|
494
|
+
warpath: { battles: (interval = 100) => this.req("GET", "/api/warpath/battles", { interval }) },
|
|
495
|
+
scoreboard: { list: (limit = 20, offset = 0) => this.req("GET", "/api/scoreboard/list", {
|
|
496
|
+
limit,
|
|
497
|
+
offset
|
|
498
|
+
}) }
|
|
499
|
+
};
|
|
500
|
+
currentSeason() {
|
|
501
|
+
const now = /* @__PURE__ */ new Date();
|
|
502
|
+
const year = now.getFullYear();
|
|
503
|
+
let month = (now.getUTCMonth() + 1).toString();
|
|
504
|
+
if (month.length === 1) month = `0${month}`;
|
|
505
|
+
return `${year}-${month}`;
|
|
506
|
+
}
|
|
507
|
+
isOfficialServer() {
|
|
508
|
+
return this.opts.url.match(/screeps\.com/) !== null;
|
|
509
|
+
}
|
|
510
|
+
mapToShard = mapToShard;
|
|
511
|
+
setServer(opts) {
|
|
512
|
+
if (!this.opts) this.opts = {};
|
|
513
|
+
Object.assign(this.opts, opts);
|
|
514
|
+
if (opts.path && !opts.pathname) this.opts.pathname = opts.path;
|
|
515
|
+
if (opts.port) {
|
|
516
|
+
this.opts.port = String(opts.port);
|
|
517
|
+
if (opts.hostname) this.opts.host = `${opts.hostname}:${opts.port}`;
|
|
518
|
+
}
|
|
519
|
+
if (!opts.url) {
|
|
520
|
+
this.opts.url = urlFormat(this.opts);
|
|
521
|
+
if (!this.opts.url.endsWith("/")) this.opts.url += "/";
|
|
522
|
+
}
|
|
523
|
+
if (opts.token) this.token = opts.token;
|
|
524
|
+
}
|
|
525
|
+
async auth(email, password, opts = {}) {
|
|
526
|
+
this.setServer(opts);
|
|
527
|
+
if (email && password) Object.assign(this.opts, {
|
|
528
|
+
email,
|
|
529
|
+
password
|
|
530
|
+
});
|
|
531
|
+
const res = await this.raw.auth.signin(this.opts.email, this.opts.password);
|
|
532
|
+
this.emit("token", res.token);
|
|
533
|
+
this.emit("auth");
|
|
534
|
+
this.__authed = true;
|
|
535
|
+
return res;
|
|
536
|
+
}
|
|
537
|
+
async req(method, path, body = {}) {
|
|
538
|
+
let url = new URL(path, this.opts.url);
|
|
539
|
+
const opts = {
|
|
540
|
+
method,
|
|
541
|
+
headers: {}
|
|
542
|
+
};
|
|
543
|
+
if (debugHttp.enabled) debugHttp(`${method} ${path}`);
|
|
544
|
+
if (this.token) Object.assign(opts.headers, {
|
|
545
|
+
"X-Token": this.token,
|
|
546
|
+
"X-Username": this.token
|
|
547
|
+
});
|
|
548
|
+
if (method === "GET") Object.entries(body).forEach(([key, value]) => {
|
|
549
|
+
if (value !== void 0) url.searchParams.append(key, String(value));
|
|
550
|
+
});
|
|
551
|
+
else {
|
|
552
|
+
opts.body = body;
|
|
553
|
+
if (typeof body === "object") {
|
|
554
|
+
opts.body = JSON.stringify(body);
|
|
555
|
+
opts.headers["Content-Type"] = "application/json";
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
const res = await fetch(url, opts);
|
|
559
|
+
const token = res.headers.get("x-token");
|
|
560
|
+
if (token) this.emit("token", token);
|
|
561
|
+
const rateLimit = this.buildRateLimit(method, path, res);
|
|
562
|
+
this.emit("rateLimit", rateLimit);
|
|
563
|
+
debugRateLimit(`${method} ${path} ${rateLimit.remaining}/${rateLimit.limit} ${rateLimit.toReset}s`);
|
|
564
|
+
if (res.headers.get("content-type")?.includes("application/json")) res.data = await res.json();
|
|
565
|
+
else res.data = await res.text();
|
|
566
|
+
if (!res.ok) {
|
|
567
|
+
if (res.status === 401) if (this.__authed && this.opts.email && this.opts.password) {
|
|
568
|
+
this.__authed = false;
|
|
569
|
+
await this.auth(this.opts.email, this.opts.password);
|
|
570
|
+
return this.req(method, path, body);
|
|
571
|
+
} else throw new Error("Not Authorized");
|
|
572
|
+
else if (res.status === 429 && !res.headers.get("x-ratelimit-limit") && this.opts.experimentalRetry429) {
|
|
573
|
+
await sleep(Math.floor(Math.random() * 500) + 200);
|
|
574
|
+
return this.req(method, path, body);
|
|
575
|
+
}
|
|
576
|
+
throw new Error(res.data);
|
|
577
|
+
}
|
|
578
|
+
this.emit("response", res);
|
|
579
|
+
return res.data;
|
|
580
|
+
}
|
|
581
|
+
gz(data) {
|
|
582
|
+
if (!data.startsWith("gz:")) return data;
|
|
583
|
+
return inflate(Buffer.from(data.slice(3), "base64"), { to: "string" });
|
|
584
|
+
}
|
|
585
|
+
inflate(data) {
|
|
586
|
+
return JSON.parse(this.gz(data));
|
|
587
|
+
}
|
|
588
|
+
buildRateLimit(method, path, res) {
|
|
589
|
+
const limit = Number(res.headers.get("x-ratelimit-limit"));
|
|
590
|
+
const remaining = Number(res.headers.get("x-ratelimit-remaining"));
|
|
591
|
+
const reset = Number(res.headers.get("x-ratelimit-reset"));
|
|
592
|
+
return {
|
|
593
|
+
method,
|
|
594
|
+
path,
|
|
595
|
+
limit,
|
|
596
|
+
remaining,
|
|
597
|
+
reset,
|
|
598
|
+
toReset: reset - Math.floor(Date.now() / 1e3)
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
};
|
|
602
|
+
/** Based on Node.js built-in URL format */
|
|
603
|
+
function urlFormat(obj) {
|
|
604
|
+
let protocol = obj.protocol || "";
|
|
605
|
+
if (protocol && !protocol.endsWith(":")) protocol += ":";
|
|
606
|
+
let pathname = obj.pathname || "";
|
|
607
|
+
let host = "";
|
|
608
|
+
if (obj.host) host = obj.host;
|
|
609
|
+
else if (obj.hostname) {
|
|
610
|
+
host = obj.hostname.includes(":") && (obj.hostname[0] !== "[" || obj.hostname[obj.hostname.length - 1] !== "]") ? "[" + obj.hostname + "]" : obj.hostname;
|
|
611
|
+
if (obj.port) host += ":" + obj.port;
|
|
612
|
+
}
|
|
613
|
+
if (pathname.includes("#") || pathname.includes("?")) {
|
|
614
|
+
let newPathname = "";
|
|
615
|
+
let lastPos = 0;
|
|
616
|
+
const len = pathname.length;
|
|
617
|
+
for (let i = 0; i < len; i++) {
|
|
618
|
+
const code = pathname.charAt(i);
|
|
619
|
+
if (code === "#" || code === "?") {
|
|
620
|
+
if (i > lastPos) newPathname += pathname.slice(lastPos, i);
|
|
621
|
+
newPathname += code === "#" ? "%23" : "%3F";
|
|
622
|
+
lastPos = i + 1;
|
|
623
|
+
}
|
|
624
|
+
}
|
|
625
|
+
if (lastPos < len) newPathname += pathname.slice(lastPos);
|
|
626
|
+
pathname = newPathname;
|
|
627
|
+
}
|
|
628
|
+
if (host) {
|
|
629
|
+
if (pathname && pathname[0] !== "/") pathname = "/" + pathname;
|
|
630
|
+
host = "//" + host;
|
|
631
|
+
}
|
|
632
|
+
return protocol + host + pathname;
|
|
633
|
+
}
|
|
634
|
+
//#endregion
|
|
635
|
+
//#region src/ConfigManager.ts
|
|
636
|
+
/**
|
|
637
|
+
* Utility class for loading Unified Credentials Files from disk, environment variables, or other sources.
|
|
638
|
+
* Only platforms without filesystem access, {@link ConfigManager.setConfig}, must be used to provide the config data directly.
|
|
639
|
+
*/
|
|
640
|
+
var ConfigManager = class {
|
|
641
|
+
path;
|
|
642
|
+
_config = null;
|
|
643
|
+
/** Custom environment variables, useful for platforms without process.env */
|
|
644
|
+
env = globalThis.process?.env || {};
|
|
645
|
+
/** Custom file reading function, useful for platforms with a virtual filesystem */
|
|
646
|
+
readFile;
|
|
647
|
+
setConfig(data, path) {
|
|
648
|
+
if (!data || typeof data !== "object" || !("servers" in data)) throw new Error(`Invalid config: 'servers' object does not exist in '${path}'`);
|
|
649
|
+
this._config = data;
|
|
650
|
+
this.path = path;
|
|
651
|
+
return this._config;
|
|
652
|
+
}
|
|
653
|
+
async refresh() {
|
|
654
|
+
this._config = null;
|
|
655
|
+
await this.getConfig();
|
|
656
|
+
}
|
|
657
|
+
async getServers() {
|
|
658
|
+
const conf = await this.getConfig();
|
|
659
|
+
return conf ? Object.keys(conf.servers) : [];
|
|
660
|
+
}
|
|
661
|
+
async getConfig() {
|
|
662
|
+
if (this._config) return this._config;
|
|
663
|
+
const paths = [];
|
|
664
|
+
if (this.env.SCREEPS_CONFIG) paths.push(this.env.SCREEPS_CONFIG);
|
|
665
|
+
const dirs = ["", import.meta.dirname];
|
|
666
|
+
for (const dir of dirs) {
|
|
667
|
+
paths.push(join$1(dir, ".screeps.yaml"));
|
|
668
|
+
paths.push(join$1(dir, ".screeps.yml"));
|
|
669
|
+
}
|
|
670
|
+
if (process.platform === "win32" && this.env.APPDATA) {
|
|
671
|
+
paths.push(join$1(this.env.APPDATA, "screeps/config.yaml"));
|
|
672
|
+
paths.push(join$1(this.env.APPDATA, "screeps/config.yml"));
|
|
673
|
+
} else {
|
|
674
|
+
if (this.env.XDG_CONFIG_HOME) {
|
|
675
|
+
paths.push(join$1(this.env.XDG_CONFIG_HOME, "screeps/config.yaml"));
|
|
676
|
+
paths.push(join$1(this.env.XDG_CONFIG_HOME, "screeps/config.yml"));
|
|
677
|
+
}
|
|
678
|
+
if (this.env.HOME) {
|
|
679
|
+
paths.push(join$1(this.env.HOME, ".config/screeps/config.yaml"));
|
|
680
|
+
paths.push(join$1(this.env.HOME, ".config/screeps/config.yml"));
|
|
681
|
+
paths.push(join$1(this.env.HOME, ".screeps.yaml"));
|
|
682
|
+
paths.push(join$1(this.env.HOME, ".screeps.yml"));
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
for (const path of paths) {
|
|
686
|
+
const data = await this.loadConfig(path);
|
|
687
|
+
if (data) return data;
|
|
688
|
+
}
|
|
689
|
+
return null;
|
|
690
|
+
}
|
|
691
|
+
async loadConfig(path) {
|
|
692
|
+
if (!this.readFile) {
|
|
693
|
+
const { readFile } = await import("fs/promises");
|
|
694
|
+
this.readFile = readFile;
|
|
695
|
+
}
|
|
696
|
+
let contents;
|
|
697
|
+
try {
|
|
698
|
+
contents = await this.readFile(path, { encoding: "utf8" });
|
|
699
|
+
} catch (e) {
|
|
700
|
+
if (e.code === "ENOENT") return null;
|
|
701
|
+
else throw e;
|
|
702
|
+
}
|
|
703
|
+
const data = yaml.load(contents);
|
|
704
|
+
return this.setConfig(data, path);
|
|
705
|
+
}
|
|
706
|
+
};
|
|
707
|
+
function join$1(a, b) {
|
|
708
|
+
return a + (a.endsWith("/") ? "" : "/") + b;
|
|
709
|
+
}
|
|
710
|
+
//#endregion
|
|
711
|
+
//#region src/ScreepsAPI.ts
|
|
712
|
+
const DEFAULTS = {
|
|
713
|
+
protocol: "https",
|
|
714
|
+
hostname: "screeps.com",
|
|
715
|
+
port: 443,
|
|
716
|
+
path: "/"
|
|
717
|
+
};
|
|
718
|
+
const configManager = new ConfigManager();
|
|
719
|
+
var ScreepsAPI = class ScreepsAPI extends RawAPI {
|
|
720
|
+
socket;
|
|
721
|
+
appConfig = {};
|
|
722
|
+
rateLimits;
|
|
723
|
+
_user;
|
|
724
|
+
_tokenInfo;
|
|
725
|
+
static async fromConfig(server = "main", config = false, opts = {}) {
|
|
726
|
+
const data = await configManager.getConfig();
|
|
727
|
+
if (!data) throw new Error("No valid config found");
|
|
728
|
+
if (!server && process.stdin.isTTY && process.stdout.isTTY) {
|
|
729
|
+
const { select, isCancel } = await import("@clack/prompts");
|
|
730
|
+
const selectedServer = await select({
|
|
731
|
+
message: "Select a server:",
|
|
732
|
+
withGuide: false,
|
|
733
|
+
options: Object.entries(data.servers).map(([value, args]) => ({
|
|
734
|
+
value,
|
|
735
|
+
hint: args.host
|
|
736
|
+
}))
|
|
737
|
+
});
|
|
738
|
+
if (isCancel(selectedServer)) throw new Error("Server selection cancelled");
|
|
739
|
+
server = selectedServer;
|
|
740
|
+
}
|
|
741
|
+
const conf = data.servers[server];
|
|
742
|
+
if (!conf) throw new Error(`Server '${server}' does not exist in '${configManager.path}'`);
|
|
743
|
+
if (conf.ptr) conf.path = "/ptr";
|
|
744
|
+
if (conf.season) conf.path = "/season";
|
|
745
|
+
const api = new ScreepsAPI(Object.assign({
|
|
746
|
+
hostname: conf.host,
|
|
747
|
+
protocol: conf.secure ? "https" : "http",
|
|
748
|
+
path: "/"
|
|
749
|
+
}, conf, opts));
|
|
750
|
+
api.appConfig = data.configs && data.configs[config] || {};
|
|
751
|
+
if (!conf.token && conf.username && conf.password) await api.auth(conf.username, conf.password);
|
|
752
|
+
return api;
|
|
753
|
+
}
|
|
754
|
+
constructor(opts = {}) {
|
|
755
|
+
opts = Object.assign({}, DEFAULTS, opts);
|
|
756
|
+
super(opts);
|
|
757
|
+
this.on("token", (token) => {
|
|
758
|
+
this.token = token;
|
|
759
|
+
this.raw.token = token;
|
|
760
|
+
});
|
|
761
|
+
const defaultLimit = (limit, period) => ({
|
|
762
|
+
limit,
|
|
763
|
+
period,
|
|
764
|
+
remaining: limit,
|
|
765
|
+
reset: 0,
|
|
766
|
+
toReset: 0
|
|
767
|
+
});
|
|
768
|
+
this.rateLimits = {
|
|
769
|
+
global: defaultLimit(120, "minute"),
|
|
770
|
+
GET: {
|
|
771
|
+
"/api/game/room-terrain": defaultLimit(360, "hour"),
|
|
772
|
+
"/api/user/code": defaultLimit(60, "hour"),
|
|
773
|
+
"/api/user/memory": defaultLimit(1440, "day"),
|
|
774
|
+
"/api/user/memory-segment": defaultLimit(360, "hour"),
|
|
775
|
+
"/api/game/market/orders-index": defaultLimit(60, "hour"),
|
|
776
|
+
"/api/game/market/orders": defaultLimit(60, "hour"),
|
|
777
|
+
"/api/game/market/my-orders": defaultLimit(60, "hour"),
|
|
778
|
+
"/api/game/market/stats": defaultLimit(60, "hour"),
|
|
779
|
+
"/api/game/user/money-history": defaultLimit(60, "hour")
|
|
780
|
+
},
|
|
781
|
+
POST: {
|
|
782
|
+
"/api/user/console": defaultLimit(360, "hour"),
|
|
783
|
+
"/api/game/map-stats": defaultLimit(60, "hour"),
|
|
784
|
+
"/api/user/code": defaultLimit(240, "day"),
|
|
785
|
+
"/api/user/set-active-branch": defaultLimit(240, "day"),
|
|
786
|
+
"/api/user/memory": defaultLimit(240, "day"),
|
|
787
|
+
"/api/user/memory-segment": defaultLimit(60, "hour")
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
this.on("rateLimit", (limits) => {
|
|
791
|
+
const rate = this.rateLimits[limits.method]?.[limits.path] || this.rateLimits.global;
|
|
792
|
+
const copy = Object.assign({}, limits);
|
|
793
|
+
delete copy.path;
|
|
794
|
+
delete copy.method;
|
|
795
|
+
Object.assign(rate, copy);
|
|
796
|
+
});
|
|
797
|
+
this.socket = new Socket(this);
|
|
798
|
+
}
|
|
799
|
+
getRateLimit(method, path) {
|
|
800
|
+
return this.rateLimits[method][path] || this.rateLimits.global;
|
|
801
|
+
}
|
|
802
|
+
get rateLimitResetUrl() {
|
|
803
|
+
return `https://screeps.com/a/#!/account/auth-tokens/noratelimit?token=${this.token.slice(0, 8)}`;
|
|
804
|
+
}
|
|
805
|
+
async me() {
|
|
806
|
+
if (this._user) return this._user;
|
|
807
|
+
if ((await this.tokenInfo()).full) this._user = await this.raw.auth.me();
|
|
808
|
+
else {
|
|
809
|
+
const { username } = await this.raw.user.name();
|
|
810
|
+
const { ok, user } = await this.raw.user.find(username);
|
|
811
|
+
this._user = {
|
|
812
|
+
...user,
|
|
813
|
+
ok
|
|
814
|
+
};
|
|
815
|
+
}
|
|
816
|
+
return this._user;
|
|
817
|
+
}
|
|
818
|
+
async tokenInfo() {
|
|
819
|
+
if (this._tokenInfo) return this._tokenInfo;
|
|
820
|
+
if ("token" in this.opts) {
|
|
821
|
+
const { token } = await this.raw.auth.queryToken(this.token);
|
|
822
|
+
this._tokenInfo = token;
|
|
823
|
+
} else this._tokenInfo = { full: true };
|
|
824
|
+
return this._tokenInfo;
|
|
825
|
+
}
|
|
826
|
+
async userID() {
|
|
827
|
+
return (await this.me())._id;
|
|
828
|
+
}
|
|
829
|
+
registerUser = this.raw.register.submit;
|
|
830
|
+
history = this.raw.history;
|
|
831
|
+
authmod = this.raw.authmod;
|
|
832
|
+
version = this.raw.version;
|
|
833
|
+
time = this.raw.game.time;
|
|
834
|
+
leaderboard = this.raw.leaderboard;
|
|
835
|
+
market = this.raw.game.market;
|
|
836
|
+
console = this.raw.user.console;
|
|
837
|
+
code = codeRepository(this);
|
|
838
|
+
memory = {
|
|
839
|
+
...this.raw.user.memory,
|
|
840
|
+
get: async (path, shard) => {
|
|
841
|
+
const { data } = await this.raw.user.memory.get(path, shard);
|
|
842
|
+
return this.gz(data);
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
segment = this.raw.user.memory.segment;
|
|
846
|
+
};
|
|
847
|
+
const codeRepository = ({ raw: { user } }) => ({
|
|
848
|
+
get: user.code.get,
|
|
849
|
+
set: async (branch, code) => {
|
|
850
|
+
const { list } = await user.branches();
|
|
851
|
+
if (list.some((b) => b.branch == branch)) return user.code.set(branch, code);
|
|
852
|
+
else return user.cloneBranch("", branch, code);
|
|
853
|
+
},
|
|
854
|
+
branches: user.branches,
|
|
855
|
+
cloneBranch: user.cloneBranch,
|
|
856
|
+
deleteBranch: user.deleteBranch,
|
|
857
|
+
setActiveBranch: user.setActiveBranch
|
|
858
|
+
});
|
|
859
|
+
//#endregion
|
|
860
|
+
//#region package.json
|
|
861
|
+
var version = "1.0.0";
|
|
862
|
+
//#endregion
|
|
863
|
+
//#region src/cli.ts
|
|
864
|
+
async function init(opts) {
|
|
865
|
+
return ScreepsAPI.fromConfig(opts.server);
|
|
866
|
+
}
|
|
867
|
+
async function out(data) {
|
|
868
|
+
data = await data;
|
|
869
|
+
data = data && data.data || data;
|
|
870
|
+
if (process.stdout.isTTY) console.log(data);
|
|
871
|
+
else process.stdout.write(JSON.stringify(data));
|
|
872
|
+
}
|
|
873
|
+
async function run() {
|
|
874
|
+
const program = new Command();
|
|
875
|
+
/** @param {string} name */
|
|
876
|
+
const commandBase = (name, args = "") => {
|
|
877
|
+
const command = new Command(name);
|
|
878
|
+
command.arguments(args).option("--server <server>", "Server config to use", "");
|
|
879
|
+
program.addCommand(command);
|
|
880
|
+
return command;
|
|
881
|
+
};
|
|
882
|
+
program.version(version);
|
|
883
|
+
commandBase("raw", "<cmd> [args...]").description("Execute raw API call").action(async function(cmd, args, opts) {
|
|
884
|
+
try {
|
|
885
|
+
const api = await init(opts);
|
|
886
|
+
const path = cmd.split(".");
|
|
887
|
+
let fn = api.raw;
|
|
888
|
+
for (const part of path) fn = fn[part];
|
|
889
|
+
if (!fn || typeof fn !== "function") {
|
|
890
|
+
console.log("Invalid cmd");
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
await out(fn.apply(api, args));
|
|
894
|
+
} catch (e) {
|
|
895
|
+
console.error(e);
|
|
896
|
+
}
|
|
897
|
+
});
|
|
898
|
+
commandBase("memory", "[path]").description(`Get Memory contents`).option("--set <file>", "Sets the memory path to the contents of file").option("--allow-root", "Allows writing without path").option("-s --shard <shard>", "Shard to read from", "shard0").option("-f --file <file>", "File to write data to").action(async function(fpath, opts) {
|
|
899
|
+
try {
|
|
900
|
+
const api = await init(opts);
|
|
901
|
+
if (opts.set) {
|
|
902
|
+
if (!fpath && !opts.allowRoot) throw new Error("Refusing to write to root! Use --allow-root if you really want this.");
|
|
903
|
+
const data = await readFile(opts.set, "utf8");
|
|
904
|
+
await api.memory.set(fpath, data, opts.shard);
|
|
905
|
+
await out("Memory written");
|
|
906
|
+
} else {
|
|
907
|
+
const data = await api.memory.get(fpath, opts.shard);
|
|
908
|
+
if (opts.file) await writeFile(opts.file, data);
|
|
909
|
+
else await out(data);
|
|
910
|
+
}
|
|
911
|
+
} catch (e) {
|
|
912
|
+
console.error(e);
|
|
913
|
+
}
|
|
914
|
+
});
|
|
915
|
+
commandBase("segment", "<segment>").description(`Get segment contents. Use 'all' to get all)`).option("--set <file>", "Sets the segment content to the contents of file").option("-s --shard <shard>", "Shard to read from", "shard0").option("-d --dir <dir>", "Directory to save in. Empty files are not written. (defaults to outputing in console)").action(async function(segment, opts) {
|
|
916
|
+
try {
|
|
917
|
+
const api = await init(opts);
|
|
918
|
+
if (opts.set) {
|
|
919
|
+
const data = await readFile(opts.set, "utf8");
|
|
920
|
+
await api.memory.segment.set(segment, data, opts.shard);
|
|
921
|
+
await out("Segment Set");
|
|
922
|
+
} else {
|
|
923
|
+
if (segment === "all") segment = Array.from({ length: 100 }, (v, k) => k).join(",");
|
|
924
|
+
const { data } = await api.memory.segment.get(segment, opts.shard);
|
|
925
|
+
const dir = opts.dir;
|
|
926
|
+
const segments = data;
|
|
927
|
+
if (dir) {
|
|
928
|
+
if (Array.isArray(segments)) await Promise.all(segments.map((d, i) => d && writeFile(join(dir, `segment_${i}`), d)));
|
|
929
|
+
else await writeFile(join(dir, `segment_${segment}`), data);
|
|
930
|
+
await out("Segments Saved");
|
|
931
|
+
} else await out(segments);
|
|
932
|
+
}
|
|
933
|
+
} catch (e) {
|
|
934
|
+
console.error(e);
|
|
935
|
+
}
|
|
936
|
+
});
|
|
937
|
+
commandBase("download").description(`Download code`).option("-b --branch <branch>", "Code branch", "default").option("-d --dir <dir>", "Directory to save in (defaults to outputing in console)").action(async function(opts) {
|
|
938
|
+
try {
|
|
939
|
+
const api = await init(opts);
|
|
940
|
+
const dir = opts.dir;
|
|
941
|
+
const { modules } = await api.code.get(opts.branch);
|
|
942
|
+
if (dir) await Promise.all(Object.keys(modules).map(async (fn) => {
|
|
943
|
+
const data = modules[fn];
|
|
944
|
+
if (typeof data === "string") await writeFile(join(dir, `${fn}.js`), data);
|
|
945
|
+
else await writeFile(join(dir, `${fn}.wasm`), Buffer.from(data.binary, "base64"));
|
|
946
|
+
console.log(`Saved ${fn}`);
|
|
947
|
+
}));
|
|
948
|
+
else await out(modules);
|
|
949
|
+
} catch (e) {
|
|
950
|
+
console.error(e);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
commandBase("upload", "<files...>").description(`Upload code`).option("-b --branch <branch>", "Code branch", "default").action(async function(files, opts) {
|
|
954
|
+
try {
|
|
955
|
+
const api = await init(opts);
|
|
956
|
+
const modules = {};
|
|
957
|
+
const ps = [];
|
|
958
|
+
for (const file of files) ps.push((async (file) => {
|
|
959
|
+
const { name, ext } = parse(file);
|
|
960
|
+
const data = await readFile(file);
|
|
961
|
+
if (ext === ".js") modules[name] = data.toString("utf8");
|
|
962
|
+
if (ext === ".wasm") modules[name] = { binary: data.toString("base64") };
|
|
963
|
+
})(file));
|
|
964
|
+
await Promise.all(ps);
|
|
965
|
+
await out(api.code.set(opts.branch, modules));
|
|
966
|
+
} catch (e) {
|
|
967
|
+
console.error(e);
|
|
968
|
+
}
|
|
969
|
+
});
|
|
970
|
+
if (!process.argv.slice(2).length) program.outputHelp();
|
|
971
|
+
await program.parseAsync();
|
|
972
|
+
}
|
|
973
|
+
run().then((data) => {
|
|
974
|
+
if (!data) return;
|
|
975
|
+
console.log(JSON.stringify(data.data || data, null, 2));
|
|
976
|
+
}).catch(console.error);
|
|
977
|
+
//#endregion
|
|
978
|
+
export {};
|