This commit is contained in:
Lukian LEIZOUR 2022-11-19 01:49:12 +01:00
parent be4fd23bcf
commit 0bd53741af
728 changed files with 86573 additions and 0 deletions

107
node_modules/telegraf/src/button.ts generated vendored Normal file
View file

@ -0,0 +1,107 @@
import { InlineKeyboardButton, KeyboardButton } from './core/types/typegram'
type Hideable<B> = B & { hide: boolean }
export function text(
text: string,
hide = false
): Hideable<KeyboardButton.CommonButton> {
return { text, hide }
}
export function contactRequest(
text: string,
hide = false
): Hideable<KeyboardButton.RequestContactButton> {
return { text, request_contact: true, hide }
}
export function locationRequest(
text: string,
hide = false
): Hideable<KeyboardButton.RequestLocationButton> {
return { text, request_location: true, hide }
}
export function pollRequest(
text: string,
type?: 'quiz' | 'regular',
hide = false
): Hideable<KeyboardButton.RequestPollButton> {
return { text, request_poll: { type }, hide }
}
export function url(
text: string,
url: string,
hide = false
): Hideable<InlineKeyboardButton.UrlButton> {
return { text, url, hide }
}
export function callback(
text: string,
data: string,
hide = false
): Hideable<InlineKeyboardButton.CallbackButton> {
return { text, callback_data: data, hide }
}
export function switchToChat(
text: string,
value: string,
hide = false
): Hideable<InlineKeyboardButton.SwitchInlineButton> {
return { text, switch_inline_query: value, hide }
}
export function switchToCurrentChat(
text: string,
value: string,
hide = false
): Hideable<InlineKeyboardButton.SwitchInlineCurrentChatButton> {
return { text, switch_inline_query_current_chat: value, hide }
}
export function game(
text: string,
hide = false
): Hideable<InlineKeyboardButton.GameButton> {
return { text, callback_game: {}, hide }
}
export function pay(
text: string,
hide = false
): Hideable<InlineKeyboardButton.PayButton> {
return { text, pay: true, hide }
}
export function login(
text: string,
url: string,
opts: {
forward_text?: string
bot_username?: string
request_write_access?: boolean
} = {},
hide = false
): Hideable<InlineKeyboardButton.LoginButton> {
return {
text,
login_url: { ...opts, url },
hide,
}
}
export function webApp(
text: string,
url: string,
hide = false
): Hideable<InlineKeyboardButton.WebAppButton> {
return {
text,
web_app: { url },
hide,
}
}

855
node_modules/telegraf/src/composer.ts generated vendored Normal file
View file

