@riseonly/sdk 0.1.1 → 0.1.3
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/README.md +51 -9
- package/dist/index.cjs +350 -79
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +84 -35
- package/dist/index.d.ts +84 -35
- package/dist/index.js +350 -81
- package/dist/index.js.map +1 -1
- package/package.json +6 -1
package/dist/index.cjs
CHANGED
|
@@ -29,6 +29,12 @@ var SUPPORTED_METHODS = [
|
|
|
29
29
|
"setMyCommands",
|
|
30
30
|
"getMyCommands",
|
|
31
31
|
"deleteMyCommands",
|
|
32
|
+
"setMyName",
|
|
33
|
+
"getMyName",
|
|
34
|
+
"setMyDescription",
|
|
35
|
+
"getMyDescription",
|
|
36
|
+
"setMyProfilePhoto",
|
|
37
|
+
"removeMyProfilePhoto",
|
|
32
38
|
"answerCallbackQuery",
|
|
33
39
|
"getChat"
|
|
34
40
|
];
|
|
@@ -94,6 +100,22 @@ var RiseonlyTimeoutError = class extends RiseonlyNetworkError {
|
|
|
94
100
|
this.name = "RiseonlyTimeoutError";
|
|
95
101
|
}
|
|
96
102
|
};
|
|
103
|
+
var RiseonlyAbortError = class extends RiseonlyNetworkError {
|
|
104
|
+
constructor(message = "Request was aborted", cause) {
|
|
105
|
+
super(message, cause, 499);
|
|
106
|
+
this.name = "RiseonlyAbortError";
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
var RiseonlyResponseError = class extends RiseonlyNetworkError {
|
|
110
|
+
status;
|
|
111
|
+
body;
|
|
112
|
+
constructor(message, status, body) {
|
|
113
|
+
super(message, void 0, status);
|
|
114
|
+
this.name = "RiseonlyResponseError";
|
|
115
|
+
this.status = status;
|
|
116
|
+
this.body = body;
|
|
117
|
+
}
|
|
118
|
+
};
|
|
97
119
|
|
|
98
120
|
// src/client.ts
|
|
99
121
|
var ApiClient = class {
|
|
@@ -108,8 +130,15 @@ var ApiClient = class {
|
|
|
108
130
|
}
|
|
109
131
|
this.token = token.trim();
|
|
110
132
|
this.baseUrl = (options.baseUrl ?? DEFAULT_API_BASE).replace(/\/$/, "");
|
|
111
|
-
|
|
133
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
134
|
+
if (typeof fetchImpl !== "function") {
|
|
135
|
+
throw new Error("global fetch is unavailable; provide ClientOptions.fetch");
|
|
136
|
+
}
|
|
137
|
+
this.fetchImpl = options.fetch ? fetchImpl : fetchImpl.bind(globalThis);
|
|
112
138
|
this.requestTimeoutMs = options.requestTimeoutMs ?? 6e4;
|
|
139
|
+
if (!Number.isFinite(this.requestTimeoutMs) || this.requestTimeoutMs <= 0) {
|
|
140
|
+
throw new Error("requestTimeoutMs must be a positive number");
|
|
141
|
+
}
|
|
113
142
|
this.useCompatibleEndpoint = options.useCompatibleEndpoint ?? false;
|
|
114
143
|
}
|
|
115
144
|
/**
|
|
@@ -117,15 +146,21 @@ var ApiClient = class {
|
|
|
117
146
|
*/
|
|
118
147
|
async getCapabilities(signal) {
|
|
119
148
|
const url = `${this.baseUrl}${CANONICAL_API_PREFIX}/capabilities`;
|
|
120
|
-
const response = await this.
|
|
149
|
+
const { response, body } = await this.fetchJson(url, {
|
|
121
150
|
method: "GET",
|
|
122
|
-
headers: { Accept: "application/json" }
|
|
123
|
-
|
|
124
|
-
});
|
|
151
|
+
headers: { Accept: "application/json" }
|
|
152
|
+
}, { signal });
|
|
125
153
|
if (!response.ok) {
|
|
126
|
-
throw new
|
|
154
|
+
throw new RiseonlyResponseError(
|
|
155
|
+
`capabilities request failed with status ${response.status}`,
|
|
156
|
+
response.status,
|
|
157
|
+
serializeResponseBody(body)
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
if (!isRecord(body) || !Array.isArray(body.methods)) {
|
|
161
|
+
throw new RiseonlyResponseError("capabilities response is invalid", response.status);
|
|
127
162
|
}
|
|
128
|
-
return
|
|
163
|
+
return body;
|
|
129
164
|
}
|
|
130
165
|
/**
|
|
131
166
|
* Calls a Bot API method and returns the typed result.
|
|
@@ -147,37 +182,36 @@ var ApiClient = class {
|
|
|
147
182
|
if (options.requestId) {
|
|
148
183
|
headers[IDEMPOTENCY_HEADER] = options.requestId;
|
|
149
184
|
}
|
|
150
|
-
const
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
let response;
|
|
154
|
-
try {
|
|
155
|
-
response = await this.fetchImpl(url, {
|
|
185
|
+
const { response, body } = await this.fetchJson(
|
|
186
|
+
url,
|
|
187
|
+
{
|
|
156
188
|
method: "POST",
|
|
157
189
|
headers,
|
|
158
|
-
body: JSON.stringify(payload)
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
throw new RiseonlyTimeoutError();
|
|
164
|
-
}
|
|
165
|
-
throw new RiseonlyNetworkError("network request failed", error);
|
|
166
|
-
} finally {
|
|
167
|
-
clearTimeout(timeout);
|
|
168
|
-
}
|
|
169
|
-
const body = await response.json();
|
|
170
|
-
if (!body.ok) {
|
|
190
|
+
body: JSON.stringify(payload)
|
|
191
|
+
},
|
|
192
|
+
options
|
|
193
|
+
);
|
|
194
|
+
if (isApiErrorResponse(body)) {
|
|
171
195
|
throw RiseonlyError.fromApiResponse(body);
|
|
172
196
|
}
|
|
197
|
+
if (!response.ok) {
|
|
198
|
+
throw new RiseonlyResponseError(
|
|
199
|
+
`Bot API request failed with status ${response.status}`,
|
|
200
|
+
response.status,
|
|
201
|
+
serializeResponseBody(body)
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
if (!isRecord(body) || body.ok !== true || !("result" in body)) {
|
|
205
|
+
throw new RiseonlyResponseError("Bot API response envelope is invalid", response.status);
|
|
206
|
+
}
|
|
173
207
|
return body;
|
|
174
208
|
}
|
|
175
209
|
async getMe(options) {
|
|
176
210
|
return this.callMethod("getMe", {}, options);
|
|
177
211
|
}
|
|
178
212
|
async sendMessage(options) {
|
|
179
|
-
const {
|
|
180
|
-
return this.callMethod("sendMessage", payload,
|
|
213
|
+
const { request, payload } = splitRequestOptions(options);
|
|
214
|
+
return this.callMethod("sendMessage", payload, request);
|
|
181
215
|
}
|
|
182
216
|
async sendPhoto(photo, options) {
|
|
183
217
|
return this.sendMedia("sendPhoto", "photo", photo, options);
|
|
@@ -198,72 +232,96 @@ var ApiClient = class {
|
|
|
198
232
|
return this.sendMedia("sendAnimation", "animation", animation, options);
|
|
199
233
|
}
|
|
200
234
|
async editMessageText(options) {
|
|
201
|
-
const {
|
|
202
|
-
return this.callMethod("editMessageText", payload,
|
|
235
|
+
const { request, payload } = splitRequestOptions(options);
|
|
236
|
+
return this.callMethod("editMessageText", payload, request);
|
|
203
237
|
}
|
|
204
238
|
async deleteMessage(options) {
|
|
205
|
-
const {
|
|
206
|
-
return this.callMethod("deleteMessage", payload,
|
|
239
|
+
const { request, payload } = splitRequestOptions(options);
|
|
240
|
+
return this.callMethod("deleteMessage", payload, request);
|
|
207
241
|
}
|
|
208
242
|
async sendChatAction(options) {
|
|
209
|
-
const {
|
|
210
|
-
return this.callMethod("sendChatAction", payload,
|
|
243
|
+
const { request, payload } = splitRequestOptions(options);
|
|
244
|
+
return this.callMethod("sendChatAction", payload, request);
|
|
211
245
|
}
|
|
212
246
|
async getUpdates(options = {}) {
|
|
213
|
-
const {
|
|
214
|
-
|
|
247
|
+
const { request, payload } = splitRequestOptions(options);
|
|
248
|
+
const serverTimeoutMs = (options.timeout ?? 0) * 1e3 + 5e3;
|
|
249
|
+
request.timeoutMs ??= Math.max(this.requestTimeoutMs, serverTimeoutMs);
|
|
250
|
+
return this.callMethod("getUpdates", payload, request);
|
|
215
251
|
}
|
|
216
252
|
async setWebhook(options) {
|
|
217
|
-
const {
|
|
218
|
-
return this.callMethod("setWebhook", payload,
|
|
253
|
+
const { request, payload } = splitRequestOptions(options);
|
|
254
|
+
return this.callMethod("setWebhook", payload, request);
|
|
219
255
|
}
|
|
220
256
|
async deleteWebhook(options = {}) {
|
|
221
|
-
const {
|
|
222
|
-
return this.callMethod("deleteWebhook", payload,
|
|
257
|
+
const { request, payload } = splitRequestOptions(options);
|
|
258
|
+
return this.callMethod("deleteWebhook", payload, request);
|
|
223
259
|
}
|
|
224
260
|
async getWebhookInfo(options) {
|
|
225
261
|
return this.callMethod("getWebhookInfo", {}, options);
|
|
226
262
|
}
|
|
227
263
|
async setMyCommands(options) {
|
|
228
|
-
const {
|
|
229
|
-
return this.callMethod("setMyCommands", payload,
|
|
264
|
+
const { request, payload } = splitRequestOptions(options);
|
|
265
|
+
return this.callMethod("setMyCommands", payload, request);
|
|
230
266
|
}
|
|
231
267
|
async getMyCommands(options = {}) {
|
|
232
|
-
const {
|
|
233
|
-
return this.callMethod("getMyCommands", payload,
|
|
268
|
+
const { request, payload } = splitRequestOptions(options);
|
|
269
|
+
return this.callMethod("getMyCommands", payload, request);
|
|
234
270
|
}
|
|
235
271
|
async deleteMyCommands(options = {}) {
|
|
236
|
-
const {
|
|
237
|
-
return this.callMethod("deleteMyCommands", payload,
|
|
272
|
+
const { request, payload } = splitRequestOptions(options);
|
|
273
|
+
return this.callMethod("deleteMyCommands", payload, request);
|
|
274
|
+
}
|
|
275
|
+
async setMyName(options) {
|
|
276
|
+
const { request, payload } = splitRequestOptions(options);
|
|
277
|
+
return this.callMethod("setMyName", payload, request);
|
|
278
|
+
}
|
|
279
|
+
async getMyName(options = {}) {
|
|
280
|
+
return this.callMethod("getMyName", {}, options);
|
|
281
|
+
}
|
|
282
|
+
async setMyDescription(options) {
|
|
283
|
+
const { request, payload } = splitRequestOptions(options);
|
|
284
|
+
return this.callMethod("setMyDescription", payload, request);
|
|
285
|
+
}
|
|
286
|
+
async getMyDescription(options = {}) {
|
|
287
|
+
return this.callMethod("getMyDescription", {}, options);
|
|
288
|
+
}
|
|
289
|
+
async setMyProfilePhoto(options) {
|
|
290
|
+
const { request, payload } = splitRequestOptions(options);
|
|
291
|
+
return this.callMethod("setMyProfilePhoto", payload, request);
|
|
292
|
+
}
|
|
293
|
+
async removeMyProfilePhoto(options = {}) {
|
|
294
|
+
return this.callMethod("removeMyProfilePhoto", {}, options);
|
|
238
295
|
}
|
|
239
296
|
async answerCallbackQuery(options) {
|
|
240
|
-
const {
|
|
241
|
-
return this.callMethod("answerCallbackQuery", payload,
|
|
297
|
+
const { request, payload } = splitRequestOptions(options);
|
|
298
|
+
return this.callMethod("answerCallbackQuery", payload, request);
|
|
242
299
|
}
|
|
243
300
|
async getChat(options) {
|
|
244
|
-
const {
|
|
245
|
-
return this.callMethod("getChat", payload,
|
|
301
|
+
const { request, payload } = splitRequestOptions(options);
|
|
302
|
+
return this.callMethod("getChat", payload, request);
|
|
246
303
|
}
|
|
247
304
|
async sendMedia(method, field, media, options) {
|
|
248
|
-
const {
|
|
305
|
+
const { request, payload } = splitRequestOptions(options);
|
|
249
306
|
return this.callMethod(
|
|
250
307
|
method,
|
|
251
308
|
{
|
|
252
309
|
...payload,
|
|
253
310
|
[field]: this.normalizeMedia(media)
|
|
254
311
|
},
|
|
255
|
-
|
|
312
|
+
request
|
|
256
313
|
);
|
|
257
314
|
}
|
|
258
315
|
normalizeMedia(media) {
|
|
259
316
|
if (typeof media === "string") {
|
|
260
317
|
return media;
|
|
261
318
|
}
|
|
262
|
-
if (media.file_id) {
|
|
263
|
-
return
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
319
|
+
if (media.file_id || media.url) {
|
|
320
|
+
return Object.fromEntries(
|
|
321
|
+
Object.entries(media).filter(
|
|
322
|
+
(entry) => typeof entry[1] === "string" || typeof entry[1] === "number"
|
|
323
|
+
)
|
|
324
|
+
);
|
|
267
325
|
}
|
|
268
326
|
throw new Error("media requires file_id or url");
|
|
269
327
|
}
|
|
@@ -273,9 +331,77 @@ var ApiClient = class {
|
|
|
273
331
|
}
|
|
274
332
|
return `${this.baseUrl}${CANONICAL_API_PREFIX}/${method}`;
|
|
275
333
|
}
|
|
334
|
+
async fetchJson(url, init, options = {}) {
|
|
335
|
+
const controller = new AbortController();
|
|
336
|
+
let timedOut = false;
|
|
337
|
+
const timeoutMs = options.timeoutMs ?? this.requestTimeoutMs;
|
|
338
|
+
if (!Number.isFinite(timeoutMs) || timeoutMs <= 0) {
|
|
339
|
+
throw new Error("timeoutMs must be a positive number");
|
|
340
|
+
}
|
|
341
|
+
const abortFromCaller = () => controller.abort(options.signal?.reason);
|
|
342
|
+
if (options.signal?.aborted) {
|
|
343
|
+
throw new RiseonlyAbortError("Request was aborted", options.signal.reason);
|
|
344
|
+
}
|
|
345
|
+
options.signal?.addEventListener("abort", abortFromCaller, { once: true });
|
|
346
|
+
const timeout = setTimeout(() => {
|
|
347
|
+
timedOut = true;
|
|
348
|
+
controller.abort();
|
|
349
|
+
}, timeoutMs);
|
|
350
|
+
let response;
|
|
351
|
+
try {
|
|
352
|
+
response = await this.fetchImpl(url, { ...init, signal: controller.signal });
|
|
353
|
+
} catch (error) {
|
|
354
|
+
if (controller.signal.aborted) {
|
|
355
|
+
if (timedOut) {
|
|
356
|
+
throw new RiseonlyTimeoutError();
|
|
357
|
+
}
|
|
358
|
+
throw new RiseonlyAbortError("Request was aborted", error);
|
|
359
|
+
}
|
|
360
|
+
throw new RiseonlyNetworkError("network request failed", error);
|
|
361
|
+
} finally {
|
|
362
|
+
clearTimeout(timeout);
|
|
363
|
+
options.signal?.removeEventListener("abort", abortFromCaller);
|
|
364
|
+
}
|
|
365
|
+
let text;
|
|
366
|
+
try {
|
|
367
|
+
text = await response.text();
|
|
368
|
+
} catch (error) {
|
|
369
|
+
throw new RiseonlyNetworkError("unable to read response body", error, response.status);
|
|
370
|
+
}
|
|
371
|
+
if (!text) {
|
|
372
|
+
throw new RiseonlyResponseError("Bot API returned an empty response", response.status);
|
|
373
|
+
}
|
|
374
|
+
try {
|
|
375
|
+
return { response, body: JSON.parse(text) };
|
|
376
|
+
} catch {
|
|
377
|
+
throw new RiseonlyResponseError(
|
|
378
|
+
"Bot API returned a non-JSON response",
|
|
379
|
+
response.status,
|
|
380
|
+
text.slice(0, 4096)
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
}
|
|
276
384
|
};
|
|
277
|
-
|
|
278
|
-
|
|
385
|
+
function splitRequestOptions(options) {
|
|
386
|
+
const { requestId, signal, timeoutMs, ...payload } = options;
|
|
387
|
+
return {
|
|
388
|
+
request: { requestId, signal, timeoutMs },
|
|
389
|
+
payload
|
|
390
|
+
};
|
|
391
|
+
}
|
|
392
|
+
function isRecord(value) {
|
|
393
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
394
|
+
}
|
|
395
|
+
function isApiErrorResponse(value) {
|
|
396
|
+
return isRecord(value) && value.ok === false && typeof value.error_code === "number" && typeof value.description === "string";
|
|
397
|
+
}
|
|
398
|
+
function serializeResponseBody(body) {
|
|
399
|
+
try {
|
|
400
|
+
return JSON.stringify(body).slice(0, 4096);
|
|
401
|
+
} catch {
|
|
402
|
+
return void 0;
|
|
403
|
+
}
|
|
404
|
+
}
|
|
279
405
|
function parseWebhookUpdate(body) {
|
|
280
406
|
if (!body || typeof body !== "object") {
|
|
281
407
|
throw new Error("webhook body must be an object");
|
|
@@ -290,28 +416,49 @@ function verifyWebhookSecret(headers, secretToken) {
|
|
|
290
416
|
if (!secretToken) {
|
|
291
417
|
return true;
|
|
292
418
|
}
|
|
293
|
-
const
|
|
419
|
+
const headerEntry = Object.entries(headers).find(
|
|
420
|
+
([name]) => name.toLowerCase() === WEBHOOK_SECRET_HEADER
|
|
421
|
+
);
|
|
422
|
+
const headerValue = headerEntry?.[1];
|
|
294
423
|
if (!headerValue) {
|
|
295
424
|
return false;
|
|
296
425
|
}
|
|
297
426
|
const received = Array.isArray(headerValue) ? headerValue[0] : headerValue;
|
|
298
|
-
|
|
427
|
+
if (typeof received !== "string") {
|
|
428
|
+
return false;
|
|
429
|
+
}
|
|
430
|
+
const actual = Buffer.from(received);
|
|
431
|
+
const expected = Buffer.from(secretToken);
|
|
432
|
+
return actual.length === expected.length && crypto$1.timingSafeEqual(actual, expected);
|
|
299
433
|
}
|
|
300
434
|
function createWebhookHandler(options) {
|
|
301
435
|
return async (request) => {
|
|
436
|
+
if (!verifyWebhookSecret(request.headers, options.secretToken)) {
|
|
437
|
+
return { status: 401, body: { ok: false, description: "invalid webhook secret" } };
|
|
438
|
+
}
|
|
439
|
+
let update;
|
|
440
|
+
try {
|
|
441
|
+
update = parseWebhookUpdate(request.body);
|
|
442
|
+
} catch (error) {
|
|
443
|
+
await notifyWebhookError(options.onError, error);
|
|
444
|
+
return { status: 400, body: { ok: false, description: "invalid webhook payload" } };
|
|
445
|
+
}
|
|
302
446
|
try {
|
|
303
|
-
if (!verifyWebhookSecret(request.headers, options.secretToken)) {
|
|
304
|
-
return { status: 401, body: { ok: false, description: "invalid webhook secret" } };
|
|
305
|
-
}
|
|
306
|
-
const update = parseWebhookUpdate(request.body);
|
|
307
447
|
await options.onUpdate(update);
|
|
308
448
|
return { status: 200, body: { ok: true } };
|
|
309
449
|
} catch (error) {
|
|
310
|
-
await options.onError
|
|
311
|
-
return { status:
|
|
450
|
+
await notifyWebhookError(options.onError, error);
|
|
451
|
+
return { status: 500, body: { ok: false, description: "webhook handler failed" } };
|
|
312
452
|
}
|
|
313
453
|
};
|
|
314
454
|
}
|
|
455
|
+
async function notifyWebhookError(handler, error) {
|
|
456
|
+
try {
|
|
457
|
+
await handler?.(error);
|
|
458
|
+
} catch {
|
|
459
|
+
return;
|
|
460
|
+
}
|
|
461
|
+
}
|
|
315
462
|
|
|
316
463
|
// src/bot.ts
|
|
317
464
|
var RiseonlyBot = class extends ApiClient {
|
|
@@ -327,6 +474,7 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
327
474
|
webhookOptions;
|
|
328
475
|
polling = false;
|
|
329
476
|
pollingAbort;
|
|
477
|
+
pollingTask;
|
|
330
478
|
offset = 0;
|
|
331
479
|
webhookServer;
|
|
332
480
|
constructor(token, options = {}) {
|
|
@@ -368,6 +516,76 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
368
516
|
});
|
|
369
517
|
return this.on(event, wrapper);
|
|
370
518
|
}
|
|
519
|
+
command(commands, handler) {
|
|
520
|
+
const accepted = new Set(
|
|
521
|
+
(Array.isArray(commands) ? commands : [commands]).map((command) => command.replace(/^\//, "").toLowerCase()).filter(Boolean)
|
|
522
|
+
);
|
|
523
|
+
return this.on("message", async (message, update) => {
|
|
524
|
+
const text = message.text?.trim();
|
|
525
|
+
if (!text?.startsWith("/")) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
const token = text.split(/\s+/, 1)[0].slice(1).split("@", 1)[0].toLowerCase();
|
|
529
|
+
if (accepted.has(token)) {
|
|
530
|
+
await handler(message, update);
|
|
531
|
+
}
|
|
532
|
+
});
|
|
533
|
+
}
|
|
534
|
+
hears(trigger, handler) {
|
|
535
|
+
return this.on("message", async (message, update) => {
|
|
536
|
+
if (message.text === void 0) {
|
|
537
|
+
return;
|
|
538
|
+
}
|
|
539
|
+
if (typeof trigger === "string") {
|
|
540
|
+
if (message.text === trigger) {
|
|
541
|
+
await handler(message, null, update);
|
|
542
|
+
}
|
|
543
|
+
return;
|
|
544
|
+
}
|
|
545
|
+
trigger.lastIndex = 0;
|
|
546
|
+
const match = trigger.exec(message.text);
|
|
547
|
+
if (match) {
|
|
548
|
+
await handler(message, match, update);
|
|
549
|
+
}
|
|
550
|
+
});
|
|
551
|
+
}
|
|
552
|
+
action(trigger, handler) {
|
|
553
|
+
return this.on("callback_query", async (query, update) => {
|
|
554
|
+
if (query.data === void 0) {
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
const matches = typeof trigger === "string" ? query.data === trigger : testPattern(trigger, query.data);
|
|
558
|
+
if (matches) {
|
|
559
|
+
await handler(query, update);
|
|
560
|
+
}
|
|
561
|
+
});
|
|
562
|
+
}
|
|
563
|
+
catch(handler) {
|
|
564
|
+
return this.on("error", handler);
|
|
565
|
+
}
|
|
566
|
+
async setup(options) {
|
|
567
|
+
if (options.name !== void 0) {
|
|
568
|
+
await this.setMyName({ name: options.name });
|
|
569
|
+
}
|
|
570
|
+
if (options.description !== void 0) {
|
|
571
|
+
await this.setMyDescription({ description: options.description });
|
|
572
|
+
}
|
|
573
|
+
if (options.profilePhoto !== void 0) {
|
|
574
|
+
await this.setMyProfilePhoto({ photo: options.profilePhoto });
|
|
575
|
+
}
|
|
576
|
+
if (options.commands) {
|
|
577
|
+
await this.setMyCommands({
|
|
578
|
+
commands: options.commands,
|
|
579
|
+
scope: options.commandScope,
|
|
580
|
+
language_code: options.languageCode
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
if (options.webhook === false) {
|
|
584
|
+
await this.deleteWebhook();
|
|
585
|
+
} else if (options.webhook) {
|
|
586
|
+
await this.setWebhook(options.webhook);
|
|
587
|
+
}
|
|
588
|
+
}
|
|
371
589
|
/**
|
|
372
590
|
* Starts long polling for bot updates.
|
|
373
591
|
*/
|
|
@@ -378,7 +596,7 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
378
596
|
this.polling = true;
|
|
379
597
|
this.pollingOptions = { ...this.pollingOptions, ...options, autoStart: true };
|
|
380
598
|
this.pollingAbort = new AbortController();
|
|
381
|
-
|
|
599
|
+
this.pollingTask = this.pollLoop();
|
|
382
600
|
}
|
|
383
601
|
/**
|
|
384
602
|
* Stops long polling.
|
|
@@ -386,7 +604,15 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
386
604
|
async stopPolling() {
|
|
387
605
|
this.polling = false;
|
|
388
606
|
this.pollingAbort?.abort();
|
|
607
|
+
await this.pollingTask;
|
|
389
608
|
this.pollingAbort = void 0;
|
|
609
|
+
this.pollingTask = void 0;
|
|
610
|
+
}
|
|
611
|
+
async launch(options = {}) {
|
|
612
|
+
await this.startPolling(options);
|
|
613
|
+
}
|
|
614
|
+
async stop() {
|
|
615
|
+
await Promise.all([this.stopPolling(), this.stopWebhook()]);
|
|
390
616
|
}
|
|
391
617
|
/**
|
|
392
618
|
* Starts a built-in HTTP server that receives webhook updates.
|
|
@@ -397,6 +623,12 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
397
623
|
const path = config.path ?? "/";
|
|
398
624
|
const host = config.host ?? "0.0.0.0";
|
|
399
625
|
const port = config.port ?? 3e3;
|
|
626
|
+
if (!path.startsWith("/")) {
|
|
627
|
+
throw new Error("webhook path must start with /");
|
|
628
|
+
}
|
|
629
|
+
if (config.maxBodyBytes !== void 0 && config.maxBodyBytes <= 0) {
|
|
630
|
+
throw new Error("maxBodyBytes must be positive");
|
|
631
|
+
}
|
|
400
632
|
const handler = createWebhookHandler({
|
|
401
633
|
secretToken: config.secretToken,
|
|
402
634
|
onUpdate: (update) => this.processUpdate(update),
|
|
@@ -411,15 +643,16 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
411
643
|
return;
|
|
412
644
|
}
|
|
413
645
|
try {
|
|
414
|
-
const body = await readJsonBody(req);
|
|
646
|
+
const body = await readJsonBody(req, config.maxBodyBytes);
|
|
415
647
|
const result = await handler({
|
|
416
648
|
headers: normalizeHeaders(req.headers),
|
|
417
649
|
body
|
|
418
650
|
});
|
|
419
651
|
respond(res, result.status, result.body);
|
|
420
652
|
} catch (error) {
|
|
421
|
-
await this.
|
|
422
|
-
|
|
653
|
+
await this.emitError(error);
|
|
654
|
+
const status = error instanceof WebhookPayloadTooLargeError ? 413 : 500;
|
|
655
|
+
respond(res, status, { ok: false });
|
|
423
656
|
}
|
|
424
657
|
});
|
|
425
658
|
await new Promise((resolve, reject) => {
|
|
@@ -491,8 +724,8 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
491
724
|
if (update.update_id < this.offset) {
|
|
492
725
|
continue;
|
|
493
726
|
}
|
|
494
|
-
this.offset = Math.max(this.offset, update.update_id + 1);
|
|
495
727
|
await this.processUpdate(update);
|
|
728
|
+
this.offset = Math.max(this.offset, update.update_id + 1);
|
|
496
729
|
processed += 1;
|
|
497
730
|
}
|
|
498
731
|
if (processed === 0) {
|
|
@@ -502,7 +735,10 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
502
735
|
if (!this.polling || isAbortError(error)) {
|
|
503
736
|
return;
|
|
504
737
|
}
|
|
505
|
-
|
|
738
|
+
try {
|
|
739
|
+
await this.emit("polling_error", error);
|
|
740
|
+
} catch {
|
|
741
|
+
}
|
|
506
742
|
if (error instanceof RiseonlyError && error.retryAfter) {
|
|
507
743
|
await sleep(error.retryAfter * 1e3);
|
|
508
744
|
} else {
|
|
@@ -512,11 +748,27 @@ var RiseonlyBot = class extends ApiClient {
|
|
|
512
748
|
}
|
|
513
749
|
}
|
|
514
750
|
async emit(event, ...args) {
|
|
751
|
+
let firstError;
|
|
515
752
|
for (const listener of [...this.listeners[event]]) {
|
|
516
753
|
try {
|
|
517
754
|
await listener(...args);
|
|
518
755
|
} catch (error) {
|
|
519
|
-
|
|
756
|
+
firstError ??= error;
|
|
757
|
+
if (event !== "error") {
|
|
758
|
+
await this.emitError(error);
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
if (firstError !== void 0) {
|
|
763
|
+
throw firstError;
|
|
764
|
+
}
|
|
765
|
+
}
|
|
766
|
+
async emitError(error) {
|
|
767
|
+
for (const listener of [...this.listeners.error]) {
|
|
768
|
+
try {
|
|
769
|
+
await listener(error);
|
|
770
|
+
} catch {
|
|
771
|
+
continue;
|
|
520
772
|
}
|
|
521
773
|
}
|
|
522
774
|
}
|
|
@@ -540,7 +792,11 @@ function sleep(ms) {
|
|
|
540
792
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
541
793
|
}
|
|
542
794
|
function isAbortError(error) {
|
|
543
|
-
return error instanceof Error && error.name === "AbortError";
|
|
795
|
+
return error instanceof Error && (error.name === "AbortError" || error.name === "RiseonlyAbortError");
|
|
796
|
+
}
|
|
797
|
+
function testPattern(pattern, value) {
|
|
798
|
+
pattern.lastIndex = 0;
|
|
799
|
+
return pattern.test(value);
|
|
544
800
|
}
|
|
545
801
|
function normalizeHeaders(headers) {
|
|
546
802
|
const normalized = {};
|
|
@@ -549,10 +805,19 @@ function normalizeHeaders(headers) {
|
|
|
549
805
|
}
|
|
550
806
|
return normalized;
|
|
551
807
|
}
|
|
552
|
-
|
|
808
|
+
var WebhookPayloadTooLargeError = class extends Error {
|
|
809
|
+
};
|
|
810
|
+
async function readJsonBody(req, maxBodyBytes = 1024 * 1024) {
|
|
553
811
|
const chunks = [];
|
|
812
|
+
let received = 0;
|
|
554
813
|
for await (const chunk of req) {
|
|
555
|
-
|
|
814
|
+
const buffer = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
815
|
+
received += buffer.length;
|
|
816
|
+
if (received > maxBodyBytes) {
|
|
817
|
+
req.resume();
|
|
818
|
+
throw new WebhookPayloadTooLargeError("webhook payload is too large");
|
|
819
|
+
}
|
|
820
|
+
chunks.push(buffer);
|
|
556
821
|
}
|
|
557
822
|
if (chunks.length === 0) {
|
|
558
823
|
return {};
|
|
@@ -595,8 +860,12 @@ function verifyInitData(botToken, encoded) {
|
|
|
595
860
|
if (left.length !== right.length || !crypto$1.timingSafeEqual(left, right)) {
|
|
596
861
|
throw new Error("init data signature is invalid");
|
|
597
862
|
}
|
|
598
|
-
const
|
|
599
|
-
|
|
863
|
+
const authDate = Number(fields.auth_date);
|
|
864
|
+
const expiresAt = Number(fields.expires_at);
|
|
865
|
+
if (!Number.isSafeInteger(authDate) || !Number.isSafeInteger(expiresAt) || expiresAt <= authDate) {
|
|
866
|
+
throw new Error("init data timestamps are invalid");
|
|
867
|
+
}
|
|
868
|
+
if (expiresAt < Date.now()) {
|
|
600
869
|
throw new Error("init data has expired");
|
|
601
870
|
}
|
|
602
871
|
return fields;
|
|
@@ -619,9 +888,11 @@ exports.CHAT_ACTIONS = CHAT_ACTIONS;
|
|
|
619
888
|
exports.COMMAND_SCOPES = COMMAND_SCOPES;
|
|
620
889
|
exports.DEFAULT_API_BASE = DEFAULT_API_BASE;
|
|
621
890
|
exports.IDEMPOTENCY_HEADER = IDEMPOTENCY_HEADER;
|
|
891
|
+
exports.RiseonlyAbortError = RiseonlyAbortError;
|
|
622
892
|
exports.RiseonlyBot = RiseonlyBot;
|
|
623
893
|
exports.RiseonlyError = RiseonlyError;
|
|
624
894
|
exports.RiseonlyNetworkError = RiseonlyNetworkError;
|
|
895
|
+
exports.RiseonlyResponseError = RiseonlyResponseError;
|
|
625
896
|
exports.RiseonlyTimeoutError = RiseonlyTimeoutError;
|
|
626
897
|
exports.SUPPORTED_METHODS = SUPPORTED_METHODS;
|
|
627
898
|
exports.UPDATE_TYPES = UPDATE_TYPES;
|