Skip to content

Commit

Permalink
feat: support setMyCommands
Browse files Browse the repository at this point in the history
  • Loading branch information
bondiano committed Mar 24, 2024
1 parent b843b2a commit 0bb115c
Show file tree
Hide file tree
Showing 2 changed files with 223 additions and 60 deletions.
54 changes: 34 additions & 20 deletions src/telega.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ 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/api.{type BotCommands, type BotCommandsOptions}
import telega/log

const telegram_url = "https://api.telegram.org/bot"
Expand Down Expand Up @@ -55,7 +55,7 @@ pub type Command {
}

pub type CommandContext {
CommandContext(message: Message, bot: Bot, command: Command)
CommandContext(ctx: Context, command: Command)
}

/// Creates a new Bot with the given options.
Expand All @@ -77,6 +77,19 @@ pub fn new(
)
}

/// 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
}
}

/// 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
Expand All @@ -93,21 +106,8 @@ pub fn set_webhook(bot: Bot) -> Result(Bool, String) {
}
}

/// 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) {
pub fn reply(ctx ctx: Context, text text: String) -> Result(Message, Nil) {
let chat_id = ctx.message.raw.chat.id

api.send_message(
Expand All @@ -120,6 +120,22 @@ pub fn reply(ctx: Context, text: String) -> Result(Message, Nil) {
|> result.nil_error
}

/// Use this method to change the list of the bot's commands. See [commands documentation](https://core.telegram.org/bots/features#commands) for more details about bot commands. Returns True on success.
pub fn set_my_commands(
ctx ctx: Context,
commands commands: BotCommands,
options options: Option(BotCommandsOptions),
) -> Result(Bool, Nil) {
api.set_my_commands(
token: ctx.bot.config.token,
telegram_url: ctx.bot.config.telegram_url,
commands: commands,
options: options,
)
|> result.map(fn(_) { True })
|> result.nil_error
}

/// Add a handler to the bot.
pub fn add_handler(bot: Bot, handler: Handler) -> Bot {
Bot(..bot, handlers: [handler, ..bot.handlers])
Expand Down Expand Up @@ -160,8 +176,7 @@ fn do_handle_update(bot: Bot, message: Message, handlers: List(Handler)) -> Nil
case message_command.command == command {
True ->
handle(CommandContext(
bot: bot,
message: message,
ctx: Context(bot: bot, message: message),
command: message_command,
))
False -> Ok(Nil)
Expand All @@ -172,8 +187,7 @@ fn do_handle_update(bot: Bot, message: Message, handlers: List(Handler)) -> Nil
case list.contains(commands, message_command.command) {
True ->
handle(CommandContext(
bot: bot,
message: message,
ctx: Context(bot: bot, message: message),
command: message_command,
))
False -> Ok(Nil)
Expand Down
229 changes: 189 additions & 40 deletions src/telega/api.gleam
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
//// > It's internal module for working with Telegram API, **not** for public use.

import gleam/http/request.{type Request}
import gleam/http/response.{type Response}
import gleam/http.{Get, Post}
Expand All @@ -18,6 +16,162 @@ type TelegramApiRequest {
TelegramApiGetRequest(url: String, query: Option(List(#(String, String))))
}

pub type BotCommand {
BotCommand(
/// Text of the command; 1-32 characters. Can contain only lowercase English letters, digits and underscores.
command: String,
/// Description of the command; 1-256 characters.
description: String,
)
}

pub type BotCommands =
List(BotCommand)

pub type IntOrString {
Int(Int)
String(String)
}

pub type BotCommandScope {
/// Represents the default scope of bot commands. Default commands are used if no commands with a narrower scope are specified for the user.
BotCommandDefaultScope
/// Represents the scope of bot commands, covering all private chats.
BotCommandAllPrivateChatsScope
/// Represents the scope of bot commands, covering all group and supergroup chats.
BotCommandScopeAllGroupChats
/// Represents the scope of bot commands, covering all group and supergroup chat administrators.
BotCommandScopeAllChatAdministrators
/// Represents the scope of bot commands, covering a specific chat.
BotCommandScopeChat(
/// Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)
chat_id: Int,
)
/// Represents the scope of bot commands, covering a specific chat.
BotCommandScopeChatString(
/// Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)
chat_id: IntOrString,
)
/// Represents the scope of bot commands, covering all administrators of a specific group or supergroup chat.
BotCommandScopeChatAdministrators(
/// Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)
chat_id: IntOrString,
)
/// Represents the scope of bot commands, covering a specific member of a group or supergroup chat.
BotCommandScopeChatMember(
/// Unique identifier for the target chat or username of the target supergroup (in the format @supergroupusername)
chat_id: IntOrString,
/// Unique identifier of the target user
user_id: Int,
)
}

pub type BotCommandsOptions {
BotCommandsOptions(
/// An object, describing scope of users for which the commands are relevant. Defaults to `BotCommandScopeDefault`.
scope: Option(BotCommandScope),
/// A two-letter ISO 639-1 language code. If empty, commands will be applied to all users from the given scope, for whose language there are no dedicated commands
language_code: Option(String),
)
}

// TODO: Support all options
/// **Official reference:** https://core.telegram.org/bots/api#setwebhook
pub fn set_webhook(
token token: String,
telegram_url telegram_url: String,
webhook_url webhook_url: String,
secret_token secret_token: Option(String),
) -> Result(Response(String), String) {
let query = [#("url", webhook_url)]
let query = case secret_token {
None -> query
Some(secret_token) -> [#("secret_token", secret_token), ..query]
}

new_get_request(
token: token,
telegram_url: telegram_url,
path: "setWebhook",
query: Some(query),
)
|> api_to_request
|> fetch
}

// TODO: Support all options
/// **Official reference:** https://core.telegram.org/bots/api#sendmessage
pub fn send_message(
token token: String,
telegram_url telegram_url: String,
chat_id chat_id: Int,
text text: String,
) -> Result(Response(String), String) {
new_post_request(
token: token,
telegram_url: telegram_url,
path: "sendMessage",
body: json.object([
#("chat_id", json.int(chat_id)),
#("text", json.string(text)),
])
|> json.to_string,
query: None,
)
|> api_to_request
|> fetch
}

/// **Official reference:** https://core.telegram.org/bots/api#setmycommands
pub fn set_my_commands(
token token: String,
telegram_url telegram_url: String,
commands commands: BotCommands,
options options: Option(BotCommandsOptions),
) -> Result(Response(String), String) {
let options = case options {
None -> []
Some(options) -> {
let scope = case options.scope {
None -> []
Some(scope) -> [#("scope", bot_command_scope_to_json(scope))]
}

case options.language_code {
None -> scope
Some(language_code) -> [
#("language_code", json.string(language_code)),
..scope
]
}
}
}

let body_json =
json.object([
#(
"commands",
json.array(commands, fn(command: BotCommand) {
json.object([
#("command", json.string(command.command)),
#("description", json.string(command.description)),
..options
])
}),
),
])

new_post_request(
token: token,
telegram_url: telegram_url,
path: "setMyCommands",
body: json.to_string(body_json),
query: None,
)
|> api_to_request
|> fetch
}

fn new_post_request(
token token: String,
telegram_url telegram_url: String,
Expand Down Expand Up @@ -84,47 +238,42 @@ fn fetch(api_request: Result(Request(String), String)) {
})
}

/// **Official reference:** https://core.telegram.org/bots/api#setwebhook
pub fn set_webhook(
token token: String,
webhook_url webhook_url: String,
telegram_url telegram_url: String,
secret_token secret_token: Option(String),
) -> Result(Response(String), String) {
let query = [#("url", webhook_url)]
let query = case secret_token {
None -> query
Some(secret_token) -> [#("secret_token", secret_token), ..query]
fn string_or_int_to_json(value: IntOrString) {
case value {
Int(value) -> json.int(value)
String(value) -> json.string(value)
}

new_get_request(
token: token,
telegram_url: telegram_url,
path: "setWebhook",
query: Some(query),
)
|> api_to_request
|> fetch
}

/// **Official reference:** https://core.telegram.org/bots/api#sendmessage
pub fn send_message(
chat_id chat_id: Int,
text text: String,
token token: String,
telegram_url telegram_url: String,
) -> Result(Response(String), String) {
new_post_request(
token: token,
telegram_url: telegram_url,
path: "sendMessage",
body: json.object([
fn bot_command_scope_to_json(scope: BotCommandScope) {
case scope {
BotCommandDefaultScope -> json.object([#("type", json.string("default"))])
BotCommandAllPrivateChatsScope ->
json.object([#("type", json.string("all_private_chats"))])
BotCommandScopeAllGroupChats ->
json.object([#("type", json.string("all_group_chats"))])
BotCommandScopeAllChatAdministrators ->
json.object([#("type", json.string("all_chat_administrators"))])
BotCommandScopeChat(chat_id: chat_id) ->
json.object([
#("type", json.string("chat")),
#("chat_id", json.int(chat_id)),
#("text", json.string(text)),
])
|> json.to_string,
query: None,
)
|> api_to_request
|> fetch
BotCommandScopeChatString(chat_id: chat_id) ->
json.object([
#("type", json.string("chat")),
#("chat_id", string_or_int_to_json(chat_id)),
])
BotCommandScopeChatAdministrators(chat_id: chat_id) ->
json.object([
#("type", json.string("chat_administrators")),
#("chat_id", string_or_int_to_json(chat_id)),
])
BotCommandScopeChatMember(chat_id: chat_id, user_id: user_id) ->
json.object([
#("type", json.string("chat_member")),
#("chat_id", string_or_int_to_json(chat_id)),
#("user_id", json.int(user_id)),
])
}
}

0 comments on commit 0bb115c

Please sign in to comment.