Micronaut Chatbots

Ease the creation of ChatBots with the Micronaut Framework

Version: 1.2.1-SNAPSHOT

1 Introduction

This module eases the creation of Telegram Bots, Basecamp Bots with the Micronaut Framework.

2 Release History

For this project, you can find a list of releases (with release notes) here:

3 Telegram

You can develop a Telegram Bot quickly with Micronaut Chatbots.

Create a Telegram Bot by talking with the @BotFather.

To see what actions the BotFather can perform for you, send /help in the chat.

First send the command /newbot to the BotFather and follow the instructions to create a new bot. At the end of this process, you will receive a token to access the HTTP API.

3.1 Telegram Bot Configuration

micronaut.chatbots.telegram.bots.mnexample.token=xxxyyyzzz
micronaut.chatbots.telegram.bots.mnexample.at-username=@MicronautExampleBot
micronaut:
  chatbots:
    telegram:
      bots:
        mnexample:
          token: 'xxxyyyzzz'
          at-username: '@MicronautExampleBot'
[micronaut]
  [micronaut.chatbots]
    [micronaut.chatbots.telegram]
      [micronaut.chatbots.telegram.bots]
        [micronaut.chatbots.telegram.bots.mnexample]
          token="xxxyyyzzz"
          at-username="@MicronautExampleBot"
micronaut {
  chatbots {
    telegram {
      bots {
        mnexample {
          token = "xxxyyyzzz"
          atUsername = "@MicronautExampleBot"
        }
      }
    }
  }
}
{
  micronaut {
    chatbots {
      telegram {
        bots {
          mnexample {
            token = "xxxyyyzzz"
            at-username = "@MicronautExampleBot"
          }
        }
      }
    }
  }
}
{
  "micronaut": {
    "chatbots": {
      "telegram": {
        "bots": {
          "mnexample": {
            "token": "xxxyyyzzz",
            "at-username": "@MicronautExampleBot"
          }
        }
      }
    }
  }
}

micronaut.chabots.telegram.bots.*.token matches the value specified as secret_token while setting the webhook.

3.2 Telegram Webhook

You have to set the webhook via the Telegram API.

curl -X "POST" "https://api.telegram.org/bot{httpApiToken}/setWebhook?url={webhookUrl}&secret_token={secretToken}"
  • httpApiToken You obtain this value via the @BotFather

  • webhookUrl is the endpoint of your Micronaut application.

  • {secretToken} - A secret token, Telegram sends in a header X-Telegram-Bot-Api-Secret-Token in every webhook request.

3.3 Telegram Bot Handlers

To develop your bot, create beans of type TelegramHandler.

@Singleton
class HelloWorldHandler implements TelegramHandler<SendMessage> {

    private final SpaceParser<Update, Chat> spaceParser;

    HelloWorldHandler(SpaceParser<Update, Chat> spaceParser) {
        this.spaceParser = spaceParser;
    }

    @Override
    public boolean canHandle(@Nullable TelegramBotConfiguration bot, @NotNull Update input) {
        return input.getMessage().getText().contains("hello");
    }

    @Override
    public Optional<SendMessage> handle(@Nullable TelegramBotConfiguration bot, @NotNull Update input) {
        return SendMessageUtils.compose(spaceParser, input, "Hello World");
    }
}
@Singleton
class HelloWorldHandler(private val spaceParser: SpaceParser<Update, Chat>) : TelegramHandler<SendMessage> {

    override fun canHandle(bot: TelegramBotConfiguration?, input: Update): Boolean = input.message.text.contains("hello")

    override fun handle(bot: TelegramBotConfiguration?, input: Update): Optional<SendMessage> =
        SendMessageUtils.compose(spaceParser, input, "Hello World")
}

To respond to Telegram Bot commands, with static content, you can extend from CommandHandler. For example to respond to an /about command with a static message, you create a CommandHandler bean:

@Singleton
class AboutCommandHandler extends CommandHandler {

    private static final String COMMAND_ABOUT = "/about";

    AboutCommandHandler(
        TelegramSlashCommandParser slashCommandParser,
        TextResourceLoader textResourceLoader,
        SpaceParser<Update, Chat> spaceParser
    ) {
        super(slashCommandParser, textResourceLoader, spaceParser);
    }

    @Override
    @NonNull
    public String getCommand() {
        return COMMAND_ABOUT;
    }
}
@Singleton
class AboutCommandHandler extends CommandHandler {

