EdJoPaTo / grammy-inline-menu

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

Conditional routing in menu #167

Closed d19-dev closed 3 years ago

d19-dev commented 3 years ago

Hi, first of all thanks a lot for your library! Secondly sorry for my english. I am trying to implement conditional routing:

bot.use(async (ctx, next) => {
  const { id: userId } = ctx.from?
  if (userId && isAuthenticated(userId)) {
    return next()
  } else {
    // how to force route on authentication menu
  }
})

Have you faced the problem of conditional routing in the menu and how did you solve it?

EdJoPaTo commented 3 years ago

I think Can I send the menu manually is a good way to use your route. Just use a different middleware and send the menu manually from the else path.

Another idea might be to add the middlewares conditionally. That way only the correct middleware is triggered.

bot.use(Composer.branch(ctx => isAuthenticated(ctx), normalMenuMiddleware, authenticationMenuMiddleware))

If both menus use the same root path (like '/') they will also trigger on paths below that dont match (currently) therefore the authentication will also trigger when an old menu is used. This is helpful when authentication only lasts for some time for example.

Hope it helps!

d19-dev commented 3 years ago

@EdJoPaTo thanks, your advice helped me figure it out! Perhaps my snippet will be useful to someone in the future(it is dirty and needs refactoring, but I think the idea is clear).

Thanks again for the library! Good luck!

router/index.ts

import { editMenuOnContext, MenuMiddleware } from 'telegraf-inline-menu'
import { subscriptionRequired, userRegistration } from '../dialogs'
import mainMenu from '../inline-menu'
import { store } from '../store'
import { getUser } from '../store/selectors'
import { IMContext } from '../types'

const permittedRoutes = ['subscription-required-dialog/', '/close']

const isPermitted = (route: string) => permittedRoutes.some(
  (permittedRoute) => route.includes(permittedRoute),
)

export const withSubscriptionMiddleware = async (ctx: IMContext, next: () => Promise<void>) => {
  if (ctx.from && 'id' in ctx.from) {
    const { id: userId } = ctx.from
    const user = getUser(store.getState(), userId)
    if (user && user.subscriptionStatus === 'active') {
      return next()
    }
  }
  if (ctx.callbackQuery && 'data' in ctx.callbackQuery) {
    const { data: route } = ctx.callbackQuery
    if (isPermitted(route)) { return next() }
  }
  await editMenuOnContext(subscriptionRequired, ctx, 'subscription-required-dialog/')
  return true
}

export const menuMiddleware = new MenuMiddleware<IMContext>('main-menu/', mainMenu)
export const subscriptionMiddleware = new MenuMiddleware<IMContext>('subscription-required-dialog/', subscriptionRequired)

export default [
  withSubscriptionMiddleware,
  menuMiddleware.middleware(),
  subscriptionMiddleware.middleware(),
]

main.ts

import { Telegraf } from 'telegraf'
import { IMContext } from './types'
import middlewareController, { menuMiddleware } from './router'

const bot = new Telegraf<IMContext>('..................................................................'!)

bot.use(...middlewareController)
bot.command('menu', async (ctx) => menuMiddleware.replyToContext(ctx))

async function startup(): Promise<void> {
  await bot.launch()
  console.log(new Date(), 'Bot started as', bot.botInfo?.username)
}

startup()
EdJoPaTo commented 3 years ago
-from 'telegraf-inline-menu/dist/source'
+from 'telegraf-inline-menu'

having IDEs which automatically add that is a bit annyoing…

stale[bot] commented 3 years ago

This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs.