EdJoPaTo / grammy-inline-menu

Inline Menus for Telegram made simple. Successor of telegraf-inline-menu.
MIT License
354 stars 46 forks source link

validation and flow control for menu.question() #12

Closed zedeus closed 4 years ago

zedeus commented 5 years ago

I currently find the questions functionality rather lacking, as it doesn't allow for any flow control or validation of the reply. Let's say I want the user to input a string in the format XYZ-ABC. If the format is wrong or some other condition is false, I'd want to reject it and ask the user to input a new string.

The second part is the lack of flow control. Currently, when the user replies it goes right back to the menu where the button was pressed. An option to continue to another submenu (if validation function is true) would be nice, as well as having a back button to cancel the question.

Consider this example: User clicks [ Create User ], which asks them to input a username. Here the username should be checked, and if valid it will go to the user creation submenu.

EdJoPaTo commented 5 years ago

Thanks for your feedback! I know the question functionality is rudimentary (more on that later).

My current approach to user text inputs are menus that validate the input and let the user see and decide how to continue. For example your username selection: Submenu with only a 'enter username' question button. Then the user enters a username (or sends some rubbish) and is returned to that same menu. Then there is a button with hide determining if the input was correct. When it is correct, the button is not hidden and the user can continue. That way also the user can check if his input was correct and correct it by himself too. (Also nice when multiple things are in question like for an event: Title, start time, end time, location, …)

The basic problem the questions have is the missing callback_data. This inline-menu library is completely without state. Every state is coming from the callback_data or from functions like hide that decide between showing a button or not. That is only possible on callback Button presses. Answering a question does not have this kind of data available. Currently the question relies on the questionText and force reply. This library checks if the replied to message text is the same as the questionText. If true it has to be the answer. If not it's something else. (Because of this questions are not allowed in dynamic submenus).

As you asked for a back button while the question is asked. I like the idea. I headed out to add something like that but sadly forceReply and inlineKeyboards can not be specified together. Maybe you have an idea how to approach this problem in a useful way?

Example of my input validation logic approach. (You can add this to the example.ts in order to try it)

function usernameFormattingBadReasons(username: string): string[] {
  const reasons = []

  if (!username) {
    reasons.push('empty')
    return reasons
  }

  if (username.length < 5) {
    reasons.push('not long enough')
  }

  if (username !== username.toLowerCase()) {
    reasons.push('not lower case')
  }

  return reasons
}

function menuText(ctx: any): string {
  let text = 'Before we start I need some information from you.'

  const usernameBad = usernameFormattingBadReasons(ctx.session.username)
  if (usernameBad.length > 0) {
    text += '\n\n⚠️ Your username is bad: ' + usernameBad.join(', ')
  }

  return text
}

const usernameSelectionMenu = new TelegrafInlineMenu(menuText)
  .question((ctx: any) => `Username: ${ctx.session.username}`, 'q', {
    questionText: 'Whats your username?',
    setFunc: (ctx: any, answer) => {
      ctx.session.username = answer
    }
  })
  .simpleButton('Continue', 'continue', {
    hide: (ctx: any) => usernameFormattingBadReasons(ctx.session.username).length > 0,
    doFunc: ctx => ctx.answerCbQuery('Username correct 🎉 ')
  })

menu.submenu('Example user text input', 'qsm', usernameSelectionMenu)
EdJoPaTo commented 4 years ago

Version 5 will not include menu.question anymore. Instead you can use telegraf-stateless-question for this. As you can implement the answer function the way you want, including validation and so on, you should be able to do it more freely this way.

If you have problems or questions with it, feel free to ask ahead. (Maybe in the telegraf-stateless-question repo if it is relevant there)

Thanks for bringing up that topic!

warfollowsme commented 4 years ago

@EdJoPaTo Any chance use telegraf-stateless-question with simpleButton in version 4? Trying something like that, but it's not working. doFunc: (ctx) => { return enterFilterValue.replyWithMarkdown("Please enter value") },

EDITED: Nevermind. Forgot about context argument. doFunc: (ctx) => { return enterFilterValue.replyWithMarkdown(ctx, "Please enter value") }, works fine. Thanks for the lib. Very helpfull