    private static final String COMMAND_ABOUT = "/about"

    AboutCommandHandler(
            TelegramSlashCommandParser slashCommandParser,
            TextResourceLoader textResourceLoader,
            SpaceParser<Update, Chat> spaceParser
    ) {
        super(slashCommandParser, textResourceLoader, spaceParser)
    }

    @Override
    @NonNull
    String getCommand() {
        COMMAND_ABOUT
    }
}
@Singleton
class AboutCommandHandler(
    slashCommandParser: TelegramSlashCommandParser,
    textResourceLoader: TextResourceLoader,
    spaceParser: SpaceParser<Update, Chat>
) : CommandHandler(slashCommandParser, textResourceLoader, spaceParser) {

    override fun getCommand() = COMMAND_ABOUT

    companion object {
        private const val COMMAND_ABOUT = "/about"
    }
}

And then create some content with a matching name in the botcommands directory:

resources/botcommands/about.md
Bot developed with 💙 using [Micronaut](https://micronaut.io)

The botcommands directory may be configured via the micronaut.chatbots.folder configuration property.

🔗
Table 1. Configuration Properties for ChatbotsConfigurationProperties
Property Type Description

micronaut.chatbots.enabled

boolean

Whether chatbots is enabled. Default value (true).

micronaut.chatbots.folder

java.lang.String

The folder to look for bot commands.

micronaut.chatbots.possible-static-command-extensions

java.util.List

Possible static command file extensions. Default values MARKDOWN, HTML, TXT

3.4 Ordering Handlers

Chatbot handlers have order, and the first handler to support a command is the one used.

To change the order of handlers, you can override the getOrder method in your handler.

@Singleton
class UnknownCommandHandler implements TelegramHandler<SendMessage> {

    private final SpaceParser<Update, Chat> spaceParser;

    UnknownCommandHandler(SpaceParser<Update, Chat> spaceParser) {
        this.spaceParser = spaceParser;
    }

    @Override
    public boolean canHandle(@Nullable TelegramBotConfiguration bot, @NotNull Update input) {
        return true; // (1)
    }

    @Override
    public Optional<SendMessage> handle(@Nullable TelegramBotConfiguration bot, @NotNull Update input) {
        return SendMessageUtils.compose(spaceParser, input, "I don't know how to handle your query: %s".formatted(input.getMessage().getText()));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE; // (2)
    }
}
@Singleton
class UnknownCommandHandler implements TelegramHandler<SendMessage> {

    private final SpaceParser<Update, Chat> spaceParser

    UnknownCommandHandler(SpaceParser<Update, Chat> spaceParser) {
        this.spaceParser = spaceParser
    }

    @Override
    boolean canHandle(@Nullable TelegramBotConfiguration bot, @NotNull Update input) {
        true // (1)
    }

    @Override
    Optional<SendMessage> handle(@Nullable TelegramBotConfiguration bot, @NotNull Update input) {
        return SendMessageUtils.compose(spaceParser, input, "I don't how to handle your query: ${input.message.text}")
    }

    @Override
    int getOrder() {
        Ordered.LOWEST_PRECEDENCE // (2)
    }
}
@Singleton
class UnknownCommandHandler(private val spaceParser: SpaceParser<Update, Chat>) : TelegramHandler<SendMessage> {

    override fun canHandle(bot: TelegramBotConfiguration?, input: Update) = true // (1)

    override fun handle(bot: TelegramBotConfiguration?, input: Update): Optional<SendMessage> =
        SendMessageUtils.compose(
            spaceParser,
            input,
            "I don't how to handle your query: ${input.message.text}"
        )

    override fun getOrder() = Ordered.LOWEST_PRECEDENCE // (2)
}
1 This handler will handle any message.
2 Ensure this handler is last in the list of handlers.

3.5 Telegram Chatbots as an AWS Lambda Function

If you want to deploy to AWS Lambda, you can use the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-telegram-lambda")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-telegram-lambda</artifactId>
</dependency>

Use the class io.micronaut.chatbots.telegram.lambda.Handler as the AWS Lambda function handler.

3.6 Telegram Chatbots as a Google Cloud Function

If you want to deploy to Google Cloud Functions, you can use the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-telegram-gcp-function")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-telegram-gcp-function</artifactId>
</dependency>

You can use the following entry point:

Use the class io.micronaut.chatbots.telegram.googlecloud.Handler as the Http Function entrypoint.

3.7 Telegram Chatbots as an Azure Function

If you want to deploy to an Azure function, you can use the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-telegram-azure-function")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-telegram-azure-function</artifactId>
</dependency>

This adds an AzureFunction with the name TelegramTrigger via the class io.micronaut.chatbots.telegram.azurefunction.Handler

3.8 Telegram Chatbots Controller

If you want to configure one endpoint as the Telegram chatbot webhook while using a runtime such as Netty or Servlet, you can include the following dependency:

implementation("io.micronaut.chatbots:micronaut-mironaut-chatbots-telegram-http")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-mironaut-chatbots-telegram-http</artifactId>
</dependency>

You can configure the path with:

🔗
Table 1. Configuration Properties for TelegramControllerConfiguration
Property Type Description

micronaut.chatbots.telegram.endpoint.enabled

boolean

Enables the controller. Default value true .

micronaut.chatbots.telegram.endpoint.path

java.lang.String

Path to the controller. Default value "/telegram" .

4 Basecamp

You can develop Basecamp Chatbots quickly with Micronaut Chatbots.

4.1 Basecamp Webhook

You have to set the webhook via the Basecamp API.

curl -s \
  -H "Authorization: Bearer ${ACCESS_TOKEN}" \
  -H "Content-Type: application/json" \
  -d '{"service_name":"${BOTNAME}","command_url":"${YOUR_HTTP_TRIGGER_URL}"}' \
  https://3.basecampapi.com/${APP_ID}/buckets/${BUCKET_ID}/chats/${CHAT_ID}/integrations.json
  • BOTNAME is the name of your bot

  • ACCESS_TOKEN is your Oauth2 Basecamp access token

  • YOUR_HTTP_TRIGGER_URL is the https address of your URL of your controller or function

  • APP_ID, BUCKET_ID and CHAT_ID are the IDs of your Basecamp application, bucket and chat respectively

4.2 Basecamp Bot Handlers

To develop your bot, create beans of type BasecampHandler.

@Singleton
class HelloWorldHandler implements BasecampHandler {

    @Override
    public boolean canHandle(BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        return input.getCommand().contains("hello");
    }

    @NonNull
    @Override
    public Optional<String> handle(BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        return Optional.of("Hello World");
    }
}
@Singleton
class HelloWorldHandler : BasecampHandler {

    override fun canHandle(bot: BasecampBotConfiguration?, input: Query) =
        input.command.contains("hello")

    override fun handle(bot: BasecampBotConfiguration?, input: Query) =
        Optional.of("Hello World")
}

To respond to specific messages with static content, you extend from BasecampHandler and utilize the TextResourceLoader class to load the content.

For example to respond to an /about command with a static message, you create a bean:

@Singleton
class AboutCommandHandler implements BasecampHandler {

    public static final String ABOUT = "about";
    private final TextResourceLoader textResourceLoader;

    AboutCommandHandler(TextResourceLoader textResourceLoader) {
        this.textResourceLoader = textResourceLoader;
    }

    @Override
    public boolean canHandle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        return input.getCommand().equalsIgnoreCase("/" + ABOUT);
    }

    @Override
    public @NonNull Optional<String> handle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        return textResourceLoader
            .composeCommandResponse(ABOUT)
            .map(CommandResponse::text);
    }
}
@Singleton
class AboutCommandHandler implements BasecampHandler {