@ -0,0 +1,855 @@
/** @format */
import * as tg from './core/types/typegram'
import * as tt from './telegram-types'
import { Middleware, MiddlewareFn, MiddlewareObj } from './middleware'
import Context from './context'
import { Expand } from './util'
export type MaybeArray<T> = T | T[]
export type MaybePromise<T> = T | Promise<T>
export type NonemptyReadonlyArray<T> = readonly [T, ...T[]]
export type Triggers<C> = MaybeArray<
string | RegExp | ((value: string, ctx: C) => RegExpExecArray | null)
>
export type Predicate<T> = (t: T) => boolean
export type AsyncPredicate<T> = (t: T) => Promise<boolean>
export type MatchedMiddleware<
C extends Context,
T extends tt.UpdateType | tt.MessageSubType = 'message' | 'channel_post'
> = NonemptyReadonlyArray<
Middleware<MatchedContext<C & { match: RegExpExecArray }, T>>
>
/** Takes: a context type and an update type (or message subtype).
Produces: a context that has some properties required, and some undefined.
The required ones are those that are always present when the given update (or message) arrives.
The undefined ones are those that are always absent when the given update (or message) arrives. */
/** @deprecated */
type MatchedContext<
C extends Context,
T extends tt.UpdateType | tt.MessageSubType
> = NarrowedContext<C, tt.MountMap[T]>
/**
* Narrows down `C['update']` (and derived getters)
* to specific update type `U`.
*
* Used by [[`Composer`]],
* possibly useful for splitting a bot into multiple files.
*/
export type NarrowedContext<
C extends Context,
U extends tg.Update
> = Context<U> & Omit<C, keyof Context>
export interface GameQueryUpdate extends tg.Update.CallbackQueryUpdate {
callback_query: Expand<
Omit<tg.CallbackQuery, 'data'> & {
game_short_name: NonNullable<tg.CallbackQuery['game_short_name']>
}
>
}
function always<T>(x: T) {
return () => x
}
const anoop = always(Promise.resolve())
export class Composer<C extends Context> implements MiddlewareObj<C> {
private handler: MiddlewareFn<C>
constructor(...fns: ReadonlyArray<Middleware<C>>) {
this.handler = Composer.compose(fns)
}
/**
* Registers a middleware.
*/
use(...fns: ReadonlyArray<Middleware<C>>) {
this.handler = Composer.compose([this.handler, ...fns])
return this
}
/**
* Registers middleware for handling updates
* matching given type guard function.
*/
guard<U extends tg.Update>(
guardFn: (update: tg.Update) => update is U,
...fns: NonemptyReadonlyArray<Middleware<NarrowedContext<C, U>>>
) {
return this.use(Composer.guard<C, U>(guardFn, ...fns))
}
/**
* Registers middleware for handling provided update types.
*/
on<T extends tt.UpdateType | tt.MessageSubType>(
updateType: MaybeArray<T>,
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, T>>>
) {
return this.use(Composer.on<C, T>(updateType, ...fns))
}
/**
* Registers middleware for handling matching text messages.
*/
hears(triggers: Triggers<C>, ...fns: MatchedMiddleware<C, 'text'>) {
return this.use(Composer.hears<C>(triggers, ...fns))
}
/**
* Registers middleware for handling specified commands.
*/
command(
command: MaybeArray<string>,
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>
) {
return this.use(Composer.command<C>(command, ...fns))
}
/**
* Registers middleware for handling matching callback queries.
*/
action(
triggers: Triggers<C>,
...fns: MatchedMiddleware<C, 'callback_query'>
) {
return this.use(Composer.action<C>(triggers, ...fns))
}
/**
* Registers middleware for handling matching inline queries.
*/
inlineQuery(
triggers: Triggers<C>,
...fns: MatchedMiddleware<C, 'inline_query'>
) {
return this.use(Composer.inlineQuery<C>(triggers, ...fns))
}
/**
* Registers middleware for handling game queries
*/
gameQuery(
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, GameQueryUpdate>>
>
) {
return this.use(Composer.gameQuery(...fns))
}
/**
* Registers middleware for dropping matching updates.
*/
drop(predicate: Predicate<C>) {
return this.use(Composer.drop(predicate))
}
filter(predicate: Predicate<C>) {
return this.use(Composer.filter(predicate))
}
private entity<
T extends 'message' | 'channel_post' | tt.MessageSubType =
| 'message'
| 'channel_post'
>(
predicate:
| MaybeArray<string>
| ((entity: tg.MessageEntity, s: string, ctx: C) => boolean),
...fns: ReadonlyArray<Middleware<MatchedContext<C, T>>>
) {
return this.use(Composer.entity<C, T>(predicate, ...fns))
}
email(email: Triggers<C>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.email<C>(email, ...fns))
}
url(url: Triggers<C>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.url<C>(url, ...fns))
}
textLink(link: Triggers<C>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.textLink<C>(link, ...fns))
}
textMention(mention: Triggers<C>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.textMention<C>(mention, ...fns))
}
mention(mention: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.mention<C>(mention, ...fns))
}
phone(number: Triggers<C>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.phone<C>(number, ...fns))
}
hashtag(hashtag: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.hashtag<C>(hashtag, ...fns))
}
cashtag(cashtag: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.cashtag<C>(cashtag, ...fns))
}
spoiler(text: Triggers<C>, ...fns: MatchedMiddleware<C>) {
return this.use(Composer.spoiler<C>(text, ...fns))
}
/**
* Registers a middleware for handling /start
*/
start(
...fns: NonemptyReadonlyArray<
Middleware<MatchedContext<C, 'text'> & { startPayload: string }>
>
) {
const handler = Composer.compose(fns)
return this.command('start', (ctx, next) => {
// First entity is the /start bot_command itself
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const entity = ctx.message.entities![0]!
const startPayload = ctx.message.text.slice(entity.length + 1)
return handler(Object.assign(ctx, { startPayload }), next)
})
}
/**
* Registers a middleware for handling /help
*/
help(...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>) {
return this.command('help', ...fns)
}
/**
* Registers a middleware for handling /settings
*/
settings(
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>
) {
return this.command('settings', ...fns)
}
middleware() {
return this.handler
}
static reply(...args: Parameters<Context['reply']>): MiddlewareFn<Context> {
return (ctx) => ctx.reply(...args)
}
static catch<C extends Context>(
errorHandler: (err: unknown, ctx: C) => void,
...fns: ReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
const handler = Composer.compose(fns)
// prettier-ignore
return (ctx, next) => Promise.resolve(handler(ctx, next))
.catch((err) => errorHandler(err, ctx))
}
/**
* Generates middleware that runs in the background.
*/
static fork<C extends Context>(middleware: Middleware<C>): MiddlewareFn<C> {
const handler = Composer.unwrap(middleware)
return async (ctx, next) => {
await Promise.all([handler(ctx, anoop), next()])
}
}
static tap<C extends Context>(middleware: Middleware<C>): MiddlewareFn<C> {
const handler = Composer.unwrap(middleware)
return (ctx, next) =>
Promise.resolve(handler(ctx, anoop)).then(() => next())
}
/**
* Generates middleware that gives up control to the next middleware.
*/
static passThru(): MiddlewareFn<Context> {
return (ctx, next) => next()
}
static lazy<C extends Context>(
factoryFn: (ctx: C) => MaybePromise<Middleware<C>>
): MiddlewareFn<C> {
if (typeof factoryFn !== 'function') {
throw new Error('Argument must be a function')
}
return (ctx, next) =>
Promise.resolve(factoryFn(ctx)).then((middleware) =>
Composer.unwrap(middleware)(ctx, next)
)
}
static log(logFn: (s: string) => void = console.log): MiddlewareFn<Context> {
return (ctx, next) => {
logFn(JSON.stringify(ctx.update, null, 2))
return next()
}
}
/**
* @param trueMiddleware middleware to run if the predicate returns true
* @param falseMiddleware middleware to run if the predicate returns false
*/
static branch<C extends Context>(
predicate: Predicate<C> | AsyncPredicate<C>,
trueMiddleware: Middleware<C>,
falseMiddleware: Middleware<C>
): MiddlewareFn<C> {
if (typeof predicate !== 'function') {
return Composer.unwrap(predicate ? trueMiddleware : falseMiddleware)
}
return Composer.lazy<C>((ctx) =>
Promise.resolve(predicate(ctx)).then((value) =>
value ? trueMiddleware : falseMiddleware
)
)
}
/**
* Generates optional middleware.
* @param predicate predicate to decide on a context object whether to run the middleware
* @param middleware middleware to run if the predicate returns true
*/
static optional<C extends Context>(
predicate: Predicate<C> | AsyncPredicate<C>,
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
return Composer.branch(
predicate,
Composer.compose(fns),
Composer.passThru()
)
}
static filter<C extends Context>(predicate: Predicate<C>): MiddlewareFn<C> {
return Composer.branch(predicate, Composer.passThru(), anoop)
}
/**
* Generates middleware for dropping matching updates.
*/
static drop<C extends Context>(predicate: Predicate<C>): MiddlewareFn<C> {
return Composer.branch(predicate, anoop, Composer.passThru())
}
static dispatch<
C extends Context,
Handlers extends Record<string | number | symbol, Middleware<C>>
>(
routeFn: (ctx: C) => MaybePromise<keyof Handlers>,
handlers: Handlers
): Middleware<C> {
return Composer.lazy<C>((ctx) =>
Promise.resolve(routeFn(ctx)).then((value) => handlers[value])
)
}
// EXPLANATION FOR THE ts-expect-error ANNOTATIONS
// The annotations around function invocations with `...fns` are there
// whenever we perform validation logic that the flow analysis of TypeScript
// cannot comprehend. We always make sure that the middleware functions are
// only invoked with properly constrained context objects, but this cannot be
// determined automatically.
/**
* Generates optional middleware based on a predicate that only operates on `ctx.update`.
*
* Example:
* ```ts
* import { Composer, Update } from 'telegraf'
*
* const predicate = (u): u is Update.MessageUpdate => 'message' in u
* const middleware = Composer.guard(predicate, (ctx) => {
* const message = ctx.update.message
* })
* ```
*
* Note that `Composer.on('message')` is preferred over this.
*
* @param guardFn predicate to decide whether to run the middleware based on the `ctx.update` object
* @param fns middleware to run if the predicate returns true
* @see `Composer.optional` for a more generic version of this method that allows the predicate to operate on `ctx` itself
*/
static guard<C extends Context, U extends tg.Update>(
guardFn: (u: tg.Update) => u is U,
...fns: NonemptyReadonlyArray<Middleware<NarrowedContext<C, U>>>
): MiddlewareFn<C> {
return Composer.optional<C>(
(ctx) => guardFn(ctx.update),
// @ts-expect-error see explanation above
...fns
)
}
/**
* Generates middleware for handling provided update types.
* @deprecated use `Composer.on`
*/
static mount<C extends Context, T extends tt.UpdateType | tt.MessageSubType>(
updateType: MaybeArray<T>,
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, T>>>
): MiddlewareFn<C> {
return Composer.on(updateType, ...fns)
}
/**
* Generates middleware for handling provided update types.
*/
static on<C extends Context, T extends tt.UpdateType | tt.MessageSubType>(
updateType: MaybeArray<T>,
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, T>>>
): MiddlewareFn<C> {
const updateTypes = normalizeTextArguments(updateType)
const predicate = (
update: tg.Update
): update is tg.Update & tt.MountMap[T] =>
updateTypes.some(
(type) =>
// Check update type
type in update ||
// Check message sub-type
('message' in update && type in update.message)
)
return Composer.guard<C, tt.MountMap[T]>(predicate, ...fns)
}
private static entity<
C extends Context,
T extends 'message' | 'channel_post' | tt.MessageSubType =
| 'message'
| 'channel_post'
>(
predicate:
| MaybeArray<string>
| ((entity: tg.MessageEntity, s: string, ctx: C) => boolean),
...fns: ReadonlyArray<Middleware<MatchedContext<C, T>>>
): MiddlewareFn<C> {
if (typeof predicate !== 'function') {
const entityTypes = normalizeTextArguments(predicate)
return Composer.entity(({ type }) => entityTypes.includes(type), ...fns)
}
return Composer.optional<C>((ctx) => {
const msg: tg.Message | undefined = ctx.message ?? ctx.channelPost
if (msg === undefined) {
return false
}
const text = getText(msg)
const entities = getEntities(msg)
if (text === undefined) return false
return entities.some((entity) =>
predicate(
entity,
text.substring(entity.offset, entity.offset + entity.length),
ctx
)
)
// @ts-expect-error see explanation above
}, ...fns)
}
static entityText<C extends Context>(
entityType: MaybeArray<string>,
predicate: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
if (fns.length === 0) {
// prettier-ignore
return Array.isArray(predicate)
// @ts-expect-error predicate is really the middleware
? Composer.entity(entityType, ...predicate)
// @ts-expect-error predicate is really the middleware
: Composer.entity(entityType, predicate)
}
const triggers = normalizeTriggers(predicate)
return Composer.entity<C>(({ type }, value, ctx) => {
if (type !== entityType) {
return false
}
for (const trigger of triggers) {
// @ts-expect-error define so far unknown property `match`
if ((ctx.match = trigger(value, ctx))) {
return true
}
}
return false
// @ts-expect-error see explanation above
}, ...fns)
}
static email<C extends Context>(
email: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>('email', email, ...fns)
}
static phone<C extends Context>(
number: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>('phone_number', number, ...fns)
}
static url<C extends Context>(
url: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>('url', url, ...fns)
}
static textLink<C extends Context>(
link: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>('text_link', link, ...fns)
}
static textMention<C extends Context>(
mention: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>('text_mention', mention, ...fns)
}
static mention<C extends Context>(
mention: MaybeArray<string>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>(
'mention',
normalizeTextArguments(mention, '@'),
...fns
)
}
static hashtag<C extends Context>(
hashtag: MaybeArray<string>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>(
'hashtag',
normalizeTextArguments(hashtag, '#'),
...fns
)
}
static cashtag<C extends Context>(
cashtag: MaybeArray<string>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>(
'cashtag',
normalizeTextArguments(cashtag, '$'),
...fns
)
}
static spoiler<C extends Context>(
text: Triggers<C>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>('spoiler', text, ...fns)
}
private static match<
C extends Context,
T extends
| 'message'
| 'channel_post'
| 'callback_query'
| 'inline_query'
| tt.MessageSubType
>(
triggers: ReadonlyArray<(text: string, ctx: C) => RegExpExecArray | null>,
...fns: MatchedMiddleware<C, T>
): MiddlewareFn<MatchedContext<C, T>> {
const handler = Composer.compose(fns)
return (ctx, next) => {
const text =
getText(ctx.message) ??
getText(ctx.channelPost) ??
getText(ctx.callbackQuery) ??
ctx.inlineQuery?.query
if (text === undefined) return next()
for (const trigger of triggers) {
// @ts-expect-error Trust me, TS!
const match = trigger(text, ctx)
if (match) {
// @ts-expect-error define so far unknown property `match`
return handler(Object.assign(ctx, { match }), next)
}
}
return next()
}
}
/**
* Generates middleware for handling matching text messages.
*/
static hears<C extends Context>(
triggers: Triggers<C>,
...fns: MatchedMiddleware<C, 'text'>
): MiddlewareFn<C> {
return Composer.on(
'text',
Composer.match<C, 'text'>(normalizeTriggers(triggers), ...fns)
)
}
/**
* Generates middleware for handling specified commands.
*/
static command<C extends Context>(
command: MaybeArray<string>,
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>
): MiddlewareFn<C> {
if (fns.length === 0) {
// @ts-expect-error command is really the middleware
return Composer.entity('bot_command', command)
}
const commands = normalizeTextArguments(command, '/')
return Composer.on<C, 'text'>(
'text',
Composer.lazy<MatchedContext<C, 'text'>>((ctx) => {
const groupCommands =
ctx.me && ctx.chat?.type.endsWith('group')
? commands.map((command) => `${command}@${ctx.me}`)
: []
return Composer.entity<MatchedContext<C, 'text'>>(
({ offset, type }, value) =>
offset === 0 &&
type === 'bot_command' &&
(commands.includes(value) || groupCommands.includes(value)),
// @ts-expect-error see explanation above
...fns
)
})
)
}
/**
* Generates middleware for handling matching callback queries.
*/
static action<C extends Context>(
triggers: Triggers<C>,
...fns: MatchedMiddleware<C, 'callback_query'>
): MiddlewareFn<C> {
return Composer.on(
'callback_query',
Composer.match<C, 'callback_query'>(normalizeTriggers(triggers), ...fns)
)
}
/**
* Generates middleware for handling matching inline queries.
*/
static inlineQuery<C extends Context>(
triggers: Triggers<C>,
...fns: MatchedMiddleware<C, 'inline_query'>
): MiddlewareFn<C> {
return Composer.on(
'inline_query',
Composer.match<C, 'inline_query'>(normalizeTriggers(triggers), ...fns)
)
}
/**
* Generates middleware responding only to specified users.
*/
static acl<C extends Context>(
userId: MaybeArray<number>,
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
if (typeof userId === 'function') {
return Composer.optional(userId, ...fns)
}
const allowed = Array.isArray(userId) ? userId : [userId]
// prettier-ignore
return Composer.optional((ctx) => !ctx.from || allowed.includes(ctx.from.id), ...fns)
}
private static memberStatus<C extends Context>(
status: MaybeArray<tg.ChatMember['status']>,
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
const statuses = Array.isArray(status) ? status : [status]
return Composer.optional(async (ctx) => {
if (ctx.message === undefined) return false
const member = await ctx.getChatMember(ctx.message.from.id)
return statuses.includes(member.status)
}, ...fns)
}
/**
* Generates middleware responding only to chat admins and chat creator.
*/
static admin<C extends Context>(
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
return Composer.memberStatus(['administrator', 'creator'], ...fns)
}
/**
* Generates middleware responding only to chat creator.
*/
static creator<C extends Context>(
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
return Composer.memberStatus('creator', ...fns)
}
/**
* Generates middleware running only in specified chat types.
*/
static chatType<C extends Context>(
type: MaybeArray<tg.Chat['type']>,
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
const types = Array.isArray(type) ? type : [type]
return Composer.optional((ctx) => {
const chat = ctx.chat
return chat !== undefined && types.includes(chat.type)
}, ...fns)
}
/**
* Generates middleware running only in private chats.
*/
static privateChat<C extends Context>(
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
return Composer.chatType('private', ...fns)
}
/**
* Generates middleware running only in groups and supergroups.
*/
static groupChat<C extends Context>(
...fns: NonemptyReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
return Composer.chatType(['group', 'supergroup'], ...fns)
}
/**
* Generates middleware for handling game queries.
*/
static gameQuery<C extends Context>(
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, GameQueryUpdate>>
>
): MiddlewareFn<C> {
return Composer.guard(
(u): u is GameQueryUpdate =>
'callback_query' in u && 'game_short_name' in u.callback_query,
...fns
)
}
static unwrap<C extends Context>(handler: Middleware<C>): MiddlewareFn<C> {
if (!handler) {
throw new Error('Handler is undefined')
}
return 'middleware' in handler ? handler.middleware() : handler
}
static compose<C extends Context>(
middlewares: ReadonlyArray<Middleware<C>>
): MiddlewareFn<C> {
if (!Array.isArray(middlewares)) {
throw new Error('Middlewares must be an array')
}
if (middlewares.length === 0) {
return Composer.passThru()
}
if (middlewares.length === 1) {
// Quite literally asserted in the above condition
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
return Composer.unwrap(middlewares[0]!)
}
return (ctx, next) => {
let index = -1
return execute(0, ctx)
async function execute(i: number, context: C): Promise<void> {
if (!(context instanceof Context)) {
throw new Error('next(ctx) called with invalid context')
}
if (i <= index) {
throw new Error('next() called multiple times')
}
index = i
const handler = Composer.unwrap(middlewares[i] ?? next)
await handler(context, async (ctx = context) => {
await execute(i + 1, ctx)
})
}
}
}
}
function escapeRegExp(s: string) {
// $& means the whole matched string
return s.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&')
}
function normalizeTriggers<C extends Context>(
triggers: Triggers<C>
): Array<(value: string, ctx: C) => RegExpExecArray | null> {
if (!Array.isArray(triggers)) {
triggers = [triggers]
}
return triggers.map((trigger) => {
if (!trigger) {
throw new Error('Invalid trigger')
}
if (typeof trigger === 'function') {
return trigger
}
if (trigger instanceof RegExp) {
return (value = '') => {
trigger.lastIndex = 0
return trigger.exec(value)
}
}
const regex = new RegExp(`^${escapeRegExp(trigger)}$`)
return (value: string) => regex.exec(value)
})
}
function getEntities(msg: tg.Message | undefined): tg.MessageEntity[] {
if (msg == null) return []
if ('caption_entities' in msg) return msg.caption_entities ?? []
if ('entities' in msg) return msg.entities ?? []
return []
}
function getText(
msg: tg.Message | tg.CallbackQuery | undefined
): string | undefined {
if (msg == null) return undefined
if ('caption' in msg) return msg.caption
if ('text' in msg) return msg.text
if ('data' in msg) return msg.data
if ('game_short_name' in msg) return msg.game_short_name
return undefined
}
function normalizeTextArguments(argument: MaybeArray<string>, prefix = '') {
const args = Array.isArray(argument) ? argument : [argument]
// prettier-ignore
return args
.filter(Boolean)
.map((arg) => prefix && typeof arg === 'string' && !arg.startsWith(prefix) ? `${prefix}${arg}` : arg)
}
export default Composer

