grammyjs/conversations

feat: `conversation.halt`

KnorpelSenf opened this issue ยท 13 comments

A lot of people ask for a way to exit conversations without returning or throwing. Similar to process.exit, we should add conversation.halt which stops the conversation immediately.

Calling ctx.conversation.exit from within a conversation doesn't work well because the conversation continues to the next wait or skip, which is undesirable and not possible to avoid.

It should be called halt or terminate not exit in order to avoid confusion.

await conversation.halt(); // never resolves

So, if we do:

...
ctx = await conversation.waitFor(
  <anything_but_text>,
  (ctx) => ctx.message?.text === "/cancel" && conversation.halt()
);
...

What happens to this update? Does it get dropped? Or does it continue to be processed by the other middlewares (like in conversation.skip()). Or should we do: conversation.halt().then(() => conversation.skip({ drop: true })) like in #43

I would say that halt should drop the update by default. But now that you say it, I do agree that there's a use case for halting the conversation, and giving back control to the middleware system. So perhaps we should rather do

ctx = await conversation.waitUnless(
  Context.has.command("cancel"),
  () => conversation.halt()
)

to end the conversation and handle it in the midleware system, and use

ctx = await conversation.waitUnless(
  Context.has.command("cancel"),
  () => conversation.halt({ drop: true })
)

to consume the update?

Yup! LGTM

Or maybe consuming the update should be the default, as you said; then you need to specify drop: false to give back control to the middleware system?

That would be the same interface as for skip but with a different default value. That would be pretty confusing, what do you think? ๐Ÿค”

Setting defaults to true in these cases feels more natural to me, but I agree with you, they should be consistent between the two. I'm ok with either one, true for both, or false for both.

We cannot change the default value for skip because that would be a breaking change. The current behaviour of skip (passing updates back the middleware system) is necessary to support parallel conversations, i.e. having several conversations in the same chat with different people.

But alright, then halt will have to be implemented with false as the default value, and permit to drop the most recent update using { drop: true }.

Thanks for your input! ๐ŸŽ‰

Yo, I have been very busy with work. What's the status of this one? Is there something that needs to be tested/reviewed?

Not yet, it still remains to be implemented. Do you want to work on this?

hi, what if user leave the conversation by selecting any other commands int he middle of running a conversation? how to detect if any other command choose by user meanwhile?

You can install the command handlers before the respective conversation using bot.command. If you don't call next in them, the conversation won't be reached. See https://grammy.dev/guide/middleware

Adding https://t.me/grammyjs/235195 as a point of consideration.

Given potential use cases where one might try to call halt or exit inside the conversation function, but through a plugin; it raises some question on whether halt can naively explode.

Yep, calling halt from inside run should be supported