mirror of
https://github.com/stjornleysi/telegram_glpi.git
synced 2026-02-11 08:40:50 +00:00
init
This commit is contained in:
169
node_modules/telegraf/src/button.ts
generated
vendored
Normal file
169
node_modules/telegraf/src/button.ts
generated
vendored
Normal 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
977
node_modules/telegraf/src/composer.ts
generated
vendored
Normal 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
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
63
node_modules/telegraf/src/core/helpers/args.ts
generated
vendored
Normal 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
71
node_modules/telegraf/src/core/helpers/check.ts
generated
vendored
Normal 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
18
node_modules/telegraf/src/core/helpers/compact.ts
generated
vendored
Normal 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
126
node_modules/telegraf/src/core/helpers/formatting.ts
generated
vendored
Normal 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
69
node_modules/telegraf/src/core/helpers/util.ts
generated
vendored
Normal 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
384
node_modules/telegraf/src/core/network/client.ts
generated
vendored
Normal 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
29
node_modules/telegraf/src/core/network/error.ts
generated
vendored
Normal 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
|
||||
45
node_modules/telegraf/src/core/network/multipart-stream.ts
generated
vendored
Normal file
45
node_modules/telegraf/src/core/network/multipart-stream.ts
generated
vendored
Normal 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
94
node_modules/telegraf/src/core/network/polling.ts
generated
vendored
Normal 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
58
node_modules/telegraf/src/core/network/webhook.ts
generated
vendored
Normal 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
54
node_modules/telegraf/src/core/types/typegram.ts
generated
vendored
Normal 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
27
node_modules/telegraf/src/deunionize.ts
generated
vendored
Normal 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
114
node_modules/telegraf/src/filters.ts
generated
vendored
Normal 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
35
node_modules/telegraf/src/format.ts
generated
vendored
Normal 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
207
node_modules/telegraf/src/future.ts
generated
vendored
Normal 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
17
node_modules/telegraf/src/index.ts
generated
vendored
Normal 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
59
node_modules/telegraf/src/input.ts
generated
vendored
Normal 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
142
node_modules/telegraf/src/markup.ts
generated
vendored
Normal 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
18
node_modules/telegraf/src/middleware.ts
generated
vendored
Normal 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
55
node_modules/telegraf/src/router.ts
generated
vendored
Normal 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
52
node_modules/telegraf/src/scenes/base.ts
generated
vendored
Normal 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
136
node_modules/telegraf/src/scenes/context.ts
generated
vendored
Normal 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
21
node_modules/telegraf/src/scenes/index.ts
generated
vendored
Normal 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
71
node_modules/telegraf/src/scenes/stage.ts
generated
vendored
Normal 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
58
node_modules/telegraf/src/scenes/wizard/context.ts
generated
vendored
Normal 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
63
node_modules/telegraf/src/scenes/wizard/index.ts
generated
vendored
Normal 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
204
node_modules/telegraf/src/session.ts
generated
vendored
Normal 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
340
node_modules/telegraf/src/telegraf.ts
generated
vendored
Normal 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
176
node_modules/telegraf/src/telegram-types.ts
generated
vendored
Normal 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
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
1
node_modules/telegraf/src/utils.ts
generated
vendored
Normal file
@@ -0,0 +1 @@
|
||||
export { argsParser } from './core/helpers/args'
|
||||
Reference in New Issue
Block a user