-
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
refactor!: stop abusing composer architecture (#1)
BREAKING CHANGES: - Scene doesn't extend Composer anymore - remove monkey patches - add Composer2 and StepComposer with special methods - remove undocumented SceneManager methods (wait, mustResume) - prefix private props/methods with _
- Loading branch information
1 parent
2b355df
commit e6b73ba
Showing
13 changed files
with
191 additions
and
172 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import { Composer, Context, MiddlewareFn } from "grammy" | ||
|
||
/** | ||
* A set of generic enhancements over grammy Composer. | ||
* | ||
* These are not specific to grammy-scenes. | ||
* | ||
* Unfortunately, they never made it to the official repo. | ||
*/ | ||
export class Composer2<C extends Context> extends Composer<C> { | ||
/** | ||
* do() is use() which always calls next() | ||
*/ | ||
do(middleware: MiddlewareFn<C>) { | ||
this.use(async (ctx, next) => { | ||
await middleware(ctx, async () => undefined) | ||
return next() | ||
}) | ||
return this | ||
} | ||
|
||
/** | ||
* Run the provided setup function against the current composer. | ||
* | ||
* See https://github.com/grammyjs/grammY/issues/163 | ||
*/ | ||
setup(setup: (composer: this) => void) { | ||
setup(this) | ||
return this | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,89 +1,93 @@ | ||
import { Composer, Middleware, MiddlewareFn } from "grammy" | ||
import { Middleware, MiddlewareFn } from "grammy" | ||
import { SafeDictionary } from "ts-essentials" | ||
|
||
import { SceneFlavoredContext, ScenesFlavoredContext } from "." | ||
import { Composer2 } from "./composer2" | ||
import { StepComposer } from "./step" | ||
|
||
export class Scene< | ||
C extends ScenesFlavoredContext = ScenesFlavoredContext, | ||
S = undefined | ||
> extends Composer<SceneFlavoredContext<C, S>> { | ||
_always?: Composer<SceneFlavoredContext<C, S>> | ||
steps: Array<Composer<SceneFlavoredContext<C, S>>> = [] | ||
pos_by_label: SafeDictionary<number> = {} | ||
> { | ||
_always?: Composer2<SceneFlavoredContext<C, S>> | ||
_steps: Array<StepComposer<SceneFlavoredContext<C, S>, S>> = [] | ||
_pos_by_label: SafeDictionary<number> = {} | ||
|
||
constructor(public readonly id: string) { | ||
super() | ||
} | ||
|
||
use(...middleware: Array<Middleware<SceneFlavoredContext<C, S>>>) { | ||
const composer = super.use(...middleware) | ||
this.steps.push(composer) | ||
return composer | ||
} | ||
constructor(public readonly id: string) {} | ||
|
||
always(...middleware: Array<Middleware<SceneFlavoredContext<C, S>>>) { | ||
this._always ??= new Composer<SceneFlavoredContext<C, S>>() | ||
this._always ??= new Composer2<SceneFlavoredContext<C, S>>() | ||
this._always.use(...middleware) | ||
return this._always | ||
} | ||
|
||
/** Set payload for ctx.scene.arg in next step */ | ||
arg(arg: any) { | ||
this.do((ctx) => { | ||
ctx.scene.next_arg = arg | ||
}) | ||
/** | ||
* Add a scene step. | ||
*/ | ||
use(...middleware: Array<MiddlewareFn<SceneFlavoredContext<C, S>>>) { | ||
const step = new StepComposer<SceneFlavoredContext<C, S>, S>(...middleware) | ||
this._steps.push(step) | ||
return step | ||
} | ||
|
||
/** | ||
* Add a scene step which will always call the next step (unless explicitly aborted). | ||
*/ | ||
do(middleware: MiddlewareFn<SceneFlavoredContext<C, S>>) { | ||
return this.use().do(middleware) | ||
} | ||
|
||
/** | ||
* Mark a named position in scene to be used by scene.goto() | ||
*/ | ||
label(label: string) { | ||
if (label in this._pos_by_label) { | ||
throw new Error(`Scene ${this.id} already has step ${label}.`) | ||
} | ||
this._pos_by_label[label] = this._steps.length | ||
return this | ||
} | ||
|
||
/** Break scene middleware flow, wait for new updates. */ | ||
wait(...middleware: Array<Middleware<SceneFlavoredContext<C, S>>>) { | ||
/** | ||
* Break scene middleware flow. | ||
* Wait for new updates and pass them to the nested middleware. | ||
* | ||
* @example | ||
* ```ts | ||
* scene.wait().on("message:text", async (ctx) => { | ||
* await ctx.reply("...") | ||
* if (...) { | ||
* ctx.scene.resume() | ||
* } | ||
* }) | ||
* ``` | ||
*/ | ||
wait() { | ||
this.use((ctx) => { | ||
ctx.scene.wait() | ||
ctx.scene._wait() | ||
}) | ||
return this.do((ctx) => { | ||
ctx.scene._must_resume() | ||
}) | ||
return this.mustResume(...middleware) | ||
} | ||
|
||
/** This middleware must call ctx.scene.resume() to go to the next middleware. */ | ||
mustResume(...middleware: Array<Middleware<SceneFlavoredContext<C, S>>>) { | ||
const composer = new Composer<SceneFlavoredContext<C, S>>((ctx, next) => { | ||
ctx.scene.mustResume() | ||
return next() | ||
}, ...middleware) | ||
this.steps.push(composer) | ||
return composer | ||
/** Set payload for ctx.scene.arg in next step */ | ||
arg(arg: any) { | ||
return this.use().arg(arg) | ||
} | ||
|
||
/** Call nested scene, then go to the next step. */ | ||
call(sceneId: string, arg?: any) { | ||
this.do((ctx) => ctx.scene.call(sceneId, arg)) | ||
this.use().call(sceneId, arg) | ||
} | ||
|
||
/** Exit scene. */ | ||
exit(arg?: any) { | ||
this.do((ctx) => ctx.scene.exit(arg)) | ||
} | ||
|
||
/** Go to scene step marked with scene.label() */ | ||
goto(label: string, arg?: any) { | ||
this.do((ctx) => ctx.scene.goto(label, arg)) | ||
this.use().exit(arg) | ||
} | ||
|
||
/** Mark a named position in scene to be used by scene.goto() */ | ||
label(label: string) { | ||
this.pos_by_label[label] = this.steps.length | ||
} | ||
|
||
middleware() { | ||
throw Error(`Scene is not supposed to be used directly as a middleware.`) | ||
return super.middleware() // Prevent type error | ||
/** Go to named step. */ | ||
goto(name: string, arg?: any) { | ||
this.use().goto(name, arg) | ||
} | ||
} | ||
|
||
/** | ||
* Predicate to filter contexts generated by ctx.scenes.resume() | ||
* See also composer.resume() shortcut. | ||
* */ | ||
export function filterResume( | ||
ctx: SceneFlavoredContext<ScenesFlavoredContext, any> | ||
) { | ||
return ctx.scene?.opts?.resume === true | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.