This commit is contained in:
Александр Тороп
2024-03-30 18:27:37 +03:00
commit 038bf44010
2765 changed files with 293753 additions and 0 deletions

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

@@ -0,0 +1,169 @@
import {
InlineKeyboardButton,
KeyboardButton,
KeyboardButtonRequestChat,
} 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 userRequest(
text: string,
/** Must fit in a signed 32 bit int */
request_id: number,
user_is_premium?: boolean,
hide = false
): Hideable<KeyboardButton.RequestUserButton> {
return { text, request_user: { request_id, user_is_premium }, hide }
}
export function botRequest(
text: string,
/** Must fit in a signed 32 bit int */
request_id: number,
hide = false
): Hideable<KeyboardButton.RequestUserButton> {
return { text, request_user: { request_id, user_is_bot: true }, hide }
}
type KeyboardButtonRequestGroup = Omit<
KeyboardButtonRequestChat,
'request_id' | 'chat_is_channel'
>
export function groupRequest(
text: string,
/** Must fit in a signed 32 bit int */
request_id: number,
extra?: KeyboardButtonRequestGroup,
hide = false
): Hideable<KeyboardButton.RequestChatButton> {
return {
text,
request_chat: { request_id, chat_is_channel: false, ...extra },
hide,
}
}
type KeyboardButtonRequestChannel = Omit<
KeyboardButtonRequestChat,
'request_id' | 'chat_is_channel' | 'chat_is_forum'
>
export function channelRequest(
text: string,
/** Must fit in a signed 32 bit int */
request_id: number,
extra?: KeyboardButtonRequestChannel,
hide = false
): Hideable<KeyboardButton.RequestChatButton> {
return {
text,
request_chat: { request_id, chat_is_channel: true, ...extra },
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
// works as both InlineKeyboardButton and KeyboardButton
): Hideable<InlineKeyboardButton.WebAppButton> {
return {
text,
web_app: { url },
hide,
}
}

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

@@ -0,0 +1,977 @@
/** @format */
import * as tg from './core/types/typegram'
import * as tt from './telegram-types'
import { Middleware, MiddlewareFn, MiddlewareObj } from './middleware'
import Context, { FilteredContext, NarrowedContext } from './context'
import {
MaybeArray,
NonemptyReadonlyArray,
MaybePromise,
Guard,
} from './core/helpers/util'
import { type CallbackQuery } from './core/types/typegram'
import { message, callbackQuery } from './filters'
import { argsParser } from './core/helpers/args'
export type Triggers<C> = MaybeArray<string | RegExp | TriggerFn<C>>
type TriggerFn<C> = (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<NarrowedContext<C, tt.MountMap[T]> & { match: RegExpExecArray }>
>
/** 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]>
function always<T>(x: T) {
return () => x
}
interface StartContextExtn {
/**
* @deprecated Use ctx.payload instead
*/
startPayload: string
}
interface CommandContextExtn {
/**
* Matched command. This will always be the actual command, excluding preceeding slash and `@botname`
*
* Examples:
* ```
* /command abc -> command
* /command@xyzbot abc -> command
* ```
*/
command: string
/**
* The unparsed payload part of the command
*
* Examples:
* ```
* /command abc def -> "abc def"
* /command "token1 token2" -> "\"token1 token2\""
* ```
*/
payload: string
/**
* Command args parsed into an array.
*
* Examples:
* ```
* /command token1 token2 -> [ "token1", "token2" ]
* /command "token1 token2" -> [ "token1 token2" ]
* /command token1 "token2 token3" -> [ "token1" "token2 token3" ]
* ```
* @unstable Parser implementation might vary until considered stable
* */
args: string[]
}
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.
* @deprecated use `Composer::on`
*/
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 updates narrowed by update types or filter queries.
*/
on<Filter extends tt.UpdateType | Guard<C['update']>>(
filters: MaybeArray<Filter>,
...fns: NonemptyReadonlyArray<Middleware<FilteredContext<C, Filter>>>
): this
/**
* Registers middleware for handling updates narrowed by update types or message subtypes.
* @deprecated Use filter utils instead. Support for Message subtype in `Composer::on` will be removed in Telegraf v5.
*/
on<Filter extends tt.UpdateType | tt.MessageSubType>(
filters: MaybeArray<Filter>,
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, tt.MountMap[Filter]>>
>
): this
on<Filter extends tt.UpdateType | tt.MessageSubType | Guard<C['update']>>(
filters: MaybeArray<Filter>,
...fns: NonemptyReadonlyArray<
Middleware<
Filter extends tt.MessageSubType
? MatchedContext<C, Filter>
: Filter extends tt.UpdateType | Guard<C['update']>
? FilteredContext<C, Filter>
: never
>
>
): this {
// @ts-expect-error This should get resolved when the overloads are removed in v5
return this.use(Composer.on(filters, ...fns))
}
/**
* Registers middleware for handling matching text messages.
*/
hears(
triggers: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
...fns: MatchedMiddleware<C, 'text'>
) {
return this.use(Composer.hears<C>(triggers, ...fns))
}
/**
* Registers middleware for handling specified commands.
*/
command(
command: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, tt.MountMap['text']> & CommandContextExtn>
>
) {
return this.use(Composer.command<C>(command, ...fns))
}
/**
* Registers middleware for handling matching callback queries.
*/
action(
triggers: Triggers<NarrowedContext<C, tt.MountMap['callback_query']>>,
...fns: MatchedMiddleware<C, 'callback_query'>
) {
return this.use(Composer.action<C>(triggers, ...fns))
}
/**
* Registers middleware for handling matching inline queries.
*/
inlineQuery(
triggers: Triggers<NarrowedContext<C, tt.MountMap['inline_query']>>,
...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,
tg.Update.CallbackQueryUpdate<CallbackQuery.GameQuery>
>
>
>
) {
return this.use(Composer.gameQuery(...fns))
}
/**
* Registers middleware for dropping matching updates.
*/
drop(predicate: Predicate<C>) {
return this.use(Composer.drop(predicate))
}
/** @deprecated use `Composer::drop` */
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<NarrowedContext<C, tt.MountMap[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<NarrowedContext<C, tt.MountMap['text']> & StartContextExtn>
>
) {
const handler = Composer.compose(fns)
return this.command('start', (ctx, next) =>
handler(Object.assign(ctx, { startPayload: ctx.payload }), next)
)
}
/**
* Registers a middleware for handling /help
*/
help(
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, tt.MountMap['text']>>
>
) {
return this.command('help', ...fns)
}
/**
* Registers a middleware for handling /settings
*/
settings(
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, tt.MountMap['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()
)
}
/** @deprecated use `Composer.drop` */
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
* @deprecated use `Composer.on`
*/
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 updates narrowed by update types or filter queries.
*/
static on<
Ctx extends Context,
Filter extends tt.UpdateType | Guard<Ctx['update']>,
>(
filters: MaybeArray<Filter>,
...fns: NonemptyReadonlyArray<Middleware<FilteredContext<Ctx, Filter>>>
): MiddlewareFn<Ctx>
/**
* Generates middleware for handling updates narrowed by update types or message subtype.
* @deprecated Use filter utils instead. Support for Message subtype in `Composer.on` will be removed in Telegraf v5.
*/
static on<
Ctx extends Context,
Filter extends tt.UpdateType | tt.MessageSubType,
>(
filters: MaybeArray<Filter>,
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<Ctx, tt.MountMap[Filter]>>
>
): MiddlewareFn<Ctx>
static on<
Ctx extends Context,
Filter extends tt.UpdateType | tt.MessageSubType | Guard<Ctx['update']>,
>(
updateType: MaybeArray<Filter>,
...fns: NonemptyReadonlyArray<Middleware<Ctx>>
): MiddlewareFn<Ctx> {
const filters = Array.isArray(updateType) ? updateType : [updateType]
const predicate = (update: tg.Update): update is tg.Update => {
for (const filter of filters) {
if (
// TODO: this should change to === 'function' once TS bug is fixed
// https://github.com/microsoft/TypeScript/pull/51502
typeof filter !== 'string'
? // filter is a type guard
filter(update)
: // check if filter is the update type
filter in update ||
// check if filter is the msg type
// TODO: remove in v5!
('message' in update && filter in update.message)
) {
return true
}
}
return false
}
return Composer.optional((ctx) => predicate(ctx.update), ...fns)
}
/**
* Generates middleware for handling provided update types.
* @deprecated use `Composer.on` instead
*/
static mount = Composer.on
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<NarrowedContext<C, tt.MountMap[T]>>>
): MiddlewareFn<C> {
if (typeof predicate !== 'function') {
const entityTypes = normaliseTextArguments(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 = normaliseTriggers(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',
normaliseTextArguments(mention, '@'),
...fns
)
}
static hashtag<C extends Context>(
hashtag: MaybeArray<string>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>(
'hashtag',
normaliseTextArguments(hashtag, '#'),
...fns
)
}
static cashtag<C extends Context>(
cashtag: MaybeArray<string>,
...fns: MatchedMiddleware<C>
): MiddlewareFn<C> {
return Composer.entityText<C>(
'cashtag',
normaliseTextArguments(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>(
triggers: ReadonlyArray<TriggerFn<C>>,
...fns: Middleware<C & { match: RegExpExecArray }>[]
): MiddlewareFn<C> {
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) {
const match = trigger(text, ctx)
if (match) return handler(Object.assign(ctx, { match }), next)
}
return next()
}
}
/**
* Generates middleware for handling matching text messages.
*/
static hears<C extends Context>(
triggers: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
...fns: MatchedMiddleware<C, 'text'>
): MiddlewareFn<C> {
return Composer.on(
'text',
Composer.match<NarrowedContext<C, tt.MountMap['text']>>(
normaliseTriggers(triggers),
...fns
)
)
}
/**
* Generates middleware for handling specified commands.
*/
static command<C extends Context>(
command: Triggers<NarrowedContext<C, tt.MountMap['text']>>,
...fns: NonemptyReadonlyArray<
Middleware<NarrowedContext<C, tt.MountMap['text']> & CommandContextExtn>
>
): MiddlewareFn<C> {
if (fns.length === 0)
// @ts-expect-error command is really the middleware
return Composer.entity('bot_command', command)
const triggers = normaliseTriggers(command)
const filter = message('text')
const handler = Composer.compose(fns)
return Composer.on<C, typeof filter>(filter, (ctx, next) => {
const { entities } = ctx.message
const cmdEntity = entities?.[0]
if (cmdEntity?.type !== 'bot_command') return next()
if (cmdEntity.offset > 0) return next()
const len = cmdEntity.length
const text = ctx.message.text
const [cmdPart, to] = text.slice(0, len).split('@')
if (!cmdPart) return next()
// always check for bot's own username case-insensitively
if (to && to.toLowerCase() !== ctx.me.toLowerCase()) return next()
const command = cmdPart.slice(1)
for (const trigger of triggers)
if (trigger(command, ctx)) {
const payloadOffset = len + 1
const payload = text.slice(payloadOffset)
const c = Object.assign(ctx, { command, payload, args: [] })
let _args: string[] | undefined = undefined
// using defineProperty only to make parsing lazy on access
Object.defineProperty(c, 'args', {
enumerable: true,
configurable: true,
get() {
if (_args != null) return _args
// once parsed, cache and don't parse again on every access
return (_args = argsParser(payload, entities, payloadOffset))
},
set(args: string[]) {
_args = args
},
})
return handler(c, next)
}
return next()
})
}
/**
* Generates middleware for handling matching callback queries.
*/
static action<C extends Context>(
triggers: Triggers<NarrowedContext<C, tt.MountMap['callback_query']>>,
...fns: MatchedMiddleware<C, 'callback_query'>
): MiddlewareFn<C> {
return Composer.on(
'callback_query',
Composer.match(normaliseTriggers(triggers), ...fns)
)
}
/**
* Generates middleware for handling matching inline queries.
*/
static inlineQuery<C extends Context>(
triggers: Triggers<NarrowedContext<C, tt.MountMap['inline_query']>>,
...fns: MatchedMiddleware<C, 'inline_query'>
): MiddlewareFn<C> {
return Composer.on(
'inline_query',
Composer.match(normaliseTriggers(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,
tg.Update.CallbackQueryUpdate<CallbackQuery.GameQuery>
>
>
>
): MiddlewareFn<C> {
return Composer.guard(callbackQuery('game_short_name'), ...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 normaliseTriggers<C extends Context>(
triggers: Triggers<C>
): Array<TriggerFn<C>> {
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 normaliseTextArguments(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

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

File diff suppressed because it is too large Load Diff

63
node_modules/telegraf/src/core/helpers/args.ts generated vendored Normal file
View File

@@ -0,0 +1,63 @@
interface Entity {
/** Type of the entity. Currently, can be “mention” (@username), “hashtag” (#hashtag), “cashtag” ($USD), “bot_command” (/start@jobs_bot), “url” (https://telegram.org), “email” (do-not-reply@telegram.org), “phone_number” (+1-212-555-0123), “bold” (bold text), “italic” (italic text), “underline” (underlined text), “strikethrough” (strikethrough text), “spoiler” (spoiler message), “code” (monowidth string), “pre” (monowidth block), “text_link” (for clickable text URLs), “text_mention” (for users without usernames), “custom_emoji” (for inline custom emoji stickers) */
type: string
/** Offset in UTF-16 code units to the start of the entity */
offset: number
/** Length of the entity in UTF-16 code units */
length: number
}
const SINGLE_QUOTE = "'"
const DOUBLE_QUOTE = '"'
export function argsParser(
str: string,
entities: Entity[] = [],
entityOffset = 0
) {
const mentions: { [offset: string]: number } = {}
for (const entity of entities) // extract all text_mentions into an { offset: length } map
if (entity.type === 'text_mention' || entity.type === 'text_link')
mentions[entity.offset - entityOffset] = entity.length
const args: string[] = []
let done = 0
let inside: `'` | `"` | undefined = undefined
let buf = ''
function flush(to: number) {
if (done !== to) args.push(buf + str.slice(done, to)), (inside = undefined)
buf = ''
done = to + 1
}
for (let i = 0; i < str.length; i++) {
const char = str[i]
// quick lookup length of mention starting at i
const mention = mentions[i]
if (mention) {
// if we're inside a quote, eagerly flush existing state
flush(i)
// this also consumes current index, so decrement
done--
// fast forward to end of mention
i += mention
flush(i)
} else if (char === SINGLE_QUOTE || char === DOUBLE_QUOTE)
if (inside)
if (inside === char) flush(i)
else continue
else flush(i), (inside = char)
else if (char === ' ')
if (inside) continue
else flush(i)
else if (char === '\n') flush(i)
else if (char === '\\')
(buf += str.slice(done, i)), (done = ++i) // skip parsing the next char
else continue
}
if (done < str.length) flush(str.length)
return args
}

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])
}

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

@@ -0,0 +1,18 @@
export function compactOptions<T extends { [key: string]: unknown }>(
options?: T
): T | undefined {
if (!options) {
return options
}
const compacted: Partial<T> = {}
for (const key in options)
if (
// todo(mkr): replace with Object.hasOwn in v5 (Node 16+)
Object.prototype.hasOwnProperty.call(options, key) &&
options[key] !== undefined
)
compacted[key] = options[key]
return compacted as T | undefined
}

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

@@ -0,0 +1,126 @@
/* eslint-disable @typescript-eslint/no-unsafe-declaration-merging */
import { MessageEntity, User } from '@telegraf/types'
import { zip } from './util'
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 | FmtString | readonly (FmtString | string)[]
// eslint-disable-next-line @typescript-eslint/ban-types
type Any = {} | undefined | null
const isArray: <T>(xs: T | readonly T[]) => xs is readonly T[] = Array.isArray
/** Given a base FmtString and something to append to it, mutates the base */
const _add = (base: FmtString, next: FmtString | Any) => {
const len = base.text.length
if (next instanceof FmtString) {
base.text = `${base.text}${next.text}`
// next.entities could be undefined and condition will fail
for (let i = 0; i < (next.entities?.length || 0); i++) {
// because of the above condition, next.entities[i] cannot be undefined
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
const entity = next.entities![i]!
// base.entities is ensured by caller
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
base.entities!.push({ ...entity, offset: entity.offset + len })
}
} else base.text = `${base.text}${next}`
}
/**
* Given an `Iterable<FmtString | string | Any>` and a separator, flattens the list into a single FmtString.
* Analogous to Array#join -> string, but for FmtString
*/
export const join = (
fragments: Iterable<FmtString | string | Any>,
separator?: string | FmtString
) => {
const result = new FmtString('')
// ensure entities array so loop doesn't need to check
result.entities = []
const iter = fragments[Symbol.iterator]()
let curr = iter.next()
while (!curr.done) {
_add(result, curr.value)
curr = iter.next()
if (separator && !curr.done) _add(result, separator)
}
// set parse_mode: undefined if entities are present
if (result.entities.length) result.parse_mode = undefined
// remove entities array if not relevant
else delete result.entities
return result
}
/** Internal constructor for all fmt helpers */
export function _fmt(
kind?: Types.Containers
): (parts: TemplateParts, ...items: (Any | FmtString)[]) => FmtString
export function _fmt(
kind: Types.NonContainers
): (parts: TemplateParts, ...items: Any[]) => FmtString
export function _fmt(
kind: 'pre',
opts: { language: string }
): (parts: TemplateParts, ...items: Any[]) => FmtString
export function _fmt(kind: Types.Text | undefined, opts?: object) {
return function fmt(parts: TemplateParts, ...items: (Any | FmtString)[]) {
parts = isArray(parts) ? parts : [parts]
const result = join(zip(parts, items))
if (kind) {
result.entities ??= []
result.entities.unshift({
type: kind,
offset: 0,
length: result.text.length,
...opts,
})
result.parse_mode = undefined
}
return result
}
}
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)
}

69
node_modules/telegraf/src/core/helpers/util.ts generated vendored Normal file
View File

@@ -0,0 +1,69 @@
import { FmtString } from './formatting'
export const env = process.env
// eslint-disable-next-line @typescript-eslint/ban-types
export type Any = {} | undefined | null
export type Expand<T> = T extends object
? T extends infer O
? { [K in keyof O]: O[K] }
: never
: T
export type MaybeArray<T> = T | T[]
export type MaybePromise<T> = T | Promise<T>
export type NonemptyReadonlyArray<T> = readonly [T, ...T[]]
// prettier-ignore
export type ExclusiveKeys<A extends object, B extends object> = keyof Omit<A, keyof B>
export function fmtCaption<
Extra extends { caption?: string | FmtString } | undefined,
>(
extra?: Extra
): Extra extends undefined
? undefined
: Omit<Extra, 'caption'> & { caption?: string }
export function fmtCaption(extra?: { caption?: string | FmtString }) {
if (!extra) return
const caption = extra.caption
if (!caption || typeof caption === 'string') return extra
const { text, entities } = caption
return {
...extra,
caption: text,
...(entities && {
caption_entities: entities,
parse_mode: undefined,
}),
}
}
/** Construct a generic type guard */
export type Guard<X = unknown, Y extends X = X> = (x: X) => x is Y
/** Extract the guarded type from a type guard, defaults to never. */
export type Guarded<F> =
// eslint-disable-next-line @typescript-eslint/no-explicit-any
F extends (x: any) => x is infer T ? T : never
export function* zip<X, Y>(xs: Iterable<X>, ys: Iterable<Y>): Iterable<X | Y> {
const x = xs[Symbol.iterator]()
const y = ys[Symbol.iterator]()
let x1 = x.next()
let y1 = y.next()
while (!x1.done) {
yield x1.value
if (!y1.done) yield y1.value
x1 = x.next()
y1 = y.next()
}
while (!y1.done) {
yield y1.value
y1 = y.next()
}
}

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

@@ -0,0 +1,384 @@
/* eslint @typescript-eslint/restrict-template-expressions: [ "error", { "allowNumber": true, "allowBoolean": true } ] */
import * as crypto from 'crypto'
import * as fs from 'fs'
import { stat, realpath } from 'fs/promises'
import * as http from 'http'
import * as https from 'https'
import * as path from 'path'
import fetch, { RequestInit } from 'node-fetch'
import { hasProp, hasPropType } from '../helpers/check'
import { InputFile, Opts, Telegram } from '../types/typegram'
import { AbortSignal } from 'abort-controller'
import { compactOptions } from '../helpers/compact'
import MultipartStream from './multipart-stream'
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',
])
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',
] as const
async function buildFormDataConfig(
payload: Opts<keyof Telegram>,
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)
await Promise.all(
Object.keys(payload).map((key) =>
// @ts-expect-error payload[key] can obviously index payload, but TS doesn't trust us
attachFormValue(formData, key, payload[key], agent)
)
)
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' || id === 'thumbnail') {
const attachmentId = crypto.randomBytes(16).toString('hex')
await attachFormMedia(form, value as InputFile, 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 InputFile, 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 InputFile, id, agent)
}
async function attachFormMedia(
form: MultipartStream,
media: InputFile,
id: string,
agent: ApiClient.Agent
) {
let fileName = media.filename ?? `${id}.${DEFAULT_EXTENSIONS[id] ?? 'dat'}`
if ('url' in media && media.url !== undefined) {
const timeout = 500_000 // ms
const res = await fetch(media.url, { agent, timeout })
return form.addPart({
headers: {
'content-disposition': `form-data; name="${id}"; filename="${fileName}"`,
},
body: res.body,
})
}
if ('source' in media && media.source) {
let mediaSource = media.source
if (typeof media.source === 'string') {
const source = await realpath(media.source)
if ((await stat(source)).isFile()) {
fileName = media.filename ?? path.basename(media.source)
mediaSource = await fs.createReadStream(media.source)
} else {
throw new TypeError(`Unable to upload '${media.source}', not a file`)
}
}
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: Opts<keyof Telegram>,
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
// @ts-expect-error AbortSignal shim is missing some props from Request.AbortSignal
config.signal = signal
config.timeout = 500_000 // ms
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

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

@@ -0,0 +1,29 @@
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 | Buffer | 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

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

@@ -0,0 +1,94 @@
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)
}
}

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

@@ -0,0 +1,54 @@
import * as Typegram from '@telegraf/types'
// internal type provisions
export * from '@telegraf/types/api'
export * from '@telegraf/types/inline'
export * from '@telegraf/types/manage'
export * from '@telegraf/types/markup'
export * from '@telegraf/types/message'
export * from '@telegraf/types/methods'
export * from '@telegraf/types/passport'
export * from '@telegraf/types/payment'
export * from '@telegraf/types/settings'
export * from '@telegraf/types/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
export type Telegram = Typegram.ApiMethods<InputFile>
export type Opts<M extends keyof Telegram> = Typegram.Opts<InputFile>[M]
export type InputMedia = Typegram.InputMedia<InputFile>
export type InputMediaPhoto = Typegram.InputMediaPhoto<InputFile>
export type InputMediaVideo = Typegram.InputMediaVideo<InputFile>
export type InputMediaAnimation = Typegram.InputMediaAnimation<InputFile>
export type InputMediaAudio = Typegram.InputMediaAudio<InputFile>
export type InputMediaDocument = Typegram.InputMediaDocument<InputFile>
// tiny helper types
export type ChatAction = Opts<'sendChatAction'>['action']
/**
* Sending video notes by a URL is currently unsupported
*/
export type InputFileVideoNote = Exclude<InputFile, InputFileByURL>

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

@@ -0,0 +1,27 @@
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]?: never }
/**
* @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.
* @deprectated
* @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>
}

114
node_modules/telegraf/src/filters.ts generated vendored Normal file
View File

@@ -0,0 +1,114 @@
/* eslint-disable @typescript-eslint/ban-types */
import type {
CallbackQuery,
CommonMessageBundle,
Message,
Update,
} from '@telegraf/types'
import type { Deunionize, UnionKeys } from './deunionize'
import { Guarded } from './core/helpers/util'
type DistinctKeys<T extends object> = Exclude<UnionKeys<T>, keyof T>
type Keyed<T extends object, K extends DistinctKeys<T>> = Record<K, {}> &
Deunionize<Record<K, {}>, T>
export type Filter<U extends Update> = (update: Update) => update is U
export { Guarded }
export type AllGuarded<Fs extends Filter<Update>[]> = Fs extends [
infer A,
...infer B,
]
? B extends []
? Guarded<A>
: // TS doesn't know otherwise that B is Filter[]
B extends Filter<Update>[]
? Guarded<A> & AllGuarded<B>
: never
: never
export const message =
<Ks extends DistinctKeys<Message>[]>(...keys: Ks) =>
(
update: Update
): update is Update.MessageUpdate<Keyed<Message, Ks[number]>> => {
if (!('message' in update)) return false
for (const key of keys) {
if (!(key in update.message)) return false
}
return true
}
export const editedMessage =
<Ks extends DistinctKeys<CommonMessageBundle>[]>(...keys: Ks) =>
(
update: Update
): update is Update.EditedMessageUpdate<
Keyed<CommonMessageBundle, Ks[number]>
> => {
if (!('edited_message' in update)) return false
for (const key of keys) {
if (!(key in update.edited_message)) return false
}
return true
}
export const channelPost =
<Ks extends DistinctKeys<Message>[]>(...keys: Ks) =>
(
update: Update
): update is Update.ChannelPostUpdate<Keyed<Message, Ks[number]>> => {
if (!('channel_post' in update)) return false
for (const key of keys) {
if (!(key in update.channel_post)) return false
}
return true
}
export const editedChannelPost =
<Ks extends DistinctKeys<CommonMessageBundle>[]>(...keys: Ks) =>
(
update: Update
): update is Update.EditedChannelPostUpdate<
Keyed<CommonMessageBundle, Ks[number]>
> => {
if (!('edited_channel_post' in update)) return false
for (const key of keys) {
if (!(key in update.edited_channel_post)) return false
}
return true
}
export const callbackQuery =
<Ks extends DistinctKeys<CallbackQuery>[]>(...keys: Ks) =>
(
update: Update
): update is Update.CallbackQueryUpdate<Keyed<CallbackQuery, Ks[number]>> => {
if (!('callback_query' in update)) return false
for (const key of keys) {
if (!(key in update.callback_query)) return false
}
return true
}
/** Any of the provided filters must match */
export const anyOf =
<Us extends Update[]>(
...filters: {
[UIdx in keyof Us]: Filter<Us[UIdx]>
}
) =>
(update: Update): update is Us[number] => {
for (const filter of filters) if (filter(update)) return true
return false
}
/** All of the provided filters must match */
export const allOf =
<U extends Update, Fs extends Filter<U>[]>(...filters: Fs) =>
(update: Update): update is AllGuarded<Fs> => {
for (const filter of filters) if (!filter(update)) return false
return true
}

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

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

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, NarrowedContext } from './context'
export { Composer } 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, SessionStore } 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

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