1243
node_modules/telegraf/src/context.ts generated vendored Normal file

File diff suppressed because it is too large Load diff

71
node_modules/telegraf/src/core/helpers/check.ts generated vendored Normal file
View file

@ -0,0 +1,71 @@
interface Mapping {
string: string
number: number
bigint: bigint
boolean: boolean
symbol: symbol
undefined: undefined
object: Record<string, unknown>
// eslint-disable-next-line @typescript-eslint/no-explicit-any
function: (...props: any[]) => any
}
/**
* Checks if a given object has a property with a given name.
*
* Example invocation:
* ```js
* let obj = { 'foo': 'bar', 'baz': () => {} }
* hasProp(obj, 'foo') // true
* hasProp(obj, 'baz') // true
* hasProp(obj, 'abc') // false
* ```
*
* @param obj An object to test
* @param prop The name of the property
*/
export function hasProp<O extends object, K extends PropertyKey>(
obj: O | undefined,
prop: K
): obj is O & Record<K, unknown> {
return obj !== undefined && prop in obj
}
/**
* Checks if a given object has a property with a given name.
* Furthermore performs a `typeof` check on the property if it exists.
*
* Example invocation:
* ```js
* let obj = { 'foo': 'bar', 'baz': () => {} }
* hasPropType(obj, 'foo', 'string') // true
* hasPropType(obj, 'baz', 'function') // true
* hasPropType(obj, 'abc', 'number') // false
* ```
*
* @param obj An object to test
* @param prop The name of the property
* @param type The type the property is expected to have
*/
export function hasPropType<
O extends object,
K extends PropertyKey,
T extends keyof Mapping,
V extends Mapping[T]
>(obj: O | undefined, prop: K, type: T): obj is O & Record<K, V> {
return hasProp(obj, prop) && type === typeof obj[prop]
}
/**
* Checks if the supplied array has two dimensions or not.
*
* Example invocations:
* is2D([]) // false
* is2D([[]]) // true
* is2D([[], []]) // true
* is2D([42]) // false
*
* @param arr an array with one or two dimensions
*/
export function is2D<E>(arr: E[] | E[][]): arr is E[][] {
return Array.isArray(arr[0])
}

12
node_modules/telegraf/src/core/helpers/compact.ts generated vendored Normal file
View file

@ -0,0 +1,12 @@
export function compactOptions<T extends { [key: string]: unknown }>(
options?: T
): T | undefined {
if (!options) {
return options
}
const keys = Object.keys(options) as Array<keyof T>
const compactKeys = keys.filter((key) => options[key] !== undefined)
const compactEntries = compactKeys.map((key) => [key, options[key]])
return Object.fromEntries(compactEntries)
}

74
node_modules/telegraf/src/core/helpers/formatting.ts generated vendored Normal file
View file

@ -0,0 +1,74 @@
import { MessageEntity, User } from 'typegram'
export interface FmtString {
text: string
entities?: MessageEntity[]
parse_mode?: undefined
}
export class FmtString implements FmtString {
constructor(public text: string, entities?: MessageEntity[]) {
if (entities) {
this.entities = entities
// force parse_mode to undefined if entities are present
this.parse_mode = undefined
}
}
static normalise(content: string | FmtString) {
if (typeof content === 'string') return new FmtString(content)
return content
}
}
export namespace Types {
// prettier-ignore
export type Containers = 'bold' | 'italic' | 'spoiler' | 'strikethrough' | 'underline'
export type NonContainers = 'code' | 'pre'
export type Text = Containers | NonContainers
}
type TemplateParts = string | TemplateStringsArray | string[]
export function _fmt(
kind: Types.Containers | 'very-plain'
): (parts: TemplateParts, ...items: (string | FmtString)[]) => FmtString
export function _fmt(
kind: Types.NonContainers
): (parts: TemplateParts, ...items: string[]) => FmtString
export function _fmt(
kind: 'pre',
opts: { language: string }
): (parts: TemplateParts, ...items: string[]) => FmtString
export function _fmt(kind: Types.Text | 'very-plain', opts?: object) {
return function fmt(parts: TemplateParts, ...items: (string | FmtString)[]) {
let text = ''
const entities: MessageEntity[] = []
parts = typeof parts === 'string' ? [parts] : parts
for (let i = 0; i < parts.length; i++) {
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
text += parts[i]!
const item = items[i]
if (!item) continue
if (typeof item === 'string') {
text += item
continue
}
for (const child of item.entities || [])
entities.push({ ...child, offset: text.length + child.offset })
text += item.text
}
if (kind !== 'very-plain')
entities.unshift({ type: kind, offset: 0, length: text.length, ...opts })
return new FmtString(text, entities.length ? entities : undefined)
}
}
export const linkOrMention = (
content: string | FmtString,
data:
| { type: 'text_link'; url: string }
| { type: 'text_mention'; user: User }
) => {
const { text, entities = [] } = FmtString.normalise(content)
entities.unshift(Object.assign(data, { offset: 0, length: text.length }))
return new FmtString(text, entities)
}

380
node_modules/telegraf/src/core/network/client.ts generated vendored Normal file
View file

@ -0,0 +1,380 @@
/* eslint @typescript-eslint/restrict-template-expressions: [ "error", { "allowNumber": true, "allowBoolean": true } ] */
import * as crypto from 'crypto'
import * as fs from 'fs'
import * as http from 'http'
import * as https from 'https'
import * as path from 'path'
import fetch, { RequestInfo, RequestInit } from 'node-fetch'
import { hasProp, hasPropType } from '../helpers/check'
import { Opts, Telegram } from '../types/typegram'
import { AbortSignal } from 'abort-controller'
import { compactOptions } from '../helpers/compact'
import MultipartStream from './multipart-stream'
import { ReadStream } from 'fs'
import TelegramError from './error'
import { URL } from 'url'
// eslint-disable-next-line @typescript-eslint/no-var-requires
const debug = require('debug')('telegraf:client')
const { isStream } = MultipartStream
const WEBHOOK_REPLY_METHOD_ALLOWLIST = new Set<keyof Telegram>([
'answerCallbackQuery',
'answerInlineQuery',
'deleteMessage',
'leaveChat',
'sendChatAction',
])
// eslint-disable-next-line @typescript-eslint/no-namespace
namespace ApiClient {
export type Agent = http.Agent | ((parsedUrl: URL) => http.Agent) | undefined
export interface Options {
/**
* Agent for communicating with the bot API.
*/
agent?: http.Agent
/**
* Agent for attaching files via URL.
* 1. Not all agents support both `http:` and `https:`.
* 2. When passing a function, create the agents once, outside of the function.
* Creating new agent every request probably breaks `keepAlive`.
*/
attachmentAgent?: Agent
apiRoot: string
/**
* @default 'bot'
* @see https://github.com/tdlight-team/tdlight-telegram-bot-api#user-mode
*/
apiMode: 'bot' | 'user'
webhookReply: boolean
testEnv: boolean
}
export interface CallApiOptions {
signal?: AbortSignal
}
}
const DEFAULT_EXTENSIONS: Record<string, string | undefined> = {
audio: 'mp3',
photo: 'jpg',
sticker: 'webp',
video: 'mp4',
animation: 'mp4',
video_note: 'mp4',
voice: 'ogg',
}
const DEFAULT_OPTIONS: ApiClient.Options = {
apiRoot: 'https://api.telegram.org',
apiMode: 'bot',
webhookReply: true,
agent: new https.Agent({
keepAlive: true,
keepAliveMsecs: 10000,
}),
attachmentAgent: undefined,
testEnv: false,
}
function includesMedia(payload: Record<string, unknown>) {
return Object.values(payload).some((value) => {
if (Array.isArray(value)) {
return value.some(
({ media }) =>
media && typeof media === 'object' && (media.source || media.url)
)
}
return (
value &&
typeof value === 'object' &&
((hasProp(value, 'source') && value.source) ||
(hasProp(value, 'url') && value.url) ||
(hasPropType(value, 'media', 'object') &&
((hasProp(value.media, 'source') && value.media.source) ||
(hasProp(value.media, 'url') && value.media.url))))
)
})
}
function replacer(_: unknown, value: unknown) {
if (value == null) return undefined
return value
}
function buildJSONConfig(payload: unknown): Promise<RequestInit> {
return Promise.resolve({
method: 'POST',
compress: true,
headers: { 'content-type': 'application/json', connection: 'keep-alive' },
body: JSON.stringify(payload, replacer),
})
}
const FORM_DATA_JSON_FIELDS = [
'results',
'reply_markup',
'mask_position',
'shipping_options',
'errors',
]
async function buildFormDataConfig(
payload: Record<string, unknown>,
agent: ApiClient.Agent
) {
for (const field of FORM_DATA_JSON_FIELDS) {
if (hasProp(payload, field) && typeof payload[field] !== 'string') {
payload[field] = JSON.stringify(payload[field])
}
}
const boundary = crypto.randomBytes(32).toString('hex')
const formData = new MultipartStream(boundary)
const tasks = Object.keys(payload).map((key) =>
attachFormValue(formData, key, payload[key], agent)
)
await Promise.all(tasks)
return {
method: 'POST',
compress: true,
headers: {
'content-type': `multipart/form-data; boundary=${boundary}`,
connection: 'keep-alive',
},
body: formData,
}
}
async function attachFormValue(
form: MultipartStream,
id: string,
value: unknown,
agent: ApiClient.Agent
) {
if (value == null) {
return
}
if (
typeof value === 'string' ||
typeof value === 'boolean' ||
typeof value === 'number'
) {
form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
body: `${value}`,
})
return
}
if (id === 'thumb') {
const attachmentId = crypto.randomBytes(16).toString('hex')
await attachFormMedia(form, value as FormMedia, attachmentId, agent)
return form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
body: `attach://${attachmentId}`,
})
}
if (Array.isArray(value)) {
const items = await Promise.all(
value.map(async (item) => {
if (typeof item.media !== 'object') {
return await Promise.resolve(item)
}
const attachmentId = crypto.randomBytes(16).toString('hex')
await attachFormMedia(form, item.media, attachmentId, agent)
return { ...item, media: `attach://${attachmentId}` }
})
)
return form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
body: JSON.stringify(items),
})
}
if (
value &&
typeof value === 'object' &&
hasProp(value, 'media') &&
hasProp(value, 'type') &&
typeof value.media !== 'undefined' &&
typeof value.type !== 'undefined'
) {
const attachmentId = crypto.randomBytes(16).toString('hex')
await attachFormMedia(form, value.media as FormMedia, attachmentId, agent)
return form.addPart({
headers: { 'content-disposition': `form-data; name="${id}"` },
body: JSON.stringify({
...value,
media: `attach://${attachmentId}`,
}),
})
}
return await attachFormMedia(form, value as FormMedia, id, agent)
}
interface FormMedia {
filename?: string
url?: RequestInfo
source?: string
}
async function attachFormMedia(
form: MultipartStream,
media: FormMedia,
id: string,
agent: ApiClient.Agent
) {
let fileName = media.filename ?? `${id}.${DEFAULT_EXTENSIONS[id] ?? 'dat'}`
if (media.url !== undefined) {
const res = await fetch(media.url, { agent })
return form.addPart({
headers: {
'content-disposition': `form-data; name="${id}"; filename="${fileName}"`,
},
body: res.body,
})
}
if (media.source) {
let mediaSource: string | ReadStream = media.source
if (fs.existsSync(media.source)) {
fileName = media.filename ?? path.basename(media.source)
mediaSource = fs.createReadStream(media.source)
}
if (isStream(mediaSource) || Buffer.isBuffer(mediaSource)) {
form.addPart({
headers: {
'content-disposition': `form-data; name="${id}"; filename="${fileName}"`,
},
body: mediaSource,
})
}
}
}
async function answerToWebhook(
response: Response,
payload: Record<string, unknown>,
options: ApiClient.Options
): Promise<true> {
if (!includesMedia(payload)) {
if (!response.headersSent) {
response.setHeader('content-type', 'application/json')
}
response.end(JSON.stringify(payload), 'utf-8')
return true
}
const { headers, body } = await buildFormDataConfig(
payload,
options.attachmentAgent
)
if (!response.headersSent) {
for (const [key, value] of Object.entries(headers)) {
response.setHeader(key, value)
}
}
await new Promise((resolve) => {
response.on('finish', resolve)
body.pipe(response)
})
return true
}
function redactToken(error: Error): never {
error.message = error.message.replace(
/\/(bot|user)(\d+):[^/]+\//,
'/$1$2:[REDACTED]/'
)
throw error
}
type Response = http.ServerResponse
class ApiClient {
readonly options: ApiClient.Options
constructor(
readonly token: string,
options?: Partial<ApiClient.Options>,
private readonly response?: Response
) {
this.options = {
...DEFAULT_OPTIONS,
...compactOptions(options),
}
if (this.options.apiRoot.startsWith('http://')) {
this.options.agent = undefined
}
}
/**
* If set to `true`, first _eligible_ call will avoid performing a POST request.
* Note that such a call:
* 1. cannot report errors or return meaningful values,
* 2. resolves before bot API has a chance to process it,
* 3. prematurely confirms the update as processed.
*
* https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates
* https://github.com/telegraf/telegraf/pull/1250
*/
set webhookReply(enable: boolean) {
this.options.webhookReply = enable
}
get webhookReply() {
return this.options.webhookReply
}
async callApi<M extends keyof Telegram>(
method: M,
payload: Opts<M>,
{ signal }: ApiClient.CallApiOptions = {}
): Promise<ReturnType<Telegram[M]>> {
const { token, options, response } = this
if (
options.webhookReply &&
response?.writableEnded === false &&
WEBHOOK_REPLY_METHOD_ALLOWLIST.has(method)
) {
debug('Call via webhook', method, payload)
// @ts-expect-error using webhookReply is an optimisation that doesn't respond with normal result
// up to the user to deal with this
return await answerToWebhook(response, { method, ...payload }, options)
}
if (!token) {
throw new TelegramError({
error_code: 401,
description: 'Bot Token is required',
})
}
debug('HTTP call', method, payload)
const config: RequestInit = includesMedia(payload)
? await buildFormDataConfig(
{ method, ...payload },
options.attachmentAgent
)
: await buildJSONConfig(payload)
const apiUrl = new URL(
`./${options.apiMode}${token}${options.testEnv ? '/test' : ''}/${method}`,
options.apiRoot
)
config.agent = options.agent
config.signal = signal
const res = await fetch(apiUrl, config).catch(redactToken)
if (res.status >= 500) {
const errorPayload = {
error_code: res.status,
description: res.statusText,
}
throw new TelegramError(errorPayload, { method, payload })
}
const data = await res.json()
if (!data.ok) {
debug('API call failed', data)
throw new TelegramError(data, { method, payload })
}
return data.result
}
}
export default ApiClient

26
node_modules/telegraf/src/core/network/error.ts generated vendored Normal file
View file

@ -0,0 +1,26 @@
import { ResponseParameters } from '../types/typegram'
interface ErrorPayload {
error_code: number
description: string
parameters?: ResponseParameters
}
export class TelegramError extends Error {
constructor(readonly response: ErrorPayload, readonly on = {}) {
super(`${response.error_code}: ${response.description}`)
}
get code() {
return this.response.error_code
}
get description() {
return this.response.description
}
get parameters() {
return this.response.parameters
}
}
export default TelegramError

View file

@ -0,0 +1,45 @@
import * as stream from 'stream'
import { hasPropType } from '../helpers/check'
import SandwichStream from 'sandwich-stream'
const CRNL = '\r\n'
interface Part {
headers: { [key: string]: string }
body: NodeJS.ReadStream | NodeJS.ReadableStream | string
}
class MultipartStream extends SandwichStream {
constructor(boundary: string) {
super({
head: `--${boundary}${CRNL}`,
tail: `${CRNL}--${boundary}--`,
separator: `${CRNL}--${boundary}${CRNL}`,
})
}
addPart(part: Part) {
const partStream = new stream.PassThrough()
for (const [key, header] of Object.entries(part.headers)) {
partStream.write(`${key}:${header}${CRNL}`)
}
partStream.write(CRNL)
if (MultipartStream.isStream(part.body)) {
part.body.pipe(partStream)
} else {
partStream.end(part.body)
}
this.add(partStream)
}
static isStream(
stream: unknown
): stream is { pipe: MultipartStream['pipe'] } {
return (
typeof stream === 'object' &&
stream !== null &&
hasPropType(stream, 'pipe', 'function')
)
}
}
export default MultipartStream

96
node_modules/telegraf/src/core/network/polling.ts generated vendored Normal file
View file

@ -0,0 +1,96 @@
import * as tg from '../types/typegram'
import * as tt from '../../telegram-types'
import AbortController from 'abort-controller'
import ApiClient from './client'
import d from 'debug'
import { promisify } from 'util'
import { TelegramError } from './error'
const debug = d('telegraf:polling')
const wait = promisify(setTimeout)
function always<T>(x: T) {
return () => x
}
const noop = always(Promise.resolve())
export class Polling {
private readonly abortController = new AbortController()
private skipOffsetSync = false
private offset = 0
constructor(
private readonly telegram: ApiClient,
private readonly allowedUpdates: readonly tt.UpdateType[]
) {}
private async *[Symbol.asyncIterator]() {
debug('Starting long polling')
do {
try {
const updates = await this.telegram.callApi(
'getUpdates',
{
timeout: 50,
offset: this.offset,
allowed_updates: this.allowedUpdates,
},
this.abortController
)
const last = updates[updates.length - 1]
if (last !== undefined) {
this.offset = last.update_id + 1
}
yield updates
} catch (error) {
const err = error as Error & {
parameters?: { retry_after: number }
}
if (err.name === 'AbortError') return
if (
err.name === 'FetchError' ||
(err instanceof TelegramError && err.code === 429) ||
(err instanceof TelegramError && err.code >= 500)
) {
const retryAfter: number = err.parameters?.retry_after ?? 5
debug('Failed to fetch updates, retrying after %ds.', retryAfter, err)
await wait(retryAfter * 1000)
continue
}
if (
err instanceof TelegramError &&
// Unauthorized Conflict
(err.code === 401 || err.code === 409)
) {
this.skipOffsetSync = true
throw err
}
throw err
}
} while (!this.abortController.signal.aborted)
}
private async syncUpdateOffset() {
if (this.skipOffsetSync) return
debug('Syncing update offset...')
await this.telegram.callApi('getUpdates', { offset: this.offset, limit: 1 })
}
async loop(handleUpdate: (updates: tg.Update) => Promise<void>) {
if (this.abortController.signal.aborted) {
throw new Error('Polling instances must not be reused!')
}
try {
for await (const updates of this) {
await Promise.all(updates.map(handleUpdate))
}
} finally {
debug('Long polling stopped')
// prevent instance reuse
this.stop()
await this.syncUpdateOffset().catch(noop)
}
}
stop() {
this.abortController.abort()
}
}

58
node_modules/telegraf/src/core/network/webhook.ts generated vendored Normal file
View file

@ -0,0 +1,58 @@
import * as http from 'http'
import d from 'debug'
import { type Update } from '../types/typegram'
const debug = d('telegraf:webhook')
export default function generateWebhook(
filter: (req: http.IncomingMessage) => boolean,
updateHandler: (update: Update, res: http.ServerResponse) => Promise<void>
) {
return async (
req: http.IncomingMessage & { body?: Update },
res: http.ServerResponse,
next = (): void => {
res.statusCode = 403
debug('Replying with status code', res.statusCode)
res.end()
}
): Promise<void> => {
debug('Incoming request', req.method, req.url)
if (!filter(req)) {
debug('Webhook filter failed', req.method, req.url)
return next()
}
let update: Update
try {
if (req.body != null) {
/* If req.body is already set, we expect it to be the parsed
request body (update object) received from Telegram
However, some libraries such as `serverless-http` set req.body to the
raw buffer, so we'll handle that additionally */
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let body: any = req.body
// if body is Buffer, parse it into string
if (body instanceof Buffer) body = String(req.body)
// if body is string, parse it into object
if (typeof body === 'string') body = JSON.parse(body)
update = body
} else {
let body = ''
// parse each buffer to string and append to body
for await (const chunk of req) body += String(chunk)
// parse body to object
update = JSON.parse(body)
}
} catch (error: unknown) {
// if any of the parsing steps fails, give up and respond with error
res.writeHead(415).end()
debug('Failed to parse request body:', error)
return
}
return await updateHandler(update, res)
}
}

55
node_modules/telegraf/src/core/types/typegram.ts generated vendored Normal file
View file

@ -0,0 +1,55 @@
import { Typegram } from 'typegram'
// internal type provisions
export * from 'typegram/api'
export * from 'typegram/markup'
export * from 'typegram/menu-button'
export * from 'typegram/inline'
export * from 'typegram/manage'
export * from 'typegram/message'
export * from 'typegram/passport'
export * from 'typegram/payment'
export * from 'typegram/update'
// telegraf input file definition
interface InputFileByPath {
source: string
filename?: string
}
interface InputFileByReadableStream {
source: NodeJS.ReadableStream
filename?: string
}
interface InputFileByBuffer {
source: Buffer
filename?: string
}
interface InputFileByURL {
url: string
filename?: string
}
export type InputFile =
| InputFileByPath
| InputFileByReadableStream
| InputFileByBuffer
| InputFileByURL
// typegram proxy type setup
type TelegrafTypegram = Typegram<InputFile>
export type Telegram = TelegrafTypegram['Telegram']
export type Opts<M extends keyof Telegram> = TelegrafTypegram['Opts'][M]
export type InputMedia = TelegrafTypegram['InputMedia']
export type InputMediaPhoto = TelegrafTypegram['InputMediaPhoto']
export type InputMediaVideo = TelegrafTypegram['InputMediaVideo']
export type InputMediaAnimation = TelegrafTypegram['InputMediaAnimation']
export type InputMediaAudio = TelegrafTypegram['InputMediaAudio']
export type InputMediaDocument = TelegrafTypegram['InputMediaDocument']
// tiny helper types
export type ChatAction = Opts<'sendChatAction'>['action']
/**
* Sending video notes by a URL is currently unsupported
*/
export type InputFileVideoNote = Exclude<InputFile, InputFileByURL>

26
node_modules/telegraf/src/deunionize.ts generated vendored Normal file
View file

@ -0,0 +1,26 @@
export type PropOr<
T extends object | undefined,
P extends string | symbol | number,
D = undefined
> = T extends Partial<Record<P, unknown>> ? T[P] : D
export type UnionKeys<T> = T extends unknown ? keyof T : never
type AddOptionalKeys<K extends PropertyKey> = { readonly [P in K]?: undefined }
/**
* @see https://millsp.github.io/ts-toolbelt/modules/union_strict.html
*/
export type Deunionize<
B extends object | undefined,
T extends B = B
> = T extends object ? T & AddOptionalKeys<Exclude<UnionKeys<B>, keyof T>> : T
/**
* Expose properties from all union variants.
* @see https://github.com/telegraf/telegraf/issues/1388#issuecomment-791573609
* @see https://millsp.github.io/ts-toolbelt/modules/union_strict.html
*/
export function deunionize<T extends object | undefined>(t: T) {
return t as Deunionize<T>
}

21
node_modules/telegraf/src/format.ts generated vendored Normal file
View file

