commit
This commit is contained in:
parent
be4fd23bcf
commit
0bd53741af
728 changed files with 86573 additions and 0 deletions
107
node_modules/telegraf/src/button.ts
generated
vendored
Normal file
107
node_modules/telegraf/src/button.ts
generated
vendored
Normal file
|
@ -0,0 +1,107 @@
|
|||
import { InlineKeyboardButton, KeyboardButton } from './core/types/typegram'
|
||||
|
||||
type Hideable<B> = B & { hide: boolean }
|
||||
|
||||
export function text(
|
||||
text: string,
|
||||
hide = false
|
||||
): Hideable<KeyboardButton.CommonButton> {
|
||||
return { text, hide }
|
||||
}
|
||||
|
||||
export function contactRequest(
|
||||
text: string,
|
||||
hide = false
|
||||
): Hideable<KeyboardButton.RequestContactButton> {
|
||||
return { text, request_contact: true, hide }
|
||||
}
|
||||
|
||||
export function locationRequest(
|
||||
text: string,
|
||||
hide = false
|
||||
): Hideable<KeyboardButton.RequestLocationButton> {
|
||||
return { text, request_location: true, hide }
|
||||
}
|
||||
|
||||
export function pollRequest(
|
||||
text: string,
|
||||
type?: 'quiz' | 'regular',
|
||||
hide = false
|
||||
): Hideable<KeyboardButton.RequestPollButton> {
|
||||
return { text, request_poll: { type }, hide }
|
||||
}
|
||||
|
||||
export function url(
|
||||
text: string,
|
||||
url: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.UrlButton> {
|
||||
return { text, url, hide }
|
||||
}
|
||||
|
||||
export function callback(
|
||||
text: string,
|
||||
data: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.CallbackButton> {
|
||||
return { text, callback_data: data, hide }
|
||||
}
|
||||
|
||||
export function switchToChat(
|
||||
text: string,
|
||||
value: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.SwitchInlineButton> {
|
||||
return { text, switch_inline_query: value, hide }
|
||||
}
|
||||
|
||||
export function switchToCurrentChat(
|
||||
text: string,
|
||||
value: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.SwitchInlineCurrentChatButton> {
|
||||
return { text, switch_inline_query_current_chat: value, hide }
|
||||
}
|
||||
|
||||
export function game(
|
||||
text: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.GameButton> {
|
||||
return { text, callback_game: {}, hide }
|
||||
}
|
||||
|
||||
export function pay(
|
||||
text: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.PayButton> {
|
||||
return { text, pay: true, hide }
|
||||
}
|
||||
|
||||
export function login(
|
||||
text: string,
|
||||
url: string,
|
||||
opts: {
|
||||
forward_text?: string
|
||||
bot_username?: string
|
||||
request_write_access?: boolean
|
||||
} = {},
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.LoginButton> {
|
||||
return {
|
||||
text,
|
||||
login_url: { ...opts, url },
|
||||
hide,
|
||||
}
|
||||
}
|
||||
|
||||
export function webApp(
|
||||
text: string,
|
||||
url: string,
|
||||
hide = false
|
||||
): Hideable<InlineKeyboardButton.WebAppButton> {
|
||||
return {
|
||||
text,
|
||||
web_app: { url },
|
||||
hide,
|
||||
}
|
||||
}
|
855
node_modules/telegraf/src/composer.ts
generated
vendored
Normal file
855
node_modules/telegraf/src/composer.ts
generated
vendored
Normal file
|
@ -0,0 +1,855 @@
|
|||
/** @format */
|
||||
|
||||
import * as tg from './core/types/typegram'
|
||||
import * as tt from './telegram-types'
|
||||
import { Middleware, MiddlewareFn, MiddlewareObj } from './middleware'
|
||||
import Context from './context'
|
||||
import { Expand } from './util'
|
||||
|
||||
export type MaybeArray<T> = T | T[]
|
||||
export type MaybePromise<T> = T | Promise<T>
|
||||
export type NonemptyReadonlyArray<T> = readonly [T, ...T[]]
|
||||
export type Triggers<C> = MaybeArray<
|
||||
string | RegExp | ((value: string, ctx: C) => RegExpExecArray | null)
|
||||
>
|
||||
export type Predicate<T> = (t: T) => boolean
|
||||
export type AsyncPredicate<T> = (t: T) => Promise<boolean>
|
||||
|
||||
export type MatchedMiddleware<
|
||||
C extends Context,
|
||||
T extends tt.UpdateType | tt.MessageSubType = 'message' | 'channel_post'
|
||||
> = NonemptyReadonlyArray<
|
||||
Middleware<MatchedContext<C & { match: RegExpExecArray }, T>>
|
||||
>
|
||||
|
||||
/** Takes: a context type and an update type (or message subtype).
|
||||
Produces: a context that has some properties required, and some undefined.
|
||||
The required ones are those that are always present when the given update (or message) arrives.
|
||||
The undefined ones are those that are always absent when the given update (or message) arrives. */
|
||||
/** @deprecated */
|
||||
type MatchedContext<
|
||||
C extends Context,
|
||||
T extends tt.UpdateType | tt.MessageSubType
|
||||
> = NarrowedContext<C, tt.MountMap[T]>
|
||||
|
||||
/**
|
||||
* Narrows down `C['update']` (and derived getters)
|
||||
* to specific update type `U`.
|
||||
*
|
||||
* Used by [[`Composer`]],
|
||||
* possibly useful for splitting a bot into multiple files.
|
||||
*/
|
||||
export type NarrowedContext<
|
||||
C extends Context,
|
||||
U extends tg.Update
|
||||
> = Context<U> & Omit<C, keyof Context>
|
||||
|
||||
export interface GameQueryUpdate extends tg.Update.CallbackQueryUpdate {
|
||||
callback_query: Expand<
|
||||
Omit<tg.CallbackQuery, 'data'> & {
|
||||
game_short_name: NonNullable<tg.CallbackQuery['game_short_name']>
|
||||
}
|
||||
>
|
||||
}
|
||||
|
||||
function always<T>(x: T) {
|
||||
return () => x
|
||||
}
|
||||
const anoop = always(Promise.resolve())
|
||||
|
||||
export class Composer<C extends Context> implements MiddlewareObj<C> {
|
||||
private handler: MiddlewareFn<C>
|
||||
|
||||
constructor(...fns: ReadonlyArray<Middleware<C>>) {
|
||||
this.handler = Composer.compose(fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a middleware.
|
||||
*/
|
||||
use(...fns: ReadonlyArray<Middleware<C>>) {
|
||||
this.handler = Composer.compose([this.handler, ...fns])
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling updates
|
||||
* matching given type guard function.
|
||||
*/
|
||||
guard<U extends tg.Update>(
|
||||
guardFn: (update: tg.Update) => update is U,
|
||||
...fns: NonemptyReadonlyArray<Middleware<NarrowedContext<C, U>>>
|
||||
) {
|
||||
return this.use(Composer.guard<C, U>(guardFn, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling provided update types.
|
||||
*/
|
||||
on<T extends tt.UpdateType | tt.MessageSubType>(
|
||||
updateType: MaybeArray<T>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, T>>>
|
||||
) {
|
||||
return this.use(Composer.on<C, T>(updateType, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling matching text messages.
|
||||
*/
|
||||
hears(triggers: Triggers<C>, ...fns: MatchedMiddleware<C, 'text'>) {
|
||||
return this.use(Composer.hears<C>(triggers, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling specified commands.
|
||||
*/
|
||||
command(
|
||||
command: MaybeArray<string>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>
|
||||
) {
|
||||
return this.use(Composer.command<C>(command, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling matching callback queries.
|
||||
*/
|
||||
action(
|
||||
triggers: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C, 'callback_query'>
|
||||
) {
|
||||
return this.use(Composer.action<C>(triggers, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling matching inline queries.
|
||||
*/
|
||||
inlineQuery(
|
||||
triggers: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C, 'inline_query'>
|
||||
) {
|
||||
return this.use(Composer.inlineQuery<C>(triggers, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for handling game queries
|
||||
*/
|
||||
gameQuery(
|
||||
...fns: NonemptyReadonlyArray<
|
||||
Middleware<NarrowedContext<C, GameQueryUpdate>>
|
||||
>
|
||||
) {
|
||||
return this.use(Composer.gameQuery(...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers middleware for dropping matching updates.
|
||||
*/
|
||||
drop(predicate: Predicate<C>) {
|
||||
return this.use(Composer.drop(predicate))
|
||||
}
|
||||
|
||||
filter(predicate: Predicate<C>) {
|
||||
return this.use(Composer.filter(predicate))
|
||||
}
|
||||
|
||||
private entity<
|
||||
T extends 'message' | 'channel_post' | tt.MessageSubType =
|
||||
| 'message'
|
||||
| 'channel_post'
|
||||
>(
|
||||
predicate:
|
||||
| MaybeArray<string>
|
||||
| ((entity: tg.MessageEntity, s: string, ctx: C) => boolean),
|
||||
...fns: ReadonlyArray<Middleware<MatchedContext<C, T>>>
|
||||
) {
|
||||
return this.use(Composer.entity<C, T>(predicate, ...fns))
|
||||
}
|
||||
|
||||
email(email: Triggers<C>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.email<C>(email, ...fns))
|
||||
}
|
||||
|
||||
url(url: Triggers<C>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.url<C>(url, ...fns))
|
||||
}
|
||||
|
||||
textLink(link: Triggers<C>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.textLink<C>(link, ...fns))
|
||||
}
|
||||
|
||||
textMention(mention: Triggers<C>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.textMention<C>(mention, ...fns))
|
||||
}
|
||||
|
||||
mention(mention: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.mention<C>(mention, ...fns))
|
||||
}
|
||||
|
||||
phone(number: Triggers<C>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.phone<C>(number, ...fns))
|
||||
}
|
||||
|
||||
hashtag(hashtag: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.hashtag<C>(hashtag, ...fns))
|
||||
}
|
||||
|
||||
cashtag(cashtag: MaybeArray<string>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.cashtag<C>(cashtag, ...fns))
|
||||
}
|
||||
|
||||
spoiler(text: Triggers<C>, ...fns: MatchedMiddleware<C>) {
|
||||
return this.use(Composer.spoiler<C>(text, ...fns))
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a middleware for handling /start
|
||||
*/
|
||||
start(
|
||||
...fns: NonemptyReadonlyArray<
|
||||
Middleware<MatchedContext<C, 'text'> & { startPayload: string }>
|
||||
>
|
||||
) {
|
||||
const handler = Composer.compose(fns)
|
||||
return this.command('start', (ctx, next) => {
|
||||
// First entity is the /start bot_command itself
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
const entity = ctx.message.entities![0]!
|
||||
const startPayload = ctx.message.text.slice(entity.length + 1)
|
||||
return handler(Object.assign(ctx, { startPayload }), next)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a middleware for handling /help
|
||||
*/
|
||||
help(...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>) {
|
||||
return this.command('help', ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a middleware for handling /settings
|
||||
*/
|
||||
settings(
|
||||
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>
|
||||
) {
|
||||
return this.command('settings', ...fns)
|
||||
}
|
||||
|
||||
middleware() {
|
||||
return this.handler
|
||||
}
|
||||
|
||||
static reply(...args: Parameters<Context['reply']>): MiddlewareFn<Context> {
|
||||
return (ctx) => ctx.reply(...args)
|
||||
}
|
||||
|
||||
static catch<C extends Context>(
|
||||
errorHandler: (err: unknown, ctx: C) => void,
|
||||
...fns: ReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
const handler = Composer.compose(fns)
|
||||
// prettier-ignore
|
||||
return (ctx, next) => Promise.resolve(handler(ctx, next))
|
||||
.catch((err) => errorHandler(err, ctx))
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware that runs in the background.
|
||||
*/
|
||||
static fork<C extends Context>(middleware: Middleware<C>): MiddlewareFn<C> {
|
||||
const handler = Composer.unwrap(middleware)
|
||||
return async (ctx, next) => {
|
||||
await Promise.all([handler(ctx, anoop), next()])
|
||||
}
|
||||
}
|
||||
|
||||
static tap<C extends Context>(middleware: Middleware<C>): MiddlewareFn<C> {
|
||||
const handler = Composer.unwrap(middleware)
|
||||
return (ctx, next) =>
|
||||
Promise.resolve(handler(ctx, anoop)).then(() => next())
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware that gives up control to the next middleware.
|
||||
*/
|
||||
static passThru(): MiddlewareFn<Context> {
|
||||
return (ctx, next) => next()
|
||||
}
|
||||
|
||||
static lazy<C extends Context>(
|
||||
factoryFn: (ctx: C) => MaybePromise<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
if (typeof factoryFn !== 'function') {
|
||||
throw new Error('Argument must be a function')
|
||||
}
|
||||
return (ctx, next) =>
|
||||
Promise.resolve(factoryFn(ctx)).then((middleware) =>
|
||||
Composer.unwrap(middleware)(ctx, next)
|
||||
)
|
||||
}
|
||||
|
||||
static log(logFn: (s: string) => void = console.log): MiddlewareFn<Context> {
|
||||
return (ctx, next) => {
|
||||
logFn(JSON.stringify(ctx.update, null, 2))
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param trueMiddleware middleware to run if the predicate returns true
|
||||
* @param falseMiddleware middleware to run if the predicate returns false
|
||||
*/
|
||||
static branch<C extends Context>(
|
||||
predicate: Predicate<C> | AsyncPredicate<C>,
|
||||
trueMiddleware: Middleware<C>,
|
||||
falseMiddleware: Middleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
if (typeof predicate !== 'function') {
|
||||
return Composer.unwrap(predicate ? trueMiddleware : falseMiddleware)
|
||||
}
|
||||
return Composer.lazy<C>((ctx) =>
|
||||
Promise.resolve(predicate(ctx)).then((value) =>
|
||||
value ? trueMiddleware : falseMiddleware
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates optional middleware.
|
||||
* @param predicate predicate to decide on a context object whether to run the middleware
|
||||
* @param middleware middleware to run if the predicate returns true
|
||||
*/
|
||||
static optional<C extends Context>(
|
||||
predicate: Predicate<C> | AsyncPredicate<C>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.branch(
|
||||
predicate,
|
||||
Composer.compose(fns),
|
||||
Composer.passThru()
|
||||
)
|
||||
}
|
||||
|
||||
static filter<C extends Context>(predicate: Predicate<C>): MiddlewareFn<C> {
|
||||
return Composer.branch(predicate, Composer.passThru(), anoop)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for dropping matching updates.
|
||||
*/
|
||||
static drop<C extends Context>(predicate: Predicate<C>): MiddlewareFn<C> {
|
||||
return Composer.branch(predicate, anoop, Composer.passThru())
|
||||
}
|
||||
|
||||
static dispatch<
|
||||
C extends Context,
|
||||
Handlers extends Record<string | number | symbol, Middleware<C>>
|
||||
>(
|
||||
routeFn: (ctx: C) => MaybePromise<keyof Handlers>,
|
||||
handlers: Handlers
|
||||
): Middleware<C> {
|
||||
return Composer.lazy<C>((ctx) =>
|
||||
Promise.resolve(routeFn(ctx)).then((value) => handlers[value])
|
||||
)
|
||||
}
|
||||
|
||||
// EXPLANATION FOR THE ts-expect-error ANNOTATIONS
|
||||
|
||||
// The annotations around function invocations with `...fns` are there
|
||||
// whenever we perform validation logic that the flow analysis of TypeScript
|
||||
// cannot comprehend. We always make sure that the middleware functions are
|
||||
// only invoked with properly constrained context objects, but this cannot be
|
||||
// determined automatically.
|
||||
|
||||
/**
|
||||
* Generates optional middleware based on a predicate that only operates on `ctx.update`.
|
||||
*
|
||||
* Example:
|
||||
* ```ts
|
||||
* import { Composer, Update } from 'telegraf'
|
||||
*
|
||||
* const predicate = (u): u is Update.MessageUpdate => 'message' in u
|
||||
* const middleware = Composer.guard(predicate, (ctx) => {
|
||||
* const message = ctx.update.message
|
||||
* })
|
||||
* ```
|
||||
*
|
||||
* Note that `Composer.on('message')` is preferred over this.
|
||||
*
|
||||
* @param guardFn predicate to decide whether to run the middleware based on the `ctx.update` object
|
||||
* @param fns middleware to run if the predicate returns true
|
||||
* @see `Composer.optional` for a more generic version of this method that allows the predicate to operate on `ctx` itself
|
||||
*/
|
||||
static guard<C extends Context, U extends tg.Update>(
|
||||
guardFn: (u: tg.Update) => u is U,
|
||||
...fns: NonemptyReadonlyArray<Middleware<NarrowedContext<C, U>>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.optional<C>(
|
||||
(ctx) => guardFn(ctx.update),
|
||||
// @ts-expect-error see explanation above
|
||||
...fns
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling provided update types.
|
||||
* @deprecated use `Composer.on`
|
||||
*/
|
||||
static mount<C extends Context, T extends tt.UpdateType | tt.MessageSubType>(
|
||||
updateType: MaybeArray<T>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, T>>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.on(updateType, ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling provided update types.
|
||||
*/
|
||||
static on<C extends Context, T extends tt.UpdateType | tt.MessageSubType>(
|
||||
updateType: MaybeArray<T>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, T>>>
|
||||
): MiddlewareFn<C> {
|
||||
const updateTypes = normalizeTextArguments(updateType)
|
||||
|
||||
const predicate = (
|
||||
update: tg.Update
|
||||
): update is tg.Update & tt.MountMap[T] =>
|
||||
updateTypes.some(
|
||||
(type) =>
|
||||
// Check update type
|
||||
type in update ||
|
||||
// Check message sub-type
|
||||
('message' in update && type in update.message)
|
||||
)
|
||||
|
||||
return Composer.guard<C, tt.MountMap[T]>(predicate, ...fns)
|
||||
}
|
||||
|
||||
private static entity<
|
||||
C extends Context,
|
||||
T extends 'message' | 'channel_post' | tt.MessageSubType =
|
||||
| 'message'
|
||||
| 'channel_post'
|
||||
>(
|
||||
predicate:
|
||||
| MaybeArray<string>
|
||||
| ((entity: tg.MessageEntity, s: string, ctx: C) => boolean),
|
||||
...fns: ReadonlyArray<Middleware<MatchedContext<C, T>>>
|
||||
): MiddlewareFn<C> {
|
||||
if (typeof predicate !== 'function') {
|
||||
const entityTypes = normalizeTextArguments(predicate)
|
||||
return Composer.entity(({ type }) => entityTypes.includes(type), ...fns)
|
||||
}
|
||||
return Composer.optional<C>((ctx) => {
|
||||
const msg: tg.Message | undefined = ctx.message ?? ctx.channelPost
|
||||
if (msg === undefined) {
|
||||
return false
|
||||
}
|
||||
const text = getText(msg)
|
||||
const entities = getEntities(msg)
|
||||
if (text === undefined) return false
|
||||
return entities.some((entity) =>
|
||||
predicate(
|
||||
entity,
|
||||
text.substring(entity.offset, entity.offset + entity.length),
|
||||
ctx
|
||||
)
|
||||
)
|
||||
// @ts-expect-error see explanation above
|
||||
}, ...fns)
|
||||
}
|
||||
|
||||
static entityText<C extends Context>(
|
||||
entityType: MaybeArray<string>,
|
||||
predicate: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
if (fns.length === 0) {
|
||||
// prettier-ignore
|
||||
return Array.isArray(predicate)
|
||||
// @ts-expect-error predicate is really the middleware
|
||||
? Composer.entity(entityType, ...predicate)
|
||||
// @ts-expect-error predicate is really the middleware
|
||||
: Composer.entity(entityType, predicate)
|
||||
}
|
||||
const triggers = normalizeTriggers(predicate)
|
||||
return Composer.entity<C>(({ type }, value, ctx) => {
|
||||
if (type !== entityType) {
|
||||
return false
|
||||
}
|
||||
for (const trigger of triggers) {
|
||||
// @ts-expect-error define so far unknown property `match`
|
||||
if ((ctx.match = trigger(value, ctx))) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
// @ts-expect-error see explanation above
|
||||
}, ...fns)
|
||||
}
|
||||
|
||||
static email<C extends Context>(
|
||||
email: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>('email', email, ...fns)
|
||||
}
|
||||
|
||||
static phone<C extends Context>(
|
||||
number: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>('phone_number', number, ...fns)
|
||||
}
|
||||
|
||||
static url<C extends Context>(
|
||||
url: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>('url', url, ...fns)
|
||||
}
|
||||
|
||||
static textLink<C extends Context>(
|
||||
link: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>('text_link', link, ...fns)
|
||||
}
|
||||
|
||||
static textMention<C extends Context>(
|
||||
mention: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>('text_mention', mention, ...fns)
|
||||
}
|
||||
|
||||
static mention<C extends Context>(
|
||||
mention: MaybeArray<string>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>(
|
||||
'mention',
|
||||
normalizeTextArguments(mention, '@'),
|
||||
...fns
|
||||
)
|
||||
}
|
||||
|
||||
static hashtag<C extends Context>(
|
||||
hashtag: MaybeArray<string>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>(
|
||||
'hashtag',
|
||||
normalizeTextArguments(hashtag, '#'),
|
||||
...fns
|
||||
)
|
||||
}
|
||||
|
||||
static cashtag<C extends Context>(
|
||||
cashtag: MaybeArray<string>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>(
|
||||
'cashtag',
|
||||
normalizeTextArguments(cashtag, '$'),
|
||||
...fns
|
||||
)
|
||||
}
|
||||
|
||||
static spoiler<C extends Context>(
|
||||
text: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.entityText<C>('spoiler', text, ...fns)
|
||||
}
|
||||
|
||||
private static match<
|
||||
C extends Context,
|
||||
T extends
|
||||
| 'message'
|
||||
| 'channel_post'
|
||||
| 'callback_query'
|
||||
| 'inline_query'
|
||||
| tt.MessageSubType
|
||||
>(
|
||||
triggers: ReadonlyArray<(text: string, ctx: C) => RegExpExecArray | null>,
|
||||
...fns: MatchedMiddleware<C, T>
|
||||
): MiddlewareFn<MatchedContext<C, T>> {
|
||||
const handler = Composer.compose(fns)
|
||||
return (ctx, next) => {
|
||||
const text =
|
||||
getText(ctx.message) ??
|
||||
getText(ctx.channelPost) ??
|
||||
getText(ctx.callbackQuery) ??
|
||||
ctx.inlineQuery?.query
|
||||
if (text === undefined) return next()
|
||||
for (const trigger of triggers) {
|
||||
// @ts-expect-error Trust me, TS!
|
||||
const match = trigger(text, ctx)
|
||||
if (match) {
|
||||
// @ts-expect-error define so far unknown property `match`
|
||||
return handler(Object.assign(ctx, { match }), next)
|
||||
}
|
||||
}
|
||||
return next()
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling matching text messages.
|
||||
*/
|
||||
static hears<C extends Context>(
|
||||
triggers: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C, 'text'>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.on(
|
||||
'text',
|
||||
Composer.match<C, 'text'>(normalizeTriggers(triggers), ...fns)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling specified commands.
|
||||
*/
|
||||
static command<C extends Context>(
|
||||
command: MaybeArray<string>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<MatchedContext<C, 'text'>>>
|
||||
): MiddlewareFn<C> {
|
||||
if (fns.length === 0) {
|
||||
// @ts-expect-error command is really the middleware
|
||||
return Composer.entity('bot_command', command)
|
||||
}
|
||||
const commands = normalizeTextArguments(command, '/')
|
||||
return Composer.on<C, 'text'>(
|
||||
'text',
|
||||
Composer.lazy<MatchedContext<C, 'text'>>((ctx) => {
|
||||
const groupCommands =
|
||||
ctx.me && ctx.chat?.type.endsWith('group')
|
||||
? commands.map((command) => `${command}@${ctx.me}`)
|
||||
: []
|
||||
return Composer.entity<MatchedContext<C, 'text'>>(
|
||||
({ offset, type }, value) =>
|
||||
offset === 0 &&
|
||||
type === 'bot_command' &&
|
||||
(commands.includes(value) || groupCommands.includes(value)),
|
||||
// @ts-expect-error see explanation above
|
||||
...fns
|
||||
)
|
||||
})
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling matching callback queries.
|
||||
*/
|
||||
static action<C extends Context>(
|
||||
triggers: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C, 'callback_query'>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.on(
|
||||
'callback_query',
|
||||
Composer.match<C, 'callback_query'>(normalizeTriggers(triggers), ...fns)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling matching inline queries.
|
||||
*/
|
||||
static inlineQuery<C extends Context>(
|
||||
triggers: Triggers<C>,
|
||||
...fns: MatchedMiddleware<C, 'inline_query'>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.on(
|
||||
'inline_query',
|
||||
Composer.match<C, 'inline_query'>(normalizeTriggers(triggers), ...fns)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware responding only to specified users.
|
||||
*/
|
||||
static acl<C extends Context>(
|
||||
userId: MaybeArray<number>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
if (typeof userId === 'function') {
|
||||
return Composer.optional(userId, ...fns)
|
||||
}
|
||||
const allowed = Array.isArray(userId) ? userId : [userId]
|
||||
// prettier-ignore
|
||||
return Composer.optional((ctx) => !ctx.from || allowed.includes(ctx.from.id), ...fns)
|
||||
}
|
||||
|
||||
private static memberStatus<C extends Context>(
|
||||
status: MaybeArray<tg.ChatMember['status']>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
const statuses = Array.isArray(status) ? status : [status]
|
||||
return Composer.optional(async (ctx) => {
|
||||
if (ctx.message === undefined) return false
|
||||
const member = await ctx.getChatMember(ctx.message.from.id)
|
||||
return statuses.includes(member.status)
|
||||
}, ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware responding only to chat admins and chat creator.
|
||||
*/
|
||||
static admin<C extends Context>(
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.memberStatus(['administrator', 'creator'], ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware responding only to chat creator.
|
||||
*/
|
||||
static creator<C extends Context>(
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.memberStatus('creator', ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware running only in specified chat types.
|
||||
*/
|
||||
static chatType<C extends Context>(
|
||||
type: MaybeArray<tg.Chat['type']>,
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
const types = Array.isArray(type) ? type : [type]
|
||||
return Composer.optional((ctx) => {
|
||||
const chat = ctx.chat
|
||||
return chat !== undefined && types.includes(chat.type)
|
||||
}, ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware running only in private chats.
|
||||
*/
|
||||
static privateChat<C extends Context>(
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.chatType('private', ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware running only in groups and supergroups.
|
||||
*/
|
||||
static groupChat<C extends Context>(
|
||||
...fns: NonemptyReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.chatType(['group', 'supergroup'], ...fns)
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates middleware for handling game queries.
|
||||
*/
|
||||
static gameQuery<C extends Context>(
|
||||
...fns: NonemptyReadonlyArray<
|
||||
Middleware<NarrowedContext<C, GameQueryUpdate>>
|
||||
>
|
||||
): MiddlewareFn<C> {
|
||||
return Composer.guard(
|
||||
(u): u is GameQueryUpdate =>
|
||||
'callback_query' in u && 'game_short_name' in u.callback_query,
|
||||
...fns
|
||||
)
|
||||
}
|
||||
|
||||
static unwrap<C extends Context>(handler: Middleware<C>): MiddlewareFn<C> {
|
||||
if (!handler) {
|
||||
throw new Error('Handler is undefined')
|
||||
}
|
||||
return 'middleware' in handler ? handler.middleware() : handler
|
||||
}
|
||||
|
||||
static compose<C extends Context>(
|
||||
middlewares: ReadonlyArray<Middleware<C>>
|
||||
): MiddlewareFn<C> {
|
||||
if (!Array.isArray(middlewares)) {
|
||||
throw new Error('Middlewares must be an array')
|
||||
}
|
||||
if (middlewares.length === 0) {
|
||||
return Composer.passThru()
|
||||
}
|
||||
if (middlewares.length === 1) {
|
||||
// Quite literally asserted in the above condition
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
return Composer.unwrap(middlewares[0]!)
|
||||
}
|
||||
return (ctx, next) => {
|
||||
let index = -1
|
||||
return execute(0, ctx)
|
||||
async function execute(i: number, context: C): Promise<void> {
|
||||
if (!(context instanceof Context)) {
|
||||
throw new Error('next(ctx) called with invalid context')
|
||||
}
|
||||
if (i <= index) {
|
||||
throw new Error('next() called multiple times')
|
||||
}
|
||||
index = i
|
||||
const handler = Composer.unwrap(middlewares[i] ?? next)
|
||||
await handler(context, async (ctx = context) => {
|
||||
await execute(i + 1, ctx)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function escapeRegExp(s: string) {
|
||||
// $& means the whole matched string
|
||||
return s.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&')
|
||||
}
|
||||
|
||||
function normalizeTriggers<C extends Context>(
|
||||
triggers: Triggers<C>
|
||||
): Array<(value: string, ctx: C) => RegExpExecArray | null> {
|
||||
if (!Array.isArray(triggers)) {
|
||||
triggers = [triggers]
|
||||
}
|
||||
return triggers.map((trigger) => {
|
||||
if (!trigger) {
|
||||
throw new Error('Invalid trigger')
|
||||
}
|
||||
if (typeof trigger === 'function') {
|
||||
return trigger
|
||||
}
|
||||
if (trigger instanceof RegExp) {
|
||||
return (value = '') => {
|
||||
trigger.lastIndex = 0
|
||||
return trigger.exec(value)
|
||||
}
|
||||
}
|
||||
const regex = new RegExp(`^${escapeRegExp(trigger)}$`)
|
||||
return (value: string) => regex.exec(value)
|
||||
})
|
||||
}
|
||||
|
||||
function getEntities(msg: tg.Message | undefined): tg.MessageEntity[] {
|
||||
if (msg == null) return []
|
||||
if ('caption_entities' in msg) return msg.caption_entities ?? []
|
||||
if ('entities' in msg) return msg.entities ?? []
|
||||
return []
|
||||
}
|
||||
function getText(
|
||||
msg: tg.Message | tg.CallbackQuery | undefined
|
||||
): string | undefined {
|
||||
if (msg == null) return undefined
|
||||
if ('caption' in msg) return msg.caption
|
||||
if ('text' in msg) return msg.text
|
||||
if ('data' in msg) return msg.data
|
||||
if ('game_short_name' in msg) return msg.game_short_name
|
||||
return undefined
|
||||
}
|
||||
|
||||
function normalizeTextArguments(argument: MaybeArray<string>, prefix = '') {
|
||||
const args = Array.isArray(argument) ? argument : [argument]
|
||||
// prettier-ignore
|
||||
return args
|
||||
.filter(Boolean)
|
||||
.map((arg) => prefix && typeof arg === 'string' && !arg.startsWith(prefix) ? `${prefix}${arg}` : arg)
|
||||
}
|
||||
|
||||
export default Composer
|
1243
node_modules/telegraf/src/context.ts
generated
vendored
Normal file
1243
node_modules/telegraf/src/context.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
71
node_modules/telegraf/src/core/helpers/check.ts
generated
vendored
Normal file
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])
|
||||
}
|
12
node_modules/telegraf/src/core/helpers/compact.ts
generated
vendored
Normal file
12
node_modules/telegraf/src/core/helpers/compact.ts
generated
vendored
Normal file
|
@ -0,0 +1,12 @@
|
|||
export function compactOptions<T extends { [key: string]: unknown }>(
|
||||
options?: T
|
||||
): T | undefined {
|
||||
if (!options) {
|
||||
return options
|
||||
}
|
||||
|
||||
const keys = Object.keys(options) as Array<keyof T>
|
||||
const compactKeys = keys.filter((key) => options[key] !== undefined)
|
||||
const compactEntries = compactKeys.map((key) => [key, options[key]])
|
||||
return Object.fromEntries(compactEntries)
|
||||
}
|
74
node_modules/telegraf/src/core/helpers/formatting.ts
generated
vendored
Normal file
74
node_modules/telegraf/src/core/helpers/formatting.ts
generated
vendored
Normal file
|
@ -0,0 +1,74 @@
|
|||
import { MessageEntity, User } from 'typegram'
|
||||
|
||||
export interface FmtString {
|
||||
text: string
|
||||
entities?: MessageEntity[]
|
||||
parse_mode?: undefined
|
||||
}
|
||||
|
||||
export class FmtString implements FmtString {
|
||||
constructor(public text: string, entities?: MessageEntity[]) {
|
||||
if (entities) {
|
||||
this.entities = entities
|
||||
// force parse_mode to undefined if entities are present
|
||||
this.parse_mode = undefined
|
||||
}
|
||||
}
|
||||
static normalise(content: string | FmtString) {
|
||||
if (typeof content === 'string') return new FmtString(content)
|
||||
return content
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Types {
|
||||
// prettier-ignore
|
||||
export type Containers = 'bold' | 'italic' | 'spoiler' | 'strikethrough' | 'underline'
|
||||
export type NonContainers = 'code' | 'pre'
|
||||
export type Text = Containers | NonContainers
|
||||
}
|
||||
|
||||
type TemplateParts = string | TemplateStringsArray | string[]
|
||||
|
||||
export function _fmt(
|
||||
kind: Types.Containers | 'very-plain'
|
||||
): (parts: TemplateParts, ...items: (string | FmtString)[]) => FmtString
|
||||
export function _fmt(
|
||||
kind: Types.NonContainers
|
||||
): (parts: TemplateParts, ...items: string[]) => FmtString
|
||||
export function _fmt(
|
||||
kind: 'pre',
|
||||
opts: { language: string }
|
||||
): (parts: TemplateParts, ...items: string[]) => FmtString
|
||||
export function _fmt(kind: Types.Text | 'very-plain', opts?: object) {
|
||||
return function fmt(parts: TemplateParts, ...items: (string | FmtString)[]) {
|
||||
let text = ''
|
||||
const entities: MessageEntity[] = []
|
||||
parts = typeof parts === 'string' ? [parts] : parts
|
||||
for (let i = 0; i < parts.length; i++) {
|
||||
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
||||
text += parts[i]!
|
||||
const item = items[i]
|
||||
if (!item) continue
|
||||
if (typeof item === 'string') {
|
||||
text += item
|
||||
continue
|
||||
}
|
||||
for (const child of item.entities || [])
|
||||
entities.push({ ...child, offset: text.length + child.offset })
|
||||
text += item.text
|
||||
}
|
||||
if (kind !== 'very-plain')
|
||||
entities.unshift({ type: kind, offset: 0, length: text.length, ...opts })
|
||||
return new FmtString(text, entities.length ? entities : undefined)
|
||||
}
|
||||
}
|
||||
export const linkOrMention = (
|
||||
content: string | FmtString,
|
||||
data:
|
||||
| { type: 'text_link'; url: string }
|
||||
| { type: 'text_mention'; user: User }
|
||||
) => {
|
||||
const { text, entities = [] } = FmtString.normalise(content)
|
||||
entities.unshift(Object.assign(data, { offset: 0, length: text.length }))
|
||||
return new FmtString(text, entities)
|
||||
}
|
380
node_modules/telegraf/src/core/network/client.ts
generated
vendored
Normal file
380
node_modules/telegraf/src/core/network/client.ts
generated
vendored
Normal file
|
@ -0,0 +1,380 @@
|
|||
/* eslint @typescript-eslint/restrict-template-expressions: [ "error", { "allowNumber": true, "allowBoolean": true } ] */
|
||||
import * as crypto from 'crypto'
|
||||
import * as fs from 'fs'
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
import * as path from 'path'
|
||||
import fetch, { RequestInfo, RequestInit } from 'node-fetch'
|
||||
import { hasProp, hasPropType } from '../helpers/check'
|
||||
import { Opts, Telegram } from '../types/typegram'
|
||||
import { AbortSignal } from 'abort-controller'
|
||||
import { compactOptions } from '../helpers/compact'
|
||||
import MultipartStream from './multipart-stream'
|
||||
import { ReadStream } from 'fs'
|
||||
import TelegramError from './error'
|
||||
import { URL } from 'url'
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const debug = require('debug')('telegraf:client')
|
||||
const { isStream } = MultipartStream
|
||||
|
||||
const WEBHOOK_REPLY_METHOD_ALLOWLIST = new Set<keyof Telegram>([
|
||||
'answerCallbackQuery',
|
||||
'answerInlineQuery',
|
||||
'deleteMessage',
|
||||
'leaveChat',
|
||||
'sendChatAction',
|
||||
])
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-namespace
|
||||
namespace ApiClient {
|
||||
export type Agent = http.Agent | ((parsedUrl: URL) => http.Agent) | undefined
|
||||
export interface Options {
|
||||
/**
|
||||
* Agent for communicating with the bot API.
|
||||
*/
|
||||
agent?: http.Agent
|
||||
/**
|
||||
* Agent for attaching files via URL.
|
||||
* 1. Not all agents support both `http:` and `https:`.
|
||||
* 2. When passing a function, create the agents once, outside of the function.
|
||||
* Creating new agent every request probably breaks `keepAlive`.
|
||||
*/
|
||||
attachmentAgent?: Agent
|
||||
apiRoot: string
|
||||
/**
|
||||
* @default 'bot'
|
||||
* @see https://github.com/tdlight-team/tdlight-telegram-bot-api#user-mode
|
||||
*/
|
||||
apiMode: 'bot' | 'user'
|
||||
webhookReply: boolean
|
||||
testEnv: boolean
|
||||
}
|
||||
|
||||
export interface CallApiOptions {
|
||||
signal?: AbortSignal
|
||||
}
|
||||
}
|
||||
|
||||
const DEFAULT_EXTENSIONS: Record<string, string | undefined> = {
|
||||
audio: 'mp3',
|
||||
photo: 'jpg',
|
||||
sticker: 'webp',
|
||||
video: 'mp4',
|
||||
animation: 'mp4',
|
||||
video_note: 'mp4',
|
||||
voice: 'ogg',
|
||||
}
|
||||
|
||||
const DEFAULT_OPTIONS: ApiClient.Options = {
|
||||
apiRoot: 'https://api.telegram.org',
|
||||
apiMode: 'bot',
|
||||
webhookReply: true,
|
||||
agent: new https.Agent({
|
||||
keepAlive: true,
|
||||
keepAliveMsecs: 10000,
|
||||
}),
|
||||
attachmentAgent: undefined,
|
||||
testEnv: false,
|
||||
}
|
||||
|
||||
function includesMedia(payload: Record<string, unknown>) {
|
||||
return Object.values(payload).some((value) => {
|
||||
if (Array.isArray(value)) {
|
||||
return value.some(
|
||||
({ media }) =>
|
||||
media && typeof media === 'object' && (media.source || media.url)
|
||||
)
|
||||
}
|
||||
return (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
((hasProp(value, 'source') && value.source) ||
|
||||
(hasProp(value, 'url') && value.url) ||
|
||||
(hasPropType(value, 'media', 'object') &&
|
||||
((hasProp(value.media, 'source') && value.media.source) ||
|
||||
(hasProp(value.media, 'url') && value.media.url))))
|
||||
)
|
||||
})
|
||||
}
|
||||
|
||||
function replacer(_: unknown, value: unknown) {
|
||||
if (value == null) return undefined
|
||||
return value
|
||||
}
|
||||
|
||||
function buildJSONConfig(payload: unknown): Promise<RequestInit> {
|
||||
return Promise.resolve({
|
||||
method: 'POST',
|
||||
compress: true,
|
||||
headers: { 'content-type': 'application/json', connection: 'keep-alive' },
|
||||
body: JSON.stringify(payload, replacer),
|
||||
})
|
||||
}
|
||||
|
||||
const FORM_DATA_JSON_FIELDS = [
|
||||
'results',
|
||||
'reply_markup',
|
||||
'mask_position',
|
||||
'shipping_options',
|
||||
'errors',
|
||||
]
|
||||
|
||||
async function buildFormDataConfig(
|
||||
payload: Record<string, unknown>,
|
||||
agent: ApiClient.Agent
|
||||
) {
|
||||
for (const field of FORM_DATA_JSON_FIELDS) {
|
||||
if (hasProp(payload, field) && typeof payload[field] !== 'string') {
|
||||
payload[field] = JSON.stringify(payload[field])
|
||||
}
|
||||
}
|
||||
const boundary = crypto.randomBytes(32).toString('hex')
|
||||
const formData = new MultipartStream(boundary)
|
||||
const tasks = Object.keys(payload).map((key) =>
|
||||
attachFormValue(formData, key, payload[key], agent)
|
||||
)
|
||||
await Promise.all(tasks)
|
||||
return {
|
||||
method: 'POST',
|
||||
compress: true,
|
||||
headers: {
|
||||
'content-type': `multipart/form-data; boundary=${boundary}`,
|
||||
connection: 'keep-alive',
|
||||
},
|
||||
body: formData,
|
||||
}
|
||||
}
|
||||
|
||||
async function attachFormValue(
|
||||
form: MultipartStream,
|
||||
id: string,
|
||||
value: unknown,
|
||||
agent: ApiClient.Agent
|
||||
) {
|
||||
if (value == null) {
|
||||
return
|
||||
}
|
||||
if (
|
||||
typeof value === 'string' ||
|
||||
typeof value === 'boolean' ||
|
||||
typeof value === 'number'
|
||||
) {
|
||||
form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: `${value}`,
|
||||
})
|
||||
return
|
||||
}
|
||||
if (id === 'thumb') {
|
||||
const attachmentId = crypto.randomBytes(16).toString('hex')
|
||||
await attachFormMedia(form, value as FormMedia, attachmentId, agent)
|
||||
return form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: `attach://${attachmentId}`,
|
||||
})
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
const items = await Promise.all(
|
||||
value.map(async (item) => {
|
||||
if (typeof item.media !== 'object') {
|
||||
return await Promise.resolve(item)
|
||||
}
|
||||
const attachmentId = crypto.randomBytes(16).toString('hex')
|
||||
await attachFormMedia(form, item.media, attachmentId, agent)
|
||||
return { ...item, media: `attach://${attachmentId}` }
|
||||
})
|
||||
)
|
||||
return form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: JSON.stringify(items),
|
||||
})
|
||||
}
|
||||
if (
|
||||
value &&
|
||||
typeof value === 'object' &&
|
||||
hasProp(value, 'media') &&
|
||||
hasProp(value, 'type') &&
|
||||
typeof value.media !== 'undefined' &&
|
||||
typeof value.type !== 'undefined'
|
||||
) {
|
||||
const attachmentId = crypto.randomBytes(16).toString('hex')
|
||||
await attachFormMedia(form, value.media as FormMedia, attachmentId, agent)
|
||||
return form.addPart({
|
||||
headers: { 'content-disposition': `form-data; name="${id}"` },
|
||||
body: JSON.stringify({
|
||||
...value,
|
||||
media: `attach://${attachmentId}`,
|
||||
}),
|
||||
})
|
||||
}
|
||||
return await attachFormMedia(form, value as FormMedia, id, agent)
|
||||
}
|
||||
|
||||
interface FormMedia {
|
||||
filename?: string
|
||||
url?: RequestInfo
|
||||
source?: string
|
||||
}
|
||||
async function attachFormMedia(
|
||||
form: MultipartStream,
|
||||
media: FormMedia,
|
||||
id: string,
|
||||
agent: ApiClient.Agent
|
||||
) {
|
||||
let fileName = media.filename ?? `${id}.${DEFAULT_EXTENSIONS[id] ?? 'dat'}`
|
||||
if (media.url !== undefined) {
|
||||
const res = await fetch(media.url, { agent })
|
||||
return form.addPart({
|
||||
headers: {
|
||||
'content-disposition': `form-data; name="${id}"; filename="${fileName}"`,
|
||||
},
|
||||
body: res.body,
|
||||
})
|
||||
}
|
||||
if (media.source) {
|
||||
let mediaSource: string | ReadStream = media.source
|
||||
if (fs.existsSync(media.source)) {
|
||||
fileName = media.filename ?? path.basename(media.source)
|
||||
mediaSource = fs.createReadStream(media.source)
|
||||
}
|
||||
if (isStream(mediaSource) || Buffer.isBuffer(mediaSource)) {
|
||||
form.addPart({
|
||||
headers: {
|
||||
'content-disposition': `form-data; name="${id}"; filename="${fileName}"`,
|
||||
},
|
||||
body: mediaSource,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function answerToWebhook(
|
||||
response: Response,
|
||||
payload: Record<string, unknown>,
|
||||
options: ApiClient.Options
|
||||
): Promise<true> {
|
||||
if (!includesMedia(payload)) {
|
||||
if (!response.headersSent) {
|
||||
response.setHeader('content-type', 'application/json')
|
||||
}
|
||||
response.end(JSON.stringify(payload), 'utf-8')
|
||||
return true
|
||||
}
|
||||
|
||||
const { headers, body } = await buildFormDataConfig(
|
||||
payload,
|
||||
options.attachmentAgent
|
||||
)
|
||||
if (!response.headersSent) {
|
||||
for (const [key, value] of Object.entries(headers)) {
|
||||
response.setHeader(key, value)
|
||||
}
|
||||
}
|
||||
await new Promise((resolve) => {
|
||||
response.on('finish', resolve)
|
||||
body.pipe(response)
|
||||
})
|
||||
return true
|
||||
}
|
||||
|
||||
function redactToken(error: Error): never {
|
||||
error.message = error.message.replace(
|
||||
/\/(bot|user)(\d+):[^/]+\//,
|
||||
'/$1$2:[REDACTED]/'
|
||||
)
|
||||
throw error
|
||||
}
|
||||
|
||||
type Response = http.ServerResponse
|
||||
class ApiClient {
|
||||
readonly options: ApiClient.Options
|
||||
|
||||
constructor(
|
||||
readonly token: string,
|
||||
options?: Partial<ApiClient.Options>,
|
||||
private readonly response?: Response
|
||||
) {
|
||||
this.options = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...compactOptions(options),
|
||||
}
|
||||
if (this.options.apiRoot.startsWith('http://')) {
|
||||
this.options.agent = undefined
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If set to `true`, first _eligible_ call will avoid performing a POST request.
|
||||
* Note that such a call:
|
||||
* 1. cannot report errors or return meaningful values,
|
||||
* 2. resolves before bot API has a chance to process it,
|
||||
* 3. prematurely confirms the update as processed.
|
||||
*
|
||||
* https://core.telegram.org/bots/faq#how-can-i-make-requests-in-response-to-updates
|
||||
* https://github.com/telegraf/telegraf/pull/1250
|
||||
*/
|
||||
set webhookReply(enable: boolean) {
|
||||
this.options.webhookReply = enable
|
||||
}
|
||||
|
||||
get webhookReply() {
|
||||
return this.options.webhookReply
|
||||
}
|
||||
|
||||
async callApi<M extends keyof Telegram>(
|
||||
method: M,
|
||||
payload: Opts<M>,
|
||||
{ signal }: ApiClient.CallApiOptions = {}
|
||||
): Promise<ReturnType<Telegram[M]>> {
|
||||
const { token, options, response } = this
|
||||
|
||||
if (
|
||||
options.webhookReply &&
|
||||
response?.writableEnded === false &&
|
||||
WEBHOOK_REPLY_METHOD_ALLOWLIST.has(method)
|
||||
) {
|
||||
debug('Call via webhook', method, payload)
|
||||
// @ts-expect-error using webhookReply is an optimisation that doesn't respond with normal result
|
||||
// up to the user to deal with this
|
||||
return await answerToWebhook(response, { method, ...payload }, options)
|
||||
}
|
||||
|
||||
if (!token) {
|
||||
throw new TelegramError({
|
||||
error_code: 401,
|
||||
description: 'Bot Token is required',
|
||||
})
|
||||
}
|
||||
|
||||
debug('HTTP call', method, payload)
|
||||
|
||||
const config: RequestInit = includesMedia(payload)
|
||||
? await buildFormDataConfig(
|
||||
{ method, ...payload },
|
||||
options.attachmentAgent
|
||||
)
|
||||
: await buildJSONConfig(payload)
|
||||
const apiUrl = new URL(
|
||||
`./${options.apiMode}${token}${options.testEnv ? '/test' : ''}/${method}`,
|
||||
options.apiRoot
|
||||
)
|
||||
config.agent = options.agent
|
||||
config.signal = signal
|
||||
const res = await fetch(apiUrl, config).catch(redactToken)
|
||||
if (res.status >= 500) {
|
||||
const errorPayload = {
|
||||
error_code: res.status,
|
||||
description: res.statusText,
|
||||
}
|
||||
throw new TelegramError(errorPayload, { method, payload })
|
||||
}
|
||||
const data = await res.json()
|
||||
if (!data.ok) {
|
||||
debug('API call failed', data)
|
||||
throw new TelegramError(data, { method, payload })
|
||||
}
|
||||
return data.result
|
||||
}
|
||||
}
|
||||
|
||||
export default ApiClient
|
26
node_modules/telegraf/src/core/network/error.ts
generated
vendored
Normal file
26
node_modules/telegraf/src/core/network/error.ts
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
import { ResponseParameters } from '../types/typegram'
|
||||
|
||||
interface ErrorPayload {
|
||||
error_code: number
|
||||
description: string
|
||||
parameters?: ResponseParameters
|
||||
}
|
||||
export class TelegramError extends Error {
|
||||
constructor(readonly response: ErrorPayload, readonly on = {}) {
|
||||
super(`${response.error_code}: ${response.description}`)
|
||||
}
|
||||
|
||||
get code() {
|
||||
return this.response.error_code
|
||||
}
|
||||
|
||||
get description() {
|
||||
return this.response.description
|
||||
}
|
||||
|
||||
get parameters() {
|
||||
return this.response.parameters
|
||||
}
|
||||
}
|
||||
|
||||
export default TelegramError
|
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 | string
|
||||
}
|
||||
|
||||
class MultipartStream extends SandwichStream {
|
||||
constructor(boundary: string) {
|
||||
super({
|
||||
head: `--${boundary}${CRNL}`,
|
||||
tail: `${CRNL}--${boundary}--`,
|
||||
separator: `${CRNL}--${boundary}${CRNL}`,
|
||||
})
|
||||
}
|
||||
|
||||
addPart(part: Part) {
|
||||
const partStream = new stream.PassThrough()
|
||||
for (const [key, header] of Object.entries(part.headers)) {
|
||||
partStream.write(`${key}:${header}${CRNL}`)
|
||||
}
|
||||
partStream.write(CRNL)
|
||||
if (MultipartStream.isStream(part.body)) {
|
||||
part.body.pipe(partStream)
|
||||
} else {
|
||||
partStream.end(part.body)
|
||||
}
|
||||
this.add(partStream)
|
||||
}
|
||||
|
||||
static isStream(
|
||||
stream: unknown
|
||||
): stream is { pipe: MultipartStream['pipe'] } {
|
||||
return (
|
||||
typeof stream === 'object' &&
|
||||
stream !== null &&
|
||||
hasPropType(stream, 'pipe', 'function')
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default MultipartStream
|
96
node_modules/telegraf/src/core/network/polling.ts
generated
vendored
Normal file
96
node_modules/telegraf/src/core/network/polling.ts
generated
vendored
Normal file
|
@ -0,0 +1,96 @@
|
|||
import * as tg from '../types/typegram'
|
||||
import * as tt from '../../telegram-types'
|
||||
import AbortController from 'abort-controller'
|
||||
import ApiClient from './client'
|
||||
import d from 'debug'
|
||||
import { promisify } from 'util'
|
||||
import { TelegramError } from './error'
|
||||
const debug = d('telegraf:polling')
|
||||
const wait = promisify(setTimeout)
|
||||
function always<T>(x: T) {
|
||||
return () => x
|
||||
}
|
||||
const noop = always(Promise.resolve())
|
||||
|
||||
export class Polling {
|
||||
private readonly abortController = new AbortController()
|
||||
private skipOffsetSync = false
|
||||
private offset = 0
|
||||
constructor(
|
||||
private readonly telegram: ApiClient,
|
||||
private readonly allowedUpdates: readonly tt.UpdateType[]
|
||||
) {}
|
||||
|
||||
private async *[Symbol.asyncIterator]() {
|
||||
debug('Starting long polling')
|
||||
do {
|
||||
try {
|
||||
const updates = await this.telegram.callApi(
|
||||
'getUpdates',
|
||||
{
|
||||
timeout: 50,
|
||||
offset: this.offset,
|
||||
allowed_updates: this.allowedUpdates,
|
||||
},
|
||||
this.abortController
|
||||
)
|
||||
const last = updates[updates.length - 1]
|
||||
if (last !== undefined) {
|
||||
this.offset = last.update_id + 1
|
||||
}
|
||||
yield updates
|
||||
} catch (error) {
|
||||
const err = error as Error & {
|
||||
parameters?: { retry_after: number }
|
||||
}
|
||||
|
||||
if (err.name === 'AbortError') return
|
||||
if (
|
||||
err.name === 'FetchError' ||
|
||||
(err instanceof TelegramError && err.code === 429) ||
|
||||
(err instanceof TelegramError && err.code >= 500)
|
||||
) {
|
||||
const retryAfter: number = err.parameters?.retry_after ?? 5
|
||||
debug('Failed to fetch updates, retrying after %ds.', retryAfter, err)
|
||||
await wait(retryAfter * 1000)
|
||||
continue
|
||||
}
|
||||
if (
|
||||
err instanceof TelegramError &&
|
||||
// Unauthorized Conflict
|
||||
(err.code === 401 || err.code === 409)
|
||||
) {
|
||||
this.skipOffsetSync = true
|
||||
throw err
|
||||
}
|
||||
throw err
|
||||
}
|
||||
} while (!this.abortController.signal.aborted)
|
||||
}
|
||||
|
||||
private async syncUpdateOffset() {
|
||||
if (this.skipOffsetSync) return
|
||||
debug('Syncing update offset...')
|
||||
await this.telegram.callApi('getUpdates', { offset: this.offset, limit: 1 })
|
||||
}
|
||||
|
||||
async loop(handleUpdate: (updates: tg.Update) => Promise<void>) {
|
||||
if (this.abortController.signal.aborted) {
|
||||
throw new Error('Polling instances must not be reused!')
|
||||
}
|
||||
try {
|
||||
for await (const updates of this) {
|
||||
await Promise.all(updates.map(handleUpdate))
|
||||
}
|
||||
} finally {
|
||||
debug('Long polling stopped')
|
||||
// prevent instance reuse
|
||||
this.stop()
|
||||
await this.syncUpdateOffset().catch(noop)
|
||||
}
|
||||
}
|
||||
|
||||
stop() {
|
||||
this.abortController.abort()
|
||||
}
|
||||
}
|
58
node_modules/telegraf/src/core/network/webhook.ts
generated
vendored
Normal file
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)
|
||||
}
|
||||
}
|
55
node_modules/telegraf/src/core/types/typegram.ts
generated
vendored
Normal file
55
node_modules/telegraf/src/core/types/typegram.ts
generated
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
import { Typegram } from 'typegram'
|
||||
|
||||
// internal type provisions
|
||||
export * from 'typegram/api'
|
||||
export * from 'typegram/markup'
|
||||
export * from 'typegram/menu-button'
|
||||
export * from 'typegram/inline'
|
||||
export * from 'typegram/manage'
|
||||
export * from 'typegram/message'
|
||||
export * from 'typegram/passport'
|
||||
export * from 'typegram/payment'
|
||||
export * from 'typegram/update'
|
||||
|
||||
// telegraf input file definition
|
||||
interface InputFileByPath {
|
||||
source: string
|
||||
filename?: string
|
||||
}
|
||||
interface InputFileByReadableStream {
|
||||
source: NodeJS.ReadableStream
|
||||
filename?: string
|
||||
}
|
||||
interface InputFileByBuffer {
|
||||
source: Buffer
|
||||
filename?: string
|
||||
}
|
||||
interface InputFileByURL {
|
||||
url: string
|
||||
filename?: string
|
||||
}
|
||||
export type InputFile =
|
||||
| InputFileByPath
|
||||
| InputFileByReadableStream
|
||||
| InputFileByBuffer
|
||||
| InputFileByURL
|
||||
|
||||
// typegram proxy type setup
|
||||
type TelegrafTypegram = Typegram<InputFile>
|
||||
|
||||
export type Telegram = TelegrafTypegram['Telegram']
|
||||
export type Opts<M extends keyof Telegram> = TelegrafTypegram['Opts'][M]
|
||||
export type InputMedia = TelegrafTypegram['InputMedia']
|
||||
export type InputMediaPhoto = TelegrafTypegram['InputMediaPhoto']
|
||||
export type InputMediaVideo = TelegrafTypegram['InputMediaVideo']
|
||||
export type InputMediaAnimation = TelegrafTypegram['InputMediaAnimation']
|
||||
export type InputMediaAudio = TelegrafTypegram['InputMediaAudio']
|
||||
export type InputMediaDocument = TelegrafTypegram['InputMediaDocument']
|
||||
|
||||
// tiny helper types
|
||||
export type ChatAction = Opts<'sendChatAction'>['action']
|
||||
|
||||
/**
|
||||
* Sending video notes by a URL is currently unsupported
|
||||
*/
|
||||
export type InputFileVideoNote = Exclude<InputFile, InputFileByURL>
|
26
node_modules/telegraf/src/deunionize.ts
generated
vendored
Normal file
26
node_modules/telegraf/src/deunionize.ts
generated
vendored
Normal file
|
@ -0,0 +1,26 @@
|
|||
export type PropOr<
|
||||
T extends object | undefined,
|
||||
P extends string | symbol | number,
|
||||
D = undefined
|
||||
> = T extends Partial<Record<P, unknown>> ? T[P] : D
|
||||
|
||||
export type UnionKeys<T> = T extends unknown ? keyof T : never
|
||||
|
||||
type AddOptionalKeys<K extends PropertyKey> = { readonly [P in K]?: undefined }
|
||||
|
||||
/**
|
||||
* @see https://millsp.github.io/ts-toolbelt/modules/union_strict.html
|
||||
*/
|
||||
export type Deunionize<
|
||||
B extends object | undefined,
|
||||
T extends B = B
|
||||
> = T extends object ? T & AddOptionalKeys<Exclude<UnionKeys<B>, keyof T>> : T
|
||||
|
||||
/**
|
||||
* Expose properties from all union variants.
|
||||
* @see https://github.com/telegraf/telegraf/issues/1388#issuecomment-791573609
|
||||
* @see https://millsp.github.io/ts-toolbelt/modules/union_strict.html
|
||||
*/
|
||||
export function deunionize<T extends object | undefined>(t: T) {
|
||||
return t as Deunionize<T>
|
||||
}
|
21
node_modules/telegraf/src/format.ts
generated
vendored
Normal file
21
node_modules/telegraf/src/format.ts
generated
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
import { User } from 'typegram'
|
||||
import { FmtString, _fmt, linkOrMention } from './core/helpers/formatting'
|
||||
|
||||
export { FmtString }
|
||||
|
||||
export const fmt = _fmt('very-plain')
|
||||
export const bold = _fmt('bold')
|
||||
export const italic = _fmt('italic')
|
||||
export const spoiler = _fmt('spoiler')
|
||||
export const strikethrough = _fmt('strikethrough')
|
||||
export const underline = _fmt('underline')
|
||||
export const code = _fmt('code')
|
||||
export const pre = (language: string) => _fmt('pre', { language })
|
||||
|
||||
export const link = (content: string | FmtString, url: string) =>
|
||||
linkOrMention(content, { type: 'text_link', url })
|
||||
|
||||
export const mention = (name: string | FmtString, user: number | User) =>
|
||||
typeof user === 'number'
|
||||
? link(name, 'tg://user?id=' + user)
|
||||
: linkOrMention(name, { type: 'text_mention', user })
|
207
node_modules/telegraf/src/future.ts
generated
vendored
Normal file
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 } from './context'
|
||||
export { Composer, NarrowedContext } from './composer'
|
||||
export { Middleware, MiddlewareFn, MiddlewareObj } from './middleware'
|
||||
export { Router } from './router'
|
||||
export { TelegramError } from './core/network/error'
|
||||
export { Telegram } from './telegram'
|
||||
export * as Types from './telegram-types'
|
||||
|
||||
export * as Markup from './markup'
|
||||
export * as Input from './input'
|
||||
export * as Format from './format'
|
||||
|
||||
export { deunionize } from './deunionize'
|
||||
export { session, MemorySessionStore } from './session'
|
||||
|
||||
export * as Scenes from './scenes'
|
59
node_modules/telegraf/src/input.ts
generated
vendored
Normal file
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
|
135
node_modules/telegraf/src/markup.ts
generated
vendored
Normal file
135
node_modules/telegraf/src/markup.ts
generated
vendored
Normal file
|
@ -0,0 +1,135 @@
|
|||
import {
|
||||
ForceReply,
|
||||
InlineKeyboardButton,
|
||||
InlineKeyboardMarkup,
|
||||
KeyboardButton,
|
||||
ReplyKeyboardMarkup,
|
||||
ReplyKeyboardRemove,
|
||||
} from './core/types/typegram'
|
||||
import { is2D } from './core/helpers/check'
|
||||
|
||||
type Hideable<B> = B & { hide?: boolean }
|
||||
type HideableKBtn = Hideable<KeyboardButton>
|
||||
type HideableIKBtn = Hideable<InlineKeyboardButton>
|
||||
|
||||
export class Markup<
|
||||
T extends
|
||||
| InlineKeyboardMarkup
|
||||
| ReplyKeyboardMarkup
|
||||
| ReplyKeyboardRemove
|
||||
| ForceReply
|
||||
> {
|
||||
constructor(readonly reply_markup: T) {}
|
||||
|
||||
selective<T extends ForceReply | ReplyKeyboardMarkup>(
|
||||
this: Markup<T>,
|
||||
value = true
|
||||
) {
|
||||
return new Markup<T>({ ...this.reply_markup, selective: value })
|
||||
}
|
||||
|
||||
placeholder<T extends ForceReply | ReplyKeyboardMarkup>(
|
||||
this: Markup<T>,
|
||||
placeholder: string
|
||||
) {
|
||||
return new Markup<T>({
|
||||
...this.reply_markup,
|
||||
input_field_placeholder: placeholder,
|
||||
})
|
||||
}
|
||||
|
||||
resize(this: Markup<ReplyKeyboardMarkup>, value = true) {
|
||||
return new Markup<ReplyKeyboardMarkup>({
|
||||
...this.reply_markup,
|
||||
resize_keyboard: value,
|
||||
})
|
||||
}
|
||||
|
||||
oneTime(this: Markup<ReplyKeyboardMarkup>, value = true) {
|
||||
return new Markup<ReplyKeyboardMarkup>({
|
||||
...this.reply_markup,
|
||||
one_time_keyboard: value,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export * as button from './button'
|
||||
|
||||
export function removeKeyboard(): Markup<ReplyKeyboardRemove> {
|
||||
return new Markup<ReplyKeyboardRemove>({ remove_keyboard: true })
|
||||
}
|
||||
|
||||
export function forceReply(): Markup<ForceReply> {
|
||||
return new Markup<ForceReply>({ force_reply: true })
|
||||
}
|
||||
|
||||
export function keyboard(buttons: HideableKBtn[][]): Markup<ReplyKeyboardMarkup>
|
||||
export function keyboard(
|
||||
buttons: HideableKBtn[],
|
||||
options?: Partial<KeyboardBuildingOptions<HideableKBtn>>
|
||||
): Markup<ReplyKeyboardMarkup>
|
||||
export function keyboard(
|
||||
buttons: HideableKBtn[] | HideableKBtn[][],
|
||||
options?: Partial<KeyboardBuildingOptions<HideableKBtn>>
|
||||
): Markup<ReplyKeyboardMarkup> {
|
||||
const keyboard = buildKeyboard(buttons, {
|
||||
columns: 1,
|
||||
...options,
|
||||
})
|
||||
return new Markup<ReplyKeyboardMarkup>({ keyboard })
|
||||
}
|
||||
|
||||
export function inlineKeyboard(
|
||||
buttons: HideableIKBtn[][]
|
||||
): Markup<InlineKeyboardMarkup>
|
||||
export function inlineKeyboard(
|
||||
buttons: HideableIKBtn[],
|
||||
options?: Partial<KeyboardBuildingOptions<HideableIKBtn>>
|
||||
): Markup<InlineKeyboardMarkup>
|
||||
export function inlineKeyboard(
|
||||
buttons: HideableIKBtn[] | HideableIKBtn[][],
|
||||
options?: Partial<KeyboardBuildingOptions<HideableIKBtn>>
|
||||
): Markup<InlineKeyboardMarkup> {
|
||||
const inlineKeyboard = buildKeyboard(buttons, {
|
||||
columns: buttons.length,
|
||||
...options,
|
||||
})
|
||||
return new Markup<InlineKeyboardMarkup>({ inline_keyboard: inlineKeyboard })
|
||||
}
|
||||
|
||||
interface KeyboardBuildingOptions<B extends HideableKBtn | HideableIKBtn> {
|
||||
wrap?: (btn: B, index: number, currentRow: B[]) => boolean
|
||||
columns: number
|
||||
}
|
||||
|
||||
function buildKeyboard<B extends HideableKBtn | HideableIKBtn>(
|
||||
buttons: B[] | B[][],
|
||||
options: KeyboardBuildingOptions<B>
|
||||
): B[][] {
|
||||
const result: B[][] = []
|
||||
if (!Array.isArray(buttons)) {
|
||||
return result
|
||||
}
|
||||
if (is2D(buttons)) {
|
||||
return buttons.map((row) => row.filter((button) => !button.hide))
|
||||
}
|
||||
const wrapFn =
|
||||
options.wrap !== undefined
|
||||
? options.wrap
|
||||
: (_btn: B, _index: number, currentRow: B[]) =>
|
||||
currentRow.length >= options.columns
|
||||
let currentRow: B[] = []
|
||||
let index = 0
|
||||
for (const btn of buttons.filter((button) => !button.hide)) {
|
||||
if (wrapFn(btn, index, currentRow) && currentRow.length > 0) {
|
||||
result.push(currentRow)
|
||||
currentRow = []
|
||||
}
|
||||
currentRow.push(btn)
|
||||
index++
|
||||
}
|
||||
if (currentRow.length > 0) {
|
||||
result.push(currentRow)
|
||||
}
|
||||
return result
|
||||
}
|
18
node_modules/telegraf/src/middleware.ts
generated
vendored
Normal file
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 = this.options.defaultSession
|
||||
|
||||
let session = this.ctx.session?.__scenes ?? defaultSession
|
||||
if (session.expires !== undefined && session.expires < now()) {
|
||||
session = defaultSession
|
||||
}
|
||||
if (this.ctx.session === undefined) {
|
||||
this.ctx.session = { __scenes: session }
|
||||
} else {
|
||||
this.ctx.session.__scenes = session
|
||||
}
|
||||
return session
|
||||
}
|
||||
|
||||
get state() {
|
||||
return (this.session.state ??= {})
|
||||
}
|
||||
|
||||
set state(value) {
|
||||
this.session.state = { ...value }
|
||||
}
|
||||
|
||||
get current() {
|
||||
const sceneId = this.session.current ?? this.options.default
|
||||
return sceneId === undefined || !this.scenes.has(sceneId)
|
||||
? undefined
|
||||
: this.scenes.get(sceneId)
|
||||
}
|
||||
|
||||
reset() {
|
||||
if (this.ctx.session !== undefined)
|
||||
this.ctx.session.__scenes = this.options.defaultSession
|
||||
}
|
||||
|
||||
async enter(sceneId: string, initialState: object = {}, silent = false) {
|
||||
if (!this.scenes.has(sceneId)) {
|
||||
throw new Error(`Can't find scene: ${sceneId}`)
|
||||
}
|
||||
if (!silent) {
|
||||
await this.leave()
|
||||
}
|
||||
debug('Entering scene', sceneId, initialState, silent)
|
||||
this.session.current = sceneId
|
||||
this.state = initialState
|
||||
const ttl = this.current?.ttl ?? this.options.ttl
|
||||
if (ttl !== undefined) {
|
||||
this.session.expires = now() + ttl
|
||||
}
|
||||
if (this.current === undefined || silent) {
|
||||
return
|
||||
}
|
||||
const handler =
|
||||
'enterMiddleware' in this.current &&
|
||||
typeof this.current.enterMiddleware === 'function'
|
||||
? this.current.enterMiddleware()
|
||||
: this.current.middleware()
|
||||
return await handler(this.ctx, noop)
|
||||
}
|
||||
|
||||
reenter() {
|
||||
return this.session.current === undefined
|
||||
? undefined
|
||||
: this.enter(this.session.current, this.state)
|
||||
}
|
||||
|
||||
private leaving = false
|
||||
async leave() {
|
||||
if (this.leaving) return
|
||||
debug('Leaving scene')
|
||||
try {
|
||||
this.leaving = true
|
||||
if (this.current === undefined) {
|
||||
return
|
||||
}
|
||||
const handler =
|
||||
'leaveMiddleware' in this.current &&
|
||||
typeof this.current.leaveMiddleware === 'function'
|
||||
? this.current.leaveMiddleware()
|
||||
: Composer.passThru()
|
||||
await handler(this.ctx, noop)
|
||||
return this.reset()
|
||||
} finally {
|
||||
this.leaving = false
|
||||
}
|
||||
}
|
||||
}
|
21
node_modules/telegraf/src/scenes/index.ts
generated
vendored
Normal file
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()])
|
||||
}
|
||||
}
|
95
node_modules/telegraf/src/session.ts
generated
vendored
Normal file
95
node_modules/telegraf/src/session.ts
generated
vendored
Normal file
|
@ -0,0 +1,95 @@
|
|||
import { Context } from './context'
|
||||
import { MaybePromise } from './composer'
|
||||
import { MiddlewareFn } from './middleware'
|
||||
|
||||
export interface SessionStore<T> {
|
||||
get: (name: string) => MaybePromise<T | undefined>
|
||||
set: (name: string, value: T) => MaybePromise<void>
|
||||
delete: (name: string) => MaybePromise<void>
|
||||
}
|
||||
|
||||
interface SessionOptions<S extends object> {
|
||||
getSessionKey?: (ctx: Context) => Promise<string | undefined>
|
||||
store?: SessionStore<S>
|
||||
}
|
||||
|
||||
export interface SessionContext<S extends object> extends Context {
|
||||
session?: S
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns middleware that adds `ctx.session` for storing arbitrary state per session key.
|
||||
*
|
||||
* The default `getSessionKey` is <code>\`${ctx.from.id}:${ctx.chat.id}\`</code>.
|
||||
* If either `ctx.from` or `ctx.chat` is `undefined`, default session key and thus `ctx.session` are also `undefined`.
|
||||
*
|
||||
* Session data is kept only in memory by default,
|
||||
* which means that all data will be lost when the process is terminated.
|
||||
* If you want to store data across restarts, or share it among workers,
|
||||
* you can [install persistent session middleware from npm](https://www.npmjs.com/search?q=telegraf-session),
|
||||
* or pass custom `storage`.
|
||||
*
|
||||
* @example https://github.com/telegraf/telegraf/blob/develop/docs/examples/session-bot.ts
|
||||
* @deprecated https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499
|
||||
*/
|
||||
export function session<S extends object>(
|
||||
options?: SessionOptions<S>
|
||||
): MiddlewareFn<SessionContext<S>> {
|
||||
const getSessionKey = options?.getSessionKey ?? defaultGetSessionKey
|
||||
const store = options?.store ?? new MemorySessionStore()
|
||||
return async (ctx, next) => {
|
||||
const key = await getSessionKey(ctx)
|
||||
if (key == null) {
|
||||
return await next()
|
||||
}
|
||||
ctx.session = await store.get(key)
|
||||
await next()
|
||||
if (ctx.session == null) {
|
||||
await store.delete(key)
|
||||
} else {
|
||||
await store.set(key, ctx.session)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async function defaultGetSessionKey(ctx: Context): Promise<string | undefined> {
|
||||
const fromId = ctx.from?.id
|
||||
const chatId = ctx.chat?.id
|
||||
if (fromId == null || chatId == null) {
|
||||
return undefined
|
||||
}
|
||||
return `${fromId}:${chatId}`
|
||||
}
|
||||
|
||||
/** @deprecated https://github.com/telegraf/telegraf/issues/1372#issuecomment-782668499 */
|
||||
export class MemorySessionStore<T> implements SessionStore<T> {
|
||||
private readonly store = new Map<string, { session: T; expires: number }>()
|
||||
|
||||
constructor(private readonly ttl = Infinity) {}
|
||||
|
||||
get(name: string): T | undefined {
|
||||
const entry = this.store.get(name)
|
||||
if (entry == null) {
|
||||
return undefined
|
||||
} else if (entry.expires < Date.now()) {
|
||||
this.delete(name)
|
||||
return undefined
|
||||
}
|
||||
return entry.session
|
||||
}
|
||||
|
||||
set(name: string, value: T): void {
|
||||
const now = Date.now()
|
||||
this.store.set(name, { session: value, expires: now + this.ttl })
|
||||
}
|
||||
|
||||
delete(name: string): void {
|
||||
this.store.delete(name)
|
||||
}
|
||||
}
|
||||
|
||||
export function isSessionContext<S extends object>(
|
||||
ctx: Context
|
||||
): ctx is SessionContext<S> {
|
||||
return 'session' in ctx
|
||||
}
|
329
node_modules/telegraf/src/telegraf.ts
generated
vendored
Normal file
329
node_modules/telegraf/src/telegraf.ts
generated
vendored
Normal file
|
@ -0,0 +1,329 @@
|
|||
import * as crypto from 'crypto'
|
||||
import * as http from 'http'
|
||||
import * as https from 'https'
|
||||
import * as tg from './core/types/typegram'
|
||||
import * as tt from './telegram-types'
|
||||
import { Composer, MaybePromise } from './composer'
|
||||
import ApiClient from './core/network/client'
|
||||
import { compactOptions } from './core/helpers/compact'
|
||||
import Context from './context'
|
||||
import d from 'debug'
|
||||
import generateCallback from './core/network/webhook'
|
||||
import { Polling } from './core/network/polling'
|
||||
import pTimeout from 'p-timeout'
|
||||
import Telegram from './telegram'
|
||||
import { TlsOptions } from 'tls'
|
||||
import { URL } from 'url'
|
||||
import safeCompare = require('safe-compare')
|
||||
const debug = d('telegraf:main')
|
||||
|
||||
const DEFAULT_OPTIONS: Telegraf.Options<Context> = {
|
||||
telegram: {},
|
||||
handlerTimeout: 90_000, // 90s in ms
|
||||
contextType: Context,
|
||||
}
|
||||
|
||||
function always<T>(x: T) {
|
||||
return () => x
|
||||
}
|
||||
|
||||
const anoop = always(Promise.resolve())
|
||||
|
||||
// eslint-disable-next-line
|
||||
export namespace Telegraf {
|
||||
export interface Options<TContext extends Context> {
|
||||
contextType: new (
|
||||
...args: ConstructorParameters<typeof Context>
|
||||
) => TContext
|
||||
handlerTimeout: number
|
||||
telegram?: Partial<ApiClient.Options>
|
||||
}
|
||||
|
||||
export interface LaunchOptions {
|
||||
dropPendingUpdates?: boolean
|
||||
/** List the types of updates you want your bot to receive */
|
||||
allowedUpdates?: tt.UpdateType[]
|
||||
/** Configuration options for when the bot is run via webhooks */
|
||||
webhook?: {
|
||||
/** Public domain for webhook. */
|
||||
domain: string
|
||||
|
||||
/** Webhook url path; will be automatically generated if not specified */
|
||||
hookPath?: string
|
||||
|
||||
host?: string
|
||||
port?: number
|
||||
|
||||
/** The fixed IP address which will be used to send webhook requests instead of the IP address resolved through DNS */
|
||||
ipAddress?: string
|
||||
|
||||
/**
|
||||
* Maximum allowed number of simultaneous HTTPS connections to the webhook for update delivery, 1-100. Defaults to 40.
|
||||
* Use lower values to limit the load on your bot's server, and higher values to increase your bot's throughput.
|
||||
*/
|
||||
maxConnections?: number
|
||||
|
||||
/** TLS server options. Omit to use http. */
|
||||
tlsOptions?: TlsOptions
|
||||
|
||||
/**
|
||||
* A secret token to be sent in a header `“X-Telegram-Bot-Api-Secret-Token”` in every webhook request.
|
||||
* 1-256 characters. Only characters `A-Z`, `a-z`, `0-9`, `_` and `-` are allowed.
|
||||
* The header is useful to ensure that the request comes from a webhook set by you.
|
||||
*/
|
||||
secretToken?: string
|
||||
|
||||
/**
|
||||
* Upload your public key certificate so that the root certificate in use can be checked.
|
||||
* See [self-signed guide](https://core.telegram.org/bots/self-signed) for details.
|
||||
*/
|
||||
certificate?: tg.InputFile
|
||||
|
||||
cb?: http.RequestListener
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const TOKEN_HEADER = 'x-telegram-bot-api-secret-token'
|
||||
|
||||
export class Telegraf<C extends Context = Context> extends Composer<C> {
|
||||
private readonly options: Telegraf.Options<C>
|
||||
private webhookServer?: http.Server | https.Server
|
||||
private polling?: Polling
|
||||
/** Set manually to avoid implicit `getMe` call in `launch` or `webhookCallback` */
|
||||
public botInfo?: tg.UserFromGetMe
|
||||
public telegram: Telegram
|
||||
readonly context: Partial<C> = {}
|
||||
|
||||
/** Assign to this to customise the webhook filter middleware.
|
||||
* `{ hookPath, secretToken }` will be bound to this rather than the Telegraf instance.
|
||||
* Remember to assign a regular function and not an arrow function so it's bindable.
|
||||
*/
|
||||
public webhookFilter = function (
|
||||
// NOTE: this function is assigned to a variable instead of being a method to signify that it's assignable
|
||||
// NOTE: the `this` binding is so custom impls don't need to double wrap
|
||||
this: { hookPath: string; secretToken?: string },
|
||||
req: http.IncomingMessage
|
||||
) {
|
||||
const debug = d('telegraf:webhook')
|
||||
|
||||
if (req.method === 'POST') {
|
||||
if (safeCompare(this.hookPath, req.url as string)) {
|
||||
// no need to check if secret_token was not set
|
||||
if (!this.secretToken) return true
|
||||
else {
|
||||
const token = req.headers[TOKEN_HEADER] as string
|
||||
if (safeCompare(this.secretToken, token)) return true
|
||||
else debug('Secret token does not match:', token, this.secretToken)
|
||||
}
|
||||
} else debug('Path does not match:', req.url, this.hookPath)
|
||||
} else debug('Unexpected request method, not POST. Received:', req.method)
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
private handleError = (err: unknown, ctx: C): MaybePromise<void> => {
|
||||
// set exit code to emulate `warn-with-error-code` behavior of
|
||||
// https://nodejs.org/api/cli.html#cli_unhandled_rejections_mode
|
||||
// to prevent a clean exit despite an error being thrown
|
||||
process.exitCode = 1
|
||||
console.error('Unhandled error while processing', ctx.update)
|
||||
throw err
|
||||
}
|
||||
|
||||
constructor(token: string, options?: Partial<Telegraf.Options<C>>) {
|
||||
super()
|
||||
// @ts-expect-error Trust me, TS
|
||||
this.options = {
|
||||
...DEFAULT_OPTIONS,
|
||||
...compactOptions(options),
|
||||
}
|
||||
this.telegram = new Telegram(token, this.options.telegram)
|
||||
debug('Created a `Telegraf` instance')
|
||||
}
|
||||
|
||||
private get token() {
|
||||
return this.telegram.token
|
||||
}
|
||||
|
||||
/** @deprecated use `ctx.telegram.webhookReply` */
|
||||
set webhookReply(webhookReply: boolean) {
|
||||
this.telegram.webhookReply = webhookReply
|
||||
}
|
||||
|
||||
/** @deprecated use `ctx.telegram.webhookReply` */
|
||||
get webhookReply() {
|
||||
return this.telegram.webhookReply
|
||||
}
|
||||
|
||||
/**
|
||||
* _Override_ error handling
|
||||
*/
|
||||
catch(handler: (err: unknown, ctx: C) => MaybePromise<void>) {
|
||||
this.handleError = handler
|
||||
return this
|
||||
}
|
||||
|
||||
/**
|
||||
* You must call `bot.telegram.setWebhook` for this to work.
|
||||
* You should probably use {@link Telegraf.createWebhook} instead.
|
||||
*/
|
||||
webhookCallback(hookPath = '/', opts: { secretToken?: string } = {}) {
|
||||
const { secretToken } = opts
|
||||
return generateCallback(
|
||||
this.webhookFilter.bind({ hookPath, secretToken }),
|
||||
(update: tg.Update, res: http.ServerResponse) =>
|
||||
this.handleUpdate(update, res)
|
||||
)
|
||||
}
|
||||
|
||||
private getDomainOpts(opts: { domain: string; path?: string }) {
|
||||
const protocol =
|
||||
opts.domain.startsWith('https://') || opts.domain.startsWith('http://')
|
||||
|
||||
if (protocol)
|
||||
debug(
|
||||
'Unexpected protocol in domain, telegraf will use https:',
|
||||
opts.domain
|
||||
)
|
||||
|
||||
const domain = protocol ? new URL(opts.domain).host : opts.domain
|
||||
const path = opts.path ?? `/telegraf/${this.secretPathComponent()}`
|
||||
const url = `https://${domain}${path}`
|
||||
|
||||
return { domain, path, url }
|
||||
}
|
||||
|
||||
/**
|
||||
* Specify a url to receive incoming updates via webhook.
|
||||
* Returns an Express-style middleware you can pass to app.use()
|
||||
*/
|
||||
async createWebhook(
|
||||
opts: { domain: string; path?: string } & tt.ExtraSetWebhook
|
||||
) {
|
||||
const { domain, path, ...extra } = opts
|
||||
|
||||
const domainOpts = this.getDomainOpts({ domain, path })
|
||||
|
||||
await this.telegram.setWebhook(domainOpts.url, extra)
|
||||
debug(`Webhook set to ${domainOpts.url}`)
|
||||
|
||||
return this.webhookCallback(domainOpts.path, {
|
||||
secretToken: extra.secret_token,
|
||||
})
|
||||
}
|
||||
|
||||
private startPolling(allowedUpdates: tt.UpdateType[] = []) {
|
||||
this.polling = new Polling(this.telegram, allowedUpdates)
|
||||
this.polling.loop(async (update) => {
|
||||
await this.handleUpdate(update)
|
||||
})
|
||||
}
|
||||
|
||||
private startWebhook(
|
||||
hookPath: string,
|
||||
tlsOptions?: TlsOptions,
|
||||
port?: number,
|
||||
host?: string,
|
||||
cb?: http.RequestListener,
|
||||
secretToken?: string
|
||||
) {
|
||||
const webhookCb = this.webhookCallback(hookPath, { secretToken })
|
||||
const callback: http.RequestListener =
|
||||
typeof cb === 'function'
|
||||
? (req, res) => webhookCb(req, res, () => cb(req, res))
|
||||
: webhookCb
|
||||
this.webhookServer =
|
||||
tlsOptions != null
|
||||
? https.createServer(tlsOptions, callback)
|
||||
: http.createServer(callback)
|
||||
this.webhookServer.listen(port, host, () => {
|
||||
debug('Webhook listening on port: %s', port)
|
||||
})
|
||||
return this
|
||||
}
|
||||
|
||||
secretPathComponent() {
|
||||
return crypto
|
||||
.createHash('sha3-256')
|
||||
.update(this.token)
|
||||
.update(process.version) // salt
|
||||
.digest('hex')
|
||||
}
|
||||
|
||||
/**
|
||||
* @see https://github.com/telegraf/telegraf/discussions/1344#discussioncomment-335700
|
||||
*/
|
||||
async launch(config: Telegraf.LaunchOptions = {}) {
|
||||
debug('Connecting to Telegram')
|
||||
this.botInfo ??= await this.telegram.getMe()
|
||||
debug(`Launching @${this.botInfo.username}`)
|
||||
|
||||
if (config.webhook === undefined) {
|
||||
await this.telegram.deleteWebhook({
|
||||
drop_pending_updates: config.dropPendingUpdates,
|
||||
})
|
||||
this.startPolling(config.allowedUpdates)
|
||||
debug('Bot started with long polling')
|
||||
return
|
||||
}
|
||||
|
||||
const domainOpts = this.getDomainOpts({
|
||||
domain: config.webhook.domain,
|
||||
path: config.webhook.hookPath,
|
||||
})
|
||||
|
||||
const { tlsOptions, port, host, cb, secretToken } = config.webhook
|
||||
|
||||
this.startWebhook(domainOpts.path, tlsOptions, port, host, cb, secretToken)
|
||||
|
||||
await this.telegram.setWebhook(domainOpts.url, {
|
||||
drop_pending_updates: config.dropPendingUpdates,
|
||||
allowed_updates: config.allowedUpdates,
|
||||
ip_address: config.webhook.ipAddress,
|
||||
max_connections: config.webhook.maxConnections,
|
||||
secret_token: config.webhook.secretToken,
|
||||
certificate: config.webhook.certificate,
|
||||
})
|
||||
|
||||
debug(`Bot started with webhook @ ${domainOpts.url}`)
|
||||
}
|
||||
|
||||
stop(reason = 'unspecified') {
|
||||
debug('Stopping bot... Reason:', reason)
|
||||
// https://github.com/telegraf/telegraf/pull/1224#issuecomment-742693770
|
||||
if (this.polling === undefined && this.webhookServer === undefined) {
|
||||
throw new Error('Bot is not running!')
|
||||
}
|
||||
this.webhookServer?.close()
|
||||
this.polling?.stop()
|
||||
}
|
||||
|
||||
private botInfoCall?: Promise<tg.UserFromGetMe>
|
||||
async handleUpdate(update: tg.Update, webhookResponse?: http.ServerResponse) {
|
||||
this.botInfo ??=
|
||||
(debug(
|
||||
'Update %d is waiting for `botInfo` to be initialized',
|
||||
update.update_id
|
||||
),
|
||||
await (this.botInfoCall ??= this.telegram.getMe()))
|
||||
debug('Processing update', update.update_id)
|
||||
const tg = new Telegram(this.token, this.telegram.options, webhookResponse)
|
||||
const TelegrafContext = this.options.contextType
|
||||
const ctx = new TelegrafContext(update, tg, this.botInfo)
|
||||
Object.assign(ctx, this.context)
|
||||
try {
|
||||
await pTimeout(
|
||||
Promise.resolve(this.middleware()(ctx, anoop)),
|
||||
this.options.handlerTimeout
|
||||
)
|
||||
} catch (err) {
|
||||
return await this.handleError(err, ctx)
|
||||
} finally {
|
||||
if (webhookResponse?.writableEnded === false) {
|
||||
webhookResponse.end()
|
||||
}
|
||||
debug('Finished processing update', update.update_id)
|
||||
}
|
||||
}
|
||||
}
|
145
node_modules/telegraf/src/telegram-types.ts
generated
vendored
Normal file
145
node_modules/telegraf/src/telegram-types.ts
generated
vendored
Normal file
|
@ -0,0 +1,145 @@
|
|||
/** @format */
|
||||
|
||||
import { Expand } from './util'
|
||||
import { Message, Opts, Telegram, Update } from './core/types/typegram'
|
||||
import { UnionKeys } from './deunionize'
|
||||
import { FmtString } from './format'
|
||||
|
||||
export { Markup } from './markup'
|
||||
|
||||
// tiny helper types
|
||||
export type ChatAction = Opts<'sendChatAction'>['action']
|
||||
|
||||
// Modify type so caption, if exists, can be FmtString
|
||||
type WrapCaption<T> = T extends { caption?: string }
|
||||
? Expand<Omit<T, 'caption'> & { caption?: string | FmtString }>
|
||||
: T
|
||||
|
||||
// extra types
|
||||
/**
|
||||
* Create an `Extra*` type from the arguments of a given method `M extends keyof Telegram` but `Omit`ting fields with key `K` from it.
|
||||
*
|
||||
* Note that `chat_id` may not be specified in `K` because it is `Omit`ted by default.
|
||||
*/
|
||||
type MakeExtra<
|
||||
M extends keyof Telegram,
|
||||
K extends keyof Omit<Opts<M>, 'chat_id'> = never
|
||||
> = WrapCaption<Omit<Opts<M>, 'chat_id' | K>>
|
||||
|
||||
export type ExtraAddStickerToSet = MakeExtra<
|
||||
'addStickerToSet',
|
||||
'name' | 'user_id'
|
||||
>
|
||||
export type ExtraAnimation = MakeExtra<'sendAnimation', 'animation'>
|
||||
export type ExtraAnswerCbQuery = MakeExtra<
|
||||
'answerCallbackQuery',
|
||||
'text' | 'callback_query_id'
|
||||
>
|
||||
export type ExtraAnswerInlineQuery = MakeExtra<
|
||||
'answerInlineQuery',
|
||||
'inline_query_id' | 'results'
|
||||
>
|
||||
export type ExtraAudio = MakeExtra<'sendAudio', 'audio'>
|
||||
export type ExtraContact = MakeExtra<
|
||||
'sendContact',
|
||||
'phone_number' | 'first_name'
|
||||
>
|
||||
export type ExtraCopyMessage = MakeExtra<
|
||||
'copyMessage',
|
||||
'from_chat_id' | 'message_id'
|
||||
>
|
||||
export type ExtraCreateChatInviteLink = MakeExtra<'createChatInviteLink'>
|
||||
export type NewInvoiceLinkParameters = MakeExtra<'createInvoiceLink'>
|
||||
export type ExtraCreateNewStickerSet = MakeExtra<
|
||||
'createNewStickerSet',
|
||||
'name' | 'title' | 'user_id'
|
||||
>
|
||||
export type ExtraDice = MakeExtra<'sendDice'>
|
||||
export type ExtraDocument = MakeExtra<'sendDocument', 'document'>
|
||||
export type ExtraEditChatInviteLink = MakeExtra<
|
||||
'editChatInviteLink',
|
||||
'invite_link'
|
||||
>
|
||||
export type ExtraEditMessageCaption = MakeExtra<
|
||||
'editMessageCaption',
|
||||
'message_id' | 'inline_message_id' | 'caption'
|
||||
>
|
||||
export type ExtraEditMessageLiveLocation = MakeExtra<
|
||||
'editMessageLiveLocation',
|
||||
'message_id' | 'inline_message_id' | 'latitude' | 'longitude'
|
||||
>
|
||||
export type ExtraEditMessageMedia = MakeExtra<
|
||||
'editMessageMedia',
|
||||
'message_id' | 'inline_message_id' | 'media'
|
||||
>
|
||||
export type ExtraEditMessageText = MakeExtra<
|
||||
'editMessageText',
|
||||
'message_id' | 'inline_message_id' | 'text'
|
||||
>
|
||||
export type ExtraGame = MakeExtra<'sendGame', 'game_short_name'>
|
||||
export type NewInvoiceParameters = MakeExtra<
|
||||
'sendInvoice',
|
||||
| 'disable_notification'
|
||||
| 'reply_to_message_id'
|
||||
| 'allow_sending_without_reply'
|
||||
| 'reply_markup'
|
||||
>
|
||||
export type ExtraInvoice = MakeExtra<'sendInvoice', keyof NewInvoiceParameters>
|
||||
export type ExtraBanChatMember = MakeExtra<
|
||||
'banChatMember',
|
||||
'user_id' | 'until_date'
|
||||
>
|
||||
export type ExtraKickChatMember = ExtraBanChatMember
|
||||
export type ExtraLocation = MakeExtra<'sendLocation', 'latitude' | 'longitude'>
|
||||
export type ExtraMediaGroup = MakeExtra<'sendMediaGroup', 'media'>
|
||||
export type ExtraPhoto = MakeExtra<'sendPhoto', 'photo'>
|
||||
export type ExtraPoll = MakeExtra<'sendPoll', 'question' | 'options' | 'type'>
|
||||
export type ExtraPromoteChatMember = MakeExtra<'promoteChatMember', 'user_id'>
|
||||
export type ExtraReplyMessage = MakeExtra<'sendMessage', 'text'>
|
||||
export type ExtraRestrictChatMember = MakeExtra<'restrictChatMember', 'user_id'>
|
||||
export type ExtraSetMyCommands = MakeExtra<'setMyCommands', 'commands'>
|
||||
export type ExtraSetWebhook = MakeExtra<'setWebhook', 'url'>
|
||||
export type ExtraSticker = MakeExtra<'sendSticker', 'sticker'>
|
||||
export type ExtraStopPoll = MakeExtra<'stopPoll', 'message_id'>
|
||||
export type ExtraVenue = MakeExtra<
|
||||
'sendVenue',
|
||||
'latitude' | 'longitude' | 'title' | 'address'
|
||||
>
|
||||
export type ExtraVideo = MakeExtra<'sendVideo', 'video'>
|
||||
export type ExtraVideoNote = MakeExtra<'sendVideoNote', 'video_note'>
|
||||
export type ExtraVoice = MakeExtra<'sendVoice', 'voice'>
|
||||
export type ExtraBanChatSenderChat = MakeExtra<
|
||||
'banChatSenderChat',
|
||||
'sender_chat_id'
|
||||
>
|
||||
|
||||
// types used for inference of ctx object
|
||||
|
||||
/** Possible update types */
|
||||
export type UpdateType = Exclude<UnionKeys<Update>, keyof Update>
|
||||
|
||||
/** Possible message subtypes. Same as the properties on a message object */
|
||||
export type MessageSubType =
|
||||
| 'forward_date'
|
||||
| Exclude<
|
||||
UnionKeys<Message>,
|
||||
keyof Message.CaptionableMessage | 'entities' | 'media_group_id'
|
||||
>
|
||||
|
||||
type ExtractPartial<T extends object, U extends object> = T extends unknown
|
||||
? Required<T> extends U
|
||||
? T
|
||||
: never
|
||||
: never
|
||||
|
||||
/**
|
||||
* Maps [[`Composer.on`]]'s `updateType` or `messageSubType` to a `tt.Update` subtype.
|
||||
*/
|
||||
export type MountMap = {
|
||||
[T in UpdateType]: Extract<Update, Record<T, object>>
|
||||
} & {
|
||||
[T in MessageSubType]: {
|
||||
message: ExtractPartial<Update.MessageUpdate['message'], Record<T, unknown>>
|
||||
update_id: number
|
||||
}
|
||||
}
|
1230
node_modules/telegraf/src/telegram.ts
generated
vendored
Normal file
1230
node_modules/telegraf/src/telegram.ts
generated
vendored
Normal file
File diff suppressed because it is too large
Load diff
54
node_modules/telegraf/src/util.ts
generated
vendored
Normal file
54
node_modules/telegraf/src/util.ts
generated
vendored
Normal file
|
@ -0,0 +1,54 @@
|
|||
import { FmtString } from './format'
|
||||
|
||||
export const env = process.env
|
||||
|
||||
export type Expand<T> = T extends object
|
||||
? T extends infer O
|
||||
? { [K in keyof O]: O[K] }
|
||||
: never
|
||||
: T
|
||||
|
||||
type MaybeExtra<Extra> = (Extra & { caption?: string }) | undefined
|
||||
|
||||
export function fmtCaption<
|
||||
Extra extends {
|
||||
caption?: string | FmtString
|
||||
} & Record<string, unknown>
|
||||
>(extra?: Extra): MaybeExtra<Extra> {
|
||||
const caption = extra?.caption
|
||||
if (!caption || typeof caption === 'string') return extra as MaybeExtra<Extra>
|
||||
const { text, entities } = caption
|
||||
return {
|
||||
...extra,
|
||||
caption: text,
|
||||
...(entities && {
|
||||
caption_entities: entities,
|
||||
parse_mode: undefined,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
export function deprecate(
|
||||
method: string,
|
||||
ignorable: string | undefined,
|
||||
use: string | undefined,
|
||||
see?: string
|
||||
) {
|
||||
// don't use deprecate() yet
|
||||
// wait for a couple minor releases of telegraf so the news reaches more people
|
||||
return
|
||||
|
||||
const ignorer = `IGNORE_DEPRECATED_${ignorable}`
|
||||
if (env[ignorer]) return
|
||||
|
||||
const stack: { stack: string } = { stack: '' }
|
||||
Error.captureStackTrace(stack)
|
||||
const line = (stack.stack.split('\n')[3] || '').trim()
|
||||
|
||||
const useOther = use ? `; use ${use} instead` : ''
|
||||
const pad = ' '.repeat('[WARN]'.length)
|
||||
|
||||
console.warn(`[WARN] ${method} is deprecated${useOther}`)
|
||||
if (line) console.warn(pad, line)
|
||||
if (see) console.warn(pad, `SEE ${see}`)
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue