refactor(dispatcher): improved surface api
This commit is contained in:
parent
c061581abb
commit
022481966b
11 changed files with 342 additions and 177 deletions
|
@ -1,5 +1,4 @@
|
||||||
/* eslint-disable no-restricted-globals */
|
const { types, toSentence, replaceSections, formatFile } = require('../../client/scripts/generate-updates.cjs')
|
||||||
const { types, toSentence, replaceSections, formatFile } = require('../../client/scripts/generate-updates')
|
|
||||||
|
|
||||||
function generateHandler() {
|
function generateHandler() {
|
||||||
const lines = []
|
const lines = []
|
||||||
|
@ -41,7 +40,7 @@ function generateDispatcher() {
|
||||||
* @param group Handler group index
|
* @param group Handler group index
|
||||||
*/
|
*/
|
||||||
on${type.handlerTypeName}(handler: ${type.handlerTypeName}Handler${
|
on${type.handlerTypeName}(handler: ${type.handlerTypeName}Handler${
|
||||||
type.state ? `<${type.context}, State extends never ? never : UpdateState<State, SceneName>>` : ''
|
type.state ? `<${type.context}, State extends never ? never : UpdateState<State>>` : ''
|
||||||
}['callback'], group?: number): void
|
}['callback'], group?: number): void
|
||||||
|
|
||||||
${
|
${
|
||||||
|
@ -58,7 +57,7 @@ ${
|
||||||
filter: UpdateFilter<${type.context}, Mod, State>,
|
filter: UpdateFilter<${type.context}, Mod, State>,
|
||||||
handler: ${type.handlerTypeName}Handler<filters.Modify<${
|
handler: ${type.handlerTypeName}Handler<filters.Modify<${
|
||||||
type.context
|
type.context
|
||||||
}, Mod>, State extends never ? never : UpdateState<State, SceneName>>['callback'],
|
}, Mod>, State extends never ? never : UpdateState<State>>['callback'],
|
||||||
group?: number
|
group?: number
|
||||||
): void
|
): void
|
||||||
` :
|
` :
|
||||||
|
@ -75,7 +74,7 @@ ${
|
||||||
on${type.handlerTypeName}<Mod>(
|
on${type.handlerTypeName}<Mod>(
|
||||||
filter: UpdateFilter<${type.context}, Mod>,
|
filter: UpdateFilter<${type.context}, Mod>,
|
||||||
handler: ${type.handlerTypeName}Handler<filters.Modify<${type.context}, Mod>${
|
handler: ${type.handlerTypeName}Handler<filters.Modify<${type.context}, Mod>${
|
||||||
type.state ? ', State extends never ? never : UpdateState<State, SceneName>' : ''
|
type.state ? ', State extends never ? never : UpdateState<State>' : ''
|
||||||
}>['callback'],
|
}>['callback'],
|
||||||
group?: number
|
group?: number
|
||||||
): void
|
): void
|
||||||
|
|
|
@ -60,22 +60,49 @@ import {
|
||||||
} from './handler.js'
|
} from './handler.js'
|
||||||
// end-codegen-imports
|
// end-codegen-imports
|
||||||
import { PropagationAction } from './propagation.js'
|
import { PropagationAction } from './propagation.js'
|
||||||
import { defaultStateKeyDelegate, IStateStorage, StateKeyDelegate, UpdateState } from './state/index.js'
|
import {
|
||||||
|
defaultStateKeyDelegate,
|
||||||
|
isCompatibleStorage,
|
||||||
|
IStateStorage,
|
||||||
|
StateKeyDelegate,
|
||||||
|
UpdateState,
|
||||||
|
} from './state/index.js'
|
||||||
|
|
||||||
|
export interface DispatcherParams {
|
||||||
|
/**
|
||||||
|
* If this dispatcher can be used as a scene, its unique name.
|
||||||
|
*
|
||||||
|
* Should not be set manually, use {@link Dispatcher#scene} instead
|
||||||
|
*/
|
||||||
|
sceneName?: string
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom storage for the dispatcher.
|
||||||
|
*
|
||||||
|
* @default Client's storage
|
||||||
|
*/
|
||||||
|
storage?: IStateStorage
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Custom key delegate for the dispatcher.
|
||||||
|
*/
|
||||||
|
key?: StateKeyDelegate
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates dispatcher
|
* Updates dispatcher
|
||||||
*/
|
*/
|
||||||
export class Dispatcher<State = never, SceneName extends string = string> {
|
export class Dispatcher<State extends object = never> {
|
||||||
private _groups: Record<number, Record<UpdateHandler['name'], UpdateHandler[]>> = {}
|
private _groups: Record<number, Record<UpdateHandler['name'], UpdateHandler[]>> = {}
|
||||||
private _groupsOrder: number[] = []
|
private _groupsOrder: number[] = []
|
||||||
|
|
||||||
private _client?: TelegramClient
|
private _client?: TelegramClient
|
||||||
|
|
||||||
private _parent?: Dispatcher<any>
|
private _parent?: Dispatcher<any>
|
||||||
private _children: Dispatcher<any, any>[] = []
|
private _children: Dispatcher<any>[] = []
|
||||||
|
|
||||||
private _scenes?: Record<string, Dispatcher<any, SceneName>>
|
private _scenes?: Record<string, Dispatcher<any>>
|
||||||
private _scene?: SceneName
|
private _scene?: string
|
||||||
private _sceneScoped?: boolean
|
private _sceneScoped?: boolean
|
||||||
|
|
||||||
private _storage?: State extends never ? undefined : IStateStorage
|
private _storage?: State extends never ? undefined : IStateStorage
|
||||||
|
@ -87,65 +114,94 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
private _errorHandler?: <T = {}>(
|
private _errorHandler?: <T = {}>(
|
||||||
err: Error,
|
err: Error,
|
||||||
update: ParsedUpdate & T,
|
update: ParsedUpdate & T,
|
||||||
state?: UpdateState<State, SceneName>,
|
state?: UpdateState<State>,
|
||||||
) => MaybeAsync<boolean>
|
) => MaybeAsync<boolean>
|
||||||
|
|
||||||
private _preUpdateHandler?: <T = {}>(
|
private _preUpdateHandler?: <T = {}>(
|
||||||
update: ParsedUpdate & T,
|
update: ParsedUpdate & T,
|
||||||
state?: UpdateState<State, SceneName>,
|
state?: UpdateState<State>,
|
||||||
) => MaybeAsync<PropagationAction | void>
|
) => MaybeAsync<PropagationAction | void>
|
||||||
|
|
||||||
private _postUpdateHandler?: <T = {}>(
|
private _postUpdateHandler?: <T = {}>(
|
||||||
handled: boolean,
|
handled: boolean,
|
||||||
update: ParsedUpdate & T,
|
update: ParsedUpdate & T,
|
||||||
state?: UpdateState<State, SceneName>,
|
state?: UpdateState<State>,
|
||||||
) => MaybeAsync<void>
|
) => MaybeAsync<void>
|
||||||
|
|
||||||
/**
|
protected constructor(client?: TelegramClient, params?: DispatcherParams) {
|
||||||
* Create a new dispatcher, that will be used as a child,
|
|
||||||
* optionally providing a custom key delegate
|
|
||||||
*/
|
|
||||||
constructor(key?: StateKeyDelegate)
|
|
||||||
/**
|
|
||||||
* Create a new dispatcher, that will be used as a child, optionally
|
|
||||||
* providing custom storage and key delegate
|
|
||||||
*/
|
|
||||||
constructor(storage: IStateStorage, key?: StateKeyDelegate)
|
|
||||||
/**
|
|
||||||
* Create a new dispatcher and bind it to client and optionally
|
|
||||||
* FSM storage
|
|
||||||
*/
|
|
||||||
constructor(
|
|
||||||
client: TelegramClient,
|
|
||||||
...args: (() => State) extends () => never ? [] : [IStateStorage, StateKeyDelegate?]
|
|
||||||
)
|
|
||||||
constructor(
|
|
||||||
client?: TelegramClient | IStateStorage | StateKeyDelegate,
|
|
||||||
storage?: IStateStorage | StateKeyDelegate,
|
|
||||||
key?: StateKeyDelegate,
|
|
||||||
) {
|
|
||||||
this.dispatchRawUpdate = this.dispatchRawUpdate.bind(this)
|
this.dispatchRawUpdate = this.dispatchRawUpdate.bind(this)
|
||||||
this.dispatchUpdate = this.dispatchUpdate.bind(this)
|
this.dispatchUpdate = this.dispatchUpdate.bind(this)
|
||||||
|
|
||||||
|
// eslint-disable-next-line prefer-const
|
||||||
|
let { storage, key, sceneName } = params ?? {}
|
||||||
|
|
||||||
if (client) {
|
if (client) {
|
||||||
if (client instanceof TelegramClient) {
|
|
||||||
this.bindToClient(client)
|
this.bindToClient(client)
|
||||||
|
|
||||||
|
if (!storage) {
|
||||||
|
const _storage = client.storage
|
||||||
|
|
||||||
|
if (!isCompatibleStorage(_storage)) {
|
||||||
|
throw new MtArgumentError(
|
||||||
|
'Storage used by the client is not compatible with the dispatcher. Please provide a compatible storage manually',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
storage = _storage
|
||||||
|
}
|
||||||
|
|
||||||
if (storage) {
|
if (storage) {
|
||||||
this._storage = storage as any
|
this._storage = storage as any
|
||||||
this._stateKeyDelegate = (key ?? defaultStateKeyDelegate) as any
|
this._stateKeyDelegate = (key ?? defaultStateKeyDelegate) as any
|
||||||
}
|
}
|
||||||
} else if (typeof client === 'function') {
|
|
||||||
// is StateKeyDelegate
|
|
||||||
this._customStateKeyDelegate = client as any
|
|
||||||
} else {
|
} else {
|
||||||
this._customStorage = client as any
|
// child dispatcher without client
|
||||||
|
|
||||||
if (storage) {
|
if (storage) {
|
||||||
this._customStateKeyDelegate = client as any
|
this._customStorage = storage as any
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key) {
|
||||||
|
this._customStateKeyDelegate = key as any
|
||||||
|
}
|
||||||
|
|
||||||
|
if (sceneName) {
|
||||||
|
if (sceneName[0] === '$') {
|
||||||
|
throw new MtArgumentError('Scene name cannot start with $')
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scene = sceneName
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new dispatcher and bind it to the client.
|
||||||
|
*/
|
||||||
|
static for<State extends object = never>(client: TelegramClient, params?: DispatcherParams): Dispatcher<State> {
|
||||||
|
return new Dispatcher<State>(client, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new child dispatcher.
|
||||||
|
*/
|
||||||
|
static child<State extends object = never>(params?: DispatcherParams): Dispatcher<State> {
|
||||||
|
return new Dispatcher<State>(undefined, params)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new scene dispatcher
|
||||||
|
*/
|
||||||
|
static scene<State extends object = Record<never, never>>(
|
||||||
|
name: string,
|
||||||
|
params?: Omit<DispatcherParams, 'sceneName'>,
|
||||||
|
): Dispatcher<State> {
|
||||||
|
return new Dispatcher<State>(undefined, { sceneName: name, ...params })
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For scene dispatchers, name of the scene */
|
||||||
|
get sceneName(): string | undefined {
|
||||||
|
return this._scene
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -289,7 +345,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
private async _dispatchUpdateNowImpl(
|
private async _dispatchUpdateNowImpl(
|
||||||
update: ParsedUpdate,
|
update: ParsedUpdate,
|
||||||
// this is getting a bit crazy lol
|
// this is getting a bit crazy lol
|
||||||
parsedState?: UpdateState<State, SceneName> | null,
|
parsedState?: UpdateState<State> | null,
|
||||||
parsedScene?: string | null,
|
parsedScene?: string | null,
|
||||||
forceScene?: true,
|
forceScene?: true,
|
||||||
parsedContext?: UpdateContextType,
|
parsedContext?: UpdateContextType,
|
||||||
|
@ -525,9 +581,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param handler Error handler
|
* @param handler Error handler
|
||||||
*/
|
*/
|
||||||
onError<T = {}>(
|
onError<T = {}>(
|
||||||
handler:
|
handler: ((err: Error, update: ParsedUpdate & T, state?: UpdateState<State>) => MaybeAsync<boolean>) | null,
|
||||||
| ((err: Error, update: ParsedUpdate & T, state?: UpdateState<State, SceneName>) => MaybeAsync<boolean>)
|
|
||||||
| null,
|
|
||||||
): void {
|
): void {
|
||||||
if (handler) this._errorHandler = handler
|
if (handler) this._errorHandler = handler
|
||||||
else this._errorHandler = undefined
|
else this._errorHandler = undefined
|
||||||
|
@ -547,10 +601,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
*/
|
*/
|
||||||
onPreUpdate<T = {}>(
|
onPreUpdate<T = {}>(
|
||||||
handler:
|
handler:
|
||||||
| ((
|
| ((update: ParsedUpdate & T, state?: UpdateState<State>) => MaybeAsync<PropagationAction | void>)
|
||||||
update: ParsedUpdate & T,
|
|
||||||
state?: UpdateState<State, SceneName>,
|
|
||||||
) => MaybeAsync<PropagationAction | void>)
|
|
||||||
| null,
|
| null,
|
||||||
): void {
|
): void {
|
||||||
if (handler) this._preUpdateHandler = handler
|
if (handler) this._preUpdateHandler = handler
|
||||||
|
@ -570,9 +621,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param handler Pre-update middleware
|
* @param handler Pre-update middleware
|
||||||
*/
|
*/
|
||||||
onPostUpdate<T = {}>(
|
onPostUpdate<T = {}>(
|
||||||
handler:
|
handler: ((handled: boolean, update: ParsedUpdate & T, state?: UpdateState<State>) => MaybeAsync<void>) | null,
|
||||||
| ((handled: boolean, update: ParsedUpdate & T, state?: UpdateState<State, SceneName>) => MaybeAsync<void>)
|
|
||||||
| null,
|
|
||||||
): void {
|
): void {
|
||||||
if (handler) this._postUpdateHandler = handler
|
if (handler) this._postUpdateHandler = handler
|
||||||
else this._postUpdateHandler = undefined
|
else this._postUpdateHandler = undefined
|
||||||
|
@ -582,17 +631,13 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* Set error handler that will propagate
|
* Set error handler that will propagate
|
||||||
* the error to the parent dispatcher
|
* the error to the parent dispatcher
|
||||||
*/
|
*/
|
||||||
propagateErrorToParent(
|
propagateErrorToParent(err: Error, update: ParsedUpdate, state?: UpdateState<State>): MaybeAsync<boolean> {
|
||||||
err: Error,
|
|
||||||
update: ParsedUpdate,
|
|
||||||
state?: UpdateState<State, SceneName>,
|
|
||||||
): MaybeAsync<boolean> {
|
|
||||||
if (!this.parent) {
|
if (!this.parent) {
|
||||||
throw new MtArgumentError('This dispatcher is not a child')
|
throw new MtArgumentError('This dispatcher is not a child')
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.parent._errorHandler) {
|
if (this.parent._errorHandler) {
|
||||||
return this.parent._errorHandler(err, update, state)
|
return this.parent._errorHandler(err, update, state as any)
|
||||||
}
|
}
|
||||||
throw err
|
throw err
|
||||||
}
|
}
|
||||||
|
@ -607,7 +652,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
return this._parent ?? null
|
return this._parent ?? null
|
||||||
}
|
}
|
||||||
|
|
||||||
private _prepareChild(child: Dispatcher<any, any>): void {
|
private _prepareChild(child: Dispatcher<any>): void {
|
||||||
if (child._client) {
|
if (child._client) {
|
||||||
throw new MtArgumentError(
|
throw new MtArgumentError(
|
||||||
'Provided dispatcher is ' +
|
'Provided dispatcher is ' +
|
||||||
|
@ -638,7 +683,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
*
|
*
|
||||||
* @param child Other dispatcher
|
* @param child Other dispatcher
|
||||||
*/
|
*/
|
||||||
addChild(child: Dispatcher<State, SceneName>): void {
|
addChild(child: Dispatcher<State>): void {
|
||||||
if (this._children.includes(child)) return
|
if (this._children.includes(child)) return
|
||||||
|
|
||||||
this._prepareChild(child)
|
this._prepareChild(child)
|
||||||
|
@ -658,7 +703,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param scene Dispatcher representing the scene
|
* @param scene Dispatcher representing the scene
|
||||||
* @param scoped Whether to use scoped FSM storage for the scene
|
* @param scoped Whether to use scoped FSM storage for the scene
|
||||||
*/
|
*/
|
||||||
addScene(uid: SceneName, scene: Dispatcher<State, SceneName>, scoped: false): void
|
addScene(scene: Dispatcher<State>, scoped: false): void
|
||||||
/**
|
/**
|
||||||
* Add a dispatcher as a scene with a scoped state
|
* Add a dispatcher as a scene with a scoped state
|
||||||
*
|
*
|
||||||
|
@ -672,26 +717,23 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param scene Dispatcher representing the scene
|
* @param scene Dispatcher representing the scene
|
||||||
* @param scoped Whether to use scoped FSM storage for the scene (defaults to `true`)
|
* @param scoped Whether to use scoped FSM storage for the scene (defaults to `true`)
|
||||||
*/
|
*/
|
||||||
addScene(uid: SceneName, scene: Dispatcher<any, SceneName>, scoped?: true): void
|
addScene(scene: Dispatcher<any>, scoped?: true): void
|
||||||
addScene(uid: SceneName, scene: Dispatcher<any, SceneName>, scoped = true): void {
|
addScene(scene: Dispatcher<any>, scoped = true): void {
|
||||||
if (!this._scenes) this._scenes = {}
|
if (!this._scenes) this._scenes = {}
|
||||||
|
|
||||||
if (uid in this._scenes) {
|
if (!scene._scene) {
|
||||||
throw new MtArgumentError(`Scene with UID ${uid} is already registered!`)
|
throw new MtArgumentError(
|
||||||
|
'Non-scene dispatcher passed to addScene. Use `Dispatcher.scene()` to create one.',
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (uid[0] === '$') {
|
if (scene._scene in this._scenes) {
|
||||||
throw new MtArgumentError('Scene UID cannot start with $')
|
throw new MtArgumentError(`Scene with name ${scene._scene} is already registered!`)
|
||||||
}
|
|
||||||
|
|
||||||
if (scene._scene) {
|
|
||||||
throw new MtArgumentError(`This dispatcher is already registered as scene ${scene._scene}`)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this._prepareChild(scene)
|
this._prepareChild(scene)
|
||||||
scene._scene = uid
|
|
||||||
scene._sceneScoped = scoped
|
scene._sceneScoped = scoped
|
||||||
this._scenes[uid] = scene
|
this._scenes[scene._scene] = scene
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -705,7 +747,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
*
|
*
|
||||||
* @param child Other dispatcher
|
* @param child Other dispatcher
|
||||||
*/
|
*/
|
||||||
removeChild(child: Dispatcher<any, any>): void {
|
removeChild(child: Dispatcher<any>): void {
|
||||||
const idx = this._children.indexOf(child)
|
const idx = this._children.indexOf(child)
|
||||||
|
|
||||||
if (idx > -1) {
|
if (idx > -1) {
|
||||||
|
@ -732,7 +774,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
*
|
*
|
||||||
* @param other Other dispatcher
|
* @param other Other dispatcher
|
||||||
*/
|
*/
|
||||||
extend(other: Dispatcher<State, SceneName>): void {
|
extend(other: Dispatcher<State>): void {
|
||||||
if (other._customStorage || other._customStateKeyDelegate) {
|
if (other._customStorage || other._customStateKeyDelegate) {
|
||||||
throw new MtArgumentError('Provided dispatcher has custom storage and cannot be extended from.')
|
throw new MtArgumentError('Provided dispatcher has custom storage and cannot be extended from.')
|
||||||
}
|
}
|
||||||
|
@ -772,7 +814,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
delete myScenes[key]
|
delete myScenes[key]
|
||||||
}
|
}
|
||||||
|
|
||||||
this.addScene(key as any, myScenes[key] as any, myScenes[key]._sceneScoped as any)
|
this.addScene(myScenes[key] as any, myScenes[key]._sceneScoped as any)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -791,8 +833,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
*
|
*
|
||||||
* @param children Whether to also clone children and scenes
|
* @param children Whether to also clone children and scenes
|
||||||
*/
|
*/
|
||||||
clone(children = false): Dispatcher<State, SceneName> {
|
clone(children = false): Dispatcher<State> {
|
||||||
const dp = new Dispatcher<State, SceneName>()
|
const dp = new Dispatcher<State>()
|
||||||
|
|
||||||
// copy handlers.
|
// copy handlers.
|
||||||
Object.keys(this._groups).forEach((key) => {
|
Object.keys(this._groups).forEach((key) => {
|
||||||
|
@ -819,12 +861,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
if (this._scenes) {
|
if (this._scenes) {
|
||||||
Object.keys(this._scenes).forEach((key) => {
|
Object.keys(this._scenes).forEach((key) => {
|
||||||
const scene = this._scenes![key].clone(true)
|
const scene = this._scenes![key].clone(true)
|
||||||
dp.addScene(
|
dp.addScene(scene as any, this._scenes![key]._sceneScoped as any)
|
||||||
key as any,
|
|
||||||
scene as any,
|
|
||||||
|
|
||||||
this._scenes![key]._sceneScoped as any,
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -841,7 +878,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param key State storage key
|
* @param key State storage key
|
||||||
* @template S State type, defaults to dispatcher's state type. Only checked at compile-time
|
* @template S State type, defaults to dispatcher's state type. Only checked at compile-time
|
||||||
*/
|
*/
|
||||||
getState<S = State>(key: string): UpdateState<S, SceneName>
|
getState<S extends object = State>(key: string): UpdateState<S>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get update state object for the given object.
|
* Get update state object for the given object.
|
||||||
|
@ -853,8 +890,8 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param object Object for which the state should be fetched
|
* @param object Object for which the state should be fetched
|
||||||
* @template S State type, defaults to dispatcher's state type. Only checked at compile-time
|
* @template S State type, defaults to dispatcher's state type. Only checked at compile-time
|
||||||
*/
|
*/
|
||||||
getState<S = State>(object: Parameters<StateKeyDelegate>[0]): Promise<UpdateState<S, SceneName>>
|
getState<S extends object = State>(object: Parameters<StateKeyDelegate>[0]): Promise<UpdateState<S>>
|
||||||
getState<S = State>(object: string | Parameters<StateKeyDelegate>[0]): MaybeAsync<UpdateState<S, SceneName>> {
|
getState<S extends object = State>(object: string | Parameters<StateKeyDelegate>[0]): MaybeAsync<UpdateState<S>> {
|
||||||
if (!this._storage) {
|
if (!this._storage) {
|
||||||
throw new MtArgumentError('Cannot use getUpdateState() filter without state storage')
|
throw new MtArgumentError('Cannot use getUpdateState() filter without state storage')
|
||||||
}
|
}
|
||||||
|
@ -895,7 +932,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* This will load the state for the given object
|
* This will load the state for the given object
|
||||||
* ignoring local custom storage, key delegate and scene scope.
|
* ignoring local custom storage, key delegate and scene scope.
|
||||||
*/
|
*/
|
||||||
getGlobalState<T>(object: Parameters<StateKeyDelegate>[0]): Promise<UpdateState<T, SceneName>> {
|
getGlobalState<T extends object>(object: Parameters<StateKeyDelegate>[0]): Promise<UpdateState<T>> {
|
||||||
if (!this._parent) {
|
if (!this._parent) {
|
||||||
throw new MtArgumentError('This dispatcher does not have a parent')
|
throw new MtArgumentError('This dispatcher does not have a parent')
|
||||||
}
|
}
|
||||||
|
@ -963,10 +1000,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param group Handler group index
|
* @param group Handler group index
|
||||||
*/
|
*/
|
||||||
onNewMessage(
|
onNewMessage(
|
||||||
handler: NewMessageHandler<
|
handler: NewMessageHandler<MessageContext, State extends never ? never : UpdateState<State>>['callback'],
|
||||||
MessageContext,
|
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
|
||||||
>['callback'],
|
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
|
||||||
|
@ -981,7 +1015,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<MessageContext, Mod, State>,
|
filter: UpdateFilter<MessageContext, Mod, State>,
|
||||||
handler: NewMessageHandler<
|
handler: NewMessageHandler<
|
||||||
filters.Modify<MessageContext, Mod>,
|
filters.Modify<MessageContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -997,7 +1031,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<MessageContext, Mod>,
|
filter: UpdateFilter<MessageContext, Mod>,
|
||||||
handler: NewMessageHandler<
|
handler: NewMessageHandler<
|
||||||
filters.Modify<MessageContext, Mod>,
|
filters.Modify<MessageContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1014,10 +1048,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param group Handler group index
|
* @param group Handler group index
|
||||||
*/
|
*/
|
||||||
onEditMessage(
|
onEditMessage(
|
||||||
handler: EditMessageHandler<
|
handler: EditMessageHandler<MessageContext, State extends never ? never : UpdateState<State>>['callback'],
|
||||||
MessageContext,
|
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
|
||||||
>['callback'],
|
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
|
||||||
|
@ -1032,7 +1063,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<MessageContext, Mod, State>,
|
filter: UpdateFilter<MessageContext, Mod, State>,
|
||||||
handler: EditMessageHandler<
|
handler: EditMessageHandler<
|
||||||
filters.Modify<MessageContext, Mod>,
|
filters.Modify<MessageContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1048,7 +1079,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<MessageContext, Mod>,
|
filter: UpdateFilter<MessageContext, Mod>,
|
||||||
handler: EditMessageHandler<
|
handler: EditMessageHandler<
|
||||||
filters.Modify<MessageContext, Mod>,
|
filters.Modify<MessageContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1065,10 +1096,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
* @param group Handler group index
|
* @param group Handler group index
|
||||||
*/
|
*/
|
||||||
onMessageGroup(
|
onMessageGroup(
|
||||||
handler: MessageGroupHandler<
|
handler: MessageGroupHandler<MessageContext, State extends never ? never : UpdateState<State>>['callback'],
|
||||||
MessageContext,
|
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
|
||||||
>['callback'],
|
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
|
||||||
|
@ -1083,7 +1111,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<MessageContext, Mod, State>,
|
filter: UpdateFilter<MessageContext, Mod, State>,
|
||||||
handler: MessageGroupHandler<
|
handler: MessageGroupHandler<
|
||||||
filters.Modify<MessageContext, Mod>,
|
filters.Modify<MessageContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1099,7 +1127,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<MessageContext, Mod>,
|
filter: UpdateFilter<MessageContext, Mod>,
|
||||||
handler: MessageGroupHandler<
|
handler: MessageGroupHandler<
|
||||||
filters.Modify<MessageContext, Mod>,
|
filters.Modify<MessageContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1222,7 +1250,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
onCallbackQuery(
|
onCallbackQuery(
|
||||||
handler: CallbackQueryHandler<
|
handler: CallbackQueryHandler<
|
||||||
CallbackQueryContext,
|
CallbackQueryContext,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1238,7 +1266,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<CallbackQueryContext, Mod, State>,
|
filter: UpdateFilter<CallbackQueryContext, Mod, State>,
|
||||||
handler: CallbackQueryHandler<
|
handler: CallbackQueryHandler<
|
||||||
filters.Modify<CallbackQueryContext, Mod>,
|
filters.Modify<CallbackQueryContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
@ -1254,7 +1282,7 @@ export class Dispatcher<State = never, SceneName extends string = string> {
|
||||||
filter: UpdateFilter<CallbackQueryContext, Mod>,
|
filter: UpdateFilter<CallbackQueryContext, Mod>,
|
||||||
handler: CallbackQueryHandler<
|
handler: CallbackQueryHandler<
|
||||||
filters.Modify<CallbackQueryContext, Mod>,
|
filters.Modify<CallbackQueryContext, Mod>,
|
||||||
State extends never ? never : UpdateState<State, SceneName>
|
State extends never ? never : UpdateState<State>
|
||||||
>['callback'],
|
>['callback'],
|
||||||
group?: number,
|
group?: number,
|
||||||
): void
|
): void
|
||||||
|
|
|
@ -12,7 +12,7 @@ import { Modify, UpdateFilter } from './types.js'
|
||||||
* @param filter
|
* @param filter
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export function every<Mod, State>(
|
export function every<Mod, State extends object>(
|
||||||
filter: UpdateFilter<Message, Mod, State>,
|
filter: UpdateFilter<Message, Mod, State>,
|
||||||
): UpdateFilter<
|
): UpdateFilter<
|
||||||
MessageContext,
|
MessageContext,
|
||||||
|
@ -57,8 +57,11 @@ export function every<Mod, State>(
|
||||||
* @param filter
|
* @param filter
|
||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
|
export function some<State extends object>(
|
||||||
// eslint-disable-next-line
|
// eslint-disable-next-line
|
||||||
export function some<State>(filter: UpdateFilter<Message, any, State>): UpdateFilter<MessageContext, {}, State> {
|
filter: UpdateFilter<Message, any, State>,
|
||||||
|
// eslint-disable-next-line
|
||||||
|
): UpdateFilter<MessageContext, {}, State> {
|
||||||
return (ctx, state) => {
|
return (ctx, state) => {
|
||||||
let i = 0
|
let i = 0
|
||||||
const upds = ctx.messages
|
const upds = ctx.messages
|
||||||
|
|
|
@ -22,7 +22,7 @@ export const any: UpdateFilter<any> = () => true
|
||||||
*
|
*
|
||||||
* @param fn Filter to negate
|
* @param fn Filter to negate
|
||||||
*/
|
*/
|
||||||
export function not<Base, Mod, State>(
|
export function not<Base, Mod, State extends object>(
|
||||||
fn: UpdateFilter<Base, Mod, State>,
|
fn: UpdateFilter<Base, Mod, State>,
|
||||||
): UpdateFilter<Base, Invert<Base, Mod>, State> {
|
): UpdateFilter<Base, Invert<Base, Mod>, State> {
|
||||||
return (upd, state) => {
|
return (upd, state) => {
|
||||||
|
@ -37,16 +37,39 @@ export function not<Base, Mod, State>(
|
||||||
// i couldn't come up with proper types for these 😭
|
// i couldn't come up with proper types for these 😭
|
||||||
// if you know how to do this better - PRs are welcome!
|
// if you know how to do this better - PRs are welcome!
|
||||||
|
|
||||||
export function and<Base1, Mod1, State1, Base2, Mod2, State2>(
|
export function and<Base1, Mod1, State1 extends object, Base2, Mod2, State2 extends object>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
): UpdateFilter<Base1 & Base2, Mod1 & Mod2, State1 | State2>
|
): UpdateFilter<Base1 & Base2, Mod1 & Mod2, State1 | State2>
|
||||||
export function and<Base1, Mod1, State1, Base2, Mod2, State2, Base3, Mod3, State3>(
|
export function and<
|
||||||
|
Base1,
|
||||||
|
Mod1,
|
||||||
|
State1 extends object,
|
||||||
|
Base2,
|
||||||
|
Mod2,
|
||||||
|
State2 extends object,
|
||||||
|
Base3,
|
||||||
|
Mod3,
|
||||||
|
State3 extends object,
|
||||||
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
fn3: UpdateFilter<Base3, Mod3, State3>,
|
fn3: UpdateFilter<Base3, Mod3, State3>,
|
||||||
): UpdateFilter<Base1 & Base2 & Base3, Mod1 & Mod2 & Mod3, State1 | State2 | State3>
|
): UpdateFilter<Base1 & Base2 & Base3, Mod1 & Mod2 & Mod3, State1 | State2 | State3>
|
||||||
export function and<Base1, Mod1, State1, Base2, Mod2, State2, Base3, Mod3, State3, Base4, Mod4, State4>(
|
export function and<
|
||||||
|
Base1,
|
||||||
|
Mod1,
|
||||||
|
State1 extends object,
|
||||||
|
Base2,
|
||||||
|
Mod2,
|
||||||
|
State2 extends object,
|
||||||
|
Base3,
|
||||||
|
Mod3,
|
||||||
|
State3 extends object,
|
||||||
|
Base4,
|
||||||
|
Mod4,
|
||||||
|
State4 extends object,
|
||||||
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
fn3: UpdateFilter<Base3, Mod3, State3>,
|
fn3: UpdateFilter<Base3, Mod3, State3>,
|
||||||
|
@ -55,19 +78,19 @@ export function and<Base1, Mod1, State1, Base2, Mod2, State2, Base3, Mod3, State
|
||||||
export function and<
|
export function and<
|
||||||
Base1,
|
Base1,
|
||||||
Mod1,
|
Mod1,
|
||||||
State1,
|
State1 extends object,
|
||||||
Base2,
|
Base2,
|
||||||
Mod2,
|
Mod2,
|
||||||
State2,
|
State2 extends object,
|
||||||
Base3,
|
Base3,
|
||||||
Mod3,
|
Mod3,
|
||||||
State3,
|
State3 extends object,
|
||||||
Base4,
|
Base4,
|
||||||
Mod4,
|
Mod4,
|
||||||
State4,
|
State4 extends object,
|
||||||
Base5,
|
Base5,
|
||||||
Mod5,
|
Mod5,
|
||||||
State5,
|
State5 extends object,
|
||||||
>(
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
|
@ -82,22 +105,22 @@ export function and<
|
||||||
export function and<
|
export function and<
|
||||||
Base1,
|
Base1,
|
||||||
Mod1,
|
Mod1,
|
||||||
State1,
|
State1 extends object,
|
||||||
Base2,
|
Base2,
|
||||||
Mod2,
|
Mod2,
|
||||||
State2,
|
State2 extends object,
|
||||||
Base3,
|
Base3,
|
||||||
Mod3,
|
Mod3,
|
||||||
State3,
|
State3 extends object,
|
||||||
Base4,
|
Base4,
|
||||||
Mod4,
|
Mod4,
|
||||||
State4,
|
State4 extends object,
|
||||||
Base5,
|
Base5,
|
||||||
Mod5,
|
Mod5,
|
||||||
State5,
|
State5 extends object,
|
||||||
Base6,
|
Base6,
|
||||||
Mod6,
|
Mod6,
|
||||||
State6,
|
State6 extends object,
|
||||||
>(
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
|
@ -159,18 +182,41 @@ export function and(...fns: UpdateFilter<any, any, any>[]): UpdateFilter<any, an
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function or<Base1, Mod1, State1, Base2, Mod2, State2>(
|
export function or<Base1, Mod1, State1 extends object, Base2, Mod2, State2 extends object>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
): UpdateFilter<Base1 & Base2, Mod1 | Mod2, State1 | State2>
|
): UpdateFilter<Base1 & Base2, Mod1 | Mod2, State1 | State2>
|
||||||
|
|
||||||
export function or<Base1, Mod1, State1, Base2, Mod2, State2, Base3, Mod3, State3>(
|
export function or<
|
||||||
|
Base1,
|
||||||
|
Mod1,
|
||||||
|
State1 extends object,
|
||||||
|
Base2,
|
||||||
|
Mod2,
|
||||||
|
State2 extends object,
|
||||||
|
Base3,
|
||||||
|
Mod3,
|
||||||
|
State3 extends object,
|
||||||
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
fn3: UpdateFilter<Base3, Mod3, State3>,
|
fn3: UpdateFilter<Base3, Mod3, State3>,
|
||||||
): UpdateFilter<Base1 & Base2 & Base3, Mod1 | Mod2 | Mod3, State1 | State2 | State3>
|
): UpdateFilter<Base1 & Base2 & Base3, Mod1 | Mod2 | Mod3, State1 | State2 | State3>
|
||||||
|
|
||||||
export function or<Base1, Mod1, State1, Base2, Mod2, State2, Base3, Mod3, State3, Base4, Mod4, State4>(
|
export function or<
|
||||||
|
Base1,
|
||||||
|
Mod1,
|
||||||
|
State1 extends object,
|
||||||
|
Base2,
|
||||||
|
Mod2,
|
||||||
|
State2 extends object,
|
||||||
|
Base3,
|
||||||
|
Mod3,
|
||||||
|
State3 extends object,
|
||||||
|
Base4,
|
||||||
|
Mod4,
|
||||||
|
State4 extends object,
|
||||||
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
fn3: UpdateFilter<Base3, Mod3, State3>,
|
fn3: UpdateFilter<Base3, Mod3, State3>,
|
||||||
|
@ -180,19 +226,19 @@ export function or<Base1, Mod1, State1, Base2, Mod2, State2, Base3, Mod3, State3
|
||||||
export function or<
|
export function or<
|
||||||
Base1,
|
Base1,
|
||||||
Mod1,
|
Mod1,
|
||||||
State1,
|
State1 extends object,
|
||||||
Base2,
|
Base2,
|
||||||
Mod2,
|
Mod2,
|
||||||
State2,
|
State2 extends object,
|
||||||
Base3,
|
Base3,
|
||||||
Mod3,
|
Mod3,
|
||||||
State3,
|
State3 extends object,
|
||||||
Base4,
|
Base4,
|
||||||
Mod4,
|
Mod4,
|
||||||
State4,
|
State4 extends object,
|
||||||
Base5,
|
Base5,
|
||||||
Mod5,
|
Mod5,
|
||||||
State5,
|
State5 extends object,
|
||||||
>(
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
|
@ -208,22 +254,22 @@ export function or<
|
||||||
export function or<
|
export function or<
|
||||||
Base1,
|
Base1,
|
||||||
Mod1,
|
Mod1,
|
||||||
State1,
|
State1 extends object,
|
||||||
Base2,
|
Base2,
|
||||||
Mod2,
|
Mod2,
|
||||||
State2,
|
State2 extends object,
|
||||||
Base3,
|
Base3,
|
||||||
Mod3,
|
Mod3,
|
||||||
State3,
|
State3 extends object,
|
||||||
Base4,
|
Base4,
|
||||||
Mod4,
|
Mod4,
|
||||||
State4,
|
State4 extends object,
|
||||||
Base5,
|
Base5,
|
||||||
Mod5,
|
Mod5,
|
||||||
State5,
|
State5 extends object,
|
||||||
Base6,
|
Base6,
|
||||||
Mod6,
|
Mod6,
|
||||||
State6,
|
State6 extends object,
|
||||||
>(
|
>(
|
||||||
fn1: UpdateFilter<Base1, Mod1, State1>,
|
fn1: UpdateFilter<Base1, Mod1, State1>,
|
||||||
fn2: UpdateFilter<Base2, Mod2, State2>,
|
fn2: UpdateFilter<Base2, Mod2, State2>,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import {
|
||||||
Video,
|
Video,
|
||||||
} from '@mtcute/client'
|
} from '@mtcute/client'
|
||||||
|
|
||||||
|
import { MessageContext } from '../index.js'
|
||||||
import { Modify, UpdateFilter } from './types.js'
|
import { Modify, UpdateFilter } from './types.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -208,3 +209,25 @@ export const sender =
|
||||||
): UpdateFilter<Message, { sender: Extract<Message['sender'], { type: T }> }> =>
|
): UpdateFilter<Message, { sender: Extract<Message['sender'], { type: T }> }> =>
|
||||||
(msg) =>
|
(msg) =>
|
||||||
msg.sender.type === type
|
msg.sender.type === type
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filter that matches messages that are replies to some other message.
|
||||||
|
*
|
||||||
|
* Optionally, you can pass a filter that will be applied to the replied message.
|
||||||
|
*/
|
||||||
|
export const replyTo =
|
||||||
|
<Mod, State extends object>(
|
||||||
|
filter?: UpdateFilter<Message, Mod, State>,
|
||||||
|
): UpdateFilter<MessageContext, { getReplyTo: () => Promise<Message & Mod> }, State> =>
|
||||||
|
async (msg, state) => {
|
||||||
|
if (!msg.replyToMessageId) return false
|
||||||
|
|
||||||
|
const reply = await msg.getReplyTo()
|
||||||
|
if (!reply) return false
|
||||||
|
|
||||||
|
msg.getReplyTo = () => Promise.resolve(reply)
|
||||||
|
|
||||||
|
if (!filter) return true
|
||||||
|
|
||||||
|
return filter(reply, state)
|
||||||
|
}
|
||||||
|
|
|
@ -20,7 +20,7 @@ export const stateEmpty: UpdateFilter<any> = async (upd, state) => {
|
||||||
*
|
*
|
||||||
* @param predicate State predicate
|
* @param predicate State predicate
|
||||||
*/
|
*/
|
||||||
export const state = <T>(
|
export const state = <T extends object>(
|
||||||
predicate: (state: T) => MaybeAsync<boolean>,
|
predicate: (state: T) => MaybeAsync<boolean>,
|
||||||
// eslint-disable-next-line @typescript-eslint/ban-types
|
// eslint-disable-next-line @typescript-eslint/ban-types
|
||||||
): UpdateFilter<any, {}, T> => {
|
): UpdateFilter<any, {}, T> => {
|
||||||
|
|
|
@ -75,7 +75,7 @@ import { UpdateState } from '../state/update-state.js'
|
||||||
*/
|
*/
|
||||||
// we need the second parameter because it carries meta information
|
// we need the second parameter because it carries meta information
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
export type UpdateFilter<Base, Mod = {}, State = never> = (
|
export type UpdateFilter<Base, Mod = {}, State extends object = never> = (
|
||||||
update: Base,
|
update: Base,
|
||||||
state?: UpdateState<State>,
|
state?: UpdateState<State>,
|
||||||
) => MaybeAsync<boolean>
|
) => MaybeAsync<boolean>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { assertNever, MaybeAsync } from '@mtcute/client'
|
import { assertNever, Chat, MaybeAsync, User } from '@mtcute/client'
|
||||||
|
|
||||||
import { CallbackQueryContext, MessageContext } from '../context/index.js'
|
import { CallbackQueryContext, MessageContext } from '../context/index.js'
|
||||||
|
|
||||||
|
@ -10,7 +10,7 @@ import { CallbackQueryContext, MessageContext } from '../context/index.js'
|
||||||
* @param msg Message or callback from which to derive the key
|
* @param msg Message or callback from which to derive the key
|
||||||
* @param scene Current scene UID, or `null` if none
|
* @param scene Current scene UID, or `null` if none
|
||||||
*/
|
*/
|
||||||
export type StateKeyDelegate = (upd: MessageContext | CallbackQueryContext) => MaybeAsync<string | null>
|
export type StateKeyDelegate = (upd: MessageContext | CallbackQueryContext | User | Chat) => MaybeAsync<string | null>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default state key delegate.
|
* Default state key delegate.
|
||||||
|
@ -24,6 +24,11 @@ export type StateKeyDelegate = (upd: MessageContext | CallbackQueryContext) => M
|
||||||
* - If in group/channel/supergroup (i.e. `upd.chatType !== 'user'`), `upd.chatId + '_' + upd.user.id`
|
* - If in group/channel/supergroup (i.e. `upd.chatType !== 'user'`), `upd.chatId + '_' + upd.user.id`
|
||||||
*/
|
*/
|
||||||
export const defaultStateKeyDelegate: StateKeyDelegate = (upd): string | null => {
|
export const defaultStateKeyDelegate: StateKeyDelegate = (upd): string | null => {
|
||||||
|
if ('type' in upd) {
|
||||||
|
// User | Chat
|
||||||
|
return String(upd.id)
|
||||||
|
}
|
||||||
|
|
||||||
if (upd._name === 'new_message') {
|
if (upd._name === 'new_message') {
|
||||||
switch (upd.chat.chatType) {
|
switch (upd.chat.chatType) {
|
||||||
case 'private':
|
case 'private':
|
||||||
|
|
|
@ -80,3 +80,18 @@ export interface IStateStorage {
|
||||||
*/
|
*/
|
||||||
resetRateLimit(key: string): MaybeAsync<void>
|
resetRateLimit(key: string): MaybeAsync<void>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isCompatibleStorage(storage: unknown): storage is IStateStorage {
|
||||||
|
return (
|
||||||
|
typeof storage === 'object' &&
|
||||||
|
storage !== null &&
|
||||||
|
'getState' in storage &&
|
||||||
|
'setState' in storage &&
|
||||||
|
'deleteState' in storage &&
|
||||||
|
'getCurrentScene' in storage &&
|
||||||
|
'setCurrentScene' in storage &&
|
||||||
|
'deleteCurrentScene' in storage &&
|
||||||
|
'getRateLimit' in storage &&
|
||||||
|
'resetRateLimit' in storage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
|
/* eslint-disable dot-notation */
|
||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
import { MtArgumentError, MtcuteError } from '@mtcute/client'
|
import { MtArgumentError, MtcuteError } from '@mtcute/client'
|
||||||
import { sleep } from '@mtcute/client/utils.js'
|
import { sleep } from '@mtcute/client/utils.js'
|
||||||
|
|
||||||
|
import type { Dispatcher } from '../dispatcher.js'
|
||||||
import { IStateStorage } from './storage.js'
|
import { IStateStorage } from './storage.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -18,13 +21,13 @@ export class RateLimitError extends MtcuteError {
|
||||||
* @template State Type that represents the state
|
* @template State Type that represents the state
|
||||||
* @template SceneName Possible scene names
|
* @template SceneName Possible scene names
|
||||||
*/
|
*/
|
||||||
export class UpdateState<State, SceneName extends string = string> {
|
export class UpdateState<State extends object> {
|
||||||
private _key: string
|
private _key: string
|
||||||
private _localKey!: string
|
private _localKey!: string
|
||||||
|
|
||||||
private _storage: IStateStorage
|
private _storage: IStateStorage
|
||||||
|
|
||||||
private _scene: SceneName | null
|
private _scene: string | null
|
||||||
private _scoped?: boolean
|
private _scoped?: boolean
|
||||||
private _cached?: State | null
|
private _cached?: State | null
|
||||||
|
|
||||||
|
@ -34,7 +37,7 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
constructor(
|
constructor(
|
||||||
storage: IStateStorage,
|
storage: IStateStorage,
|
||||||
key: string,
|
key: string,
|
||||||
scene: SceneName | null,
|
scene: string | null,
|
||||||
scoped?: boolean,
|
scoped?: boolean,
|
||||||
customStorage?: IStateStorage,
|
customStorage?: IStateStorage,
|
||||||
customKey?: string,
|
customKey?: string,
|
||||||
|
@ -50,7 +53,8 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
this._updateLocalKey()
|
this._updateLocalKey()
|
||||||
}
|
}
|
||||||
|
|
||||||
get scene(): SceneName | null {
|
/** Name of the current scene */
|
||||||
|
get scene(): string | null {
|
||||||
return this._scene
|
return this._scene
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,7 +72,7 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
* @param fallback Default state value
|
* @param fallback Default state value
|
||||||
* @param force Whether to ignore cached state (def. `false`)
|
* @param force Whether to ignore cached state (def. `false`)
|
||||||
*/
|
*/
|
||||||
async get(fallback: State, force?: boolean): Promise<State>
|
async get(fallback: State | (() => State), force?: boolean): Promise<State>
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieve the state from the storage, falling back to default
|
* Retrieve the state from the storage, falling back to default
|
||||||
|
@ -77,27 +81,32 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
* @param fallback Default state value
|
* @param fallback Default state value
|
||||||
* @param force Whether to ignore cached state (def. `false`)
|
* @param force Whether to ignore cached state (def. `false`)
|
||||||
*/
|
*/
|
||||||
async get(fallback?: State, force?: boolean): Promise<State | null>
|
async get(fallback?: State | (() => State), force?: boolean): Promise<State | null>
|
||||||
/**
|
/**
|
||||||
* Retrieve the state from the storage
|
* Retrieve the state from the storage
|
||||||
*
|
*
|
||||||
* @param force Whether to ignore cached state (def. `false`)
|
* @param force Whether to ignore cached state (def. `false`)
|
||||||
*/
|
*/
|
||||||
async get(force?: boolean): Promise<State | null>
|
async get(force?: boolean): Promise<State | null>
|
||||||
async get(fallback?: State | boolean, force?: boolean): Promise<State | null> {
|
async get(fallback?: State | (() => State) | boolean, force?: boolean): Promise<State | null> {
|
||||||
if (typeof fallback === 'boolean') {
|
if (typeof fallback === 'boolean') {
|
||||||
force = fallback
|
force = fallback
|
||||||
fallback = undefined
|
fallback = undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!force && this._cached !== undefined) {
|
if (!force && this._cached !== undefined) {
|
||||||
if (!this._cached && fallback) return fallback
|
if (!this._cached && fallback) {
|
||||||
|
return typeof fallback === 'function' ? fallback() : fallback
|
||||||
|
}
|
||||||
|
|
||||||
return this._cached
|
return this._cached
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = (await this._localStorage.getState(this._localKey)) as State | null
|
let res = (await this._localStorage.getState(this._localKey)) as State | null
|
||||||
if (!res && fallback) res = fallback
|
|
||||||
|
if (!res && fallback) {
|
||||||
|
res = typeof fallback === 'function' ? fallback() : fallback
|
||||||
|
}
|
||||||
this._cached = res
|
this._cached = res
|
||||||
|
|
||||||
return res
|
return res
|
||||||
|
@ -128,7 +137,16 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
* @param ttl TTL for the new state (in seconds)
|
* @param ttl TTL for the new state (in seconds)
|
||||||
* @param forceLoad Whether to force load the old state from storage
|
* @param forceLoad Whether to force load the old state from storage
|
||||||
*/
|
*/
|
||||||
async merge(state: Partial<State>, fallback?: State, ttl?: number, forceLoad = false): Promise<State> {
|
async merge(
|
||||||
|
state: Partial<State>,
|
||||||
|
params: {
|
||||||
|
fallback?: State | (() => State)
|
||||||
|
ttl?: number
|
||||||
|
forceLoad?: boolean
|
||||||
|
} = {},
|
||||||
|
): Promise<State> {
|
||||||
|
const { fallback, ttl, forceLoad } = params
|
||||||
|
|
||||||
const old = await this.get(forceLoad)
|
const old = await this.get(forceLoad)
|
||||||
|
|
||||||
if (!old) {
|
if (!old) {
|
||||||
|
@ -136,7 +154,9 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
throw new MtArgumentError('Cannot use merge on empty state without fallback.')
|
throw new MtArgumentError('Cannot use merge on empty state without fallback.')
|
||||||
}
|
}
|
||||||
|
|
||||||
await this.set({ ...fallback, ...state }, ttl)
|
const fallback_ = typeof fallback === 'function' ? fallback() : fallback
|
||||||
|
|
||||||
|
await this.set({ ...fallback_, ...state }, ttl)
|
||||||
} else {
|
} else {
|
||||||
await this.set({ ...old, ...state }, ttl)
|
await this.set({ ...old, ...state }, ttl)
|
||||||
}
|
}
|
||||||
|
@ -156,14 +176,43 @@ export class UpdateState<State, SceneName extends string = string> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Enter some scene
|
* Enter some scene
|
||||||
*
|
|
||||||
* @param scene Scene name
|
|
||||||
* @param ttl TTL for the scene (in seconds)
|
|
||||||
*/
|
*/
|
||||||
async enter(scene: SceneName, ttl?: number): Promise<void> {
|
async enter<SceneState extends object, Scene extends Dispatcher<SceneState>>(
|
||||||
this._scene = scene
|
scene: Scene,
|
||||||
|
params?: {
|
||||||
|
/**
|
||||||
|
* Initial state for the scene
|
||||||
|
*
|
||||||
|
* Note that this will only work if the scene uses the same key delegate as this state.
|
||||||
|
*/
|
||||||
|
with?: SceneState
|
||||||
|
|
||||||
|
/** TTL for the scene (in seconds) */
|
||||||
|
ttl?: number
|
||||||
|
},
|
||||||
|
): Promise<void> {
|
||||||
|
const { with: with_, ttl } = params ?? {}
|
||||||
|
|
||||||
|
if (!scene['_scene']) {
|
||||||
|
throw new MtArgumentError('Cannot enter a non-scene Dispatcher')
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!scene['_parent']) {
|
||||||
|
throw new MtArgumentError('This scene has not been registered')
|
||||||
|
}
|
||||||
|
|
||||||
|
this._scene = scene['_scene']
|
||||||
this._updateLocalKey()
|
this._updateLocalKey()
|
||||||
await this._storage.setCurrentScene(this._key, scene, ttl)
|
|
||||||
|
await this._storage.setCurrentScene(this._key, this._scene, ttl)
|
||||||
|
|
||||||
|
if (with_) {
|
||||||
|
if (scene['_customStateKeyDelegate']) {
|
||||||
|
throw new MtArgumentError('Cannot use `with` parameter when the scene uses a custom state key delegate')
|
||||||
|
}
|
||||||
|
|
||||||
|
await scene.getState(this._key).set(with_, ttl)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -32,10 +32,7 @@ interface WizardInternalState {
|
||||||
* that can be used to simplify implementing
|
* that can be used to simplify implementing
|
||||||
* step-by-step scenes.
|
* step-by-step scenes.
|
||||||
*/
|
*/
|
||||||
export class WizardScene<State, SceneName extends string = string> extends Dispatcher<
|
export class WizardScene<State extends object> extends Dispatcher<State & WizardInternalState> {
|
||||||
State & WizardInternalState,
|
|
||||||
SceneName
|
|
||||||
> {
|
|
||||||
private _steps = 0
|
private _steps = 0
|
||||||
|
|
||||||
private _defaultState: State & WizardInternalState = {} as State & WizardInternalState
|
private _defaultState: State & WizardInternalState = {} as State & WizardInternalState
|
||||||
|
@ -54,7 +51,7 @@ export class WizardScene<State, SceneName extends string = string> extends Dispa
|
||||||
/**
|
/**
|
||||||
* Go to the Nth step
|
* Go to the Nth step
|
||||||
*/
|
*/
|
||||||
async goToStep(state: UpdateState<WizardInternalState, SceneName>, step: number) {
|
async goToStep(state: UpdateState<WizardInternalState>, step: number) {
|
||||||
if (step >= this._steps) {
|
if (step >= this._steps) {
|
||||||
await state.exit()
|
await state.exit()
|
||||||
} else {
|
} else {
|
||||||
|
@ -65,7 +62,7 @@ export class WizardScene<State, SceneName extends string = string> extends Dispa
|
||||||
/**
|
/**
|
||||||
* Skip N steps
|
* Skip N steps
|
||||||
*/
|
*/
|
||||||
async skip(state: UpdateState<WizardInternalState, SceneName>, count = 1) {
|
async skip(state: UpdateState<WizardInternalState>, count = 1) {
|
||||||
const { $step } = (await state.get()) || {}
|
const { $step } = (await state.get()) || {}
|
||||||
if ($step === undefined) throw new Error('Wizard state is not initialized')
|
if ($step === undefined) throw new Error('Wizard state is not initialized')
|
||||||
|
|
||||||
|
@ -96,7 +93,7 @@ export class WizardScene<State, SceneName extends string = string> extends Dispa
|
||||||
addStep(
|
addStep(
|
||||||
handler: (
|
handler: (
|
||||||
msg: MessageContext,
|
msg: MessageContext,
|
||||||
state: UpdateState<State & WizardInternalState, SceneName>,
|
state: UpdateState<State & WizardInternalState>,
|
||||||
) => MaybeAsync<WizardSceneAction | number>,
|
) => MaybeAsync<WizardSceneAction | number>,
|
||||||
): void {
|
): void {
|
||||||
const step = this._steps++
|
const step = this._steps++
|
||||||
|
|
Loading…
Reference in a new issue