import gleam/result.{try} import gleam/option.{type Option, None, Some} import gleam/list import gleam/string import telega/message.{type Message, CommandMessage, TextMessage} import telega/api import telega/log const telegram_url = "https://api.telegram.org/bot" pub opaque type Config { Config( token: String, server_url: String, webhook_path: String, /// An optional string to compare to X-Telegram-Bot-Api-Secret-Token secret_token: Option(String), telegram_url: String, ) } pub type Bot { Bot(config: Config, handlers: List(Handler)) } pub type Handler { /// Handle all messages. HandleAll(handler: fn(Context) -> Result(Nil, Nil)) /// Handle a specific command. HandleCommand( handler: fn(CommandContext) -> Result(Nil, Nil), command: String, ) /// Handle multiple commands. HandleCommands( handler: fn(CommandContext) -> Result(Nil, Nil), commands: List(String), ) /// Handle text messages. HandleText(handler: fn(Context) -> Result(Nil, Nil)) } /// Handlers context. pub type Context { Context(message: Message, bot: Bot) } pub type Command { Command( text: String, command: String, /// The command arguments, if any. payload: Option(String), ) } pub type CommandContext { CommandContext(message: Message, bot: Bot, command: Command) } /// Creates a new Bot with the given options. pub fn new( token token: String, url server_url: String, webhook_path webhook_path: String, secret_token secret_token: Option(String), ) -> Bot { Bot( handlers: [], config: Config( token: token, server_url: server_url, webhook_path: webhook_path, telegram_url: telegram_url, secret_token: secret_token, ), ) } /// Set the webhook URL using [setWebhook](https://core.telegram.org/bots/api#setwebhook) API. pub fn set_webhook(bot: Bot) -> Result(Bool, String) { let webhook_url = bot.config.server_url <> "/" <> bot.config.webhook_path use response <- try(api.set_webhook( webhook_url: webhook_url, token: bot.config.token, telegram_url: bot.config.telegram_url, secret_token: bot.config.secret_token, )) case response.status { 200 -> Ok(True) _ -> Error("Failed to set webhook") } } /// Check if a path is the webhook path for the bot. pub fn is_webhook_path(bot: Bot, path: String) -> Bool { bot.config.webhook_path == path } /// Check if a token is the secret token for the bot. pub fn is_secret_token_valid(bot: Bot, token: String) -> Bool { case bot.config.secret_token { Some(secret) -> secret == token None -> True } } /// Use this method to send text messages. pub fn reply(ctx: Context, text: String) -> Result(Message, Nil) { let chat_id = ctx.message.raw.chat.id api.send_message( token: ctx.bot.config.token, telegram_url: ctx.bot.config.telegram_url, chat_id: chat_id, text: text, ) |> result.map(fn(_) { ctx.message }) |> result.nil_error } /// Add a handler to the bot. pub fn add_handler(bot: Bot, handler: Handler) -> Bot { Bot(..bot, handlers: [handler, ..bot.handlers]) } /// Handle an update from the Telegram API. pub fn handle_update(bot: Bot, message: Message) -> Nil { do_handle_update(bot, message, bot.handlers) } fn extract_command(message: Message) -> Command { case message.raw.text { None -> Command(text: "", command: "", payload: None) Some(text) -> { case string.split(text, " ") { [command, ..payload] -> { Command(text: text, command: command, payload: case payload { [] -> None [payload, ..] -> Some(payload) }) } _ -> Command(text: text, command: "", payload: None) } } } } fn do_handle_update(bot: Bot, message: Message, handlers: List(Handler)) -> Nil { case handlers { [handler, ..rest] -> { let handle_result = case handler, message.kind { HandleAll(handle), _ -> handle(Context(bot: bot, message: message)) HandleText(handle), TextMessage -> handle(Context(bot: bot, message: message)) HandleCommand(handle, command), CommandMessage -> { let message_command = extract_command(message) case message_command.command == command { True -> handle(CommandContext( bot: bot, message: message, command: message_command, )) False -> Ok(Nil) } } HandleCommands(handle, commands), CommandMessage -> { let message_command = extract_command(message) case list.contains(commands, message_command.command) { True -> handle(CommandContext( bot: bot, message: message, command: message_command, )) False -> Ok(Nil) } } _, _ -> Ok(Nil) } case handle_result { Ok(_) -> do_handle_update(bot, message, rest) Error(_) -> { log.error("Failed to handle message") Nil } } } _ -> Nil } }