gramio 0.0.50 → 0.1.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/README.md +1 -1
- package/dist/index.cjs +1191 -0
- package/dist/index.d.cts +1012 -0
- package/dist/index.d.ts +1009 -13
- package/dist/index.js +1157 -31
- package/package.json +24 -11
- package/dist/bot.d.ts +0 -404
- package/dist/bot.js +0 -824
- package/dist/composer.d.ts +0 -34
- package/dist/composer.js +0 -71
- package/dist/errors.d.ts +0 -17
- package/dist/errors.js +0 -29
- package/dist/filters.d.ts +0 -20
- package/dist/filters.js +0 -23
- package/dist/plugin.d.ts +0 -218
- package/dist/plugin.js +0 -269
- package/dist/types.d.ts +0 -242
- package/dist/types.js +0 -2
- package/dist/updates.d.ts +0 -17
- package/dist/updates.js +0 -87
- package/dist/webhook/adapters.d.ts +0 -53
- package/dist/webhook/adapters.js +0 -58
- package/dist/webhook/index.d.ts +0 -35
- package/dist/webhook/index.js +0 -46
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,1191 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
var fs = require('node:fs/promises');
|
|
4
|
+
var node_stream = require('node:stream');
|
|
5
|
+
var callbackData = require('@gramio/callback-data');
|
|
6
|
+
var contexts = require('@gramio/contexts');
|
|
7
|
+
var files = require('@gramio/files');
|
|
8
|
+
var format = require('@gramio/format');
|
|
9
|
+
var debug = require('debug');
|
|
10
|
+
var inspectable = require('inspectable');
|
|
11
|
+
var middlewareIo = require('middleware-io');
|
|
12
|
+
var keyboards = require('@gramio/keyboards');
|
|
13
|
+
|
|
14
|
+
const ErrorKind = Symbol("ErrorKind");
|
|
15
|
+
class TelegramError extends Error {
|
|
16
|
+
/** Name of the API Method */
|
|
17
|
+
method;
|
|
18
|
+
/** Params that were sent */
|
|
19
|
+
params;
|
|
20
|
+
/** See {@link TelegramAPIResponseError.error_code}*/
|
|
21
|
+
code;
|
|
22
|
+
/** Describes why a request was unsuccessful. */
|
|
23
|
+
payload;
|
|
24
|
+
/** Construct new TelegramError */
|
|
25
|
+
constructor(error, method, params) {
|
|
26
|
+
super(error.description);
|
|
27
|
+
this.name = method;
|
|
28
|
+
this.method = method;
|
|
29
|
+
this.params = params;
|
|
30
|
+
this.code = error.error_code;
|
|
31
|
+
if (error.parameters) this.payload = error.parameters;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
TelegramError.constructor[ErrorKind] = "TELEGRAM";
|
|
35
|
+
|
|
36
|
+
class Composer {
|
|
37
|
+
composer = middlewareIo.Composer.builder();
|
|
38
|
+
length = 0;
|
|
39
|
+
composed;
|
|
40
|
+
onError;
|
|
41
|
+
constructor(onError) {
|
|
42
|
+
this.onError = onError || ((_, error) => {
|
|
43
|
+
throw error;
|
|
44
|
+
});
|
|
45
|
+
this.recompose();
|
|
46
|
+
}
|
|
47
|
+
/** Register handler to one or many Updates */
|
|
48
|
+
on(updateName, handler) {
|
|
49
|
+
return this.use(async (context, next) => {
|
|
50
|
+
if (context.is(updateName)) return await handler(context, next);
|
|
51
|
+
return await next();
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
/** Register handler to any Update */
|
|
55
|
+
use(handler) {
|
|
56
|
+
this.composer.caught(this.onError).use(handler);
|
|
57
|
+
return this.recompose();
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Derive some data to handlers
|
|
61
|
+
*
|
|
62
|
+
* @example
|
|
63
|
+
* ```ts
|
|
64
|
+
* new Bot("token").derive((context) => {
|
|
65
|
+
* return {
|
|
66
|
+
* superSend: () => context.send("Derived method")
|
|
67
|
+
* }
|
|
68
|
+
* })
|
|
69
|
+
* ```
|
|
70
|
+
*/
|
|
71
|
+
derive(updateNameOrHandler, handler) {
|
|
72
|
+
if (typeof updateNameOrHandler === "function")
|
|
73
|
+
this.use(async (context, next) => {
|
|
74
|
+
for (const [key, value] of Object.entries(
|
|
75
|
+
await updateNameOrHandler(context)
|
|
76
|
+
)) {
|
|
77
|
+
context[key] = value;
|
|
78
|
+
}
|
|
79
|
+
next();
|
|
80
|
+
});
|
|
81
|
+
else if (handler)
|
|
82
|
+
this.on(updateNameOrHandler, async (context, next) => {
|
|
83
|
+
for (const [key, value] of Object.entries(await handler(context))) {
|
|
84
|
+
context[key] = value;
|
|
85
|
+
}
|
|
86
|
+
next();
|
|
87
|
+
});
|
|
88
|
+
return this;
|
|
89
|
+
}
|
|
90
|
+
recompose() {
|
|
91
|
+
this.composed = this.composer.compose();
|
|
92
|
+
this.length = this.composer.length;
|
|
93
|
+
return this;
|
|
94
|
+
}
|
|
95
|
+
compose(context, next = middlewareIo.noopNext) {
|
|
96
|
+
this.composed(context, next);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
var __create$1 = Object.create;
|
|
101
|
+
var __defProp$1 = Object.defineProperty;
|
|
102
|
+
var __getOwnPropDesc$1 = Object.getOwnPropertyDescriptor;
|
|
103
|
+
var __knownSymbol$1 = (name, symbol) => (symbol = Symbol[name]) ? symbol : Symbol.for("Symbol." + name);
|
|
104
|
+
var __typeError$1 = (msg) => {
|
|
105
|
+
throw TypeError(msg);
|
|
106
|
+
};
|
|
107
|
+
var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
108
|
+
var __name$1 = (target, value) => __defProp$1(target, "name", { value, configurable: true });
|
|
109
|
+
var __decoratorStart$1 = (base) => [, , , __create$1(null)];
|
|
110
|
+
var __decoratorStrings$1 = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
111
|
+
var __expectFn$1 = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError$1("Function expected") : fn;
|
|
112
|
+
var __decoratorContext$1 = (kind, name, done, metadata, fns) => ({ kind: __decoratorStrings$1[kind], name, metadata, addInitializer: (fn) => done._ ? __typeError$1("Already initialized") : fns.push(__expectFn$1(fn || null)) });
|
|
113
|
+
var __decoratorMetadata$1 = (array, target) => __defNormalProp$1(target, __knownSymbol$1("metadata"), array[3]);
|
|
114
|
+
var __runInitializers$1 = (array, flags, self, value) => {
|
|
115
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
|
|
116
|
+
return value;
|
|
117
|
+
};
|
|
118
|
+
var __decorateElement$1 = (array, flags, name, decorators, target, extra) => {
|
|
119
|
+
var it, done, ctx, k = flags & 7, p = !!(flags & 16);
|
|
120
|
+
var j = 0;
|
|
121
|
+
var extraInitializers = array[j] || (array[j] = []);
|
|
122
|
+
var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc$1(target , name));
|
|
123
|
+
__name$1(target, name);
|
|
124
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
125
|
+
ctx = __decoratorContext$1(k, name, done = {}, array[3], extraInitializers);
|
|
126
|
+
it = (0, decorators[i])(target, ctx), done._ = 1;
|
|
127
|
+
__expectFn$1(it) && (target = it);
|
|
128
|
+
}
|
|
129
|
+
return __decoratorMetadata$1(array, target), desc && __defProp$1(target, name, desc), p ? k ^ 4 ? extra : desc : target;
|
|
130
|
+
};
|
|
131
|
+
var _Plugin_decorators, _init$1;
|
|
132
|
+
_Plugin_decorators = [inspectable.Inspectable({
|
|
133
|
+
serialize: (plugin) => ({
|
|
134
|
+
name: plugin._.name,
|
|
135
|
+
dependencies: plugin._.dependencies
|
|
136
|
+
})
|
|
137
|
+
})];
|
|
138
|
+
class Plugin {
|
|
139
|
+
/**
|
|
140
|
+
* @internal
|
|
141
|
+
* Set of Plugin data
|
|
142
|
+
*
|
|
143
|
+
*
|
|
144
|
+
*/
|
|
145
|
+
_ = {
|
|
146
|
+
/** Name of plugin */
|
|
147
|
+
name: "",
|
|
148
|
+
/** List of plugin dependencies. If user does't extend from listed there dependencies it throw a error */
|
|
149
|
+
dependencies: [],
|
|
150
|
+
/** remap generic type. {} in runtime */
|
|
151
|
+
Errors: {},
|
|
152
|
+
/** remap generic type. {} in runtime */
|
|
153
|
+
Derives: {},
|
|
154
|
+
/** Composer */
|
|
155
|
+
composer: new Composer(),
|
|
156
|
+
/** Store plugin preRequests hooks */
|
|
157
|
+
preRequests: [],
|
|
158
|
+
/** Store plugin onResponses hooks */
|
|
159
|
+
onResponses: [],
|
|
160
|
+
/** Store plugin onResponseErrors hooks */
|
|
161
|
+
onResponseErrors: [],
|
|
162
|
+
/**
|
|
163
|
+
* Store plugin groups
|
|
164
|
+
*
|
|
165
|
+
* If you use `on` or `use` in group and on plugin-level groups handlers are registered after plugin-level handlers
|
|
166
|
+
* */
|
|
167
|
+
groups: [],
|
|
168
|
+
/** Store plugin onStarts hooks */
|
|
169
|
+
onStarts: [],
|
|
170
|
+
/** Store plugin onStops hooks */
|
|
171
|
+
onStops: [],
|
|
172
|
+
/** Store plugin onErrors hooks */
|
|
173
|
+
onErrors: [],
|
|
174
|
+
/** Map of plugin errors */
|
|
175
|
+
errorsDefinitions: {},
|
|
176
|
+
decorators: {}
|
|
177
|
+
};
|
|
178
|
+
/** Create new Plugin. Please provide `name` */
|
|
179
|
+
constructor(name, { dependencies } = {}) {
|
|
180
|
+
this._.name = name;
|
|
181
|
+
if (dependencies) this._.dependencies = dependencies;
|
|
182
|
+
}
|
|
183
|
+
/** Currently not isolated!!!
|
|
184
|
+
*
|
|
185
|
+
* > [!WARNING]
|
|
186
|
+
* > If you use `on` or `use` in a `group` and at the plugin level, the group handlers are registered **after** the handlers at the plugin level
|
|
187
|
+
*/
|
|
188
|
+
group(grouped) {
|
|
189
|
+
this._.groups.push(grouped);
|
|
190
|
+
return this;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* Register custom class-error in plugin
|
|
194
|
+
**/
|
|
195
|
+
error(kind, error) {
|
|
196
|
+
error[ErrorKind] = kind;
|
|
197
|
+
this._.errorsDefinitions[kind] = error;
|
|
198
|
+
return this;
|
|
199
|
+
}
|
|
200
|
+
derive(updateNameOrHandler, handler) {
|
|
201
|
+
this._.composer.derive(updateNameOrHandler, handler);
|
|
202
|
+
return this;
|
|
203
|
+
}
|
|
204
|
+
decorate(nameOrValue, value) {
|
|
205
|
+
if (typeof nameOrValue === "string") this._.decorators[nameOrValue] = value;
|
|
206
|
+
else {
|
|
207
|
+
for (const [name, value2] of Object.entries(nameOrValue)) {
|
|
208
|
+
this._.decorators[name] = value2;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
return this;
|
|
212
|
+
}
|
|
213
|
+
/** Register handler to one or many Updates */
|
|
214
|
+
on(updateName, handler) {
|
|
215
|
+
this._.composer.on(updateName, handler);
|
|
216
|
+
return this;
|
|
217
|
+
}
|
|
218
|
+
/** Register handler to any Updates */
|
|
219
|
+
use(handler) {
|
|
220
|
+
this._.composer.use(handler);
|
|
221
|
+
return this;
|
|
222
|
+
}
|
|
223
|
+
preRequest(methodsOrHandler, handler) {
|
|
224
|
+
if ((typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) && handler)
|
|
225
|
+
this._.preRequests.push([handler, methodsOrHandler]);
|
|
226
|
+
else if (typeof methodsOrHandler === "function")
|
|
227
|
+
this._.preRequests.push([methodsOrHandler, void 0]);
|
|
228
|
+
return this;
|
|
229
|
+
}
|
|
230
|
+
onResponse(methodsOrHandler, handler) {
|
|
231
|
+
if ((typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) && handler)
|
|
232
|
+
this._.onResponses.push([handler, methodsOrHandler]);
|
|
233
|
+
else if (typeof methodsOrHandler === "function")
|
|
234
|
+
this._.onResponses.push([methodsOrHandler, void 0]);
|
|
235
|
+
return this;
|
|
236
|
+
}
|
|
237
|
+
onResponseError(methodsOrHandler, handler) {
|
|
238
|
+
if ((typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) && handler)
|
|
239
|
+
this._.onResponseErrors.push([handler, methodsOrHandler]);
|
|
240
|
+
else if (typeof methodsOrHandler === "function")
|
|
241
|
+
this._.onResponseErrors.push([methodsOrHandler, void 0]);
|
|
242
|
+
return this;
|
|
243
|
+
}
|
|
244
|
+
/**
|
|
245
|
+
* This hook called when the bot is `started`.
|
|
246
|
+
*
|
|
247
|
+
* @example
|
|
248
|
+
* ```typescript
|
|
249
|
+
* import { Bot } from "gramio";
|
|
250
|
+
*
|
|
251
|
+
* const bot = new Bot(process.env.TOKEN!).onStart(
|
|
252
|
+
* ({ plugins, info, updatesFrom }) => {
|
|
253
|
+
* console.log(`plugin list - ${plugins.join(", ")}`);
|
|
254
|
+
* console.log(`bot username is @${info.username}`);
|
|
255
|
+
* console.log(`updates from ${updatesFrom}`);
|
|
256
|
+
* }
|
|
257
|
+
* );
|
|
258
|
+
*
|
|
259
|
+
* bot.start();
|
|
260
|
+
* ```
|
|
261
|
+
*
|
|
262
|
+
* [Documentation](https://gramio.dev/hooks/on-start.html)
|
|
263
|
+
* */
|
|
264
|
+
onStart(handler) {
|
|
265
|
+
this._.onStarts.push(handler);
|
|
266
|
+
return this;
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* This hook called when the bot stops.
|
|
270
|
+
*
|
|
271
|
+
* @example
|
|
272
|
+
* ```typescript
|
|
273
|
+
* import { Bot } from "gramio";
|
|
274
|
+
*
|
|
275
|
+
* const bot = new Bot(process.env.TOKEN!).onStop(
|
|
276
|
+
* ({ plugins, info, updatesFrom }) => {
|
|
277
|
+
* console.log(`plugin list - ${plugins.join(", ")}`);
|
|
278
|
+
* console.log(`bot username is @${info.username}`);
|
|
279
|
+
* }
|
|
280
|
+
* );
|
|
281
|
+
*
|
|
282
|
+
* bot.start();
|
|
283
|
+
* bot.stop();
|
|
284
|
+
* ```
|
|
285
|
+
*
|
|
286
|
+
* [Documentation](https://gramio.dev/hooks/on-stop.html)
|
|
287
|
+
* */
|
|
288
|
+
onStop(handler) {
|
|
289
|
+
this._.onStops.push(handler);
|
|
290
|
+
return this;
|
|
291
|
+
}
|
|
292
|
+
onError(updateNameOrHandler, handler) {
|
|
293
|
+
if (typeof updateNameOrHandler === "function") {
|
|
294
|
+
this._.onErrors.push(updateNameOrHandler);
|
|
295
|
+
return this;
|
|
296
|
+
}
|
|
297
|
+
if (handler) {
|
|
298
|
+
this._.onErrors.push(async (errContext) => {
|
|
299
|
+
if (errContext.context.is(updateNameOrHandler))
|
|
300
|
+
await handler(errContext);
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return this;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
_init$1 = __decoratorStart$1();
|
|
307
|
+
Plugin = __decorateElement$1(_init$1, 0, "Plugin", _Plugin_decorators, Plugin);
|
|
308
|
+
__runInitializers$1(_init$1, 1, Plugin);
|
|
309
|
+
|
|
310
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
311
|
+
class Updates {
|
|
312
|
+
bot;
|
|
313
|
+
isStarted = false;
|
|
314
|
+
offset = 0;
|
|
315
|
+
composer;
|
|
316
|
+
constructor(bot, onError) {
|
|
317
|
+
this.bot = bot;
|
|
318
|
+
this.composer = new Composer(onError);
|
|
319
|
+
}
|
|
320
|
+
async handleUpdate(data) {
|
|
321
|
+
const updateType = Object.keys(data).at(1);
|
|
322
|
+
this.offset = data.update_id + 1;
|
|
323
|
+
const UpdateContext = contexts.contextsMappings[updateType];
|
|
324
|
+
if (!UpdateContext) throw new Error(updateType);
|
|
325
|
+
const updatePayload = data[updateType];
|
|
326
|
+
if (!updatePayload) throw new Error("");
|
|
327
|
+
try {
|
|
328
|
+
let context = new UpdateContext({
|
|
329
|
+
bot: this.bot,
|
|
330
|
+
update: data,
|
|
331
|
+
// @ts-expect-error
|
|
332
|
+
payload: updatePayload,
|
|
333
|
+
type: updateType,
|
|
334
|
+
updateId: data.update_id
|
|
335
|
+
});
|
|
336
|
+
if ("isEvent" in context && context.isEvent() && context.eventType) {
|
|
337
|
+
const payload = data.message ?? data.edited_message ?? data.channel_post ?? data.edited_channel_post ?? data.business_message;
|
|
338
|
+
if (!payload) throw new Error("Unsupported event??");
|
|
339
|
+
context = new contexts.contextsMappings[context.eventType]({
|
|
340
|
+
bot: this.bot,
|
|
341
|
+
update: data,
|
|
342
|
+
payload,
|
|
343
|
+
// @ts-expect-error
|
|
344
|
+
type: context.eventType,
|
|
345
|
+
updateId: data.update_id
|
|
346
|
+
});
|
|
347
|
+
}
|
|
348
|
+
this.composer.compose(context);
|
|
349
|
+
} catch (error) {
|
|
350
|
+
throw new Error(`[UPDATES] Update type ${updateType} not supported.`);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
/** @deprecated use bot.start instead @internal */
|
|
354
|
+
async startPolling(params = {}) {
|
|
355
|
+
if (this.isStarted) throw new Error("[UPDATES] Polling already started!");
|
|
356
|
+
this.isStarted = true;
|
|
357
|
+
this.startFetchLoop(params);
|
|
358
|
+
return;
|
|
359
|
+
}
|
|
360
|
+
async startFetchLoop(params = {}) {
|
|
361
|
+
while (this.isStarted) {
|
|
362
|
+
try {
|
|
363
|
+
const updates = await this.bot.api.getUpdates({
|
|
364
|
+
...params,
|
|
365
|
+
offset: this.offset
|
|
366
|
+
});
|
|
367
|
+
for await (const update of updates) {
|
|
368
|
+
await this.handleUpdate(update).catch(console.error);
|
|
369
|
+
}
|
|
370
|
+
} catch (error) {
|
|
371
|
+
console.error("Error received when fetching updates", error);
|
|
372
|
+
await sleep(this.bot.options.api.retryGetUpdatesWait ?? 1e3);
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
stopPolling() {
|
|
377
|
+
this.isStarted = false;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
var __create = Object.create;
|
|
382
|
+
var __defProp = Object.defineProperty;
|
|
383
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
384
|
+
var __knownSymbol = (name2, symbol) => (symbol = Symbol[name2]) ? symbol : Symbol.for("Symbol." + name2);
|
|
385
|
+
var __typeError = (msg) => {
|
|
386
|
+
throw TypeError(msg);
|
|
387
|
+
};
|
|
388
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
389
|
+
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
|
|
390
|
+
var __decoratorStart = (base) => [, , , __create(null)];
|
|
391
|
+
var __decoratorStrings = ["class", "method", "getter", "setter", "accessor", "field", "value", "get", "set"];
|
|
392
|
+
var __expectFn = (fn) => fn !== void 0 && typeof fn !== "function" ? __typeError("Function expected") : fn;
|
|
393
|
+
var __decoratorContext = (kind, name2, done, metadata, fns) => ({ kind: __decoratorStrings[kind], name: name2, metadata, addInitializer: (fn) => done._ ? __typeError("Already initialized") : fns.push(__expectFn(fn || null)) });
|
|
394
|
+
var __decoratorMetadata = (array, target) => __defNormalProp(target, __knownSymbol("metadata"), array[3]);
|
|
395
|
+
var __runInitializers = (array, flags, self, value) => {
|
|
396
|
+
for (var i = 0, fns = array[flags >> 1], n = fns && fns.length; i < n; i++) fns[i].call(self) ;
|
|
397
|
+
return value;
|
|
398
|
+
};
|
|
399
|
+
var __decorateElement = (array, flags, name2, decorators, target, extra) => {
|
|
400
|
+
var it, done, ctx, k = flags & 7, p = !!(flags & 16);
|
|
401
|
+
var j = 0;
|
|
402
|
+
var extraInitializers = array[j] || (array[j] = []);
|
|
403
|
+
var desc = k && ((target = target.prototype), k < 5 && (k > 3 || !p) && __getOwnPropDesc(target , name2));
|
|
404
|
+
__name(target, name2);
|
|
405
|
+
for (var i = decorators.length - 1; i >= 0; i--) {
|
|
406
|
+
ctx = __decoratorContext(k, name2, done = {}, array[3], extraInitializers);
|
|
407
|
+
it = (0, decorators[i])(target, ctx), done._ = 1;
|
|
408
|
+
__expectFn(it) && (target = it);
|
|
409
|
+
}
|
|
410
|
+
return __decoratorMetadata(array, target), desc && __defProp(target, name2, desc), p ? k ^ 4 ? extra : desc : target;
|
|
411
|
+
};
|
|
412
|
+
var _Bot_decorators, _init;
|
|
413
|
+
const $debugger = debug("gramio");
|
|
414
|
+
const debug$api = $debugger.extend("api");
|
|
415
|
+
_Bot_decorators = [inspectable.Inspectable({
|
|
416
|
+
serialize: () => ({})
|
|
417
|
+
})];
|
|
418
|
+
class Bot {
|
|
419
|
+
_ = {
|
|
420
|
+
/** @internal. Remap generic */
|
|
421
|
+
derives: {}
|
|
422
|
+
};
|
|
423
|
+
/** @internal. Remap generic */
|
|
424
|
+
__Derives;
|
|
425
|
+
filters = {
|
|
426
|
+
context: (name2) => (context) => context.is(name2)
|
|
427
|
+
};
|
|
428
|
+
/** Options provided to instance */
|
|
429
|
+
options;
|
|
430
|
+
/** Bot data (filled in when calling bot.init/bot.start) */
|
|
431
|
+
info;
|
|
432
|
+
/**
|
|
433
|
+
* Send API Request to Telegram Bot API
|
|
434
|
+
*
|
|
435
|
+
* @example
|
|
436
|
+
* ```ts
|
|
437
|
+
* const response = await bot.api.sendMessage({
|
|
438
|
+
* chat_id: "@gramio_forum",
|
|
439
|
+
* text: "some text",
|
|
440
|
+
* });
|
|
441
|
+
* ```
|
|
442
|
+
*
|
|
443
|
+
* [Documentation](https://gramio.dev/bot-api.html)
|
|
444
|
+
*/
|
|
445
|
+
api = new Proxy({}, {
|
|
446
|
+
get: (_target, method) => (
|
|
447
|
+
// @ts-expect-error
|
|
448
|
+
// biome-ignore lint/suspicious/noAssignInExpressions: <explanation>
|
|
449
|
+
_target[method] ??= (args) => this._callApi(method, args)
|
|
450
|
+
)
|
|
451
|
+
});
|
|
452
|
+
lazyloadPlugins = [];
|
|
453
|
+
dependencies = [];
|
|
454
|
+
errorsDefinitions = {
|
|
455
|
+
TELEGRAM: TelegramError
|
|
456
|
+
};
|
|
457
|
+
errorHandler(context, error) {
|
|
458
|
+
if (!this.hooks.onError.length)
|
|
459
|
+
return console.error("[Default Error Handler]", context, error);
|
|
460
|
+
return this.runImmutableHooks("onError", {
|
|
461
|
+
context,
|
|
462
|
+
//@ts-expect-error ErrorKind exists if user register error-class with .error("kind", SomeError);
|
|
463
|
+
kind: error.constructor[ErrorKind] ?? "UNKNOWN",
|
|
464
|
+
error
|
|
465
|
+
});
|
|
466
|
+
}
|
|
467
|
+
/** This instance handle updates */
|
|
468
|
+
updates = new Updates(this, this.errorHandler.bind(this));
|
|
469
|
+
hooks = {
|
|
470
|
+
preRequest: [],
|
|
471
|
+
onResponse: [],
|
|
472
|
+
onResponseError: [],
|
|
473
|
+
onError: [],
|
|
474
|
+
onStart: [],
|
|
475
|
+
onStop: []
|
|
476
|
+
};
|
|
477
|
+
constructor(tokenOrOptions, optionsRaw) {
|
|
478
|
+
const token = typeof tokenOrOptions === "string" ? tokenOrOptions : tokenOrOptions?.token;
|
|
479
|
+
const options = typeof tokenOrOptions === "object" ? tokenOrOptions : optionsRaw;
|
|
480
|
+
if (!token || typeof token !== "string")
|
|
481
|
+
throw new Error(`Token is ${typeof token} but it should be a string!`);
|
|
482
|
+
this.options = {
|
|
483
|
+
...options,
|
|
484
|
+
token,
|
|
485
|
+
api: {
|
|
486
|
+
baseURL: "https://api.telegram.org/bot",
|
|
487
|
+
retryGetUpdatesWait: 1e3,
|
|
488
|
+
...options?.api
|
|
489
|
+
}
|
|
490
|
+
};
|
|
491
|
+
if (options?.info) this.info = options.info;
|
|
492
|
+
if (!(optionsRaw?.plugins && "format" in optionsRaw.plugins && !optionsRaw.plugins.format))
|
|
493
|
+
this.extend(
|
|
494
|
+
new Plugin("@gramio/format").preRequest((context) => {
|
|
495
|
+
if (!context.params) return context;
|
|
496
|
+
const formattable = format.FormattableMap[context.method];
|
|
497
|
+
if (formattable) context.params = formattable(context.params);
|
|
498
|
+
return context;
|
|
499
|
+
})
|
|
500
|
+
);
|
|
501
|
+
}
|
|
502
|
+
async runHooks(type, context) {
|
|
503
|
+
let data = context;
|
|
504
|
+
for await (const hook of this.hooks[type]) {
|
|
505
|
+
data = await hook(data);
|
|
506
|
+
}
|
|
507
|
+
return data;
|
|
508
|
+
}
|
|
509
|
+
async runImmutableHooks(type, ...context) {
|
|
510
|
+
for await (const hook of this.hooks[type]) {
|
|
511
|
+
await hook(...context);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
async _callApi(method, params = {}) {
|
|
515
|
+
const debug$api$method = debug$api.extend(method);
|
|
516
|
+
const url = `${this.options.api.baseURL}${this.options.token}/${method}`;
|
|
517
|
+
const reqOptions = {
|
|
518
|
+
method: "POST",
|
|
519
|
+
...this.options.api.fetchOptions,
|
|
520
|
+
// @ts-ignore types node/bun and global missmatch
|
|
521
|
+
headers: new Headers(this.options.api.fetchOptions?.headers)
|
|
522
|
+
};
|
|
523
|
+
const context = await this.runHooks(
|
|
524
|
+
"preRequest",
|
|
525
|
+
// TODO: fix type error
|
|
526
|
+
// @ts-expect-error
|
|
527
|
+
{
|
|
528
|
+
method,
|
|
529
|
+
params
|
|
530
|
+
}
|
|
531
|
+
);
|
|
532
|
+
params = context.params;
|
|
533
|
+
if (params && files.isMediaUpload(method, params)) {
|
|
534
|
+
const formData = await files.convertJsonToFormData(method, params);
|
|
535
|
+
reqOptions.body = formData;
|
|
536
|
+
} else {
|
|
537
|
+
reqOptions.headers.set("Content-Type", "application/json");
|
|
538
|
+
reqOptions.body = JSON.stringify(params);
|
|
539
|
+
}
|
|
540
|
+
debug$api$method("options: %j", reqOptions);
|
|
541
|
+
const response = await fetch(url, reqOptions);
|
|
542
|
+
const data = await response.json();
|
|
543
|
+
debug$api$method("response: %j", data);
|
|
544
|
+
if (!data.ok) {
|
|
545
|
+
const err = new TelegramError(data, method, params);
|
|
546
|
+
this.runImmutableHooks("onResponseError", err, this.api);
|
|
547
|
+
if (!params?.suppress) throw err;
|
|
548
|
+
return err;
|
|
549
|
+
}
|
|
550
|
+
this.runImmutableHooks(
|
|
551
|
+
"onResponse",
|
|
552
|
+
// TODO: fix type error
|
|
553
|
+
// @ts-expect-error
|
|
554
|
+
{
|
|
555
|
+
method,
|
|
556
|
+
params,
|
|
557
|
+
response: data.result
|
|
558
|
+
}
|
|
559
|
+
);
|
|
560
|
+
return data.result;
|
|
561
|
+
}
|
|
562
|
+
async downloadFile(attachment, path) {
|
|
563
|
+
function getFileId(attachment2) {
|
|
564
|
+
if (attachment2 instanceof contexts.PhotoAttachment) {
|
|
565
|
+
return attachment2.bigSize.fileId;
|
|
566
|
+
}
|
|
567
|
+
if ("fileId" in attachment2 && typeof attachment2.fileId === "string")
|
|
568
|
+
return attachment2.fileId;
|
|
569
|
+
if ("file_id" in attachment2) return attachment2.file_id;
|
|
570
|
+
throw new Error("Invalid attachment");
|
|
571
|
+
}
|
|
572
|
+
const fileId = typeof attachment === "string" ? attachment : getFileId(attachment);
|
|
573
|
+
const file = await this.api.getFile({ file_id: fileId });
|
|
574
|
+
const url = `${this.options.api.baseURL.replace("/bot", "/file/bot")}${this.options.token}/${file.file_path}`;
|
|
575
|
+
const res = await fetch(url);
|
|
576
|
+
if (path) {
|
|
577
|
+
if (!res.body)
|
|
578
|
+
throw new Error("Response without body (should be never throw)");
|
|
579
|
+
await fs.writeFile(path, node_stream.Readable.fromWeb(res.body));
|
|
580
|
+
return path;
|
|
581
|
+
}
|
|
582
|
+
const buffer = await res.arrayBuffer();
|
|
583
|
+
return buffer;
|
|
584
|
+
}
|
|
585
|
+
/**
|
|
586
|
+
* Register custom class-error for type-safe catch in `onError` hook
|
|
587
|
+
*
|
|
588
|
+
* @example
|
|
589
|
+
* ```ts
|
|
590
|
+
* export class NoRights extends Error {
|
|
591
|
+
* needRole: "admin" | "moderator";
|
|
592
|
+
*
|
|
593
|
+
* constructor(role: "admin" | "moderator") {
|
|
594
|
+
* super();
|
|
595
|
+
* this.needRole = role;
|
|
596
|
+
* }
|
|
597
|
+
* }
|
|
598
|
+
*
|
|
599
|
+
* const bot = new Bot(process.env.TOKEN!)
|
|
600
|
+
* .error("NO_RIGHTS", NoRights)
|
|
601
|
+
* .onError(({ context, kind, error }) => {
|
|
602
|
+
* if (context.is("message") && kind === "NO_RIGHTS")
|
|
603
|
+
* return context.send(
|
|
604
|
+
* format`You don't have enough rights! You need to have an «${bold(
|
|
605
|
+
* error.needRole
|
|
606
|
+
* )}» role.`
|
|
607
|
+
* );
|
|
608
|
+
* });
|
|
609
|
+
*
|
|
610
|
+
* bot.updates.on("message", (context) => {
|
|
611
|
+
* if (context.text === "bun") throw new NoRights("admin");
|
|
612
|
+
* });
|
|
613
|
+
* ```
|
|
614
|
+
*/
|
|
615
|
+
error(kind, error) {
|
|
616
|
+
error[ErrorKind] = kind;
|
|
617
|
+
this.errorsDefinitions[kind] = error;
|
|
618
|
+
return this;
|
|
619
|
+
}
|
|
620
|
+
onError(updateNameOrHandler, handler) {
|
|
621
|
+
if (typeof updateNameOrHandler === "function") {
|
|
622
|
+
this.hooks.onError.push(updateNameOrHandler);
|
|
623
|
+
return this;
|
|
624
|
+
}
|
|
625
|
+
if (handler) {
|
|
626
|
+
this.hooks.onError.push(async (errContext) => {
|
|
627
|
+
if (errContext.context.is(updateNameOrHandler))
|
|
628
|
+
await handler(errContext);
|
|
629
|
+
});
|
|
630
|
+
}
|
|
631
|
+
return this;
|
|
632
|
+
}
|
|
633
|
+
derive(updateNameOrHandler, handler) {
|
|
634
|
+
this.updates.composer.derive(updateNameOrHandler, handler);
|
|
635
|
+
return this;
|
|
636
|
+
}
|
|
637
|
+
decorate(nameOrRecordValue, value) {
|
|
638
|
+
for (const contextName of Object.keys(contexts.contextsMappings)) {
|
|
639
|
+
if (typeof nameOrRecordValue === "string")
|
|
640
|
+
Object.defineProperty(contexts.contextsMappings[contextName].prototype, name, {
|
|
641
|
+
value
|
|
642
|
+
});
|
|
643
|
+
else
|
|
644
|
+
Object.defineProperties(
|
|
645
|
+
// @ts-expect-error
|
|
646
|
+
contexts.contextsMappings[contextName].prototype,
|
|
647
|
+
Object.keys(nameOrRecordValue).reduce(
|
|
648
|
+
(acc, key) => {
|
|
649
|
+
acc[key] = { value: nameOrRecordValue[key] };
|
|
650
|
+
return acc;
|
|
651
|
+
},
|
|
652
|
+
{}
|
|
653
|
+
)
|
|
654
|
+
);
|
|
655
|
+
}
|
|
656
|
+
return this;
|
|
657
|
+
}
|
|
658
|
+
/**
|
|
659
|
+
* This hook called when the bot is `started`.
|
|
660
|
+
*
|
|
661
|
+
* @example
|
|
662
|
+
* ```typescript
|
|
663
|
+
* import { Bot } from "gramio";
|
|
664
|
+
*
|
|
665
|
+
* const bot = new Bot(process.env.TOKEN!).onStart(
|
|
666
|
+
* ({ plugins, info, updatesFrom }) => {
|
|
667
|
+
* console.log(`plugin list - ${plugins.join(", ")}`);
|
|
668
|
+
* console.log(`bot username is @${info.username}`);
|
|
669
|
+
* console.log(`updates from ${updatesFrom}`);
|
|
670
|
+
* }
|
|
671
|
+
* );
|
|
672
|
+
*
|
|
673
|
+
* bot.start();
|
|
674
|
+
* ```
|
|
675
|
+
*
|
|
676
|
+
* [Documentation](https://gramio.dev/hooks/on-start.html)
|
|
677
|
+
* */
|
|
678
|
+
onStart(handler) {
|
|
679
|
+
this.hooks.onStart.push(handler);
|
|
680
|
+
return this;
|
|
681
|
+
}
|
|
682
|
+
/**
|
|
683
|
+
* This hook called when the bot stops.
|
|
684
|
+
*
|
|
685
|
+
* @example
|
|
686
|
+
* ```typescript
|
|
687
|
+
* import { Bot } from "gramio";
|
|
688
|
+
*
|
|
689
|
+
* const bot = new Bot(process.env.TOKEN!).onStop(
|
|
690
|
+
* ({ plugins, info, updatesFrom }) => {
|
|
691
|
+
* console.log(`plugin list - ${plugins.join(", ")}`);
|
|
692
|
+
* console.log(`bot username is @${info.username}`);
|
|
693
|
+
* }
|
|
694
|
+
* );
|
|
695
|
+
*
|
|
696
|
+
* bot.start();
|
|
697
|
+
* bot.stop();
|
|
698
|
+
* ```
|
|
699
|
+
*
|
|
700
|
+
* [Documentation](https://gramio.dev/hooks/on-stop.html)
|
|
701
|
+
* */
|
|
702
|
+
onStop(handler) {
|
|
703
|
+
this.hooks.onStop.push(handler);
|
|
704
|
+
return this;
|
|
705
|
+
}
|
|
706
|
+
preRequest(methodsOrHandler, handler) {
|
|
707
|
+
if (typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) {
|
|
708
|
+
if (!handler) throw new Error("TODO:");
|
|
709
|
+
const methods = typeof methodsOrHandler === "string" ? [methodsOrHandler] : methodsOrHandler;
|
|
710
|
+
this.hooks.preRequest.push(async (context) => {
|
|
711
|
+
if (methods.includes(context.method)) return handler(context);
|
|
712
|
+
return context;
|
|
713
|
+
});
|
|
714
|
+
} else this.hooks.preRequest.push(methodsOrHandler);
|
|
715
|
+
return this;
|
|
716
|
+
}
|
|
717
|
+
onResponse(methodsOrHandler, handler) {
|
|
718
|
+
if (typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) {
|
|
719
|
+
if (!handler) throw new Error("TODO:");
|
|
720
|
+
const methods = typeof methodsOrHandler === "string" ? [methodsOrHandler] : methodsOrHandler;
|
|
721
|
+
this.hooks.onResponse.push(async (context) => {
|
|
722
|
+
if (methods.includes(context.method)) return handler(context);
|
|
723
|
+
return context;
|
|
724
|
+
});
|
|
725
|
+
} else this.hooks.onResponse.push(methodsOrHandler);
|
|
726
|
+
return this;
|
|
727
|
+
}
|
|
728
|
+
onResponseError(methodsOrHandler, handler) {
|
|
729
|
+
if (typeof methodsOrHandler === "string" || Array.isArray(methodsOrHandler)) {
|
|
730
|
+
if (!handler) throw new Error("TODO:");
|
|
731
|
+
const methods = typeof methodsOrHandler === "string" ? [methodsOrHandler] : methodsOrHandler;
|
|
732
|
+
this.hooks.onResponseError.push(async (context) => {
|
|
733
|
+
if (methods.includes(context.method)) return handler(context);
|
|
734
|
+
return context;
|
|
735
|
+
});
|
|
736
|
+
} else this.hooks.onResponseError.push(methodsOrHandler);
|
|
737
|
+
return this;
|
|
738
|
+
}
|
|
739
|
+
// onExperimental(
|
|
740
|
+
// // filter: Filters,
|
|
741
|
+
// filter: (
|
|
742
|
+
// f: Filters<
|
|
743
|
+
// Context<typeof this> & Derives["global"],
|
|
744
|
+
// [{ equal: { prop: number }; addition: { some: () => 2 } }]
|
|
745
|
+
// >,
|
|
746
|
+
// ) => Filters,
|
|
747
|
+
// handler: Handler<Context<typeof this> & Derives["global"]>,
|
|
748
|
+
// ) {}
|
|
749
|
+
/** Register handler to one or many Updates */
|
|
750
|
+
on(updateName, handler) {
|
|
751
|
+
this.updates.composer.on(updateName, handler);
|
|
752
|
+
return this;
|
|
753
|
+
}
|
|
754
|
+
/** Register handler to any Updates */
|
|
755
|
+
use(handler) {
|
|
756
|
+
this.updates.composer.use(handler);
|
|
757
|
+
return this;
|
|
758
|
+
}
|
|
759
|
+
/**
|
|
760
|
+
* Extend {@link Plugin} logic and types
|
|
761
|
+
*
|
|
762
|
+
* @example
|
|
763
|
+
* ```ts
|
|
764
|
+
* import { Plugin, Bot } from "gramio";
|
|
765
|
+
*
|
|
766
|
+
* export class PluginError extends Error {
|
|
767
|
+
* wow: "type" | "safe" = "type";
|
|
768
|
+
* }
|
|
769
|
+
*
|
|
770
|
+
* const plugin = new Plugin("gramio-example")
|
|
771
|
+
* .error("PLUGIN", PluginError)
|
|
772
|
+
* .derive(() => {
|
|
773
|
+
* return {
|
|
774
|
+
* some: ["derived", "props"] as const,
|
|
775
|
+
* };
|
|
776
|
+
* });
|
|
777
|
+
*
|
|
778
|
+
* const bot = new Bot(process.env.TOKEN!)
|
|
779
|
+
* .extend(plugin)
|
|
780
|
+
* .onError(({ context, kind, error }) => {
|
|
781
|
+
* if (context.is("message") && kind === "PLUGIN") {
|
|
782
|
+
* console.log(error.wow);
|
|
783
|
+
* }
|
|
784
|
+
* })
|
|
785
|
+
* .use((context) => {
|
|
786
|
+
* console.log(context.some);
|
|
787
|
+
* });
|
|
788
|
+
* ```
|
|
789
|
+
*/
|
|
790
|
+
extend(plugin) {
|
|
791
|
+
if (plugin instanceof Promise) {
|
|
792
|
+
this.lazyloadPlugins.push(plugin);
|
|
793
|
+
return this;
|
|
794
|
+
}
|
|
795
|
+
if (plugin._.dependencies.some((dep) => !this.dependencies.includes(dep)))
|
|
796
|
+
throw new Error(
|
|
797
|
+
`The \xAB${plugin._.name}\xBB plugin needs dependencies registered before: ${plugin._.dependencies.filter((dep) => !this.dependencies.includes(dep)).join(", ")}`
|
|
798
|
+
);
|
|
799
|
+
if (plugin._.composer.length) {
|
|
800
|
+
this.use(plugin._.composer.composed);
|
|
801
|
+
}
|
|
802
|
+
this.decorate(plugin._.decorators);
|
|
803
|
+
for (const [key, value] of Object.entries(plugin._.errorsDefinitions)) {
|
|
804
|
+
if (this.errorsDefinitions[key]) this.errorsDefinitions[key] = value;
|
|
805
|
+
}
|
|
806
|
+
for (const value of plugin._.preRequests) {
|
|
807
|
+
const [preRequest, updateName] = value;
|
|
808
|
+
if (!updateName) this.preRequest(preRequest);
|
|
809
|
+
else this.preRequest(updateName, preRequest);
|
|
810
|
+
}
|
|
811
|
+
for (const value of plugin._.onResponses) {
|
|
812
|
+
const [onResponse, updateName] = value;
|
|
813
|
+
if (!updateName) this.onResponse(onResponse);
|
|
814
|
+
else this.onResponse(updateName, onResponse);
|
|
815
|
+
}
|
|
816
|
+
for (const value of plugin._.onResponseErrors) {
|
|
817
|
+
const [onResponseError, updateName] = value;
|
|
818
|
+
if (!updateName) this.onResponseError(onResponseError);
|
|
819
|
+
else this.onResponseError(updateName, onResponseError);
|
|
820
|
+
}
|
|
821
|
+
for (const handler of plugin._.groups) {
|
|
822
|
+
this.group(handler);
|
|
823
|
+
}
|
|
824
|
+
this.dependencies.push(plugin._.name);
|
|
825
|
+
return this;
|
|
826
|
+
}
|
|
827
|
+
/**
|
|
828
|
+
* Register handler to reaction (`message_reaction` update)
|
|
829
|
+
*
|
|
830
|
+
* @example
|
|
831
|
+
* ```ts
|
|
832
|
+
* new Bot().reaction("👍", async (context) => {
|
|
833
|
+
* await context.reply(`Thank you!`);
|
|
834
|
+
* });
|
|
835
|
+
* ```
|
|
836
|
+
* */
|
|
837
|
+
reaction(trigger, handler) {
|
|
838
|
+
const reactions = Array.isArray(trigger) ? trigger : [trigger];
|
|
839
|
+
return this.on("message_reaction", (context, next) => {
|
|
840
|
+
const newReactions = [];
|
|
841
|
+
for (const reaction of context.newReactions) {
|
|
842
|
+
if (reaction.type !== "emoji") continue;
|
|
843
|
+
const foundIndex = context.oldReactions.findIndex(
|
|
844
|
+
(oldReaction) => oldReaction.type === "emoji" && oldReaction.emoji === reaction.emoji
|
|
845
|
+
);
|
|
846
|
+
if (foundIndex === -1) {
|
|
847
|
+
newReactions.push(reaction);
|
|
848
|
+
} else {
|
|
849
|
+
context.oldReactions.splice(foundIndex, 1);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
if (!newReactions.some(
|
|
853
|
+
(x) => x.type === "emoji" && reactions.includes(x.emoji)
|
|
854
|
+
))
|
|
855
|
+
return next();
|
|
856
|
+
return handler(context);
|
|
857
|
+
});
|
|
858
|
+
}
|
|
859
|
+
/**
|
|
860
|
+
* Register handler to `callback_query` event
|
|
861
|
+
*
|
|
862
|
+
* @example
|
|
863
|
+
* ```ts
|
|
864
|
+
* const someData = new CallbackData("example").number("id");
|
|
865
|
+
*
|
|
866
|
+
* new Bot()
|
|
867
|
+
* .command("start", (context) =>
|
|
868
|
+
* context.send("some", {
|
|
869
|
+
* reply_markup: new InlineKeyboard().text(
|
|
870
|
+
* "example",
|
|
871
|
+
* someData.pack({
|
|
872
|
+
* id: 1,
|
|
873
|
+
* })
|
|
874
|
+
* ),
|
|
875
|
+
* })
|
|
876
|
+
* )
|
|
877
|
+
* .callbackQuery(someData, (context) => {
|
|
878
|
+
* context.queryData; // is type-safe
|
|
879
|
+
* });
|
|
880
|
+
* ```
|
|
881
|
+
*/
|
|
882
|
+
callbackQuery(trigger, handler) {
|
|
883
|
+
return this.on("callback_query", (context, next) => {
|
|
884
|
+
if (!context.data) return next();
|
|
885
|
+
if (typeof trigger === "string" && context.data !== trigger)
|
|
886
|
+
return next();
|
|
887
|
+
if (trigger instanceof callbackData.CallbackData && !trigger.regexp().test(context.data))
|
|
888
|
+
return next();
|
|
889
|
+
if (trigger instanceof RegExp && !trigger.test(context.data))
|
|
890
|
+
return next();
|
|
891
|
+
if (trigger instanceof callbackData.CallbackData)
|
|
892
|
+
context.queryData = trigger.unpack(context.data);
|
|
893
|
+
return handler(context);
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
/** Register handler to `chosen_inline_result` update */
|
|
897
|
+
chosenInlineResult(trigger, handler) {
|
|
898
|
+
return this.on("chosen_inline_result", (context, next) => {
|
|
899
|
+
if (typeof trigger === "string" && context.query === trigger || // @ts-expect-error
|
|
900
|
+
typeof trigger === "function" && trigger(context) || trigger instanceof RegExp && trigger.test(context.query)) {
|
|
901
|
+
context.args = trigger instanceof RegExp ? context.query?.match(trigger) : null;
|
|
902
|
+
return handler(context);
|
|
903
|
+
}
|
|
904
|
+
return next();
|
|
905
|
+
});
|
|
906
|
+
}
|
|
907
|
+
/**
|
|
908
|
+
* Register handler to `inline_query` update
|
|
909
|
+
*
|
|
910
|
+
* @example
|
|
911
|
+
* ```ts
|
|
912
|
+
* new Bot().inlineQuery(
|
|
913
|
+
* /regular expression with (.*)/i,
|
|
914
|
+
* async (context) => {
|
|
915
|
+
* if (context.args) {
|
|
916
|
+
* await context.answer(
|
|
917
|
+
* [
|
|
918
|
+
* InlineQueryResult.article(
|
|
919
|
+
* "id-1",
|
|
920
|
+
* context.args[1],
|
|
921
|
+
* InputMessageContent.text("some"),
|
|
922
|
+
* {
|
|
923
|
+
* reply_markup: new InlineKeyboard().text(
|
|
924
|
+
* "some",
|
|
925
|
+
* "callback-data"
|
|
926
|
+
* ),
|
|
927
|
+
* }
|
|
928
|
+
* ),
|
|
929
|
+
* ],
|
|
930
|
+
* {
|
|
931
|
+
* cache_time: 0,
|
|
932
|
+
* }
|
|
933
|
+
* );
|
|
934
|
+
* }
|
|
935
|
+
* },
|
|
936
|
+
* {
|
|
937
|
+
* onResult: (context) => context.editText("Message edited!"),
|
|
938
|
+
* }
|
|
939
|
+
* );
|
|
940
|
+
* ```
|
|
941
|
+
* */
|
|
942
|
+
inlineQuery(trigger, handler, options = {}) {
|
|
943
|
+
if (options.onResult) this.chosenInlineResult(trigger, options.onResult);
|
|
944
|
+
return this.on("inline_query", (context, next) => {
|
|
945
|
+
if (typeof trigger === "string" && context.query === trigger || // @ts-expect-error
|
|
946
|
+
typeof trigger === "function" && trigger(context) || trigger instanceof RegExp && trigger.test(context.query)) {
|
|
947
|
+
context.args = trigger instanceof RegExp ? context.query?.match(trigger) : null;
|
|
948
|
+
return handler(context);
|
|
949
|
+
}
|
|
950
|
+
return next();
|
|
951
|
+
});
|
|
952
|
+
}
|
|
953
|
+
/**
|
|
954
|
+
* Register handler to `message` and `business_message` event
|
|
955
|
+
*
|
|
956
|
+
* new Bot().hears(/regular expression with (.*)/i, async (context) => {
|
|
957
|
+
* if (context.args) await context.send(`Params ${context.args[1]}`);
|
|
958
|
+
* });
|
|
959
|
+
*/
|
|
960
|
+
hears(trigger, handler) {
|
|
961
|
+
return this.on("message", (context, next) => {
|
|
962
|
+
const text = context.text ?? context.caption;
|
|
963
|
+
if (typeof trigger === "string" && text === trigger || // @ts-expect-error
|
|
964
|
+
typeof trigger === "function" && trigger(context) || trigger instanceof RegExp && text && trigger.test(text)) {
|
|
965
|
+
context.args = trigger instanceof RegExp ? text?.match(trigger) : null;
|
|
966
|
+
return handler(context);
|
|
967
|
+
}
|
|
968
|
+
return next();
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
/**
|
|
972
|
+
* Register handler to `message` and `business_message` event when entities contains a command
|
|
973
|
+
*
|
|
974
|
+
* new Bot().command("start", async (context) => {
|
|
975
|
+
* return context.send(`You message is /start ${context.args}`);
|
|
976
|
+
* });
|
|
977
|
+
*/
|
|
978
|
+
command(command, handler, options) {
|
|
979
|
+
if (command.startsWith("/"))
|
|
980
|
+
throw new Error("Do not use / in command name");
|
|
981
|
+
return this.on(["message", "business_message"], (context, next) => {
|
|
982
|
+
if (context.entities?.some((entity) => {
|
|
983
|
+
if (entity.type !== "bot_command" || entity.offset > 0) return false;
|
|
984
|
+
const cmd = context.text?.slice(1, entity.length)?.replace(`@${this.info.username}`, "");
|
|
985
|
+
context.args = context.text?.slice(entity.length).trim() || null;
|
|
986
|
+
return cmd === command;
|
|
987
|
+
}))
|
|
988
|
+
return handler(context);
|
|
989
|
+
return next();
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
/** Currently not isolated!!! */
|
|
993
|
+
group(grouped) {
|
|
994
|
+
return grouped(this);
|
|
995
|
+
}
|
|
996
|
+
/**
|
|
997
|
+
* Init bot. Call it manually only if you doesn't use {@link Bot.start}
|
|
998
|
+
*/
|
|
999
|
+
async init() {
|
|
1000
|
+
await Promise.all(
|
|
1001
|
+
this.lazyloadPlugins.map(async (plugin) => this.extend(await plugin))
|
|
1002
|
+
);
|
|
1003
|
+
if (!this.info) {
|
|
1004
|
+
const info = await this.api.getMe({
|
|
1005
|
+
suppress: true
|
|
1006
|
+
});
|
|
1007
|
+
if (info instanceof TelegramError) {
|
|
1008
|
+
if (info.code === 404)
|
|
1009
|
+
info.message = "The bot token is incorrect. Check it in BotFather.";
|
|
1010
|
+
throw info;
|
|
1011
|
+
}
|
|
1012
|
+
this.info = info;
|
|
1013
|
+
}
|
|
1014
|
+
}
|
|
1015
|
+
/**
|
|
1016
|
+
* Start receive updates via long-polling or webhook
|
|
1017
|
+
*
|
|
1018
|
+
* @example
|
|
1019
|
+
* ```ts
|
|
1020
|
+
* import { Bot } from "gramio";
|
|
1021
|
+
*
|
|
1022
|
+
* const bot = new Bot("") // put you token here
|
|
1023
|
+
* .command("start", (context) => context.send("Hi!"))
|
|
1024
|
+
* .onStart(console.log);
|
|
1025
|
+
*
|
|
1026
|
+
* bot.start();
|
|
1027
|
+
* ```
|
|
1028
|
+
*/
|
|
1029
|
+
async start({
|
|
1030
|
+
webhook,
|
|
1031
|
+
dropPendingUpdates,
|
|
1032
|
+
allowedUpdates
|
|
1033
|
+
} = {}) {
|
|
1034
|
+
await this.init();
|
|
1035
|
+
if (!webhook) {
|
|
1036
|
+
await this.api.deleteWebhook({
|
|
1037
|
+
drop_pending_updates: dropPendingUpdates
|
|
1038
|
+
});
|
|
1039
|
+
await this.updates.startPolling({
|
|
1040
|
+
allowed_updates: allowedUpdates
|
|
1041
|
+
});
|
|
1042
|
+
this.runImmutableHooks("onStart", {
|
|
1043
|
+
plugins: this.dependencies,
|
|
1044
|
+
// biome-ignore lint/style/noNonNullAssertion: bot.init() guarantees this.info
|
|
1045
|
+
info: this.info,
|
|
1046
|
+
updatesFrom: "long-polling"
|
|
1047
|
+
});
|
|
1048
|
+
return this.info;
|
|
1049
|
+
}
|
|
1050
|
+
if (this.updates.isStarted) this.updates.stopPolling();
|
|
1051
|
+
await this.api.setWebhook({
|
|
1052
|
+
...webhook,
|
|
1053
|
+
drop_pending_updates: dropPendingUpdates,
|
|
1054
|
+
allowed_updates: allowedUpdates
|
|
1055
|
+
});
|
|
1056
|
+
this.runImmutableHooks("onStart", {
|
|
1057
|
+
plugins: this.dependencies,
|
|
1058
|
+
// biome-ignore lint/style/noNonNullAssertion: bot.init() guarantees this.info
|
|
1059
|
+
info: this.info,
|
|
1060
|
+
updatesFrom: "webhook"
|
|
1061
|
+
});
|
|
1062
|
+
return this.info;
|
|
1063
|
+
}
|
|
1064
|
+
/**
|
|
1065
|
+
* Stops receiving events via long-polling or webhook
|
|
1066
|
+
* Currently does not implement graceful shutdown
|
|
1067
|
+
* */
|
|
1068
|
+
async stop() {
|
|
1069
|
+
if (this.updates.isStarted) this.updates.stopPolling();
|
|
1070
|
+
else await this.api.deleteWebhook();
|
|
1071
|
+
await this.runImmutableHooks("onStop", {
|
|
1072
|
+
plugins: this.dependencies,
|
|
1073
|
+
// biome-ignore lint/style/noNonNullAssertion: bot.init() guarantees this.info
|
|
1074
|
+
info: this.info
|
|
1075
|
+
});
|
|
1076
|
+
}
|
|
1077
|
+
}
|
|
1078
|
+
_init = __decoratorStart();
|
|
1079
|
+
Bot = __decorateElement(_init, 0, "Bot", _Bot_decorators, Bot);
|
|
1080
|
+
__runInitializers(_init, 1, Bot);
|
|
1081
|
+
|
|
1082
|
+
const SECRET_TOKEN_HEADER = "X-Telegram-Bot-Api-Secret-Token";
|
|
1083
|
+
const WRONG_TOKEN_ERROR = "secret token is invalid";
|
|
1084
|
+
const responseOK = new Response("ok!");
|
|
1085
|
+
const responseUnauthorized = new Response(WRONG_TOKEN_ERROR, {
|
|
1086
|
+
status: 401
|
|
1087
|
+
// @ts-ignore
|
|
1088
|
+
});
|
|
1089
|
+
const frameworks = {
|
|
1090
|
+
elysia: ({ body, headers }) => ({
|
|
1091
|
+
update: body,
|
|
1092
|
+
header: headers[SECRET_TOKEN_HEADER],
|
|
1093
|
+
unauthorized: () => responseUnauthorized
|
|
1094
|
+
}),
|
|
1095
|
+
fastify: (request, reply) => ({
|
|
1096
|
+
update: request.body,
|
|
1097
|
+
header: request.headers[SECRET_TOKEN_HEADER],
|
|
1098
|
+
unauthorized: () => reply.code(401).send(WRONG_TOKEN_ERROR)
|
|
1099
|
+
}),
|
|
1100
|
+
hono: (c) => ({
|
|
1101
|
+
update: c.req.json(),
|
|
1102
|
+
header: c.req.header(SECRET_TOKEN_HEADER),
|
|
1103
|
+
unauthorized: () => c.text(WRONG_TOKEN_ERROR, 401)
|
|
1104
|
+
}),
|
|
1105
|
+
express: (req, res) => ({
|
|
1106
|
+
update: req.body,
|
|
1107
|
+
header: req.header(SECRET_TOKEN_HEADER),
|
|
1108
|
+
unauthorized: () => res.status(401).send(WRONG_TOKEN_ERROR)
|
|
1109
|
+
}),
|
|
1110
|
+
koa: (ctx) => ({
|
|
1111
|
+
update: ctx.request.body,
|
|
1112
|
+
header: ctx.get(SECRET_TOKEN_HEADER),
|
|
1113
|
+
unauthorized: () => {
|
|
1114
|
+
ctx.status === 401;
|
|
1115
|
+
ctx.body = WRONG_TOKEN_ERROR;
|
|
1116
|
+
}
|
|
1117
|
+
}),
|
|
1118
|
+
http: (req, res) => ({
|
|
1119
|
+
update: new Promise((resolve) => {
|
|
1120
|
+
let body = "";
|
|
1121
|
+
req.on("data", (chunk) => {
|
|
1122
|
+
body += chunk.toString();
|
|
1123
|
+
});
|
|
1124
|
+
req.on("end", () => resolve(JSON.parse(body)));
|
|
1125
|
+
}),
|
|
1126
|
+
header: req.headers[SECRET_TOKEN_HEADER.toLowerCase()],
|
|
1127
|
+
unauthorized: () => res.writeHead(401).end(WRONG_TOKEN_ERROR)
|
|
1128
|
+
}),
|
|
1129
|
+
"std/http": (req) => ({
|
|
1130
|
+
update: req.json(),
|
|
1131
|
+
header: req.headers.get(SECRET_TOKEN_HEADER),
|
|
1132
|
+
response: () => responseOK,
|
|
1133
|
+
unauthorized: () => responseUnauthorized
|
|
1134
|
+
}),
|
|
1135
|
+
"Bun.serve": (req) => ({
|
|
1136
|
+
update: req.json(),
|
|
1137
|
+
header: req.headers.get(SECRET_TOKEN_HEADER),
|
|
1138
|
+
response: () => responseOK,
|
|
1139
|
+
unauthorized: () => responseUnauthorized
|
|
1140
|
+
})
|
|
1141
|
+
};
|
|
1142
|
+
|
|
1143
|
+
function webhookHandler(bot, framework, secretToken) {
|
|
1144
|
+
const frameworkAdapter = frameworks[framework];
|
|
1145
|
+
return async (...args) => {
|
|
1146
|
+
const { update, response, header, unauthorized } = frameworkAdapter(
|
|
1147
|
+
...args
|
|
1148
|
+
);
|
|
1149
|
+
if (secretToken && header !== secretToken) return unauthorized();
|
|
1150
|
+
await bot.updates.handleUpdate(await update);
|
|
1151
|
+
if (response) return response();
|
|
1152
|
+
};
|
|
1153
|
+
}
|
|
1154
|
+
|
|
1155
|
+
Symbol.metadata ??= Symbol("Symbol.metadata");
|
|
1156
|
+
|
|
1157
|
+
exports.Bot = Bot;
|
|
1158
|
+
exports.ErrorKind = ErrorKind;
|
|
1159
|
+
exports.Plugin = Plugin;
|
|
1160
|
+
exports.TelegramError = TelegramError;
|
|
1161
|
+
exports.webhookHandler = webhookHandler;
|
|
1162
|
+
Object.keys(callbackData).forEach(function (k) {
|
|
1163
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
1164
|
+
enumerable: true,
|
|
1165
|
+
get: function () { return callbackData[k]; }
|
|
1166
|
+
});
|
|
1167
|
+
});
|
|
1168
|
+
Object.keys(contexts).forEach(function (k) {
|
|
1169
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
1170
|
+
enumerable: true,
|
|
1171
|
+
get: function () { return contexts[k]; }
|
|
1172
|
+
});
|
|
1173
|
+
});
|
|
1174
|
+
Object.keys(files).forEach(function (k) {
|
|
1175
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
1176
|
+
enumerable: true,
|
|
1177
|
+
get: function () { return files[k]; }
|
|
1178
|
+
});
|
|
1179
|
+
});
|
|
1180
|
+
Object.keys(format).forEach(function (k) {
|
|
1181
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
1182
|
+
enumerable: true,
|
|
1183
|
+
get: function () { return format[k]; }
|
|
1184
|
+
});
|
|
1185
|
+
});
|
|
1186
|
+
Object.keys(keyboards).forEach(function (k) {
|
|
1187
|
+
if (k !== 'default' && !Object.prototype.hasOwnProperty.call(exports, k)) Object.defineProperty(exports, k, {
|
|
1188
|
+
enumerable: true,
|
|
1189
|
+
get: function () { return keyboards[k]; }
|
|
1190
|
+
});
|
|
1191
|
+
});
|