register webhook tg bot using spring boot

Viewed 1784

I am using telegrambots-spring-boot-starter v 5.2.0 and trying register my bot Here's my bot config:

bot.url=https://74e437885ee9.ngrok.io

bot.path=adam

    @Slf4j
@Configuration
public class BotConfig {
  @Value("${bot.url}")
  private String BOT_URL;

  @Bean
  public SetWebhook setWebhookInstance() {
    return SetWebhook.builder().url(BOT_URL).build();
  }
  // Create it as
  @Bean
  public AdamSmithBot adamSmithBot(SetWebhook setWebhookInstance) throws TelegramApiException {
    AdamSmithBot adamSmithBot = new AdamSmithBot(setWebhookInstance);
    //        DefaultWebhook defaultWebhook = new DefaultWebhook();
    //        defaultWebhook.setInternalUrl(BOT_URL);
    //        defaultWebhook.registerWebhook(adamSmithBot);

    TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class);
    log.info("SetWebHook from AdamSmith bot {}", adamSmithBot.getSetWebhook());
    telegramBotsApi.registerBot(adamSmithBot, adamSmithBot.getSetWebhook());
    return adamSmithBot;
  }
}

But it dont working, but when i send this request, it working perfectly and updates recieve to me https://api.telegram.org/MY_TOKEN_HERE/setWebhook?url=https://74e437885ee9.ngrok.io

I think my mistake in BotConfig,but i also publush my other clases bot and controller:

    public class AdamSmithBot extends SpringWebhookBot {
  @Value("${bot.token}")
  private String TOKEN;

  @Value("${bot.name}")
  private String BOT_USERNAME;

  @Value("${bot.path}")
  private String BOT_PATH;

  public AdamSmithBot(SetWebhook setWebhook) {

    super(setWebhook);
  }

  public AdamSmithBot(DefaultBotOptions options, SetWebhook setWebhook) {
    super(options, setWebhook);
  }

  @Override
  public String getBotUsername() {
    return BOT_USERNAME;
  }

  @Override
  public String getBotToken() {
    return TOKEN;
  }

  @Override
  public BotApiMethod<?> onWebhookUpdateReceived(Update update) {

    if (update.getMessage() != null && update.getMessage().hasText()) {
      Long chatId = update.getMessage().getChatId();
      try {
        execute(new SendMessage(chatId.toString(), "HI HANDSOME " + update.getMessage().getText()));

      } catch (TelegramApiException e) {
        throw new IllegalStateException(e);
      }
    }
    return null;
  }

  @Override
  public String getBotPath() {
    return "adam";
  }
}

Controller:

    @Slf4j
@RestController
public class WebHookBotRecieveController {
    @Autowired
    private AdamSmithBot adamSmithBot;
    @PostMapping("/")
    public void getUpdate(@RequestBody Update update){
        log.info("some update recieved {}",update.toString());
        adamSmithBot.onWebhookUpdateReceived(update);

    }
    @PostMapping("/callback/adam")
    public void getUpdateWithDifferentUrl(@RequestBody Update update){
        log.info("some update recieved {}",update.toString());
        adamSmithBot.onWebhookUpdateReceived(update);

    }
}

NOTE: I seemd some info here: https://github.com/rubenlagus/TelegramBots/wiki/How-To-Update

They do that: https://i.stack.imgur.com/9JKRT.png

But when i trying put DefaultWebhook class instead it produce NullPointerException What i made wrong?

EDIT : I refactored some code @Value("${bot.url}") private String BOT_URL; - where was null value (fixed),reloaded library, but now i have that exception:

Caused by: javax.ws.rs.ProcessingException: Failed to start Grizzly HTTP server: Cannot assign requested address: bind
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:270) ~[jersey-container-grizzly2-http-2.33.jar:na]
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:93) ~[jersey-container-grizzly2-http-2.33.jar:na]
    at org.telegram.telegrambots.updatesreceivers.DefaultWebhook.startServer(DefaultWebhook.java:64) ~[telegrambots-5.2.0.jar:na]
    at org.telegram.telegrambots.meta.TelegramBotsApi.<init>(TelegramBotsApi.java:50) ~[telegrambots-meta-5.2.0.jar:na]
    at ru.website.selenium.bot.telegram.config.BotConfig.adamSmithBot(BotConfig.java:44) ~[classes/:na]
    at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d.CGLIB$adamSmithBot$0(<generated>) ~[classes/:na]
    at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d$$FastClassBySpringCGLIB$$1e185cfd.invoke(<generated>) ~[classes/:na]
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.3.8.jar:5.3.8]
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:331) ~[spring-context-5.3.8.jar:5.3.8]
    at ru.website.selenium.bot.telegram.config.BotConfig$$EnhancerBySpringCGLIB$$4eb8259d.adamSmithBot(<generated>) ~[classes/:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
    at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.3.8.jar:5.3.8]
    ... 39 common frames omitted
Caused by: java.net.BindException: Cannot assign requested address: bind
    at java.base/sun.nio.ch.Net.bind0(Native Method) ~[na:na]
    at java.base/sun.nio.ch.Net.bind(Net.java:461) ~[na:na]
    at java.base/sun.nio.ch.Net.bind(Net.java:453) ~[na:na]
    at java.base/sun.nio.ch.ServerSocketChannelImpl.bind(ServerSocketChannelImpl.java:227) ~[na:na]
    at java.base/sun.nio.ch.ServerSocketAdaptor.bind(ServerSocketAdaptor.java:80) ~[na:na]
    at org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler.bindToChannelAndAddress(TCPNIOBindingHandler.java:107) ~[grizzly-framework-2.4.4.jar:2.4.4]
    at org.glassfish.grizzly.nio.transport.TCPNIOBindingHandler.bind(TCPNIOBindingHandler.java:64) ~[grizzly-framework-2.4.4.jar:2.4.4]
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:215) ~[grizzly-framework-2.4.4.jar:2.4.4]
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:195) ~[grizzly-framework-2.4.4.jar:2.4.4]
    at org.glassfish.grizzly.nio.transport.TCPNIOTransport.bind(TCPNIOTransport.java:186) ~[grizzly-framework-2.4.4.jar:2.4.4]
    at org.glassfish.grizzly.http.server.NetworkListener.start(NetworkListener.java:711) ~[grizzly-http-server-2.4.4.jar:2.4.4]
    at org.glassfish.grizzly.http.server.HttpServer.start(HttpServer.java:256) ~[grizzly-http-server-2.4.4.jar:2.4.4]
    at org.glassfish.jersey.grizzly2.httpserver.GrizzlyHttpServerFactory.createHttpServer(GrizzlyHttpServerFactory.java:267) ~[jersey-container-grizzly2-http-2.33.jar:na]
    ... 53 common frames omitted

Process finished with exit code 0

2 Answers

Okay, I get it, I didn't know that TelegramBotApi runs a grizzly server underneath and that was the reason for my mistakes For those who ever come here with the same problem, I will describe the solution : Let's start with the fact that for the test on localhost we need a public address , e.g. ngrok , then we have to do the following, start grizzli on , let's say port 80 (localhost), specify the correct ngrok url , and then run spring boot application on , let's say port 8080, examples in the code

For ex. ngrok forwarding: Forwarding http://b44ecce72666.ngrok.io -> http://localhost:8080 Forwarding https://b44ecce72666.ngrok.io -> http://localhost:8080

@Slf4j
@Configuration
public class BotConfig {
//  @Value("${bot.url}")
//  @NotNull
//  @NotEmpty
//  private String BOT_URL;

  @Value("${bot.token}")
  @NotNull
  @NotEmpty
  private String TOKEN;

  @Value("${bot.name}")
  @NotNull
  @NotEmpty
  private String BOT_USERNAME;

  @Bean
  public SetWebhook setWebhookInstance() {
    return SetWebhook.builder().url("https://b44ecce72666.ngrok.io").build();
  } // public address, now it is ngrok, in the future it will (i think) be the server address
  // Create it as
  @Bean
  public AdamSmithBot adamSmithBot(SetWebhook setWebhookInstance) throws TelegramApiException {

    AdamSmithBot adamSmithBot = new AdamSmithBot(setWebhookInstance);
    adamSmithBot.setBOT_USERNAME(BOT_USERNAME);
    adamSmithBot.setTOKEN(TOKEN);
    adamSmithBot.setBOT_PATH("adam");
    DefaultWebhook defaultWebhook = new DefaultWebhook();

    defaultWebhook.setInternalUrl(
        "http://localhost:80"); // the port to start the server, on the localhost computer, on the server it
                                // be the server address
    //   defaultWebhook.registerWebhook(adamSmithBot);

    TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhook);

    log.info("SetWebHook from AdamSmith bot {}", setWebhookInstance);
    adamSmithBot.getBotUsername();
    telegramBotsApi.registerBot(adamSmithBot, setWebhookInstance);
    return adamSmithBot;
   
  }
}

Next is code of webhook bot:

@Slf4j
@Setter
public class AdamSmithBot extends SpringWebhookBot {
  @Value("${bot.token}")
  private String TOKEN;

  @Value("${bot.name}")
  private String BOT_USERNAME;

  @Value("${bot.path}")
  private String BOT_PATH;
  @Autowired
  private MainService mainService;

  public AdamSmithBot(SetWebhook setWebhook) {

    super(setWebhook);
  }

  public AdamSmithBot(DefaultBotOptions options, SetWebhook setWebhook) {
    super(options, setWebhook);
  }

  @Override
  public String getBotUsername() {

    log.info("BOT_USERNAME FROM ADAM BOT {}",BOT_USERNAME);
    return BOT_USERNAME;
  }

  @Override
  public String getBotToken() {
    return TOKEN;
  }

  @Override
  public BotApiMethod<?> onWebhookUpdateReceived(Update update) { //All messages coming from the grizzly server will trigger this method
    log.info("Message teext {}",update.toString());
    if (update.getMessage() != null && update.getMessage().hasText()) {
      Long chatId = update.getMessage().getChatId();

      List<PartialBotApiMethod<Message>> listOfCommands=  mainService.receiveUpdate(update);
        listOfCommands.forEach(x->{
          try {
          if(x instanceof SendMessage){

              execute((SendMessage)x);

          }
          if(x instanceof SendPhoto){
            execute((SendPhoto) x);
          }
          } catch (TelegramApiException e) {
            e.printStackTrace();
          }
        });


    }
    return null;
  }

  @Override
  public String getBotPath() {
    return "adam"; //any other path here
  }
}

My code is excessive in some places, and too fancy, but it's not hard to figure out. If you have any suggestions on how to make it better, I'd love to hear them.

This was the most helpful question and answer from @handsome so decided to post here some more additional info that was not so evident for me at the beginning.

@Configuration
public class BotConfig {

    @Value("${telegram.webhook-host}")
    private String webhookHost;
    @Value("${telegram.endpoint}")
    private String botEndpoint;
    @Value("${telegram.token}")
    private String botToken;
    @Value("${telegram.username}")
    private String botUsername;

    @Bean
    public SetWebhook setWebhookInstance() {
        return SetWebhook.builder().url(webhookHost).build();
    }

    @Bean
    public ParserBot getBot(SetWebhook setWebhook) {
        ParserBot parserBot = new ParserBot(setWebhook);
        parserBot.setBotPath(botEndpoint);
        parserBot.setBotToken(botToken);
        parserBot.setBotUsername(botUsername);
        try {
            DefaultWebhook defaultWebhook = new DefaultWebhook();
            defaultWebhook.setInternalUrl("http://localhost:80");

            TelegramBotsApi telegramBotsApi = new TelegramBotsApi(DefaultBotSession.class, defaultWebhook);
            telegramBotsApi.registerBot(parserBot, parserBot.getSetWebhook());
        } catch (TelegramApiException e) {
            throw new RuntimeException("Can't register telegram bot", e);
        }
        return parserBot;
    }
}

Most of the info is clear but sometimes it is necessary to say:

  1. You can register webhook endpoint manually by calling https://api.telegram.org/bot<your_token>/setWebHook?url=<your_url> In this case your URL can be any path you want. If you register your webhook endpoint using TelegramBotsApi, then your endpoint will look like https://host/callback/ , as example

telegram.webhook-host=https://12345124.eu.ngrok.io
telegram.endpoint="webhook"

then your endpoint is https://12345124.eu.ngrok.io/callback/webhook Yes, you can't set any path to your endpoint but you don't need to set it manually

  1. Another thing. To activate WebhookBot in application you have to use DefaultWebhook to activate TelegramBotsApi.useWebhook property. This is according to source.
  2. Your endpoint have to be https. It can't be http.
  3. Webhook can be set up only on ports 80, 88, 443 or 8443
Related