@ -0,0 +1,21 @@
import { User } from 'typegram'
import { FmtString, _fmt, linkOrMention } from './core/helpers/formatting'
export { FmtString }
export const fmt = _fmt('very-plain')
export const bold = _fmt('bold')
export const italic = _fmt('italic')
export const spoiler = _fmt('spoiler')
export const strikethrough = _fmt('strikethrough')
export const underline = _fmt('underline')
export const code = _fmt('code')
export const pre = (language: string) => _fmt('pre', { language })
export const link = (content: string | FmtString, url: string) =>
linkOrMention(content, { type: 'text_link', url })
export const mention = (name: string | FmtString, user: number | User) =>
typeof user === 'number'
? link(name, 'tg://user?id=' + user)
: linkOrMention(name, { type: 'text_mention', user })

207
node_modules/telegraf/src/future.ts generated vendored Normal file
View file

@ -0,0 +1,207 @@
import Context from './context'
import { Middleware } from './middleware'
type ReplyContext = { [key in keyof Context & `reply${string}`]: Context[key] }
function makeReply<
C extends Context,
E extends { reply_to_message_id?: number }
>(ctx: C, extra?: E) {
const reply_to_message_id = ctx.message?.message_id
return { reply_to_message_id, ...extra }
}
const replyContext: ReplyContext = {
replyWithChatAction: function () {
throw new TypeError(
'ctx.replyWithChatAction has been removed, use ctx.sendChatAction instead'
)
},
reply(this: Context, text, extra) {
this.assert(this.chat, 'reply')
return this.telegram.sendMessage(this.chat.id, text, makeReply(this, extra))
},
replyWithAnimation(this: Context, animation, extra) {
this.assert(this.chat, 'replyWithAnimation')
return this.telegram.sendAnimation(
this.chat.id,
animation,
makeReply(this, extra)
)
},
replyWithAudio(this: Context, audio, extra) {
this.assert(this.chat, 'replyWithAudio')
return this.telegram.sendAudio(this.chat.id, audio, makeReply(this, extra))
},
replyWithContact(this: Context, phoneNumber, firstName, extra) {
this.assert(this.chat, 'replyWithContact')
return this.telegram.sendContact(
this.chat.id,
phoneNumber,
firstName,
makeReply(this, extra)
)
},
replyWithDice(this: Context, extra) {
this.assert(this.chat, 'replyWithDice')
return this.telegram.sendDice(this.chat.id, makeReply(this, extra))
},
replyWithDocument(this: Context, document, extra) {
this.assert(this.chat, 'replyWithDocument')
return this.telegram.sendDocument(
this.chat.id,
document,
makeReply(this, extra)
)
},
replyWithGame(this: Context, gameName, extra) {
this.assert(this.chat, 'replyWithGame')
return this.telegram.sendGame(
this.chat.id,
gameName,
makeReply(this, extra)
)
},
replyWithHTML(this: Context, html, extra) {
this.assert(this.chat, 'replyWithHTML')
return this.telegram.sendMessage(this.chat.id, html, {
parse_mode: 'HTML',
reply_to_message_id: this.message?.message_id,
...extra,
})
},
replyWithInvoice(this: Context, invoice, extra) {
this.assert(this.chat, 'replyWithInvoice')
return this.telegram.sendInvoice(
this.chat.id,
invoice,
makeReply(this, extra)
)
},
replyWithLocation(this: Context, latitude, longitude, extra) {
this.assert(this.chat, 'replyWithLocation')
return this.telegram.sendLocation(
this.chat.id,
latitude,
longitude,
makeReply(this, extra)
)
},
replyWithMarkdown(this: Context, markdown, extra) {
this.assert(this.chat, 'replyWithMarkdown')
return this.telegram.sendMessage(this.chat.id, markdown, {
parse_mode: 'Markdown',
reply_to_message_id: this.message?.message_id,
...extra,
})
},
replyWithMarkdownV2(this: Context, markdown, extra) {
this.assert(this.chat, 'replyWithMarkdownV2')
return this.telegram.sendMessage(this.chat.id, markdown, {
parse_mode: 'MarkdownV2',
reply_to_message_id: this.message?.message_id,
...extra,
})
},
replyWithMediaGroup(this: Context, media, extra) {
this.assert(this.chat, 'replyWithMediaGroup')
return this.telegram.sendMediaGroup(
this.chat.id,
media,
makeReply(this, extra)
)
},
replyWithPhoto(this: Context, photo, extra) {
this.assert(this.chat, 'replyWithPhoto')
return this.telegram.sendPhoto(this.chat.id, photo, makeReply(this, extra))
},
replyWithPoll(this: Context, question, options, extra) {
this.assert(this.chat, 'replyWithPoll')
return this.telegram.sendPoll(
this.chat.id,
question,
options,
makeReply(this, extra)
)
},
replyWithQuiz(this: Context, question, options, extra) {
this.assert(this.chat, 'replyWithQuiz')
return this.telegram.sendQuiz(
this.chat.id,
question,
options,
makeReply(this, extra)
)
},
replyWithSticker(this: Context, sticker, extra) {
this.assert(this.chat, 'replyWithSticker')
return this.telegram.sendSticker(
this.chat.id,
sticker,
makeReply(this, extra)
)
},
replyWithVenue(this: Context, latitude, longitude, title, address, extra) {
this.assert(this.chat, 'replyWithVenue')
return this.telegram.sendVenue(
this.chat.id,
latitude,
longitude,
title,
address,
makeReply(this, extra)
)
},
replyWithVideo(this: Context, video, extra) {
this.assert(this.chat, 'replyWithVideo')
return this.telegram.sendVideo(this.chat.id, video, makeReply(this, extra))
},
replyWithVideoNote(this: Context, videoNote, extra) {
this.assert(this.chat, 'replyWithVideoNote')
return this.telegram.sendVideoNote(
this.chat.id,
videoNote,
makeReply(this, extra)
)
},
replyWithVoice(this: Context, voice, extra) {
this.assert(this.chat, 'replyWithVoice')
return this.telegram.sendVoice(this.chat.id, voice, makeReply(this, extra))
},
}
/**
* Sets up Context to use the new reply methods.
* This middleware makes `ctx.reply()` and `ctx.replyWith*()` methods will actually reply to the message they are replying to.
* Use `ctx.sendMessage()` to send a message in chat without replying to it.
*
* If the message to reply is deleted, `reply()` will send a normal message.
* If the update is not a message and we are unable to reply, `reply()` will send a normal message.
*/
export function useNewReplies<C extends Context>(): Middleware<C> {
return (ctx, next) => {
ctx.reply = replyContext.reply
ctx.replyWithPhoto = replyContext.replyWithPhoto
ctx.replyWithMediaGroup = replyContext.replyWithMediaGroup
ctx.replyWithAudio = replyContext.replyWithAudio
ctx.replyWithDice = replyContext.replyWithDice
ctx.replyWithDocument = replyContext.replyWithDocument
ctx.replyWithSticker = replyContext.replyWithSticker
ctx.replyWithVideo = replyContext.replyWithVideo
ctx.replyWithAnimation = replyContext.replyWithAnimation
ctx.replyWithVideoNote = replyContext.replyWithVideoNote
ctx.replyWithInvoice = replyContext.replyWithInvoice
ctx.replyWithGame = replyContext.replyWithGame
ctx.replyWithVoice = replyContext.replyWithVoice
ctx.replyWithPoll = replyContext.replyWithPoll
ctx.replyWithQuiz = replyContext.replyWithQuiz
ctx.replyWithChatAction = replyContext.replyWithChatAction
ctx.replyWithLocation = replyContext.replyWithLocation
ctx.replyWithVenue = replyContext.replyWithVenue
ctx.replyWithContact = replyContext.replyWithContact
ctx.replyWithMarkdown = replyContext.replyWithMarkdown
ctx.replyWithMarkdownV2 = replyContext.replyWithMarkdownV2
ctx.replyWithHTML = replyContext.replyWithHTML
return next()
}
}

17
node_modules/telegraf/src/index.ts generated vendored Normal file
View file

@ -0,0 +1,17 @@
export { Telegraf } from './telegraf'
export { Context } from './context'
export { Composer, NarrowedContext } from './composer'
export { Middleware, MiddlewareFn, MiddlewareObj } from './middleware'
export { Router } from './router'
export { TelegramError } from './core/network/error'
export { Telegram } from './telegram'
export * as Types from './telegram-types'
export * as Markup from './markup'
export * as Input from './input'
export * as Format from './format'
export { deunionize } from './deunionize'
export { session, MemorySessionStore } from './session'
export * as Scenes from './scenes'

59
node_modules/telegraf/src/input.ts generated vendored Normal file
View file

@ -0,0 +1,59 @@
import { InputFile } from './core/types/typegram'
/**
* The local file specified by path will be uploaded to Telegram using multipart/form-data.
*
* 10 MB max size for photos, 50 MB for other files.
*/
// prettier-ignore
export const fromLocalFile = (path: string, filename?: string): InputFile => ({ source: path, filename })
/**
* The buffer will be uploaded as file to Telegram using multipart/form-data.
*
* 10 MB max size for photos, 50 MB for other files.
*/
// prettier-ignore
export const fromBuffer = (buffer: Buffer, filename?: string): InputFile => ({ source: buffer, filename })
/**
* Contents of the stream will be uploaded as file to Telegram using multipart/form-data.
*
* 10 MB max size for photos, 50 MB for other files.
*/
// prettier-ignore
export const fromReadableStream = (stream: NodeJS.ReadableStream, filename?: string): InputFile => ({ source: stream, filename })
/**
* Contents of the URL will be streamed to Telegram.
*
* 10 MB max size for photos, 50 MB for other files.
*/
// prettier-ignore
export const fromURLStream = (url: string | URL, filename?: string): InputFile => ({ url: url.toString(), filename })
/**
* Provide Telegram with an HTTP URL for the file to be sent.
* Telegram will download and send the file.
*
* * The target file must have the correct MIME type (e.g., audio/mpeg for `sendAudio`, etc.).
* * `sendDocument` with URL will currently only work for GIF, PDF and ZIP files.
* * To use `sendVoice`, the file must have the type audio/ogg and be no more than 1MB in size.
* 1-20MB voice notes will be sent as files.
*
* 5 MB max size for photos and 20 MB max for other types of content.
*/
export const fromURL = (url: string | URL): string => url.toString()
/**
* If the file is already stored somewhere on the Telegram servers, you don't need to reupload it:
* each file object has a file_id field, simply pass this file_id as a parameter instead of uploading.
*
* It is not possible to change the file type when resending by file_id.
*
* It is not possible to resend thumbnails using file_id.
* They have to be uploaded using one of the other Input methods.
*
* There are no limits for files sent this way.
*/
export const fromFileId = (fileId: string): string => fileId

135
node_modules/telegraf/src/markup.ts generated vendored Normal file
View file