    static final String ABOUT = "about"
    private final TextResourceLoader textResourceLoader

    AboutCommandHandler(TextResourceLoader textResourceLoader) {
        this.textResourceLoader = textResourceLoader
    }

    @Override
    boolean canHandle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        input.command.equalsIgnoreCase("/$ABOUT")
    }

    @Override
    @NonNull Optional<String> handle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        textResourceLoader
            .composeCommandResponse(ABOUT)
            .map(CommandResponse::text)
    }
}
@Singleton
class AboutCommandHandler(
    private val textResourceLoader: TextResourceLoader
) : BasecampHandler {

    override fun canHandle(bot: BasecampBotConfiguration?, input: Query) =
        input.command.equals("/$ABOUT", ignoreCase = true)

    override fun handle(bot: BasecampBotConfiguration?, input: Query) =
        textResourceLoader.composeCommandResponse(ABOUT).map(CommandResponse::text)

    companion object {
        const val ABOUT: String = "about"
    }
}

And then create some content with a matching name in the botcommands directory:

resources/botcommands/about.md
Bot developed with 💙 using [Micronaut](https://micronaut.io)

The botcommands directory may be configured via the micronaut.chatbots.folder configuration property.

🔗
Table 1. Configuration Properties for ChatbotsConfigurationProperties
Property Type Description

micronaut.chatbots.enabled

boolean

Whether chatbots is enabled. Default value (true).

micronaut.chatbots.folder

java.lang.String

The folder to look for bot commands.

micronaut.chatbots.possible-static-command-extensions

java.util.List

Possible static command file extensions. Default values MARKDOWN, HTML, TXT

4.3 Ordering Handlers

Chatbot handlers have order, and the first handler to support a command is the one used.

To change the order of handlers, you can override the getOrder method in your handler.

@Singleton
class UnknownCommandHandler implements BasecampHandler {

    @Override
    public boolean canHandle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        return true; // (1)
    }

    @Override
    public @NonNull Optional<String> handle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        return Optional.of("I don't know how to handle your query: %s".formatted(input.getCommand()));
    }

    @Override
    public int getOrder() {
        return Ordered.LOWEST_PRECEDENCE; // (2)
    }
}
@Singleton
class UnknownCommandHandler implements BasecampHandler {

