EdJoPaTo / grammy-inline-menu

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

How to send menu/template manually and get access to session context #146

Closed guttermouth99 closed 3 years ago

guttermouth99 commented 3 years ago

I am trying to create a menu dynamically from data from my database and then send it manually to a chatId which is also saved in my database. The template I am building should be an image that has a toggle button and a few url buttons which all get created dynamically. I managed to build a function that does all of the above. However I am running into an issue when trying to apply template.toggle(). I think because I am sending the template via the generateSendMenuToChatFunction I lose context and thus I cannot build the menu. When trying to send the menu with I receive the following error:

Cannot read property 'isActive' of undefined at Object.isSet.

const { MenuTemplate, MenuMiddleware, generateSendMenuToChatFunction } = require('telegraf-inline-menu')
const session = require('telegraf/session')
const {
  Telegraf
} = require('telegraf')

//Creating Template Dynamically
const createNotificationTemplate = (data) => {
  let template = new MenuTemplate((ctx, path) => {
    return {
      type: 'photo',
      media: {
        url: data.image
      },
      text: 'Some caption',
      parse_mode: 'Markdown'
    }
  })
  template.url('Link 1', 'https://google.com')
  template.url('Link 2', 'https://google.com')
  template.toggle('Text', 'unique', {
    isSet: ctx => ctx.session.isActive,
    set: (ctx, newState) => {
      ctx.session.isActive = newState
    }
  })
  return template
}

const testBot = new Telegraf(process.env.BOT_TOKEN)

testBot.use(session())

export const sendTestNotifyTemplate =  (data)  => {
  let template = createNotificationTemplate(data)
  return {
    sendMenuFunction: generateSendMenuToChatFunction(testBot.telegram, template,
      '/test/')
  }
}

I am calling the sendTestNotifyTemplate() like this:

(async function() {
        let template = sendTestNotifyTemplate({image: 'https://i.picsum.photos/id/1035/1080/1080'})
             await template.sendMenuFunction('435150609', {manualContext: 'test'}));
})();

Is there anyway I can get the context and session to make the toggle functionality work in the set telegram button.

Thanks for your help and this amazing project!

EdJoPaTo commented 3 years ago

The idea of the Template is to get data dynamically in by itself. The dynamically passed data is named context and is, when used with the Middleware normally just the Telegraf ctx.

When you want to create a menu independent from Telegraf you need to make sure you create a context for that purpose. In your case you named it data.

const template = new MenuTemplate(data => {
  return {
    type: 'photo',
    media: { photo: data.image }
  }
})

template.toggle('Text', 'unique', {
  isSet: data => getSession(data.userId).isActive,
  set: (data, newState) => setSessionProperty(data.userId, 'isActive', newState)
}

const sendMenuFunction = generateSendMenuToChatFunction(bot.telegram, template, '/test/'')

Sending your a menu with that template would be like this then:

(async function() {
  const data = {userId: '42', image: 'https://i.picsum.photos/id/1035/1080/1080'}
  await sendMenuFunction('42', data))
})();

The only thing missing then are the getSession and setSessionProperty function (or something like that doing what you need). They depend on the session you are using.

If you want to use the same template with the MenuMiddleware too you can also fake a context

(async function() {
  const context = {
    from: {id: '42'},
    image: 'https://i.picsum.photos/id/1035/1080/1080',
    session: loadSession('42')
  }

  await sendMenuFunction('42', session))
  await saveSession('42', session)
})();

I hope it helps to get a better understanding how the MenuTemplate was meant to be helpful without Telegraf?

guttermouth99 commented 3 years ago

Thanks, I think I see what you mean. I will try it to create my own session function and maybe sync it with my database at that point.

I actually just ran into another problem when using my own listener for bot.on('callback_query', async (ctx) => {...}) function. Somehow it overwrites the functionality of your library and some of the menus I built are not working anymore. Do you have any idea how to combine my on callback_query listener with the ones built into your system.

Thanks a lot for your help.

guttermouth99 commented 3 years ago

I actually just solved the issue I mentioned in my previous post. I added next as a second argument and returned it when the condition is not met. For anyone else with a similar problem here:

 bot.on('callback_query', async (ctx, next) => {

      console.log(ctx.callbackQuery.data)

      if(ctx.callbackQuery.data.includes('some value')) {
        return 'xxx'
      }
      else {
        return next()
      }
    })
EdJoPaTo commented 3 years ago

This is exactly what the telegraf construct bot.action is for. Your example can be written like the following:

bot.action('some value', ctx => {
  // do something
})

if it is not the right callback data, it internally does the next() part for you.

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.