@ -0,0 +1,135 @@
import {
ForceReply,
InlineKeyboardButton,
InlineKeyboardMarkup,
KeyboardButton,
ReplyKeyboardMarkup,
ReplyKeyboardRemove,
} from './core/types/typegram'
import { is2D } from './core/helpers/check'
type Hideable<B> = B & { hide?: boolean }
type HideableKBtn = Hideable<KeyboardButton>
type HideableIKBtn = Hideable<InlineKeyboardButton>
export class Markup<
T extends
| InlineKeyboardMarkup
| ReplyKeyboardMarkup
| ReplyKeyboardRemove
| ForceReply
> {
constructor(readonly reply_markup: T) {}
selective<T extends ForceReply | ReplyKeyboardMarkup>(
this: Markup<T>,
value = true
) {
return new Markup<T>({ ...this.reply_markup, selective: value })
}
placeholder<T extends ForceReply | ReplyKeyboardMarkup>(
this: Markup<T>,
placeholder: string
) {
return new Markup<T>({
...this.reply_markup,
input_field_placeholder: placeholder,
})
}
resize(this: Markup<ReplyKeyboardMarkup>, value = true) {
return new Markup<ReplyKeyboardMarkup>({
...this.reply_markup,
resize_keyboard: value,
})
}
oneTime(this: Markup<ReplyKeyboardMarkup>, value = true) {
return new Markup<ReplyKeyboardMarkup>({
...this.reply_markup,
one_time_keyboard: value,
})
}
}
export * as button from './button'
export function removeKeyboard(): Markup<ReplyKeyboardRemove> {
return new Markup<ReplyKeyboardRemove>({ remove_keyboard: true })
}
export function forceReply(): Markup<ForceReply> {
return new Markup<ForceReply>({ force_reply: true })
}
export function keyboard(buttons: HideableKBtn[][]): Markup<ReplyKeyboardMarkup>
export function keyboard(
buttons: HideableKBtn[],
options?: Partial<KeyboardBuildingOptions<HideableKBtn>>
): Markup<ReplyKeyboardMarkup>
export function keyboard(
buttons: HideableKBtn[] | HideableKBtn[][],
options?: Partial<KeyboardBuildingOptions<HideableKBtn>>
): Markup<ReplyKeyboardMarkup> {
const keyboard = buildKeyboard(buttons, {
columns: 1,
...options,
})
return new Markup<ReplyKeyboardMarkup>({ keyboard })
}
export function inlineKeyboard(
buttons: HideableIKBtn[][]
): Markup<InlineKeyboardMarkup>
export function inlineKeyboard(
buttons: HideableIKBtn[],
options?: Partial<KeyboardBuildingOptions<HideableIKBtn>>
): Markup<InlineKeyboardMarkup>
export function inlineKeyboard(
buttons: HideableIKBtn[] | HideableIKBtn[][],
options?: Partial<KeyboardBuildingOptions<HideableIKBtn>>
): Markup<InlineKeyboardMarkup> {
const inlineKeyboard = buildKeyboard(buttons, {
columns: buttons.length,
...options,
})
return new Markup<InlineKeyboardMarkup>({ inline_keyboard: inlineKeyboard })
}
interface KeyboardBuildingOptions<B extends HideableKBtn | HideableIKBtn> {
wrap?: (btn: B, index: number, currentRow: B[]) => boolean
columns: number
}
function buildKeyboard<B extends HideableKBtn | HideableIKBtn>(
buttons: B[] | B[][],
options: KeyboardBuildingOptions<B>
): B[][] {
const result: B[][] = []
if (!Array.isArray(buttons)) {
return result
}
if (is2D(buttons)) {
return buttons.map((row) => row.filter((button) => !button.hide))
}
const wrapFn =
options.wrap !== undefined
? options.wrap
: (_btn: B, _index: number, currentRow: B[]) =>
currentRow.length >= options.columns
let currentRow: B[] = []
let index = 0
for (const btn of buttons.filter((button) => !button.hide)) {
if (wrapFn(btn, index, currentRow) && currentRow.length > 0) {
result.push(currentRow)
currentRow = []
}
currentRow.push(btn)
index++
}
if (currentRow.length > 0) {
result.push(currentRow)
}
return result
}

18
node_modules/telegraf/src/middleware.ts generated vendored Normal file
View file

@ -0,0 +1,18 @@
import { Context } from './context'
/*
next's parameter is in a contravariant position, and thus, trying to type it
prevents assigning `MiddlewareFn<ContextMessageUpdate>`
to `MiddlewareFn<CustomContext>`.
Middleware passing the parameter should be a separate type instead.
*/
export type MiddlewareFn<C extends Context> = (
ctx: C,
next: () => Promise<void>
) => Promise<unknown> | void
export interface MiddlewareObj<C extends Context> {
middleware: () => MiddlewareFn<C>
}
export type Middleware<C extends Context> = MiddlewareFn<C> | MiddlewareObj<C>

55
node_modules/telegraf/src/router.ts generated vendored Normal file
View file

@ -0,0 +1,55 @@
/** @format */
import { Middleware, MiddlewareObj } from './middleware'
import Composer from './composer'
import Context from './context'
type NonemptyReadonlyArray<T> = readonly [T, ...T[]]
type RouteFn<TContext extends Context> = (ctx: TContext) => {
route: string
context?: Partial<TContext>
state?: Partial<TContext['state']>
} | null
/** @deprecated in favor of {@link Composer.dispatch} */
export class Router<C extends Context> implements MiddlewareObj<C> {
private otherwiseHandler: Middleware<C> = Composer.passThru()
constructor(
private readonly routeFn: RouteFn<C>,
public handlers = new Map<string, Middleware<C>>()
) {
if (typeof routeFn !== 'function') {
throw new Error('Missing routing function')
}
}
on(route: string, ...fns: NonemptyReadonlyArray<Middleware<C>>) {
if (fns.length === 0) {
throw new TypeError('At least one handler must be provided')
}
this.handlers.set(route, Composer.compose(fns))
return this
}
otherwise(...fns: NonemptyReadonlyArray<Middleware<C>>) {
if (fns.length === 0) {
throw new TypeError('At least one otherwise handler must be provided')
}
this.otherwiseHandler = Composer.compose(fns)
return this
}
middleware() {
return Composer.lazy<C>((ctx) => {
const result = this.routeFn(ctx)
if (result == null) {
return this.otherwiseHandler
}
Object.assign(ctx, result.context)
Object.assign(ctx.state, result.state)
return this.handlers.get(result.route) ?? this.otherwiseHandler
})
}
}

52
node_modules/telegraf/src/scenes/base.ts generated vendored Normal file
View file

@ -0,0 +1,52 @@
import { Middleware, MiddlewareFn } from '../middleware'
import Composer from '../composer'
import Context from '../context'
const { compose } = Composer
export interface SceneOptions<C extends Context> {
ttl?: number
handlers: ReadonlyArray<MiddlewareFn<C>>
enterHandlers: ReadonlyArray<MiddlewareFn<C>>
leaveHandlers: ReadonlyArray<MiddlewareFn<C>>
}
export class BaseScene<C extends Context = Context> extends Composer<C> {
id: string
ttl?: number
enterHandler: MiddlewareFn<C>
leaveHandler: MiddlewareFn<C>
constructor(id: string, options?: SceneOptions<C>) {
const opts: SceneOptions<C> = {
handlers: [],
enterHandlers: [],
leaveHandlers: [],
...options,
}
super(...opts.handlers)
this.id = id
this.ttl = opts.ttl
this.enterHandler = compose(opts.enterHandlers)
this.leaveHandler = compose(opts.leaveHandlers)
}
enter(...fns: Array<Middleware<C>>) {
this.enterHandler = compose([this.enterHandler, ...fns])
return this
}
leave(...fns: Array<Middleware<C>>) {
this.leaveHandler = compose([this.leaveHandler, ...fns])
return this
}
enterMiddleware() {
return this.enterHandler
}
leaveMiddleware() {
return this.leaveHandler
}
}
export default BaseScene

136
node_modules/telegraf/src/scenes/context.ts generated vendored Normal file
View file

@ -0,0 +1,136 @@
import BaseScene from './base'
import Composer from '../composer'
import Context from '../context'
import d from 'debug'
import { SessionContext } from '../session'
const debug = d('telegraf:scenes:context')
const noop = () => Promise.resolve()
const now = () => Math.floor(Date.now() / 1000)
export interface SceneContext<D extends SceneSessionData = SceneSessionData>
extends Context {
session: SceneSession<D>
scene: SceneContextScene<SceneContext<D>, D>
}
export interface SceneSessionData {
current?: string
expires?: number
state?: object
}
export interface SceneSession<S extends SceneSessionData = SceneSessionData> {
__scenes: S
}
export interface SceneContextSceneOptions<D extends SceneSessionData> {
ttl?: number
default?: string
defaultSession: D
}
export default class SceneContextScene<
C extends SessionContext<SceneSession<D>>,
D extends SceneSessionData = SceneSessionData
> {
private readonly options: SceneContextSceneOptions<D>
constructor(
private readonly ctx: C,
private readonly scenes: Map<string, BaseScene<C>>,
options: Partial<SceneContextSceneOptions<D>>
) {
// @ts-expect-error {} might not be assignable to D
const fallbackSessionDefault: D = {}
this.options = { defaultSession: fallbackSessionDefault, ...options }
}
get session(): D {
const defaultSession = this.options.defaultSession
let session = this.ctx.session?.__scenes ?? defaultSession
if (session.expires !== undefined && session.expires < now()) {
session = defaultSession
}
if (this.ctx.session === undefined) {
this.ctx.session = { __scenes: session }
} else {
this.ctx.session.__scenes = session
}
return session
}
get state() {
return (this.session.state ??= {})
}
set state(value) {
this.session.state = { ...value }
}
get current() {
const sceneId = this.session.current ?? this.options.default
return sceneId === undefined || !this.scenes.has(sceneId)
? undefined
: this.scenes.get(sceneId)
}
reset() {
if (this.ctx.session !== undefined)
this.ctx.session.__scenes = this.options.defaultSession
}
async enter(sceneId: string, initialState: object = {}, silent = false) {
if (!this.scenes.has(sceneId)) {
throw new Error(`Can't find scene: ${sceneId}`)
}
if (!silent) {
await this.leave()
}
debug('Entering scene', sceneId, initialState, silent)
this.session.current = sceneId
this.state = initialState
const ttl = this.current?.ttl ?? this.options.ttl
if (ttl !== undefined) {
this.session.expires = now() + ttl
}
if (this.current === undefined || silent) {
return
}
const handler =
'enterMiddleware' in this.current &&
typeof this.current.enterMiddleware === 'function'
? this.current.enterMiddleware()
: this.current.middleware()
return await handler(this.ctx, noop)
}
reenter() {
return this.session.current === undefined
? undefined
: this.enter(this.session.current, this.state)
}
private leaving = false
async leave() {
if (this.leaving) return
debug('Leaving scene')
try {
this.leaving = true
if (this.current === undefined) {
return
}
const handler =
'leaveMiddleware' in this.current &&
typeof this.current.leaveMiddleware === 'function'
? this.current.leaveMiddleware()
: Composer.passThru()
await handler(this.ctx, noop)
return this.reset()
} finally {
this.leaving = false
}
}
}

21
node_modules/telegraf/src/scenes/index.ts generated vendored Normal file
View file

@ -0,0 +1,21 @@
/**
* @see https://github.com/telegraf/telegraf/issues/705#issuecomment-549056045
* @see https://www.npmjs.com/package/telegraf-stateless-question
* @packageDocumentation
*/
export { Stage } from './stage'
export {
SceneContext,
SceneSession,
default as SceneContextScene,
SceneSessionData,
} from './context'
export { BaseScene } from './base'
export { WizardScene } from './wizard'
export {
WizardContext,
WizardSession,
default as WizardContextWizard,
WizardSessionData,
} from './wizard/context'

71
node_modules/telegraf/src/scenes/stage.ts generated vendored Normal file
View file

@ -0,0 +1,71 @@
import { isSessionContext, SessionContext } from '../session'
import SceneContextScene, {
SceneContextSceneOptions,
SceneSession,
SceneSessionData,
} from './context'
import { BaseScene } from './base'
import { Composer } from '../composer'
import { Context } from '../context'
export class Stage<
C extends SessionContext<SceneSession<D>> & {
scene: SceneContextScene<C, D>
},
D extends SceneSessionData = SceneSessionData
> extends Composer<C> {
options: Partial<SceneContextSceneOptions<D>>
scenes: Map<string, BaseScene<C>>
constructor(
scenes: ReadonlyArray<BaseScene<C>> = [],
options?: Partial<SceneContextSceneOptions<D>>
) {
super()
this.options = { ...options }
this.scenes = new Map<string, BaseScene<C>>()
scenes.forEach((scene) => this.register(scene))
}
register(...scenes: ReadonlyArray<BaseScene<C>>) {
scenes.forEach((scene) => {
if (scene?.id == null || typeof scene.middleware !== 'function') {
throw new Error('telegraf: Unsupported scene')
}
this.scenes.set(scene.id, scene)
})
return this
}
middleware() {
const handler = Composer.compose<C>([
(ctx, next) => {
const scenes: Map<string, BaseScene<C>> = this.scenes
const scene = new SceneContextScene<C, D>(ctx, scenes, this.options)
ctx.scene = scene
return next()
},
super.middleware(),
Composer.lazy<C>((ctx) => ctx.scene.current ?? Composer.passThru()),
])
return Composer.optional(isSessionContext, handler)
}
static enter<C extends Context & { scene: SceneContextScene<C> }>(
...args: Parameters<SceneContextScene<C>['enter']>
) {
return (ctx: C) => ctx.scene.enter(...args)
}
static reenter<C extends Context & { scene: SceneContextScene<C> }>(
...args: Parameters<SceneContextScene<C>['reenter']>
) {
return (ctx: C) => ctx.scene.reenter(...args)
}
static leave<C extends Context & { scene: SceneContextScene<C> }>(
...args: Parameters<SceneContextScene<C>['leave']>
) {
return (ctx: C) => ctx.scene.leave(...args)
}
}

58
node_modules/telegraf/src/scenes/wizard/context.ts generated vendored Normal file
View file

@ -0,0 +1,58 @@
import SceneContextScene, { SceneSession, SceneSessionData } from '../context'
import Context from '../../context'
import { Middleware } from '../../middleware'
import { SessionContext } from '../../session'
export interface WizardContext<D extends WizardSessionData = WizardSessionData>
extends Context {
session: WizardSession<D>
scene: SceneContextScene<WizardContext<D>, D>
wizard: WizardContextWizard<WizardContext<D>>
}
export interface WizardSessionData extends SceneSessionData {
cursor: number
}
export interface WizardSession<S extends WizardSessionData = WizardSessionData>
extends SceneSession<S> {}
export default class WizardContextWizard<
C extends SessionContext<WizardSession> & {
scene: SceneContextScene<C, WizardSessionData>
}
> {
readonly state: object
constructor(
private readonly ctx: C,
private readonly steps: ReadonlyArray<Middleware<C>>
) {
this.state = ctx.scene.state
this.cursor = ctx.scene.session.cursor ?? 0
}
get step() {
return this.steps[this.cursor]
}
get cursor() {
return this.ctx.scene.session.cursor
}
set cursor(cursor: number) {
this.ctx.scene.session.cursor = cursor
}
selectStep(index: number) {
this.cursor = index
return this
}
next() {
return this.selectStep(this.cursor + 1)
}
back() {
return this.selectStep(this.cursor - 1)
}
}

63
node_modules/telegraf/src/scenes/wizard/index.ts generated vendored Normal file
View file

@ -0,0 +1,63 @@
import BaseScene, { SceneOptions } from '../base'
import { Middleware, MiddlewareObj } from '../../middleware'
import WizardContextWizard, { WizardSessionData } from './context'
import Composer from '../../composer'
import Context from '../../context'
import SceneContextScene from '../context'
export class WizardScene<
C extends Context & {
scene: SceneContextScene<C, WizardSessionData>
wizard: WizardContextWizard<C>
}
>
extends BaseScene<C>
implements MiddlewareObj<C>
{
steps: Array<Middleware<C>>
constructor(id: string, ...steps: Array<Middleware<C>>)
constructor(
id: string,
options: SceneOptions<C>,
...steps: Array<Middleware<C>>
)
constructor(
id: string,
options: SceneOptions<C> | Middleware<C>,
...steps: Array<Middleware<C>>
) {
let opts: SceneOptions<C> | undefined
let s: Array<Middleware<C>>
if (typeof options === 'function' || 'middleware' in options) {
opts = undefined
s = [options, ...steps]
} else {
opts = options
s = steps
}
super(id, opts)
this.steps = s
}
middleware() {
return Composer.compose<C>([
(ctx, next) => {
ctx.wizard = new WizardContextWizard<C>(ctx, this.steps)
return next()
},
super.middleware(),
(ctx, next) => {
if (ctx.wizard.step === undefined) {
ctx.wizard.selectStep(0)
return ctx.scene.leave()
}
return Composer.unwrap(ctx.wizard.step)(ctx, next)
},
])
}
enterMiddleware() {
return Composer.compose([this.enterHandler, this.middleware()])
}
}

95
node_modules/telegraf/src/session.ts generated vendored Normal file
View file

@ -0,0 +1,95 @@
import { Context } from './context'
import { MaybePromise } from './composer'
import { MiddlewareFn } from './middleware'
export interface SessionStore<T> {
get: (name: string) => MaybePromise<T | undefined>
set: (name: string, value: T) => MaybePromise<void>
delete: (name: string) => MaybePromise<void>
}
interface SessionOptions<S extends object> {
getSessionKey?: (ctx: Context) => Promise<string | undefined>
store?: SessionStore<S>
}
export interface SessionContext<S extends object> extends Context {
session?: S
}
/**
* Returns middleware that adds `ctx.session` for storing arbitrary state per session key.
*
* The default `getSessionKey` is <code>\`${ctx.from.id}:${ctx.chat.id}\`</code>.
* If either `ctx.from` or `ctx.chat` is `undefined`, default session key and thus `ctx.session` are also `undefined`.
*
* Session data is kept only in memory by default,
* which means that all data will be lost when the process is terminated.
* If you want to store data across restarts, or share it among workers,
* you can [install persistent session middleware from npm](https://www.npmjs.com/search?q=telegraf-session),
* or pass custom `storage`.
*
* @example https://github.com/telegraf/telegraf/blob/develop/docs/examples/session-bot.ts
* @deprecated https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499
*/
export function session<S extends object>(
options?: SessionOptions<S>
): MiddlewareFn<SessionContext<S>> {
const getSessionKey = options?.getSessionKey ?? defaultGetSessionKey
const store = options?.store ?? new MemorySessionStore()
return async (ctx, next) => {
const key = await getSessionKey(ctx)
if (key == null) {
return await next()
}
ctx.session = await store.get(key)
await next()
if (ctx.session == null) {
await store.delete(key)
} else {
await store.set(key, ctx.session)
}
}
}
async function defaultGetSessionKey(ctx: Context): Promise<string | undefined> {
const fromId = ctx.from?.id
const chatId = ctx.chat?.id
if (fromId == null || chatId == null) {
return undefined
}
return `${fromId}:${chatId}`
}
/** @deprecated https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499 */
export class MemorySessionStore<T> implements SessionStore<T> {
private readonly store = new Map<string, { session: T; expires: number }>()
constructor(private readonly ttl = Infinity) {}
get(name: string): T | undefined {
const entry = this.store.get(name)
if (entry == null) {
return undefined
} else if (entry.expires < Date.now()) {
this.delete(name)
return undefined
}
return entry.session
}
set(name: string, value: T): void {
const now = Date.now()
this.store.set(name, { session: value, expires: now + this.ttl })
}
delete(name: string): void {
this.store.delete(name)
}
}
export function isSessionContext<S extends object>(
ctx: Context
): ctx is SessionContext<S> {
return 'session' in ctx
}

329
node_modules/telegraf/src/telegraf.ts generated vendored Normal file
View file

@ -0,0 +1,329 @@
import * as crypto from 'crypto'
import * as http from 'http'
import * as https from 'https'
import * as tg from './core/types/typegram'
import * as tt from './telegram-types'
import { Composer, MaybePromise } from './composer'
import ApiClient from './core/network/client'
import { compactOptions } from './core/helpers/compact'
import Context from './context'
import d from 'debug'
import generateCallback from './core/network/webhook'
import { Polling } from './core/network/polling'
import pTimeout from 'p-timeout'
import Telegram from './telegram'
import { TlsOptions } from 'tls'
import { URL } from 'url'
import safeCompare = require('safe-compare')
const debug = d('telegraf:main')
const DEFAULT_OPTIONS: Telegraf.Options<Context> = {
telegram: {},
handlerTimeout: 90_000, // 90s in ms
contextType: Context,
}
function always<T>(x: T) {
return () => x
}
const anoop = always(Promise.resolve())
// eslint-disable-next-line
export namespace Telegraf {
export interface Options<TContext extends Context> {
contextType: new (
...args: ConstructorParameters<typeof Context>
) => TContext
handlerTimeout: number
telegram?: Partial<ApiClient.Options>
}
export interface LaunchOptions {
dropPendingUpdates?: boolean
/** List the types of updates you want your bot to receive */
allowedUpdates?: tt.UpdateType[]
/** Configuration options for when the bot is run via webhooks */
webhook?: {
/** Public domain for webhook. */
domain: string
/** Webhook url path; will be automatically generated if not specified */
hookPath?: string
host?: string
port?: number
/** The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS */
ipAddress?: string
/**
* Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40.
* Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput.
*/
maxConnections?: number
/** TLS server options. Omit to use http. */
tlsOptions?: TlsOptions
/**
* A secret token to be sent in a header `“X-Telegram-Bot-Api-Secret-Token”` in every webhook request.
* 1-256 characters. Only characters `A-Z`, `a-z`, `0-9`, `_` and `-` are allowed.
* The header is useful to ensure that the request comes from a webhook set by you.
*/
secretToken?: string
/**
* Upload your public key certificate so that the root certificate in use can be checked.
* See [self-signed guide](https://core.telegram.org/bots/self-signed) for details.
*/
certificate?: tg.InputFile
cb?: http.RequestListener
}
}
}
const TOKEN_HEADER = 'x-telegram-bot-api-secret-token'
export class Telegraf<C extends Context = Context> extends Composer<C> {
private readonly options: Telegraf.Options<C>
private webhookServer?: http.Server | https.Server
private polling?: Polling
/** Set manually to avoid implicit `getMe` call in `launch` or `webhookCallback` */
public botInfo?: tg.UserFromGetMe
public telegram: Telegram
readonly context: Partial<C> = {}
/** Assign to this to customise the webhook filter middleware.
* `{ hookPath, secretToken }` will be bound to this rather than the Telegraf instance.
* Remember to assign a regular function and not an arrow function so it's bindable.
*/
public webhookFilter = function (
// NOTE: this function is assigned to a variable instead of being a method to signify that it's assignable
// NOTE: the `this` binding is so custom impls don't need to double wrap
this: { hookPath: string; secretToken?: string },
req: http.IncomingMessage
) {
const debug = d('telegraf:webhook')
if (req.method === 'POST') {
if (safeCompare(this.hookPath, req.url as string)) {
// no need to check if secret_token was not set
if (!this.secretToken) return true
else {
const token = req.headers[TOKEN_HEADER] as string
if (safeCompare(this.secretToken, token)) return true
else debug('Secret token does not match:', token, this.secretToken)
}
} else debug('Path does not match:', req.url, this.hookPath)
} else debug('Unexpected request method, not POST. Received:', req.method)
return false
}
private handleError = (err: unknown, ctx: C): MaybePromise<void> => {
// set exit code to emulate `warn-with-error-code` behavior of
// https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode
// to prevent a clean exit despite an error being thrown
process.exitCode = 1
console.error('Unhandled error while processing', ctx.update)
throw err
}
constructor(token: string, options?: Partial<Telegraf.Options<C>>) {
super()
// @ts-expect-error Trust me, TS
this.options = {
...DEFAULT_OPTIONS,
...compactOptions(options),
}
this.telegram = new Telegram(token, this.options.telegram)
debug('Created a `Telegraf` instance')
}
private get token() {
return this.telegram.token
}
/** @deprecated use `ctx.telegram.webhookReply` */
set webhookReply(webhookReply: boolean) {
this.telegram.webhookReply = webhookReply
}
/** @deprecated use `ctx.telegram.webhookReply` */
get webhookReply() {
return this.telegram.webhookReply
}
/**
* _Override_ error handling
*/
catch(handler: (err: unknown, ctx: C) => MaybePromise<void>) {
this.handleError = handler
return this
}
/**
* You must call `bot.telegram.setWebhook` for this to work.
* You should probably use {@link Telegraf.createWebhook} instead.
*/
webhookCallback(hookPath = '/', opts: { secretToken?: string } = {}) {
const { secretToken } = opts
return generateCallback(
this.webhookFilter.bind({ hookPath, secretToken }),
(update: tg.Update, res: http.ServerResponse) =>
this.handleUpdate(update, res)
)
}
private getDomainOpts(opts: { domain: string; path?: string }) {
const protocol =
opts.domain.startsWith('https://') || opts.domain.startsWith('http://')
if (protocol)
debug(
'Unexpected protocol in domain, telegraf will use https:',
opts.domain
)
const domain = protocol ? new URL(opts.domain).host : opts.domain
const path = opts.path ?? `/telegraf/${this.secretPathComponent()}`
const url = `https://${domain}${path}`
return { domain, path, url }
}
/**
* Specify a url to receive incoming updates via webhook.
* Returns an Express-style middleware you can pass to app.use()
*/
async createWebhook(
opts: { domain: string; path?: string } & tt.ExtraSetWebhook
) {
const { domain, path, ...extra } = opts
const domainOpts = this.getDomainOpts({ domain, path })
await this.telegram.setWebhook(domainOpts.url, extra)
debug(`Webhook set to ${domainOpts.url}`)
return this.webhookCallback(domainOpts.path, {
secretToken: extra.secret_token,
})
}
private startPolling(allowedUpdates: tt.UpdateType[] = []) {
this.polling = new Polling(this.telegram, allowedUpdates)
this.polling.loop(async (update) => {
await this.handleUpdate(update)
})
}
private startWebhook(
hookPath: string,
tlsOptions?: TlsOptions,
port?: number,
host?: string,
cb?: http.RequestListener,
secretToken?: string
) {
const webhookCb = this.webhookCallback(hookPath, { secretToken })
const callback: http.RequestListener =
typeof cb === 'function'
? (req, res) => webhookCb(req, res, () => cb(req, res))
: webhookCb
this.webhookServer =
tlsOptions != null
? https.createServer(tlsOptions, callback)
: http.createServer(callback)
this.webhookServer.listen(port, host, () => {
debug('Webhook listening on port: %s', port)
})
return this
}
secretPathComponent() {
return crypto
.createHash('sha3-256')
.update(this.token)
.update(process.version) // salt
.digest('hex')
}
/**
* @see https://github.com/telegraf/telegraf/discussions/1344#discussioncomment-335700
*/
async launch(config: Telegraf.LaunchOptions = {}) {
debug('Connecting to Telegram')
this.botInfo ??= await this.telegram.getMe()
debug(`Launching @${this.botInfo.username}`)
if (config.webhook === undefined) {
await this.telegram.deleteWebhook({
drop_pending_updates: config.dropPendingUpdates,
})
this.startPolling(config.allowedUpdates)
debug('Bot started with long polling')
return
}
const domainOpts = this.getDomainOpts({
domain: config.webhook.domain,
path: config.webhook.hookPath,
})
const { tlsOptions, port, host, cb, secretToken } = config.webhook
this.startWebhook(domainOpts.path, tlsOptions, port, host, cb, secretToken)
await this.telegram.setWebhook(domainOpts.url, {
drop_pending_updates: config.dropPendingUpdates,
allowed_updates: config.allowedUpdates,
ip_address: config.webhook.ipAddress,
max_connections: config.webhook.maxConnections,
secret_token: config.webhook.secretToken,
certificate: config.webhook.certificate,
})
debug(`Bot started with webhook @ ${domainOpts.url}`)
}
stop(reason = 'unspecified') {
debug('Stopping bot... Reason:', reason)
// https://github.com/telegraf/telegraf/pull/1224#issuecomment-742693770
if (this.polling === undefined && this.webhookServer === undefined) {
throw new Error('Bot is not running!')
}
this.webhookServer?.close()
this.polling?.stop()
}
private botInfoCall?: Promise<tg.UserFromGetMe>
async handleUpdate(update: tg.Update, webhookResponse?: http.ServerResponse) {
this.botInfo ??=
(debug(
'Update %d is waiting for `botInfo` to be initialized',
update.update_id
),
await (this.botInfoCall ??= this.telegram.getMe()))
debug('Processing update', update.update_id)
const tg = new Telegram(this.token, this.telegram.options, webhookResponse)
const TelegrafContext = this.options.contextType
const ctx = new TelegrafContext(update, tg, this.botInfo)
Object.assign(ctx, this.context)
try {
await pTimeout(
Promise.resolve(this.middleware()(ctx, anoop)),
this.options.handlerTimeout
)
} catch (err) {
return await this.handleError(err, ctx)
} finally {
if (webhookResponse?.writableEnded === false) {
webhookResponse.end()
}
debug('Finished processing update', update.update_id)
}
}
}

145
node_modules/telegraf/src/telegram-types.ts generated vendored Normal file
View file

@ -0,0 +1,145 @@
/** @format */
import { Expand } from './util'
import { Message, Opts, Telegram, Update } from './core/types/typegram'
import { UnionKeys } from './deunionize'
import { FmtString } from './format'
export { Markup } from './markup'
// tiny helper types
export type ChatAction = Opts<'sendChatAction'>['action']
// Modify type so caption, if exists, can be FmtString
type WrapCaption<T> = T extends { caption?: string }
? Expand<Omit<T, 'caption'> & { caption?: string | FmtString }>
: T
// extra types
/**
* Create an `Extra*` type from the arguments of a given method `M extends keyof Telegram` but `Omit`ting fields with key `K` from it.
*
* Note that `chat_id` may not be specified in `K` because it is `Omit`ted by default.
*/
type MakeExtra<
M extends keyof Telegram,
K extends keyof Omit<Opts<M>, 'chat_id'> = never
> = WrapCaption<Omit<Opts<M>, 'chat_id' | K>>
export type ExtraAddStickerToSet = MakeExtra<
'addStickerToSet',
'name' | 'user_id'
>
export type ExtraAnimation = MakeExtra<'sendAnimation', 'animation'>
export type ExtraAnswerCbQuery = MakeExtra<
'answerCallbackQuery',
'text' | 'callback_query_id'
>
export type ExtraAnswerInlineQuery = MakeExtra<
'answerInlineQuery',
'inline_query_id' | 'results'
>
export type ExtraAudio = MakeExtra<'sendAudio', 'audio'>
export type ExtraContact = MakeExtra<
'sendContact',
'phone_number' | 'first_name'
>
export type ExtraCopyMessage = MakeExtra<
'copyMessage',
'from_chat_id' | 'message_id'
>
export type ExtraCreateChatInviteLink = MakeExtra<'createChatInviteLink'>
export type NewInvoiceLinkParameters = MakeExtra<'createInvoiceLink'>
export type ExtraCreateNewStickerSet = MakeExtra<
'createNewStickerSet',
'name' | 'title' | 'user_id'
>
export type ExtraDice = MakeExtra<'sendDice'>
export type ExtraDocument = MakeExtra<'sendDocument', 'document'>
export type ExtraEditChatInviteLink = MakeExtra<
'editChatInviteLink',
'invite_link'
>
export type ExtraEditMessageCaption = MakeExtra<
'editMessageCaption',
'message_id' | 'inline_message_id' | 'caption'
>
export type ExtraEditMessageLiveLocation = MakeExtra<
'editMessageLiveLocation',
'message_id' | 'inline_message_id' | 'latitude' | 'longitude'
>
export type ExtraEditMessageMedia = MakeExtra<
'editMessageMedia',
'message_id' | 'inline_message_id' | 'media'
>
export type ExtraEditMessageText = MakeExtra<
'editMessageText',
'message_id' | 'inline_message_id' | 'text'
>
export type ExtraGame = MakeExtra<'sendGame', 'game_short_name'>
export type NewInvoiceParameters = MakeExtra<
'sendInvoice',
| 'disable_notification'
| 'reply_to_message_id'
| 'allow_sending_without_reply'
| 'reply_markup'
>
export type ExtraInvoice = MakeExtra<'sendInvoice', keyof NewInvoiceParameters>
export type ExtraBanChatMember = MakeExtra<
'banChatMember',
'user_id' | 'until_date'
>
export type ExtraKickChatMember = ExtraBanChatMember
export type ExtraLocation = MakeExtra<'sendLocation', 'latitude' | 'longitude'>
export type ExtraMediaGroup = MakeExtra<'sendMediaGroup', 'media'>
export type ExtraPhoto = MakeExtra<'sendPhoto', 'photo'>
export type ExtraPoll = MakeExtra<'sendPoll', 'question' | 'options' | 'type'>
export type ExtraPromoteChatMember = MakeExtra<'promoteChatMember', 'user_id'>
export type ExtraReplyMessage = MakeExtra<'sendMessage', 'text'>
export type ExtraRestrictChatMember = MakeExtra<'restrictChatMember', 'user_id'>
export type ExtraSetMyCommands = MakeExtra<'setMyCommands', 'commands'>
export type ExtraSetWebhook = MakeExtra<'setWebhook', 'url'>
export type ExtraSticker = MakeExtra<'sendSticker', 'sticker'>
export type ExtraStopPoll = MakeExtra<'stopPoll', 'message_id'>
export type ExtraVenue = MakeExtra<
'sendVenue',
'latitude' | 'longitude' | 'title' | 'address'
>
export type ExtraVideo = MakeExtra<'sendVideo', 'video'>
export type ExtraVideoNote = MakeExtra<'sendVideoNote', 'video_note'>
export type ExtraVoice = MakeExtra<'sendVoice', 'voice'>
export type ExtraBanChatSenderChat = MakeExtra<
'banChatSenderChat',
'sender_chat_id'
>
// types used for inference of ctx object
/** Possible update types */
export type UpdateType = Exclude<UnionKeys<Update>, keyof Update>
/** Possible message subtypes. Same as the properties on a message object */
export type MessageSubType =
| 'forward_date'
| Exclude<
UnionKeys<Message>,
keyof Message.CaptionableMessage | 'entities' | 'media_group_id'
>
type ExtractPartial<T extends object, U extends object> = T extends unknown
? Required<T> extends U
? T
: never
: never
/**
* Maps [[`Composer.on`]]'s `updateType` or `messageSubType` to a `tt.Update` subtype.
*/
export type MountMap = {
[T in UpdateType]: Extract<Update, Record<T, object>>
} & {
[T in MessageSubType]: {
message: ExtractPartial<Update.MessageUpdate['message'], Record<T, unknown>>
update_id: number
}
}

1230
node_modules/telegraf/src/telegram.ts generated vendored Normal file

File diff suppressed because it is too large Load diff

54
node_modules/telegraf/src/util.ts generated vendored Normal file
View file

@ -0,0 +1,54 @@
import { FmtString } from './format'
export const env = process.env
export type Expand<T> = T extends object
? T extends infer O
? { [K in keyof O]: O[K] }
: never
: T
type MaybeExtra<Extra> = (Extra & { caption?: string }) | undefined
export function fmtCaption<
Extra extends {
caption?: string | FmtString
} & Record<string, unknown>
>(extra?: Extra): MaybeExtra<Extra> {
const caption = extra?.caption
if (!caption || typeof caption === 'string') return extra as MaybeExtra<Extra>
const { text, entities } = caption
return {
...extra,
caption: text,
...(entities && {
caption_entities: entities,
parse_mode: undefined,
}),
}
}
export function deprecate(
method: string,
ignorable: string | undefined,
use: string | undefined,
see?: string
) {
// don't use deprecate() yet
// wait for a couple minor releases of telegraf so the news reaches more people
return
const ignorer = `IGNORE_DEPRECATED_${ignorable}`
if (env[ignorer]) return
const stack: { stack: string } = { stack: '' }
Error.captureStackTrace(stack)
const line = (stack.stack.split('\n')[3] || '').trim()
const useOther = use ? `; use ${use} instead` : ''
const pad = ' '.repeat('[WARN]'.length)
console.warn(`[WARN] ${method} is deprecated${useOther}`)
if (line) console.warn(pad, line)
if (see) console.warn(pad, `SEE ${see}`)
}