@@ -0,0 +1,142 @@
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,
})
}
persistent(this: Markup<ReplyKeyboardMarkup>, value = true) {
return new Markup<ReplyKeyboardMarkup>({
...this.reply_markup,
is_persistent: 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 = Object.assign({}, 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 = Object.assign({}, 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()])
}
}

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

@@ -0,0 +1,204 @@
import { Context } from './context'
import { ExclusiveKeys, MaybePromise } from './core/helpers/util'
import { MiddlewareFn } from './middleware'
import d from 'debug'
const debug = d('telegraf:session')
export interface SyncSessionStore<T> {
get: (name: string) => T | undefined
set: (name: string, value: T) => void
delete: (name: string) => void
}
export interface AsyncSessionStore<T> {
get: (name: string) => Promise<T | undefined>
set: (name: string, value: T) => Promise<unknown>
delete: (name: string) => Promise<unknown>
}
export type SessionStore<T> = SyncSessionStore<T> | AsyncSessionStore<T>
interface SessionOptions<S, C extends Context, P extends string> {
/** Customise the session prop. Defaults to "session" and is available as ctx.session. */
property?: P
getSessionKey?: (ctx: C) => MaybePromise<string | undefined>
store?: SessionStore<S>
defaultSession?: (ctx: C) => S
}
/** @deprecated session can use custom properties now. Construct this type directly. */
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 `${ctx.from.id}:${ctx.chat.id}`.
* 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 persist data across process restarts, or share it among multiple instances, you should use
* [@telegraf/session](https://www.npmjs.com/package/@telegraf/session), or pass custom `storage`.
*
* @see {@link https://github.com/feathers-studio/telegraf-docs/blob/b694bcc36b4f71fb1cd650a345c2009ab4d2a2a5/guide/session.md Telegraf Docs | Session}
* @see {@link https://github.com/feathers-studio/telegraf-docs/blob/master/examples/session-bot.ts Example}
*/
export function session<
S extends NonNullable<C[P]>,
C extends Context & { [key in P]?: C[P] },
P extends (ExclusiveKeys<C, Context> & string) | 'session' = 'session',
// ^ Only allow prop names that aren't keys in base Context.
// At type level, this is cosmetic. To not get cluttered with all Context keys.
>(options?: SessionOptions<S, C, P>): MiddlewareFn<C> {
const prop = options?.property ?? ('session' as P)
const getSessionKey = options?.getSessionKey ?? defaultGetSessionKey
const store = options?.store ?? new MemorySessionStore()
// caches value from store in-memory while simultaneous updates share it
// when counter reaches 0, the cached ref will be freed from memory
const cache = new Map<string, { ref?: S; counter: number }>()
// temporarily stores concurrent requests
const concurrents = new Map<string, MaybePromise<S | undefined>>()
// this function must be handled with care
// read full description on the original PR: https://github.com/telegraf/telegraf/pull/1713
// make sure to update the tests in test/session.js if you make any changes or fix bugs here
return async (ctx, next) => {
const updId = ctx.update.update_id
let released = false
function releaseChecks() {
if (released && process.env.EXPERIMENTAL_SESSION_CHECKS)
throw new Error(
"Session was accessed or assigned to after the middleware chain exhausted. This is a bug in your code. You're probably accessing session asynchronously and missing awaits."
)
}
// because this is async, requests may still race here, but it will get autocorrected at (1)
// v5 getSessionKey should probably be synchronous to avoid that
const key = await getSessionKey(ctx)
if (!key) {
// Leaving this here could be useful to check for `prop in ctx` in future middleware
ctx[prop] = undefined as unknown as S
return await next()
}
let cached = cache.get(key)
if (cached) {
debug(`(${updId}) found cached session, reusing from cache`)
++cached.counter
} else {
debug(`(${updId}) did not find cached session`)
// if another concurrent request has already sent a store request, fetch that instead
let promise = concurrents.get(key)
if (promise)
debug(`(${updId}) found a concurrent request, reusing promise`)
else {
debug(`(${updId}) fetching from upstream store`)
promise = store.get(key)
}
// synchronously store promise so concurrent requests can share response
concurrents.set(key, promise)
const upstream = await promise
// all concurrent awaits will have promise in their closure, safe to remove now
concurrents.delete(key)
debug(`(${updId}) updating cache`)
// another request may have beaten us to the punch
const c = cache.get(key)
if (c) {
// another request did beat us to the punch
c.counter++
// (1) preserve cached reference; in-memory reference is always newer than from store
cached = c
} else {
// we're the first, so we must cache the reference
cached = { ref: upstream ?? options?.defaultSession?.(ctx), counter: 1 }
cache.set(key, cached)
}
}
// TS already knows cached is always defined by this point, but does not guard cached.
// It will, however, guard `c` here.
const c = cached
let touched = false
Object.defineProperty(ctx, prop, {
get() {
releaseChecks()
touched = true
return c.ref
},
set(value: S) {
releaseChecks()
touched = true
c.ref = value
},
})
try {
await next()
released = true
} finally {
if (--c.counter === 0) {
// decrement to avoid memory leak
debug(`(${updId}) refcounter reached 0, removing cached`)
cache.delete(key)
}
debug(`(${updId}) middlewares completed, checking session`)
// only update store if ctx.session was touched
if (touched)
if (c.ref == null) {
debug(`(${updId}) ctx.${prop} missing, removing from store`)
await store.delete(key)
} else {
debug(`(${updId}) ctx.${prop} found, updating store`)
await store.set(key, c.ref)
}
}
}
}
function defaultGetSessionKey(ctx: Context): string | undefined {
const fromId = ctx.from?.id
const chatId = ctx.chat?.id
if (fromId == null || chatId == null) return undefined
return `${fromId}:${chatId}`
}
/** @deprecated Use `Map` */
export class MemorySessionStore<T> implements SyncSessionStore<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)
}
}
/** @deprecated session can use custom properties now. Directly use `'session' in ctx` instead */
export function isSessionContext<S extends object>(
ctx: Context
): ctx is SessionContext<S> {
return 'session' in ctx
}

340
node_modules/telegraf/src/telegraf.ts generated vendored Normal file
View File

@@ -0,0 +1,340 @@
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 } from './composer'
import { MaybePromise } from './core/helpers/util'
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())
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
* @deprecated Pass `path` instead
* */
hookPath?: string
/** Webhook url path; will be automatically generated if not specified */
path?: 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.
* `{ path, 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: {
/** @deprecated Use path instead */
hookPath: string
path: string
secretToken?: string
},
req: http.IncomingMessage
) {
const debug = d('telegraf:webhook')
if (req.method === 'POST') {
if (safeCompare(this.path, 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.path)
} 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(path = '/', opts: { secretToken?: string } = {}) {
const { secretToken } = opts
return generateCallback(
this.webhookFilter.bind({ hookPath: path, path, 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)
return this.polling.loop(async (update) => {
await this.handleUpdate(update)
})
}
private startWebhook(
path: string,
tlsOptions?: TlsOptions,
port?: number,
host?: string,
cb?: http.RequestListener,
secretToken?: string
) {
const webhookCb = this.webhookCallback(path, { 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,
})
debug('Bot started with long polling')
await this.startPolling(config.allowedUpdates)
return
}
const domainOpts = this.getDomainOpts({
domain: config.webhook.domain,
path: config.webhook.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)
}
}
}

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

@@ -0,0 +1,176 @@
/** @format */
import { Expand } from './core/helpers/util'
import {
Message,
Opts,
Telegram,
Update,
InputMediaAudio,
InputMediaDocument,
InputMediaPhoto,
InputMediaVideo,
} 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
export 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 ExtraSetChatPermissions = MakeExtra<
'setChatPermissions',
'permissions'
>
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'
| 'message_thread_id'
>
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 ExtraForwardMessage = MakeExtra<
'forwardMessage',
'from_chat_id' | 'message_id'
>
export type ExtraSendChatAction = MakeExtra<'sendChatAction', 'action'>
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'
>
export type ExtraCreateForumTopic = MakeExtra<'createForumTopic', 'name'>
export type ExtraEditForumTopic = MakeExtra<
'editForumTopic',
'message_thread_id'
>
export type MediaGroup =
| readonly (InputMediaPhoto | InputMediaVideo)[]
| readonly InputMediaAudio[]
| readonly InputMediaDocument[]
// 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.
* @deprecated
*/
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
}
}

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

File diff suppressed because it is too large Load Diff

1
node_modules/telegraf/src/utils.ts generated vendored Normal file
View File

@@ -0,0 +1 @@
export { argsParser } from './core/helpers/args'