@tgify/tgify 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +23 -0
- package/README.md +356 -0
- package/filters.d.ts +1 -0
- package/filters.js +1 -0
- package/format.d.ts +1 -0
- package/format.js +1 -0
- package/future.d.ts +1 -0
- package/future.js +1 -0
- package/lib/button.js +100 -0
- package/lib/cli.mjs +105 -0
- package/lib/composer.js +582 -0
- package/lib/context.js +1219 -0
- package/lib/core/helpers/args.js +57 -0
- package/lib/core/helpers/check.js +55 -0
- package/lib/core/helpers/compact.js +16 -0
- package/lib/core/helpers/deunionize.js +12 -0
- package/lib/core/helpers/formatting.js +91 -0
- package/lib/core/helpers/util.js +50 -0
- package/lib/core/network/client.js +330 -0
- package/lib/core/network/error.js +21 -0
- package/lib/core/network/multipart-stream.js +71 -0
- package/lib/core/network/polling.js +87 -0
- package/lib/core/network/webhook.js +54 -0
- package/lib/core/types/typegram.js +27 -0
- package/lib/filters.js +69 -0
- package/lib/format.js +38 -0
- package/lib/future.js +149 -0
- package/lib/index.js +58 -0
- package/lib/input.js +61 -0
- package/lib/markup.js +121 -0
- package/lib/middleware.js +2 -0
- package/lib/reactions.js +84 -0
- package/lib/router.js +46 -0
- package/lib/scenes/base.js +39 -0
- package/lib/scenes/context.js +104 -0
- package/lib/scenes/index.js +21 -0
- package/lib/scenes/stage.js +49 -0
- package/lib/scenes/wizard/context.js +31 -0
- package/lib/scenes/wizard/index.js +45 -0
- package/lib/scenes.js +17 -0
- package/lib/session.js +166 -0
- package/lib/telegraf.js +256 -0
- package/lib/telegram-types.js +6 -0
- package/lib/telegram.js +1240 -0
- package/lib/types.js +2 -0
- package/lib/utils.js +5 -0
- package/markup.d.ts +1 -0
- package/markup.js +1 -0
- package/package.json +140 -0
- package/scenes.d.ts +1 -0
- package/scenes.js +1 -0
- package/session.d.ts +1 -0
- package/session.js +1 -0
- package/src/button.ts +182 -0
- package/src/composer.ts +1008 -0
- package/src/context.ts +1661 -0
- package/src/core/helpers/args.ts +63 -0
- package/src/core/helpers/check.ts +71 -0
- package/src/core/helpers/compact.ts +18 -0
- package/src/core/helpers/deunionize.ts +26 -0
- package/src/core/helpers/formatting.ts +119 -0
- package/src/core/helpers/util.ts +96 -0
- package/src/core/network/client.ts +396 -0
- package/src/core/network/error.ts +29 -0
- package/src/core/network/multipart-stream.ts +45 -0
- package/src/core/network/polling.ts +94 -0
- package/src/core/network/webhook.ts +58 -0
- package/src/core/types/typegram.ts +54 -0
- package/src/filters.ts +109 -0
- package/src/format.ts +110 -0
- package/src/future.ts +213 -0
- package/src/index.ts +17 -0
- package/src/input.ts +59 -0
- package/src/markup.ts +142 -0
- package/src/middleware.ts +24 -0
- package/src/reactions.ts +118 -0
- package/src/router.ts +55 -0
- package/src/scenes/base.ts +52 -0
- package/src/scenes/context.ts +136 -0
- package/src/scenes/index.ts +21 -0
- package/src/scenes/stage.ts +71 -0
- package/src/scenes/wizard/context.ts +58 -0
- package/src/scenes/wizard/index.ts +63 -0
- package/src/scenes.ts +1 -0
- package/src/session.ts +204 -0
- package/src/telegraf.ts +354 -0
- package/src/telegram-types.ts +219 -0
- package/src/telegram.ts +1635 -0
- package/src/types.ts +2 -0
- package/src/utils.ts +1 -0
- package/types.d.ts +1 -0
- package/types.js +1 -0
- package/typings/button.d.ts +36 -0
- package/typings/button.d.ts.map +1 -0
- package/typings/composer.d.ts +227 -0
- package/typings/composer.d.ts.map +1 -0
- package/typings/context.d.ts +655 -0
- package/typings/context.d.ts.map +1 -0
- package/typings/core/helpers/args.d.ts +11 -0
- package/typings/core/helpers/args.d.ts.map +1 -0
- package/typings/core/helpers/check.d.ts +56 -0
- package/typings/core/helpers/check.d.ts.map +1 -0
- package/typings/core/helpers/compact.d.ts +4 -0
- package/typings/core/helpers/compact.d.ts.map +1 -0
- package/typings/core/helpers/deunionize.d.ts +18 -0
- package/typings/core/helpers/deunionize.d.ts.map +1 -0
- package/typings/core/helpers/formatting.d.ts +30 -0
- package/typings/core/helpers/formatting.d.ts.map +1 -0
- package/typings/core/helpers/util.d.ts +26 -0
- package/typings/core/helpers/util.d.ts.map +1 -0
- package/typings/core/network/client.d.ts +53 -0
- package/typings/core/network/client.d.ts.map +1 -0
- package/typings/core/network/error.d.ts +16 -0
- package/typings/core/network/error.d.ts.map +1 -0
- package/typings/core/network/multipart-stream.d.ts +16 -0
- package/typings/core/network/multipart-stream.d.ts.map +1 -0
- package/typings/core/network/polling.d.ts +16 -0
- package/typings/core/network/polling.d.ts.map +1 -0
- package/typings/core/network/webhook.d.ts +6 -0
- package/typings/core/network/webhook.d.ts.map +1 -0
- package/typings/core/types/typegram.d.ts +42 -0
- package/typings/core/types/typegram.d.ts.map +1 -0
- package/typings/filters.d.ts +18 -0
- package/typings/filters.d.ts.map +1 -0
- package/typings/format.d.ts +22 -0
- package/typings/format.d.ts.map +1 -0
- package/typings/future.d.ts +12 -0
- package/typings/future.d.ts.map +1 -0
- package/typings/index.d.ts +15 -0
- package/typings/index.d.ts.map +1 -0
- package/typings/input.d.ts +50 -0
- package/typings/input.d.ts.map +1 -0
- package/typings/markup.d.ts +27 -0
- package/typings/markup.d.ts.map +1 -0
- package/typings/middleware.d.ts +8 -0
- package/typings/middleware.d.ts.map +1 -0
- package/typings/reactions.d.ts +32 -0
- package/typings/reactions.d.ts.map +1 -0
- package/typings/router.d.ts +21 -0
- package/typings/router.d.ts.map +1 -0
- package/typings/scenes/base.d.ts +22 -0
- package/typings/scenes/base.d.ts.map +1 -0
- package/typings/scenes/context.d.ts +36 -0
- package/typings/scenes/context.d.ts.map +1 -0
- package/typings/scenes/index.d.ts +11 -0
- package/typings/scenes/index.d.ts.map +1 -0
- package/typings/scenes/stage.d.ts +24 -0
- package/typings/scenes/stage.d.ts.map +1 -0
- package/typings/scenes/wizard/context.d.ts +29 -0
- package/typings/scenes/wizard/context.d.ts.map +1 -0
- package/typings/scenes/wizard/index.d.ts +16 -0
- package/typings/scenes/wizard/index.d.ts.map +1 -0
- package/typings/scenes.d.ts +2 -0
- package/typings/scenes.d.ts.map +1 -0
- package/typings/session.d.ts +55 -0
- package/typings/session.d.ts.map +1 -0
- package/typings/telegraf.d.ts +115 -0
- package/typings/telegraf.d.ts.map +1 -0
- package/typings/telegram-types.d.ts +117 -0
- package/typings/telegram-types.d.ts.map +1 -0
- package/typings/telegram.d.ts +675 -0
- package/typings/telegram.d.ts.map +1 -0
- package/typings/types.d.ts +3 -0
- package/typings/types.d.ts.map +1 -0
- package/typings/utils.d.ts +2 -0
- package/typings/utils.d.ts.map +1 -0
- package/utils.d.ts +1 -0
- package/utils.js +1 -0
package/src/reactions.ts
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { Deunionize } from './core/helpers/deunionize'
|
|
2
|
+
import { indexed } from './core/helpers/util'
|
|
3
|
+
import * as tg from './core/types/typegram'
|
|
4
|
+
|
|
5
|
+
export type Digit = '0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'
|
|
6
|
+
export const Digit = new Set(['0', '1', '2', '3', '4', '5', '6', '7', '8', '9'])
|
|
7
|
+
export type Reaction =
|
|
8
|
+
| tg.TelegramEmoji
|
|
9
|
+
| `${Digit}${string}`
|
|
10
|
+
| Deunionize<tg.ReactionType>
|
|
11
|
+
|
|
12
|
+
type ReactionCtx = { update: Partial<tg.Update.MessageReactionUpdate> }
|
|
13
|
+
|
|
14
|
+
const inspectReaction = (reaction: tg.ReactionType) => {
|
|
15
|
+
if (reaction.type === 'custom_emoji')
|
|
16
|
+
return `Custom(${reaction.custom_emoji_id})`
|
|
17
|
+
else return reaction.emoji
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export class ReactionList {
|
|
21
|
+
// this is a lie, proxy will be used to access the properties
|
|
22
|
+
[index: number]: Deunionize<tg.ReactionType>
|
|
23
|
+
|
|
24
|
+
protected constructor(protected list: tg.ReactionType[]) {}
|
|
25
|
+
|
|
26
|
+
static fromArray(list: tg.ReactionType[] = []): ReactionList {
|
|
27
|
+
return indexed(
|
|
28
|
+
new ReactionList(list),
|
|
29
|
+
function (this: ReactionList, index) {
|
|
30
|
+
return this.list[index]
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
static has(reactions: tg.ReactionType[], reaction: Reaction): boolean {
|
|
36
|
+
if (typeof reaction === 'string')
|
|
37
|
+
if (Digit.has(reaction[0] as string))
|
|
38
|
+
return reactions.some(
|
|
39
|
+
(r: Deunionize<tg.ReactionType>) => r.custom_emoji_id === reaction
|
|
40
|
+
)
|
|
41
|
+
else
|
|
42
|
+
return reactions.some(
|
|
43
|
+
(r: Deunionize<tg.ReactionType>) => r.emoji === reaction
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
return reactions.some((r: Deunionize<tg.ReactionType>) => {
|
|
47
|
+
if (r.type === 'custom_emoji')
|
|
48
|
+
return r.custom_emoji_id === reaction.custom_emoji_id
|
|
49
|
+
else if (r.type === 'emoji') return r.emoji === reaction.emoji
|
|
50
|
+
})
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
toArray(): tg.ReactionType[] {
|
|
54
|
+
return [...this.list]
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
filter(
|
|
58
|
+
filterFn: (value: tg.ReactionType, index: number) => boolean
|
|
59
|
+
): ReactionList {
|
|
60
|
+
return ReactionList.fromArray(this.list.filter(filterFn))
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
has(reaction: Reaction): boolean {
|
|
64
|
+
return ReactionList.has(this.list, reaction)
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
get count(): number {
|
|
68
|
+
return this.list.length
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
[Symbol.iterator]() {
|
|
72
|
+
return this.list[Symbol.iterator]()
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
[Symbol.for('nodejs.util.inspect.custom')]() {
|
|
76
|
+
const flattened = this.list.map(inspectReaction).join(', ')
|
|
77
|
+
return ['ReactionList {', flattened, '}'].join(' ')
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export class MessageReactions extends ReactionList {
|
|
82
|
+
private constructor(public ctx: ReactionCtx) {
|
|
83
|
+
super(ctx.update.message_reaction?.new_reaction ?? [])
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
static from(ctx: ReactionCtx) {
|
|
87
|
+
return indexed(
|
|
88
|
+
new MessageReactions(ctx),
|
|
89
|
+
function (this: MessageReactions, index) {
|
|
90
|
+
return this.list[index]
|
|
91
|
+
}
|
|
92
|
+
)
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
get old() {
|
|
96
|
+
return ReactionList.fromArray(
|
|
97
|
+
this.ctx.update.message_reaction?.old_reaction
|
|
98
|
+
)
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
get new() {
|
|
102
|
+
return ReactionList.fromArray(
|
|
103
|
+
this.ctx.update.message_reaction?.new_reaction
|
|
104
|
+
)
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
get added(): ReactionList {
|
|
108
|
+
return this.new.filter((reaction) => !this.old.has(reaction))
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
get removed(): ReactionList {
|
|
112
|
+
return this.old.filter((reaction) => !this.new.has(reaction))
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
get kept(): ReactionList {
|
|
116
|
+
return this.new.filter((reaction) => this.old.has(reaction))
|
|
117
|
+
}
|
|
118
|
+
}
|
package/src/router.ts
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/** @format */
|
|
2
|
+
|
|
3
|
+
import { Middleware, MiddlewareObj } from './middleware'
|
|
4
|
+
import Composer from './composer'
|
|
5
|
+
import Context from './context'
|
|
6
|
+
|
|
7
|
+
type NonemptyReadonlyArray<T> = readonly [T, ...T[]]
|
|
8
|
+
|
|
9
|
+
type RouteFn<TContext extends Context> = (ctx: TContext) => {
|
|
10
|
+
route: string
|
|
11
|
+
context?: Partial<TContext>
|
|
12
|
+
state?: Partial<TContext['state']>
|
|
13
|
+
} | null
|
|
14
|
+
|
|
15
|
+
/** @deprecated in favor of {@link Composer.dispatch} */
|
|
16
|
+
export class Router<C extends Context> implements MiddlewareObj<C> {
|
|
17
|
+
private otherwiseHandler: Middleware<C> = Composer.passThru()
|
|
18
|
+
|
|
19
|
+
constructor(
|
|
20
|
+
private readonly routeFn: RouteFn<C>,
|
|
21
|
+
public handlers = new Map<string, Middleware<C>>()
|
|
22
|
+
) {
|
|
23
|
+
if (typeof routeFn !== 'function') {
|
|
24
|
+
throw new Error('Missing routing function')
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
on(route: string, ...fns: NonemptyReadonlyArray<Middleware<C>>) {
|
|
29
|
+
if (fns.length === 0) {
|
|
30
|
+
throw new TypeError('At least one handler must be provided')
|
|
31
|
+
}
|
|
32
|
+
this.handlers.set(route, Composer.compose(fns))
|
|
33
|
+
return this
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
otherwise(...fns: NonemptyReadonlyArray<Middleware<C>>) {
|
|
37
|
+
if (fns.length === 0) {
|
|
38
|
+
throw new TypeError('At least one otherwise handler must be provided')
|
|
39
|
+
}
|
|
40
|
+
this.otherwiseHandler = Composer.compose(fns)
|
|
41
|
+
return this
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
middleware() {
|
|
45
|
+
return Composer.lazy<C>((ctx) => {
|
|
46
|
+
const result = this.routeFn(ctx)
|
|
47
|
+
if (result == null) {
|
|
48
|
+
return this.otherwiseHandler
|
|
49
|
+
}
|
|
50
|
+
Object.assign(ctx, result.context)
|
|
51
|
+
Object.assign(ctx.state, result.state)
|
|
52
|
+
return this.handlers.get(result.route) ?? this.otherwiseHandler
|
|
53
|
+
})
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { Middleware, MiddlewareFn } from '../middleware'
|
|
2
|
+
import Composer from '../composer'
|
|
3
|
+
import Context from '../context'
|
|
4
|
+
|
|
5
|
+
const { compose } = Composer
|
|
6
|
+
|
|
7
|
+
export interface SceneOptions<C extends Context> {
|
|
8
|
+
ttl?: number
|
|
9
|
+
handlers: ReadonlyArray<MiddlewareFn<C>>
|
|
10
|
+
enterHandlers: ReadonlyArray<MiddlewareFn<C>>
|
|
11
|
+
leaveHandlers: ReadonlyArray<MiddlewareFn<C>>
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class BaseScene<C extends Context = Context> extends Composer<C> {
|
|
15
|
+
id: string
|
|
16
|
+
ttl?: number
|
|
17
|
+
enterHandler: MiddlewareFn<C>
|
|
18
|
+
leaveHandler: MiddlewareFn<C>
|
|
19
|
+
constructor(id: string, options?: SceneOptions<C>) {
|
|
20
|
+
const opts: SceneOptions<C> = {
|
|
21
|
+
handlers: [],
|
|
22
|
+
enterHandlers: [],
|
|
23
|
+
leaveHandlers: [],
|
|
24
|
+
...options,
|
|
25
|
+
}
|
|
26
|
+
super(...opts.handlers)
|
|
27
|
+
this.id = id
|
|
28
|
+
this.ttl = opts.ttl
|
|
29
|
+
this.enterHandler = compose(opts.enterHandlers)
|
|
30
|
+
this.leaveHandler = compose(opts.leaveHandlers)
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
enter(...fns: Array<Middleware<C>>) {
|
|
34
|
+
this.enterHandler = compose([this.enterHandler, ...fns])
|
|
35
|
+
return this
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
leave(...fns: Array<Middleware<C>>) {
|
|
39
|
+
this.leaveHandler = compose([this.leaveHandler, ...fns])
|
|
40
|
+
return this
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
enterMiddleware() {
|
|
44
|
+
return this.enterHandler
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
leaveMiddleware() {
|
|
48
|
+
return this.leaveHandler
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export default BaseScene
|
|
@@ -0,0 +1,136 @@
|
|
|
1
|
+
import BaseScene from './base'
|
|
2
|
+
import Composer from '../composer'
|
|
3
|
+
import Context from '../context'
|
|
4
|
+
import d from 'debug'
|
|
5
|
+
import { SessionContext } from '../session'
|
|
6
|
+
const debug = d('telegraf:scenes:context')
|
|
7
|
+
|
|
8
|
+
const noop = () => Promise.resolve()
|
|
9
|
+
const now = () => Math.floor(Date.now() / 1000)
|
|
10
|
+
|
|
11
|
+
export interface SceneContext<D extends SceneSessionData = SceneSessionData>
|
|
12
|
+
extends Context {
|
|
13
|
+
session: SceneSession<D>
|
|
14
|
+
scene: SceneContextScene<SceneContext<D>, D>
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface SceneSessionData {
|
|
18
|
+
current?: string
|
|
19
|
+
expires?: number
|
|
20
|
+
state?: object
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SceneSession<S extends SceneSessionData = SceneSessionData> {
|
|
24
|
+
__scenes?: S
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export interface SceneContextSceneOptions<D extends SceneSessionData> {
|
|
28
|
+
ttl?: number
|
|
29
|
+
default?: string
|
|
30
|
+
defaultSession: D
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default class SceneContextScene<
|
|
34
|
+
C extends SessionContext<SceneSession<D>>,
|
|
35
|
+
D extends SceneSessionData = SceneSessionData,
|
|
36
|
+
> {
|
|
37
|
+
private readonly options: SceneContextSceneOptions<D>
|
|
38
|
+
|
|
39
|
+
constructor(
|
|
40
|
+
private readonly ctx: C,
|
|
41
|
+
private readonly scenes: Map<string, BaseScene<C>>,
|
|
42
|
+
options: Partial<SceneContextSceneOptions<D>>
|
|
43
|
+
) {
|
|
44
|
+
// @ts-expect-error {} might not be assignable to D
|
|
45
|
+
const fallbackSessionDefault: D = {}
|
|
46
|
+
|
|
47
|
+
this.options = { defaultSession: fallbackSessionDefault, ...options }
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
get session(): D {
|
|
51
|
+
const defaultSession = Object.assign({}, this.options.defaultSession)
|
|
52
|
+
|
|
53
|
+
let session = this.ctx.session?.__scenes ?? defaultSession
|
|
54
|
+
if (session.expires !== undefined && session.expires < now()) {
|
|
55
|
+
session = defaultSession
|
|
56
|
+
}
|
|
57
|
+
if (this.ctx.session === undefined) {
|
|
58
|
+
this.ctx.session = { __scenes: session }
|
|
59
|
+
} else {
|
|
60
|
+
this.ctx.session.__scenes = session
|
|
61
|
+
}
|
|
62
|
+
return session
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
get state() {
|
|
66
|
+
return (this.session.state ??= {})
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
set state(value) {
|
|
70
|
+
this.session.state = { ...value }
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
get current() {
|
|
74
|
+
const sceneId = this.session.current ?? this.options.default
|
|
75
|
+
return sceneId === undefined || !this.scenes.has(sceneId)
|
|
76
|
+
? undefined
|
|
77
|
+
: this.scenes.get(sceneId)
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
reset() {
|
|
81
|
+
if (this.ctx.session !== undefined)
|
|
82
|
+
this.ctx.session.__scenes = Object.assign({}, this.options.defaultSession)
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
async enter(sceneId: string, initialState: object = {}, silent = false) {
|
|
86
|
+
if (!this.scenes.has(sceneId)) {
|
|
87
|
+
throw new Error(`Can't find scene: ${sceneId}`)
|
|
88
|
+
}
|
|
89
|
+
if (!silent) {
|
|
90
|
+
await this.leave()
|
|
91
|
+
}
|
|
92
|
+
debug('Entering scene', sceneId, initialState, silent)
|
|
93
|
+
this.session.current = sceneId
|
|
94
|
+
this.state = initialState
|
|
95
|
+
const ttl = this.current?.ttl ?? this.options.ttl
|
|
96
|
+
if (ttl !== undefined) {
|
|
97
|
+
this.session.expires = now() + ttl
|
|
98
|
+
}
|
|
99
|
+
if (this.current === undefined || silent) {
|
|
100
|
+
return
|
|
101
|
+
}
|
|
102
|
+
const handler =
|
|
103
|
+
'enterMiddleware' in this.current &&
|
|
104
|
+
typeof this.current.enterMiddleware === 'function'
|
|
105
|
+
? this.current.enterMiddleware()
|
|
106
|
+
: this.current.middleware()
|
|
107
|
+
return await handler(this.ctx, noop)
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
reenter() {
|
|
111
|
+
return this.session.current === undefined
|
|
112
|
+
? undefined
|
|
113
|
+
: this.enter(this.session.current, this.state)
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
private leaving = false
|
|
117
|
+
async leave() {
|
|
118
|
+
if (this.leaving) return
|
|
119
|
+
debug('Leaving scene')
|
|
120
|
+
try {
|
|
121
|
+
this.leaving = true
|
|
122
|
+
if (this.current === undefined) {
|
|
123
|
+
return
|
|
124
|
+
}
|
|
125
|
+
const handler =
|
|
126
|
+
'leaveMiddleware' in this.current &&
|
|
127
|
+
typeof this.current.leaveMiddleware === 'function'
|
|
128
|
+
? this.current.leaveMiddleware()
|
|
129
|
+
: Composer.passThru()
|
|
130
|
+
await handler(this.ctx, noop)
|
|
131
|
+
return this.reset()
|
|
132
|
+
} finally {
|
|
133
|
+
this.leaving = false
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @see https://github.com/telegraf/telegraf/issues/705#issuecomment-549056045
|
|
3
|
+
* @see https://www.npmjs.com/package/telegraf-stateless-question
|
|
4
|
+
* @packageDocumentation
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export { Stage } from './stage'
|
|
8
|
+
export {
|
|
9
|
+
SceneContext,
|
|
10
|
+
SceneSession,
|
|
11
|
+
default as SceneContextScene,
|
|
12
|
+
SceneSessionData,
|
|
13
|
+
} from './context'
|
|
14
|
+
export { BaseScene } from './base'
|
|
15
|
+
export { WizardScene } from './wizard'
|
|
16
|
+
export {
|
|
17
|
+
WizardContext,
|
|
18
|
+
WizardSession,
|
|
19
|
+
default as WizardContextWizard,
|
|
20
|
+
WizardSessionData,
|
|
21
|
+
} from './wizard/context'
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { isSessionContext, SessionContext } from '../session'
|
|
2
|
+
import SceneContextScene, {
|
|
3
|
+
SceneContextSceneOptions,
|
|
4
|
+
SceneSession,
|
|
5
|
+
SceneSessionData,
|
|
6
|
+
} from './context'
|
|
7
|
+
import { BaseScene } from './base'
|
|
8
|
+
import { Composer } from '../composer'
|
|
9
|
+
import { Context } from '../context'
|
|
10
|
+
|
|
11
|
+
export class Stage<
|
|
12
|
+
C extends SessionContext<SceneSession<D>> & {
|
|
13
|
+
scene: SceneContextScene<C, D>
|
|
14
|
+
},
|
|
15
|
+
D extends SceneSessionData = SceneSessionData,
|
|
16
|
+
> extends Composer<C> {
|
|
17
|
+
options: Partial<SceneContextSceneOptions<D>>
|
|
18
|
+
scenes: Map<string, BaseScene<C>>
|
|
19
|
+
|
|
20
|
+
constructor(
|
|
21
|
+
scenes: ReadonlyArray<BaseScene<C>> = [],
|
|
22
|
+
options?: Partial<SceneContextSceneOptions<D>>
|
|
23
|
+
) {
|
|
24
|
+
super()
|
|
25
|
+
this.options = { ...options }
|
|
26
|
+
this.scenes = new Map<string, BaseScene<C>>()
|
|
27
|
+
scenes.forEach((scene) => this.register(scene))
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
register(...scenes: ReadonlyArray<BaseScene<C>>) {
|
|
31
|
+
scenes.forEach((scene) => {
|
|
32
|
+
if (scene?.id == null || typeof scene.middleware !== 'function') {
|
|
33
|
+
throw new Error('telegraf: Unsupported scene')
|
|
34
|
+
}
|
|
35
|
+
this.scenes.set(scene.id, scene)
|
|
36
|
+
})
|
|
37
|
+
return this
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
middleware() {
|
|
41
|
+
const handler = Composer.compose<C>([
|
|
42
|
+
(ctx, next) => {
|
|
43
|
+
const scenes: Map<string, BaseScene<C>> = this.scenes
|
|
44
|
+
const scene = new SceneContextScene<C, D>(ctx, scenes, this.options)
|
|
45
|
+
ctx.scene = scene
|
|
46
|
+
return next()
|
|
47
|
+
},
|
|
48
|
+
super.middleware(),
|
|
49
|
+
Composer.lazy<C>((ctx) => ctx.scene.current ?? Composer.passThru()),
|
|
50
|
+
])
|
|
51
|
+
return Composer.optional(isSessionContext, handler)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
static enter<C extends Context & { scene: SceneContextScene<C> }>(
|
|
55
|
+
...args: Parameters<SceneContextScene<C>['enter']>
|
|
56
|
+
) {
|
|
57
|
+
return (ctx: C) => ctx.scene.enter(...args)
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
static reenter<C extends Context & { scene: SceneContextScene<C> }>(
|
|
61
|
+
...args: Parameters<SceneContextScene<C>['reenter']>
|
|
62
|
+
) {
|
|
63
|
+
return (ctx: C) => ctx.scene.reenter(...args)
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
static leave<C extends Context & { scene: SceneContextScene<C> }>(
|
|
67
|
+
...args: Parameters<SceneContextScene<C>['leave']>
|
|
68
|
+
) {
|
|
69
|
+
return (ctx: C) => ctx.scene.leave(...args)
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import SceneContextScene, { SceneSession, SceneSessionData } from '../context'
|
|
2
|
+
import Context from '../../context'
|
|
3
|
+
import { Middleware } from '../../middleware'
|
|
4
|
+
import { SessionContext } from '../../session'
|
|
5
|
+
|
|
6
|
+
export interface WizardContext<D extends WizardSessionData = WizardSessionData>
|
|
7
|
+
extends Context {
|
|
8
|
+
session: WizardSession<D>
|
|
9
|
+
scene: SceneContextScene<WizardContext<D>, D>
|
|
10
|
+
wizard: WizardContextWizard<WizardContext<D>>
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export interface WizardSessionData extends SceneSessionData {
|
|
14
|
+
cursor: number
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface WizardSession<S extends WizardSessionData = WizardSessionData>
|
|
18
|
+
extends SceneSession<S> {}
|
|
19
|
+
|
|
20
|
+
export default class WizardContextWizard<
|
|
21
|
+
C extends SessionContext<WizardSession> & {
|
|
22
|
+
scene: SceneContextScene<C, WizardSessionData>
|
|
23
|
+
},
|
|
24
|
+
> {
|
|
25
|
+
readonly state: object
|
|
26
|
+
constructor(
|
|
27
|
+
private readonly ctx: C,
|
|
28
|
+
private readonly steps: ReadonlyArray<Middleware<C>>
|
|
29
|
+
) {
|
|
30
|
+
this.state = ctx.scene.state
|
|
31
|
+
this.cursor = ctx.scene.session.cursor ?? 0
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
get step() {
|
|
35
|
+
return this.steps[this.cursor]
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
get cursor() {
|
|
39
|
+
return this.ctx.scene.session.cursor
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
set cursor(cursor: number) {
|
|
43
|
+
this.ctx.scene.session.cursor = cursor
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
selectStep(index: number) {
|
|
47
|
+
this.cursor = index
|
|
48
|
+
return this
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
next() {
|
|
52
|
+
return this.selectStep(this.cursor + 1)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
back() {
|
|
56
|
+
return this.selectStep(this.cursor - 1)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import BaseScene, { SceneOptions } from '../base'
|
|
2
|
+
import { Middleware, MiddlewareObj } from '../../middleware'
|
|
3
|
+
import WizardContextWizard, { WizardSessionData } from './context'
|
|
4
|
+
import Composer from '../../composer'
|
|
5
|
+
import Context from '../../context'
|
|
6
|
+
import SceneContextScene from '../context'
|
|
7
|
+
|
|
8
|
+
export class WizardScene<
|
|
9
|
+
C extends Context & {
|
|
10
|
+
scene: SceneContextScene<C, WizardSessionData>
|
|
11
|
+
wizard: WizardContextWizard<C>
|
|
12
|
+
},
|
|
13
|
+
>
|
|
14
|
+
extends BaseScene<C>
|
|
15
|
+
implements MiddlewareObj<C>
|
|
16
|
+
{
|
|
17
|
+
steps: Array<Middleware<C>>
|
|
18
|
+
|
|
19
|
+
constructor(id: string, ...steps: Array<Middleware<C>>)
|
|
20
|
+
constructor(
|
|
21
|
+
id: string,
|
|
22
|
+
options: SceneOptions<C>,
|
|
23
|
+
...steps: Array<Middleware<C>>
|
|
24
|
+
)
|
|
25
|
+
constructor(
|
|
26
|
+
id: string,
|
|
27
|
+
options: SceneOptions<C> | Middleware<C>,
|
|
28
|
+
...steps: Array<Middleware<C>>
|
|
29
|
+
) {
|
|
30
|
+
let opts: SceneOptions<C> | undefined
|
|
31
|
+
let s: Array<Middleware<C>>
|
|
32
|
+
if (typeof options === 'function' || 'middleware' in options) {
|
|
33
|
+
opts = undefined
|
|
34
|
+
s = [options, ...steps]
|
|
35
|
+
} else {
|
|
36
|
+
opts = options
|
|
37
|
+
s = steps
|
|
38
|
+
}
|
|
39
|
+
super(id, opts)
|
|
40
|
+
this.steps = s
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
middleware() {
|
|
44
|
+
return Composer.compose<C>([
|
|
45
|
+
(ctx, next) => {
|
|
46
|
+
ctx.wizard = new WizardContextWizard<C>(ctx, this.steps)
|
|
47
|
+
return next()
|
|
48
|
+
},
|
|
49
|
+
super.middleware(),
|
|
50
|
+
(ctx, next) => {
|
|
51
|
+
if (ctx.wizard.step === undefined) {
|
|
52
|
+
ctx.wizard.selectStep(0)
|
|
53
|
+
return ctx.scene.leave()
|
|
54
|
+
}
|
|
55
|
+
return Composer.unwrap(ctx.wizard.step)(ctx, next)
|
|
56
|
+
},
|
|
57
|
+
])
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
enterMiddleware() {
|
|
61
|
+
return Composer.compose([this.enterHandler, this.middleware()])
|
|
62
|
+
}
|
|
63
|
+
}
|
package/src/scenes.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './scenes/index.js'
|