    @Override
    boolean canHandle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        true // (1)
    }

    @Override
    @NonNull Optional<String> handle(@Nullable BasecampBotConfiguration bot, @NonNull @NotNull Query input) {
        Optional.of("I don't know how to handle your query: $input.command".toString())
    }

    @Override
    public int getOrder() {
        Ordered.LOWEST_PRECEDENCE // (2)
    }
}
@Singleton
class UnknownCommandHandler : BasecampHandler {

    override fun canHandle(bot: BasecampBotConfiguration?, input: Query) = true // (1)

    override fun handle(bot: BasecampBotConfiguration?, input: Query) =
        Optional.of("I don't know how to handle your query: ${input.command}")

    override fun getOrder() = Ordered.LOWEST_PRECEDENCE // (2)
1 This handler will handle any message.
2 Ensure this handler is last in the list of handlers.

4.4 Basecamp Chatbots as an AWS Lambda Function

If you want to deploy to AWS Lambda, you can use the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-basecamp-lambda")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-basecamp-lambda</artifactId>
</dependency>

Use the class io.micronaut.chatbots.basecamp.lambda.Handler as the AWS Lambda function handler.

4.5 Basecamp Chatbots as a Google Cloud Function

If you want to deploy to Google Cloud Functions, you can use the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-basecamp-gcp-function")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-basecamp-gcp-function</artifactId>
</dependency>

You can use the following entry point:

Use the class: io.micronaut.chatbots.basecamp.googlecloud.Handler as the Http Function entrypoint.

4.6 Basecamp Chatbots as an Azure Function

If you want to deploy to an Azure function, you can use the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-basecamp-azure-function")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-basecamp-azure-function</artifactId>
</dependency>

This adds an AzureFunction with the name BasecampTrigger via the class io.micronaut.chatbots.basecamp.azurefunction.Handler

4.7 Basecamp Chatbots Controller

If you want to configure one endpoint as the Basecamp chatbot webhook while using a runtime such as Netty or Servlet, you can include the following dependency:

implementation("io.micronaut.chatbots:micronaut-chatbots-basecamp-http")
<dependency>
    <groupId>io.micronaut.chatbots</groupId>
    <artifactId>micronaut-chatbots-basecamp-http</artifactId>
</dependency>

You can configure the path with:

🔗
Table 1. Configuration Properties for BasecampControllerConfiguration
Property Type Description

micronaut.chatbots.basecamp.endpoint.enabled

boolean

Enables the controller. Default value true.

micronaut.chatbots.basecamp.endpoint.path

java.lang.String

Path to the controller. Default value "/basecamp".

5 Breaking Changes

This section documents breaking changes between Micronaut Chatbots versions:

Micronaut Chatbots 2.0.0

Deprecations

  • The private class field io.micronaut.chatbots.google.api.Space.type deprecated previously has been removed. As a consequence the corresponding accessor/mutator methods getType() and setType(Type) have also been removed, even though they were not deprecated explicitly. Use the accessor/mutator methods for singleUserBotDm or spaceType (developer preview) instead.

6 Repository

You can find the source code of this project in this repository: