'Telegraf.js handle callback buttons

I have wizardScene:

const { Scenes, Markup } = require('telegraf')
const db = require('../lib/db')
const Post = require('../models/post')
const keyboard = require('../lib/keyboards')

const randomPost = new Scenes.WizardScene('randomPost',
  async (ctx) => {
    const post = await db.getRandomPost()
    if (post['post'].type === 'chat') {
        let reply = `Случайная запись:\n\n`
        reply += `${post['post'].title}\n`
        reply += `${post['post'].text}`

        await ctx.reply(reply, await keyboard.getPostInline())
        await ctx.reply('Продолжить?', await keyboard.getRandomKeyboard())
        ctx.wizard.next()
    }
  },
     async (ctx) => {
        if (ctx.message.text === 'Да') {
            return ctx.scene.reenter()
        }
        if (ctx.message.text === 'Нет') {
            await ctx.reply('Выход...')
            return ctx.scene.leave()
        }
        await ctx.reply('Нажмите на кнопку для продолжения.')
  })

randomPost.hears('like', (ctx) => console.log('like'))
randomPost.hears('comment', (ctx) => console.log('comment')) // Doesn't work

module.exports = randomPost
async function getPostInline() {
    return Markup.inlineKeyboard([
        [Markup.button.callback('❤️', 'like'), Markup.button.callback('💬', 'comment')]
    ]).oneTime().resize()
}

getPostInline method returns an inline keyboard, when you press the button on it, it crashes with an error due to ctx.message.text - undefined - TypeError: Cannot read properties of undefined (reading 'text') (on the:

async (ctx) => {
        if (ctx.message.text === 'Да') {
            return ctx.scene.reenter()

I tried to intercept this with:

randomPost.hears('like', (ctx) => console.log('like'))
randomPost.hears('comment', (ctx) => console.log('comment')) // Doesn't work

But it doesn't work. What do I need to do so that there is no error, preferably without deleting await ctx.reply('Нажмите на кнопку для продолжения.') P.S. I can add if but may be there is more correct solution



Solution 1:[1]

Update:

When we have to decide before context creation (.i.e. ApplicationListener is no Component/Bean), then we (just mimic profiles) set:

# env: SET MY_FLAG=foo -> System.getenv("MY_FLAG")
# system: java -jar -Dmy.flag=foo myApp.jar -> System.getProperty("my.flag")
# cmd arg:
java -jar myApp.jar aws

..and issue it in our (spring boot) main method like:

if("aws".equalsIgnoreCase(args[0])) // alternatively: System.get...
  application.addListeners(new DatabasePropertiesListener());

Misunderstood answer

Sure we can: With spring (boot) core features!

Assuming our ApplicationListener is a "spring managed bean", we annotate it with:

@Component
@Profile("aws") // custom profile (name)
class MyApplicationListener implements ...
{ ... }

...this will not load this "bean" into our context, unless we define:

spring.profiles.acitve=aws,... # a comma separated list of profiles to activate

Profiles

Spring Profiles provide a way to segregate parts of your application configuration and make it be available only in certain environments. Any @Component (Or @Bean,@Service,@Repository...descendants), @Configuration or @ConfigurationProperties can be marked with @Profile to limit when it is loaded, as shown in the ...

... above example.

An advanced application of @Profile annotation:

  • with multiple profiles: "or"/"and" semantics
  • "not" (!) operator
@Profile("!local","aws") // (String[]: OR semantics) the annotated component/configuration/property will
// be loaded, when active profiles NOT contains "local" OR contains "aws"
// for AND semantics, we'd use (1 string): "!local & aws"

Activating Profile(s!)

spring.profiles.acitve can be set/added (like any spring property source) through several (14^^, precisely priorized) locations.

E.g. setting environment variable (5th lowest priority, but higher than application.properties (3rd lowest)):

SPRING_PROFILES_ACTIVE=aws

Or as a command line flag (when starting the application, 11th lowest/3rd highest priority):

java -jar myApp.jar --spring.profiles.active=aws,...
#comma separated list

For (spring) tests additionally exists an @ActiveProfiles annotation.

Remarks/Note

  • Deciding for profiles, we should ensure to "make it consistently" (not raising Nullpointer/BeaninitalizationExceptions ... with dependencies!;). If needed: Creating replacement/local/test (@Profile("!aws")) beans.

  • Activating a profile "xyz", automatically tries to load application-xyz.properties (with higher priority than application.properties (prio 3.1 - 3.4))...also in (spring-)cloud-config.

  • Not to forget: The default profile (activated by spring automatically, only when no explicit profile is activated).


Reference

For detailed documentation, please refer to:


Profile sample from Configuration Javadoc:

@Configuration classes may be marked with the @Profile annotation to indicate they should be processed only if a given profile or profiles are active:

@Profile("development")
@Configuration
public class EmbeddedDatabaseConfig {
     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return embedded DataSource
     }
}
@Profile("production")
@Configuration
public class ProductionDatabaseConfig {
     @Bean
     public DataSource dataSource() {
         // instantiate, configure and return production DataSource
     }
}

Alternatively, you may also declare profile conditions at the @Bean method level — for example, for alternative bean variants within the same configuration class:

@Configuration
public class ProfileDatabaseConfig {

     @Bean("dataSource")
     @Profile("development")
     public DataSource embeddedDatabase() { ... }

     @Bean("dataSource")
     @Profile("production")
     public DataSource productionDatabase() { ... }
}

See the @Profile and Environment javadocs for further details.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1