diff --git a/packages/channels/example/app.ts b/packages/channels/example/app.ts index a0f86b5db..42653c0cd 100644 --- a/packages/channels/example/app.ts +++ b/packages/channels/example/app.ts @@ -2,12 +2,15 @@ import clc from 'cli-color' import { Router } from 'express' import Joi from 'joi' import { Channel } from '../src/base/channel' +import { TelegramChannel } from '../src/telegram/channel' import payloads from './payloads.json' export class App { constructor(private router: Router, private config: any) {} - async setup() {} + async setup() { + await this.setupChannel('telegram', new TelegramChannel()) + } async setupChannel(name: string, channel: Channel) { await channel.setup(this.router, { diff --git a/packages/channels/package.json b/packages/channels/package.json index fab085e48..c62407da8 100644 --- a/packages/channels/package.json +++ b/packages/channels/package.json @@ -32,6 +32,7 @@ "lodash": "^4.17.21", "lru-cache": "^6.0.0", "ms": "^2.1.3", + "telegraf": "^4.6.0", "uuid": "^8.3.2", "yn": "^4.0.0" } diff --git a/packages/channels/src/index.ts b/packages/channels/src/index.ts index be6b994dd..ebe197a95 100644 --- a/packages/channels/src/index.ts +++ b/packages/channels/src/index.ts @@ -1,2 +1,3 @@ export * from './base/channel' export * from './base/endpoint' +export * from './telegram/channel' diff --git a/packages/channels/src/telegram/README.md b/packages/channels/src/telegram/README.md new file mode 100644 index 000000000..2247361e2 --- /dev/null +++ b/packages/channels/src/telegram/README.md @@ -0,0 +1,29 @@ +### Sending + +| Channels | Telegram | +| -------- | :------: | +| Text | ✅ | +| Image | ✅ | +| Choice | ✅ | +| Dropdown | ✅ | +| Card | ✅ | +| Carousel | ✅ | +| File | ✅ | +| Audio | ✅ | +| Video | ✅ | +| Location | ✅ | + +### Receiving + +| Channels | Telegram | +| ------------- | :------: | +| Text | ✅ | +| Quick Reply | N/A | +| Postback | ✅ | +| Say Something | ✅ | +| Voice | ❌ | +| Image | ❌ | +| File | ❌ | +| Audio | ❌ | +| Video | ❌ | +| Location | ❌ | diff --git a/packages/channels/src/telegram/api.ts b/packages/channels/src/telegram/api.ts new file mode 100644 index 000000000..d0f201c53 --- /dev/null +++ b/packages/channels/src/telegram/api.ts @@ -0,0 +1,106 @@ +import { Response } from 'express' +import { Context, NarrowedContext } from 'telegraf' +import { Update } from 'telegraf/typings/core/types/typegram' +import yn from 'yn' +import { ChannelApi, ChannelApiManager, ChannelApiRequest } from '../base/api' +import { ChannelInitializeEvent, ChannelStartEvent, ChannelStopEvent } from '../base/service' +import { POSTBACK_PREFIX, SAY_PREFIX } from './renderers/carousel' +import { TelegramService } from './service' + +export class TelegramApi extends ChannelApi { + async setup(router: ChannelApiManager) { + router.post('/telegram/:token', this.handleRequest.bind(this)) + + this.service.on('start', this.handleStart.bind(this)) + this.service.on('initialize', this.handleInitialize.bind(this)) + this.service.on('stop', this.handleStop.bind(this)) + } + + private async handleRequest(req: ChannelApiRequest, res: Response) { + const { config, callback } = this.service.get(req.scope) + + if (req.params.token === config.botToken) { + req.url = '/' // by-passes verification in telegraf since we do it here instead + callback!(req, res) + } else { + res.sendStatus(401) + } + } + + private async handleInitialize({ scope }: ChannelInitializeEvent) { + if (this.useWebhook()) { + const { telegraf, config } = this.service.get(scope) + const webhook = `${await this.urlCallback!(scope)}/${config.botToken}` + await telegraf.telegram.setWebhook(webhook) + } + } + + private async handleStart({ scope }: ChannelStartEvent) { + const { telegraf } = this.service.get(scope) + + telegraf.on('message', this.asyncCallback(scope, this.handleTelegrafMessage.bind(this))) + telegraf.on('callback_query', this.asyncCallback(scope, this.handleTelegrafCallbackQuery.bind(this))) + + if (this.useWebhook()) { + this.service.get(scope).callback = telegraf.webhookCallback('/') + } else { + await telegraf.telegram.deleteWebhook() + await telegraf.launch() + } + } + + private async handleStop({ scope }: ChannelStopEvent) { + if (!this.useWebhook()) { + this.service.get(scope).telegraf.stop() + } + } + + private async handleTelegrafMessage(scope: string, ctx: NarrowedContext, Update.MessageUpdate>) { + if ('text' in ctx.message) { + await this.service.receive(scope, this.extractEndpoint(ctx), { type: 'text', text: ctx.message.text }) + } + } + + private async handleTelegrafCallbackQuery( + scope: string, + ctx: NarrowedContext, Update.CallbackQueryUpdate> + ) { + if ('data' in ctx.callbackQuery) { + const data = ctx.callbackQuery.data + + if (data.startsWith(SAY_PREFIX)) { + await this.service.receive(scope, this.extractEndpoint(ctx), { + type: 'say_something', + text: data.replace(SAY_PREFIX, '') + }) + } else if (data.startsWith(POSTBACK_PREFIX)) { + await this.service.receive(scope, this.extractEndpoint(ctx), { + type: 'postback', + payload: data.replace(POSTBACK_PREFIX, '') + }) + } + } + + await ctx.answerCbQuery() + } + + private extractEndpoint(ctx: Context) { + const chatId = ctx.chat?.id + const userId = ctx.from?.id + + return { identity: '*', sender: userId!.toString(), thread: chatId!.toString() } + } + + private asyncCallback(scope: string, fn: (scope: string, ctx: any) => Promise) { + return (ctx: any) => { + fn(scope, ctx).catch((e) => { + this.service.logger?.error('Error occurred in telegram callback', e) + }) + } + } + + private useWebhook() { + // TODO: remove this dependency on server env vars + return !yn(process.env.SPINNED) || yn(process.env.CLUSTER_ENABLED) + } +} diff --git a/packages/channels/src/telegram/channel.ts b/packages/channels/src/telegram/channel.ts new file mode 100644 index 000000000..50752ceac --- /dev/null +++ b/packages/channels/src/telegram/channel.ts @@ -0,0 +1,23 @@ +import { ChannelTemplate } from '../base/channel' +import { TelegramApi } from './api' +import { TelegramConfig, TelegramConfigSchema } from './config' +import { TelegramService } from './service' +import { TelegramStream } from './stream' + +export class TelegramChannel extends ChannelTemplate { + get meta() { + return { + id: 'e578723f-ab57-463c-bc13-b483db9bf547', + name: 'telegram', + version: '1.0.0', + schema: TelegramConfigSchema, + initiable: true, + lazy: true + } + } + + constructor() { + const service = new TelegramService() + super(service, new TelegramApi(service), new TelegramStream(service)) + } +} diff --git a/packages/channels/src/telegram/config.ts b/packages/channels/src/telegram/config.ts new file mode 100644 index 000000000..877682f13 --- /dev/null +++ b/packages/channels/src/telegram/config.ts @@ -0,0 +1,9 @@ +import Joi from 'joi' + +export interface TelegramConfig { + botToken: string +} + +export const TelegramConfigSchema = { + botToken: Joi.string().required() +} diff --git a/packages/channels/src/telegram/context.ts b/packages/channels/src/telegram/context.ts new file mode 100644 index 000000000..2ded01858 --- /dev/null +++ b/packages/channels/src/telegram/context.ts @@ -0,0 +1,23 @@ +import { ChatAction, InputFile } from 'telegraf/typings/core/types/typegram' +import { ChannelContext } from '../base/context' +import { TelegramState } from './service' + +export type TelegramContext = ChannelContext & { + messages: TelegramMessage[] +} + +export interface TelegramMessage { + text?: string + animation?: string + photo?: InputFile + markdown?: boolean + action?: ChatAction + document?: InputFile + audio?: InputFile + video?: InputFile + location?: { + latitude: number + longitude: number + } + extra?: any +} diff --git a/packages/channels/src/telegram/renderers/audio.ts b/packages/channels/src/telegram/renderers/audio.ts new file mode 100644 index 000000000..f75edb45f --- /dev/null +++ b/packages/channels/src/telegram/renderers/audio.ts @@ -0,0 +1,13 @@ +import path from 'path' +import { AudioRenderer } from '../../base/renderers/audio' +import { AudioContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramAudioRenderer extends AudioRenderer { + renderAudio(context: TelegramContext, payload: AudioContent) { + context.messages.push({ + document: { url: payload.audio, filename: path.basename(payload.audio) }, + extra: { caption: payload.title } + }) + } +} diff --git a/packages/channels/src/telegram/renderers/carousel.ts b/packages/channels/src/telegram/renderers/carousel.ts new file mode 100644 index 000000000..2db7e101c --- /dev/null +++ b/packages/channels/src/telegram/renderers/carousel.ts @@ -0,0 +1,51 @@ +import path from 'path' +import { Markup } from 'telegraf' +import { InlineKeyboardButton } from 'telegraf/typings/core/types/typegram' +import { CarouselContext, CarouselRenderer } from '../../base/renderers/carousel' +import { ActionOpenURL, ActionPostback, ActionSaySomething, CardContent } from '../../content/types' +import { TelegramContext } from '../context' + +type Context = CarouselContext & { + buttons: InlineKeyboardButton[] +} + +export const POSTBACK_PREFIX = 'postback::' +export const SAY_PREFIX = 'say::' + +export class TelegramCarouselRenderer extends CarouselRenderer { + startRenderCard(context: Context, _card: CardContent) { + context.buttons = [] + } + + renderButtonUrl(context: Context, button: ActionOpenURL) { + context.buttons.push(Markup.button.url(button.title, button.url)) + } + + renderButtonPostback(context: Context, button: ActionPostback) { + context.buttons.push(Markup.button.callback(button.title, `${POSTBACK_PREFIX}${button.payload}`)) + } + + renderButtonSay(context: Context, button: ActionSaySomething) { + context.buttons.push(Markup.button.callback(button.title, `${SAY_PREFIX}${button.text}`)) + } + + endRenderCard(context: Context, card: CardContent) { + const text = `*${card.title}*${card.subtitle ? '\n' + card.subtitle : ''}` + + if (card.image) { + context.channel.messages.push({ action: 'upload_photo' }) + context.channel.messages.push({ + photo: { + url: card.image, + filename: path.basename(card.image) + }, + extra: { caption: text, parse_mode: 'Markdown', ...Markup.inlineKeyboard(context.buttons) } + }) + } else { + context.channel.messages.push({ + text, + extra: Markup.inlineKeyboard(context.buttons) + }) + } + } +} diff --git a/packages/channels/src/telegram/renderers/choices.ts b/packages/channels/src/telegram/renderers/choices.ts new file mode 100644 index 000000000..345978046 --- /dev/null +++ b/packages/channels/src/telegram/renderers/choices.ts @@ -0,0 +1,15 @@ +import { Markup } from 'telegraf' +import { ChoicesRenderer } from '../../base/renderers/choices' +import { ChoiceContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramChoicesRenderer extends ChoicesRenderer { + renderChoice(context: TelegramContext, payload: ChoiceContent) { + if (!context.messages.length) { + context.messages.push({}) + } + + const buttons = payload.choices.map((x) => Markup.button.callback(x.title, x.value)) + context.messages[0].extra = Markup.keyboard(buttons).oneTime() + } +} diff --git a/packages/channels/src/telegram/renderers/file.ts b/packages/channels/src/telegram/renderers/file.ts new file mode 100644 index 000000000..c489a7825 --- /dev/null +++ b/packages/channels/src/telegram/renderers/file.ts @@ -0,0 +1,13 @@ +import path from 'path' +import { FileRenderer } from '../../base/renderers/file' +import { FileContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramFileRenderer extends FileRenderer { + renderFile(context: TelegramContext, payload: FileContent) { + context.messages.push({ + document: { url: payload.file, filename: path.basename(payload.file) }, + extra: { caption: payload.title } + }) + } +} diff --git a/packages/channels/src/telegram/renderers/image.ts b/packages/channels/src/telegram/renderers/image.ts new file mode 100644 index 000000000..999ff5c16 --- /dev/null +++ b/packages/channels/src/telegram/renderers/image.ts @@ -0,0 +1,17 @@ +import path from 'path' +import { ImageRenderer } from '../../base/renderers/image' +import { ImageContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramImageRenderer extends ImageRenderer { + renderImage(context: TelegramContext, payload: ImageContent) { + if (payload.image.toLowerCase().endsWith('.gif')) { + context.messages.push({ animation: payload.image, extra: { caption: payload.title } }) + } else { + context.messages.push({ + photo: { url: payload.image, filename: path.basename(payload.image) }, + extra: { caption: payload.title } + }) + } + } +} diff --git a/packages/channels/src/telegram/renderers/index.ts b/packages/channels/src/telegram/renderers/index.ts new file mode 100644 index 000000000..e33a28845 --- /dev/null +++ b/packages/channels/src/telegram/renderers/index.ts @@ -0,0 +1,19 @@ +import { TelegramAudioRenderer } from './audio' +import { TelegramCarouselRenderer } from './carousel' +import { TelegramChoicesRenderer } from './choices' +import { TelegramFileRenderer } from './file' +import { TelegramImageRenderer } from './image' +import { TelegramLocationRenderer } from './location' +import { TelegramTextRenderer } from './text' +import { TelegramVideoRenderer } from './video' + +export const TelegramRenderers = [ + new TelegramTextRenderer(), + new TelegramImageRenderer(), + new TelegramCarouselRenderer(), + new TelegramChoicesRenderer(), + new TelegramFileRenderer(), + new TelegramAudioRenderer(), + new TelegramVideoRenderer(), + new TelegramLocationRenderer() +] diff --git a/packages/channels/src/telegram/renderers/location.ts b/packages/channels/src/telegram/renderers/location.ts new file mode 100644 index 000000000..4e523da5b --- /dev/null +++ b/packages/channels/src/telegram/renderers/location.ts @@ -0,0 +1,22 @@ +import { LocationRenderer } from '../../base/renderers/location' +import { LocationContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramLocationRenderer extends LocationRenderer { + renderLocation(context: TelegramContext, payload: LocationContent) { + context.messages.push({ + location: { latitude: payload.latitude, longitude: payload.longitude } + // For some reason this does not work, so we need to send a seperate text message + // extra: { caption: payload.title } + }) + + let text = payload.title + if (payload.address) { + text = (text ? `*${text}*\n` : '') + payload.address + } + + if (payload.title) { + context.messages.push({ text, extra: { parse_mode: 'Markdown' } }) + } + } +} diff --git a/packages/channels/src/telegram/renderers/text.ts b/packages/channels/src/telegram/renderers/text.ts new file mode 100644 index 000000000..56f0679fb --- /dev/null +++ b/packages/channels/src/telegram/renderers/text.ts @@ -0,0 +1,13 @@ +import { TextRenderer } from '../../base/renderers/text' +import { TextContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramTextRenderer extends TextRenderer { + renderText(context: TelegramContext, payload: TextContent) { + context.messages.push({ + text: payload.text, + markdown: payload.markdown, + extra: payload.markdown ? { parse_mode: 'Markdown' } : {} + }) + } +} diff --git a/packages/channels/src/telegram/renderers/video.ts b/packages/channels/src/telegram/renderers/video.ts new file mode 100644 index 000000000..76554879c --- /dev/null +++ b/packages/channels/src/telegram/renderers/video.ts @@ -0,0 +1,13 @@ +import path from 'path' +import { VideoRenderer } from '../../base/renderers/video' +import { VideoContent } from '../../content/types' +import { TelegramContext } from '../context' + +export class TelegramVideoRenderer extends VideoRenderer { + renderVideo(context: TelegramContext, payload: VideoContent) { + context.messages.push({ + document: { url: payload.video, filename: path.basename(payload.video) }, + extra: { caption: payload.title } + }) + } +} diff --git a/packages/channels/src/telegram/senders/common.ts b/packages/channels/src/telegram/senders/common.ts new file mode 100644 index 000000000..b1a671cb1 --- /dev/null +++ b/packages/channels/src/telegram/senders/common.ts @@ -0,0 +1,36 @@ +import { CommonSender } from '../../base/senders/common' +import { TelegramContext } from '../context' + +export class TelegramCommonSender extends CommonSender { + async send(context: TelegramContext) { + const telegram = context.state.telegraf.telegram + const chatId = context.thread + + for (const message of context.messages) { + if (message.action) { + await telegram.sendChatAction(chatId, message.action) + } + if (message.text) { + await telegram.sendMessage(chatId, message.text, message.extra) + } + if (message.photo) { + await telegram.sendPhoto(chatId, message.photo, message.extra) + } + if (message.animation) { + await telegram.sendAnimation(chatId, message.animation, message.extra) + } + if (message.document) { + await telegram.sendDocument(chatId, message.document, message.extra) + } + if (message.audio) { + await telegram.sendAudio(chatId, message.audio, message.extra) + } + if (message.video) { + await telegram.sendVideo(chatId, message.video, message.extra) + } + if (message.location) { + await telegram.sendLocation(chatId, message.location.latitude, message.location.longitude, message.extra) + } + } + } +} diff --git a/packages/channels/src/telegram/senders/index.ts b/packages/channels/src/telegram/senders/index.ts new file mode 100644 index 000000000..527660207 --- /dev/null +++ b/packages/channels/src/telegram/senders/index.ts @@ -0,0 +1,4 @@ +import { TelegramCommonSender } from './common' +import { TelegramTypingSender } from './typing' + +export const TelegramSenders = [new TelegramTypingSender(), new TelegramCommonSender()] diff --git a/packages/channels/src/telegram/senders/typing.ts b/packages/channels/src/telegram/senders/typing.ts new file mode 100644 index 000000000..1e7529eb6 --- /dev/null +++ b/packages/channels/src/telegram/senders/typing.ts @@ -0,0 +1,8 @@ +import { TypingSender } from '../../base/senders/typing' +import { TelegramContext } from '../context' + +export class TelegramTypingSender extends TypingSender { + async sendIndicator(context: TelegramContext) { + await context.state.telegraf.telegram.sendChatAction(context.thread, 'typing') + } +} diff --git a/packages/channels/src/telegram/service.ts b/packages/channels/src/telegram/service.ts new file mode 100644 index 000000000..4991772d4 --- /dev/null +++ b/packages/channels/src/telegram/service.ts @@ -0,0 +1,20 @@ +import { Request, Response } from 'express' +import { Telegraf } from 'telegraf' +import { ChannelService, ChannelState } from '../base/service' +import { TelegramConfig } from './config' + +export interface TelegramState extends ChannelState { + telegraf: Telegraf + callback?: (req: Request, res: Response) => any +} + +export class TelegramService extends ChannelService { + async create(scope: string, config: TelegramConfig) { + const telegraf = new Telegraf(config.botToken) + + return { + config, + telegraf + } + } +} diff --git a/packages/channels/src/telegram/stream.ts b/packages/channels/src/telegram/stream.ts new file mode 100644 index 000000000..5984598d8 --- /dev/null +++ b/packages/channels/src/telegram/stream.ts @@ -0,0 +1,26 @@ +import _ from 'lodash' +import { ChannelContext } from '../base/context' +import { CardToCarouselRenderer } from '../base/renderers/card' +import { DropdownToChoicesRenderer } from '../base/renderers/dropdown' +import { ChannelStream } from '../base/stream' +import { TelegramContext } from './context' +import { TelegramRenderers } from './renderers' +import { TelegramSenders } from './senders' +import { TelegramService } from './service' + +export class TelegramStream extends ChannelStream { + get renderers() { + return [new CardToCarouselRenderer(), new DropdownToChoicesRenderer(), ...TelegramRenderers] + } + + get senders() { + return TelegramSenders + } + + protected async getContext(base: ChannelContext): Promise { + return { + ...base, + messages: [] + } + } +} diff --git a/packages/server/src/channels/service.ts b/packages/server/src/channels/service.ts index 4e3f24a0a..46717aa83 100644 --- a/packages/server/src/channels/service.ts +++ b/packages/server/src/channels/service.ts @@ -1,16 +1,17 @@ import { uuid } from '@botpress/messaging-base' +import { Channel, TelegramChannel } from '@botpress/messaging-channels' import { - Channel, - MessengerChannel, - SlackChannel, - SmoochChannel, - TeamsChannel, - TelegramChannel, - TwilioChannel, - VonageChannel + MessengerChannel as MessengerChannelLegacy, + SlackChannel as SlackChannelLegacy, + SmoochChannel as SmoochChannelLegacy, + TeamsChannel as TeamsChannelLegacy, + TelegramChannel as TelegramChannelLegacy, + TwilioChannel as TwilioChannelLegacy, + VonageChannel as VonageChannelLegacy } from '@botpress/messaging-channels-legacy' import { Service, DatabaseService } from '@botpress/messaging-engine' import semver from 'semver' +import yn from 'yn' import { ChannelTable } from './table' export class ChannelService extends Service { @@ -26,15 +27,24 @@ export class ChannelService extends Service { this.table = new ChannelTable() - this.channels = [ - new MessengerChannel(), - new SlackChannel(), - new TeamsChannel(), - new TelegramChannel(), - new TwilioChannel(), - new SmoochChannel(), - new VonageChannel() - ] + this.channels = [] + + if (yn(process.env.ENABLE_EXPERIMENTAL_CHANNELS)) { + this.channels = [...this.channels, new TelegramChannel()] + } + + if (!yn(process.env.DISABLE_LEGACY_CHANNELS)) { + this.channels = [ + ...this.channels, + new MessengerChannelLegacy(), + new SlackChannelLegacy(), + new TeamsChannelLegacy(), + new TelegramChannelLegacy(), + new TwilioChannelLegacy(), + new SmoochChannelLegacy(), + new VonageChannelLegacy() + ] + } this.channelsByNameAndVersion = {} this.channelsByName = {} diff --git a/packages/server/src/launcher.ts b/packages/server/src/launcher.ts index a5021146d..2f29ea3fc 100644 --- a/packages/server/src/launcher.ts +++ b/packages/server/src/launcher.ts @@ -155,7 +155,7 @@ export class Launcher { for (const channel of this.app.channels.list()) { enabled++ - text += `\n${padding}${clc.green('⦿')} ${channel.meta.name}` + text += `\n${padding}${clc.green('⦿')} ${channel.meta.name}@${channel.meta.version}` } this.logger.info(`Using ${clc.bold(enabled)} channels` + text) diff --git a/yarn.lock b/yarn.lock index cc0008cae..21bc05187 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2085,6 +2085,7 @@ __metadata: lodash: ^4.17.21 lru-cache: ^6.0.0 ms: ^2.1.3 + telegraf: ^4.6.0 uuid: ^8.3.2 yn: ^4.0.0 languageName: unknown @@ -6775,6 +6776,15 @@ __metadata: languageName: node linkType: hard +"abort-controller@npm:^3.0.0": + version: 3.0.0 + resolution: "abort-controller@npm:3.0.0" + dependencies: + event-target-shim: ^5.0.0 + checksum: 170bdba9b47b7e65906a28c8ce4f38a7a369d78e2271706f020849c1bfe0ee2067d4261df8bbb66eb84f79208fd5b710df759d64191db58cfba7ce8ef9c54b75 + languageName: node + linkType: hard + "abortcontroller-polyfill@npm:^1.1.9": version: 1.7.3 resolution: "abortcontroller-polyfill@npm:1.7.3" @@ -8250,6 +8260,23 @@ __metadata: languageName: node linkType: hard +"buffer-alloc-unsafe@npm:^1.1.0": + version: 1.1.0 + resolution: "buffer-alloc-unsafe@npm:1.1.0" + checksum: c5e18bf51f67754ec843c9af3d4c005051aac5008a3992938dda1344e5cfec77c4b02b4ca303644d1e9a6e281765155ce6356d85c6f5ccc5cd21afc868def396 + languageName: node + linkType: hard + +"buffer-alloc@npm:^1.2.0": + version: 1.2.0 + resolution: "buffer-alloc@npm:1.2.0" + dependencies: + buffer-alloc-unsafe: ^1.1.0 + buffer-fill: ^1.0.0 + checksum: 560cd27f3cbe73c614867da373407d4506309c62fe18de45a1ce191f3785ec6ca2488d802ff82065798542422980ca25f903db078c57822218182c37c3576df5 + languageName: node + linkType: hard + "buffer-equal-constant-time@npm:1.0.1": version: 1.0.1 resolution: "buffer-equal-constant-time@npm:1.0.1" @@ -8257,6 +8284,13 @@ __metadata: languageName: node linkType: hard +"buffer-fill@npm:^1.0.0": + version: 1.0.0 + resolution: "buffer-fill@npm:1.0.0" + checksum: c29b4723ddeab01e74b5d3b982a0c6828f2ded49cef049ddca3dac661c874ecdbcecb5dd8380cf0f4adbeb8cff90a7de724126750a1f1e5ebd4eb6c59a1315b1 + languageName: node + linkType: hard + "buffer-from@npm:^1.0.0": version: 1.1.2 resolution: "buffer-from@npm:1.1.2" @@ -11255,6 +11289,13 @@ __metadata: languageName: node linkType: hard +"event-target-shim@npm:^5.0.0": + version: 5.0.1 + resolution: "event-target-shim@npm:5.0.1" + checksum: 1ffe3bb22a6d51bdeb6bf6f7cf97d2ff4a74b017ad12284cc9e6a279e727dc30a5de6bb613e5596ff4dc3e517841339ad09a7eec44266eccb1aa201a30448166 + languageName: node + linkType: hard + "eventemitter3@npm:^3.1.0": version: 3.1.2 resolution: "eventemitter3@npm:3.1.2" @@ -17566,6 +17607,13 @@ __metadata: languageName: node linkType: hard +"p-timeout@npm:^4.1.0": + version: 4.1.0 + resolution: "p-timeout@npm:4.1.0" + checksum: 321fec524c23a754e3f1487f2b0a5516fd32aba960d5610490eac56f8a0114b549a93f9919ffc05aa68956dc52e8330e0519f3ddf951d208d19c845f9cd778de + languageName: node + linkType: hard + "p-try@npm:^1.0.0": version: 1.0.0 resolution: "p-try@npm:1.0.0" @@ -20348,6 +20396,15 @@ __metadata: languageName: node linkType: hard +"safe-compare@npm:^1.1.4": + version: 1.1.4 + resolution: "safe-compare@npm:1.1.4" + dependencies: + buffer-alloc: ^1.2.0 + checksum: 8c0a08f7a2bec1b33400e17bb7cce40ebe0e53902dbba13735e0131fa432140650de2ab4750637623e02e28edab10fa439825b57a7e8b705cae1deb80a58e516 + languageName: node + linkType: hard + "safe-regex@npm:^1.1.0": version: 1.1.0 resolution: "safe-regex@npm:1.1.0" @@ -20364,7 +20421,7 @@ __metadata: languageName: node linkType: hard -"sandwich-stream@npm:^2.0.1": +"sandwich-stream@npm:^2.0.1, sandwich-stream@npm:^2.0.2": version: 2.0.2 resolution: "sandwich-stream@npm:2.0.2" checksum: 666d3276e5390786a4ad69f04da472fb69940d1a279a22aebd49ae78ca8f12855503e69d71f77d4b415144a284ffa14efa811a097ad7df501a68c01438b91282 @@ -21797,6 +21854,25 @@ __metadata: languageName: node linkType: hard +"telegraf@npm:^4.6.0": + version: 4.6.0 + resolution: "telegraf@npm:4.6.0" + dependencies: + abort-controller: ^3.0.0 + debug: ^4.3.3 + minimist: ^1.2.5 + module-alias: ^2.2.2 + node-fetch: ^2.6.6 + p-timeout: ^4.1.0 + safe-compare: ^1.1.4 + sandwich-stream: ^2.0.2 + typegram: ^3.7.0 + bin: + telegraf: bin/telegraf + checksum: 62003775f2121116d9117f8a1e2a471cbdb76c513c5f8b541a3de020951990699448a3fbbb366a1457ddefb80e0e36ba4a9cc2d9f1184151f5c5f55282672434 + languageName: node + linkType: hard + "telejson@npm:^5.3.2, telejson@npm:^5.3.3": version: 5.3.3 resolution: "telejson@npm:5.3.3" @@ -22569,6 +22645,13 @@ __metadata: languageName: node linkType: hard +"typegram@npm:^3.7.0": + version: 3.7.0 + resolution: "typegram@npm:3.7.0" + checksum: a88a8f6f0116d49707f51ad6cb9fe594e72c3899b3c17bb77d4e42a4ddae97a10c6b8338fe9c1832aa085c2a04391ce43c3e4dbd5fc7243b1d863a21935e20e0 + languageName: node + linkType: hard + "typescript@npm:>=3.0.0, typescript@npm:^4.5.4": version: 4.5.4 resolution: "typescript@npm:4.5.4"