UNPKG

10.4 kBJavaScriptView Raw
1"use strict";
2var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3 if (k2 === undefined) k2 = k;
4 var desc = Object.getOwnPropertyDescriptor(m, k);
5 if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6 desc = { enumerable: true, get: function() { return m[k]; } };
7 }
8 Object.defineProperty(o, k2, desc);
9}) : (function(o, m, k, k2) {
10 if (k2 === undefined) k2 = k;
11 o[k2] = m[k];
12}));
13var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14 Object.defineProperty(o, "default", { enumerable: true, value: v });
15}) : function(o, v) {
16 o["default"] = v;
17});
18var __importStar = (this && this.__importStar) || function (mod) {
19 if (mod && mod.__esModule) return mod;
20 var result = {};
21 if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22 __setModuleDefault(result, mod);
23 return result;
24};
25var __importDefault = (this && this.__importDefault) || function (mod) {
26 return (mod && mod.__esModule) ? mod : { "default": mod };
27};
28Object.defineProperty(exports, "__esModule", { value: true });
29exports.Telegraf = void 0;
30const crypto = __importStar(require("crypto"));
31const http = __importStar(require("http"));
32const https = __importStar(require("https"));
33const composer_1 = require("./composer");
34const compact_1 = require("./core/helpers/compact");
35const context_1 = __importDefault(require("./context"));
36const debug_1 = __importDefault(require("debug"));
37const webhook_1 = __importDefault(require("./core/network/webhook"));
38const polling_1 = require("./core/network/polling");
39const p_timeout_1 = __importDefault(require("p-timeout"));
40const telegram_1 = __importDefault(require("./telegram"));
41const url_1 = require("url");
42const safeCompare = require("safe-compare");
43const debug = (0, debug_1.default)('telegraf:main');
44const DEFAULT_OPTIONS = {
45 telegram: {},
46 handlerTimeout: 90000,
47 contextType: context_1.default,
48};
49function always(x) {
50 return () => x;
51}
52const anoop = always(Promise.resolve());
53const TOKEN_HEADER = 'x-telegram-bot-api-secret-token';
54class Telegraf extends composer_1.Composer {
55 constructor(token, options) {
56 super();
57 this.context = {};
58 /** Assign to this to customise the webhook filter middleware.
59 * `{ hookPath, secretToken }` will be bound to this rather than the Telegraf instance.
60 * Remember to assign a regular function and not an arrow function so it's bindable.
61 */
62 this.webhookFilter = function (req) {
63 const debug = (0, debug_1.default)('telegraf:webhook');
64 if (req.method === 'POST') {
65 if (safeCompare(this.hookPath, req.url)) {
66 // no need to check if secret_token was not set
67 if (!this.secretToken)
68 return true;
69 else {
70 const token = req.headers[TOKEN_HEADER];
71 if (safeCompare(this.secretToken, token))
72 return true;
73 else
74 debug('Secret token does not match:', token, this.secretToken);
75 }
76 }
77 else
78 debug('Path does not match:', req.url, this.hookPath);
79 }
80 else
81 debug('Unexpected request method, not POST. Received:', req.method);
82 return false;
83 };
84 this.handleError = (err, ctx) => {
85 // set exit code to emulate `warn-with-error-code` behavior of
86 // https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode
87 // to prevent a clean exit despite an error being thrown
88 process.exitCode = 1;
89 console.error('Unhandled error while processing', ctx.update);
90 throw err;
91 };
92 // @ts-expect-error Trust me, TS
93 this.options = {
94 ...DEFAULT_OPTIONS,
95 ...(0, compact_1.compactOptions)(options),
96 };
97 this.telegram = new telegram_1.default(token, this.options.telegram);
98 debug('Created a `Telegraf` instance');
99 }
100 get token() {
101 return this.telegram.token;
102 }
103 /** @deprecated use `ctx.telegram.webhookReply` */
104 set webhookReply(webhookReply) {
105 this.telegram.webhookReply = webhookReply;
106 }
107 /** @deprecated use `ctx.telegram.webhookReply` */
108 get webhookReply() {
109 return this.telegram.webhookReply;
110 }
111 /**
112 * _Override_ error handling
113 */
114 catch(handler) {
115 this.handleError = handler;
116 return this;
117 }
118 /**
119 * You must call `bot.telegram.setWebhook` for this to work.
120 * You should probably use {@link Telegraf.createWebhook} instead.
121 */
122 webhookCallback(hookPath = '/', opts = {}) {
123 const { secretToken } = opts;
124 return (0, webhook_1.default)(this.webhookFilter.bind({ hookPath, secretToken }), (update, res) => this.handleUpdate(update, res));
125 }
126 getDomainOpts(opts) {
127 var _a;
128 const protocol = opts.domain.startsWith('https://') || opts.domain.startsWith('http://');
129 if (protocol)
130 debug('Unexpected protocol in domain, telegraf will use https:', opts.domain);
131 const domain = protocol ? new url_1.URL(opts.domain).host : opts.domain;
132 const path = (_a = opts.path) !== null && _a !== void 0 ? _a : `/telegraf/${this.secretPathComponent()}`;
133 const url = `https://${domain}${path}`;
134 return { domain, path, url };
135 }
136 /**
137 * Specify a url to receive incoming updates via webhook.
138 * Returns an Express-style middleware you can pass to app.use()
139 */
140 async createWebhook(opts) {
141 const { domain, path, ...extra } = opts;
142 const domainOpts = this.getDomainOpts({ domain, path });
143 await this.telegram.setWebhook(domainOpts.url, extra);
144 debug(`Webhook set to ${domainOpts.url}`);
145 return this.webhookCallback(domainOpts.path, {
146 secretToken: extra.secret_token,
147 });
148 }
149 startPolling(allowedUpdates = []) {
150 this.polling = new polling_1.Polling(this.telegram, allowedUpdates);
151 return this.polling.loop(async (update) => {
152 await this.handleUpdate(update);
153 });
154 }
155 startWebhook(hookPath, tlsOptions, port, host, cb, secretToken) {
156 const webhookCb = this.webhookCallback(hookPath, { secretToken });
157 const callback = typeof cb === 'function'
158 ? (req, res) => webhookCb(req, res, () => cb(req, res))
159 : webhookCb;
160 this.webhookServer =
161 tlsOptions != null
162 ? https.createServer(tlsOptions, callback)
163 : http.createServer(callback);
164 this.webhookServer.listen(port, host, () => {
165 debug('Webhook listening on port: %s', port);
166 });
167 return this;
168 }
169 secretPathComponent() {
170 return crypto
171 .createHash('sha3-256')
172 .update(this.token)
173 .update(process.version) // salt
174 .digest('hex');
175 }
176 /**
177 * @see https://github.com/telegraf/telegraf/discussions/1344#discussioncomment-335700
178 */
179 async launch(config = {}) {
180 var _a;
181 debug('Connecting to Telegram');
182 (_a = this.botInfo) !== null && _a !== void 0 ? _a : (this.botInfo = await this.telegram.getMe());
183 debug(`Launching @${this.botInfo.username}`);
184 if (config.webhook === undefined) {
185 await this.telegram.deleteWebhook({
186 drop_pending_updates: config.dropPendingUpdates,
187 });
188 debug('Bot started with long polling');
189 await this.startPolling(config.allowedUpdates);
190 return;
191 }
192 const domainOpts = this.getDomainOpts({
193 domain: config.webhook.domain,
194 path: config.webhook.hookPath,
195 });
196 const { tlsOptions, port, host, cb, secretToken } = config.webhook;
197 this.startWebhook(domainOpts.path, tlsOptions, port, host, cb, secretToken);
198 await this.telegram.setWebhook(domainOpts.url, {
199 drop_pending_updates: config.dropPendingUpdates,
200 allowed_updates: config.allowedUpdates,
201 ip_address: config.webhook.ipAddress,
202 max_connections: config.webhook.maxConnections,
203 secret_token: config.webhook.secretToken,
204 certificate: config.webhook.certificate,
205 });
206 debug(`Bot started with webhook @ ${domainOpts.url}`);
207 }
208 stop(reason = 'unspecified') {
209 var _a, _b;
210 debug('Stopping bot... Reason:', reason);
211 // https://github.com/telegraf/telegraf/pull/1224#issuecomment-742693770
212 if (this.polling === undefined && this.webhookServer === undefined) {
213 throw new Error('Bot is not running!');
214 }
215 (_a = this.webhookServer) === null || _a === void 0 ? void 0 : _a.close();
216 (_b = this.polling) === null || _b === void 0 ? void 0 : _b.stop();
217 }
218 async handleUpdate(update, webhookResponse) {
219 var _a, _b;
220 (_a = this.botInfo) !== null && _a !== void 0 ? _a : (this.botInfo = (debug('Update %d is waiting for `botInfo` to be initialized', update.update_id),
221 await ((_b = this.botInfoCall) !== null && _b !== void 0 ? _b : (this.botInfoCall = this.telegram.getMe()))));
222 debug('Processing update', update.update_id);
223 const tg = new telegram_1.default(this.token, this.telegram.options, webhookResponse);
224 const TelegrafContext = this.options.contextType;
225 const ctx = new TelegrafContext(update, tg, this.botInfo);
226 Object.assign(ctx, this.context);
227 try {
228 await (0, p_timeout_1.default)(Promise.resolve(this.middleware()(ctx, anoop)), this.options.handlerTimeout);
229 }
230 catch (err) {
231 return await this.handleError(err, ctx);
232 }
233 finally {
234 if ((webhookResponse === null || webhookResponse === void 0 ? void 0 : webhookResponse.writableEnded) === false) {
235 webhookResponse.end();
236 }
237 debug('Finished processing update', update.update_id);
238 }
239 }
240}
241exports.Telegraf = Telegraf;