Skip to content

Commit

Permalink
feat: add get me method
Browse files Browse the repository at this point in the history
  • Loading branch information
bondiano committed Mar 30, 2024
1 parent e6534f5 commit 8765d5a
Show file tree
Hide file tree
Showing 6 changed files with 59 additions and 127 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@

A [Gleam](https://gleam.run/) library for the Telegram Bot API.

## It provides:

- an inteface to the Telegram Bot HTTP-based APIs `telega/api`
- adapter to use with [wisp](https://github.com/gleam-wisp/wisp)

## Installation

```sh
gleam add telega
```
Expand Down
12 changes: 6 additions & 6 deletions examples/commands-bot/src/bot.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import gleam/bool
import dotenv_gleam
import mist
import wisp.{type Request, type Response}
import telega.{type Bot, type CommandContext, HandleCommand}
import telega.{type Bot, type Context, HandleCommand}
import telega/adapters/wisp as telega_wisp
import telega/api as telega_api
import telega/model as telega_model
Expand Down Expand Up @@ -34,23 +34,23 @@ fn handle_request(bot: Bot, req: Request) -> Response {
}
}

fn dice_command_handler(command_ctx: CommandContext) -> Result(Nil, Nil) {
telega_api.send_dice(command_ctx.ctx, None)
fn dice_command_handler(ctx: Context, _) -> Result(Nil, Nil) {
telega_api.send_dice(ctx, None)
|> result.map(fn(_) { Nil })
|> result.map_error(fn(e) { wisp.log_error("Failed to send dice: " <> e) })
|> result.nil_error
}

fn start_command_handler(command_ctx: CommandContext) -> Result(Nil, Nil) {
fn start_command_handler(ctx: Context, _) -> Result(Nil, Nil) {
telega_api.set_my_commands(
command_ctx.ctx,
ctx,
telega_model.bot_commands_from([#("/dice", "Roll a dice")]),
None,
)
|> result.map_error(fn(e) { wisp.log_error("Failed to set commands: " <> e) })
|> result.then(fn(_) {
telega_api.reply(
command_ctx.ctx,
ctx,
"Hello! I'm a dice bot. You can roll a dice by sending /dice command.",
)
|> result.map(fn(_) { Nil })
Expand Down
32 changes: 6 additions & 26 deletions src/telega.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -24,15 +24,15 @@ pub type Handler {
/// Handle a specific command.
HandleCommand(
command: String,
handler: fn(CommandContext) -> Result(Nil, Nil),
handler: fn(Context, Command) -> Result(Nil, Nil),
)
/// Handle multiple commands.
HandleCommands(
commands: List(String),
handler: fn(CommandContext) -> Result(Nil, Nil),
handler: fn(Context, Command) -> Result(Nil, Nil),
)
/// Handle text messages.
HandleText(handler: fn(TextContext) -> Result(Nil, Nil))
HandleText(handler: fn(Context, String) -> Result(Nil, Nil))
}

/// Handlers context.
Expand All @@ -49,15 +49,6 @@ pub type Command {
)
}

/// Command handlers context.
pub type CommandContext {
CommandContext(ctx: Context, command: Command)
}

pub type TextContext {
TextContext(ctx: Context, text: String)
}

/// Creates a new Bot with the given options.
pub fn new(
token token: String,
Expand Down Expand Up @@ -126,30 +117,19 @@ fn do_handle_update(bot: Bot, message: Message, handlers: List(Handler)) -> Nil
let handle_result = case handler, message.kind {
HandleAll(handle), _ -> handle(Context(bot: bot, message: message))
HandleText(handle), TextMessage ->
handle(TextContext(
ctx: Context(bot: bot, message: message),
text: extract_text(message),
))
handle(Context(bot: bot, message: message), extract_text(message))

HandleCommand(command, handle), CommandMessage -> {
let message_command = extract_command(message)
case message_command.command == command {
True ->
handle(CommandContext(
ctx: Context(bot: bot, message: message),
command: message_command,
))
True -> handle(Context(bot: bot, message: message), message_command)
False -> Ok(Nil)
}
}
HandleCommands(commands, handle), CommandMessage -> {
let message_command = extract_command(message)
case list.contains(commands, message_command.command) {
True ->
handle(CommandContext(
ctx: Context(bot: bot, message: message),
command: message_command,
))
True -> handle(Context(bot: bot, message: message), message_command)
False -> Ok(Nil)
}
}
Expand Down
114 changes: 31 additions & 83 deletions src/telega/api.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import gleam/dynamic.{type DecodeError, type Dynamic}
import telega.{type Bot, type Context}
import telega/model.{
type BotCommand, type BotCommandParameters, type Message,
type SendDiceParameters,
type SendDiceParameters, type User,
}

const telegram_url = "https://api.telegram.org/bot"
Expand All @@ -23,65 +23,44 @@ type TelegramApiRequest {
TelegramApiGetRequest(url: String, query: Option(List(#(String, String))))
}

pub type ApiResponse(result) {
type ApiResponse(result) {
ApiResponse(ok: Bool, result: result)
}

// TODO: Support all options from the official reference.
/// Set the webhook URL using [setWebhook](https://core.telegram.org/bots/api#setwebhook) API.
/// **Official reference:** https://core.telegram.org/bots/api#setwebhook
pub fn set_webhook(bot: Bot) -> Result(Bool, String) {
let webhook_url = bot.config.server_url <> "/" <> bot.config.webhook_path
do_set_webhook(
webhook_url: webhook_url,
token: bot.config.token,
secret_token: bot.config.secret_token,
)
}

// TODO: Support all options
fn do_set_webhook(
token token: String,
webhook_url webhook_url: String,
secret_token secret_token: Option(String),
) -> Result(Bool, String) {
let query = [#("url", webhook_url)]
let query = case secret_token {
let query = case bot.config.secret_token {
None -> query
Some(secret_token) -> [#("secret_token", secret_token), ..query]
}

new_get_request(token: token, path: "setWebhook", query: Some(query))
|> api_to_request
new_get_request(
token: bot.config.token,
path: "setWebhook",
query: Some(query),
)
|> fetch
|> map_resonse(dynamic.bool)
}

// TODO: Support all options from the official reference.
/// Use this method to send text messages.
/// **Official reference:** https://core.telegram.org/bots/api#sendmessage
pub fn reply(ctx ctx: Context, text text: String) -> Result(Message, String) {
do_send_message(
token: ctx.bot.config.token,
chat_id: ctx.message.raw.chat.id,
text: text,
)
}

// TODO: Support all options
fn do_send_message(
token token: String,
chat_id chat_id: Int,
text text: String,
) -> Result(Message, String) {
new_post_request(
token: token,
token: ctx.bot.config.token,
path: "sendMessage",
body: json.object([
#("chat_id", json.int(chat_id)),
#("chat_id", json.int(ctx.message.raw.chat.id)),
#("text", json.string(text)),
])
|> json.to_string,
query: None,
)
|> api_to_request
|> fetch
|> map_resonse(model.decode_message)
}
Expand All @@ -92,18 +71,6 @@ pub fn set_my_commands(
ctx ctx: Context,
commands commands: List(BotCommand),
parameters parameters: Option(BotCommandParameters),
) -> Result(Bool, String) {
do_set_my_commands(
token: ctx.bot.config.token,
commands: commands,
parameters: parameters,
)
}

fn do_set_my_commands(
token token: String,
commands commands: List(BotCommand),
parameters parameters: Option(BotCommandParameters),
) -> Result(Bool, String) {
let parameters =
option.unwrap(parameters, model.new_botcommand_parameters())
Expand All @@ -124,12 +91,11 @@ fn do_set_my_commands(
])

new_post_request(
token: token,
token: ctx.bot.config.token,
path: "setMyCommands",
body: json.to_string(body_json),
query: None,
)
|> api_to_request
|> fetch
|> map_resonse(dynamic.bool)
}
Expand All @@ -139,13 +105,6 @@ fn do_set_my_commands(
pub fn delete_my_commands(
ctx: Context,
parameters parameters: Option(BotCommandParameters),
) -> Result(Bool, String) {
do_delete_my_commands(token: ctx.bot.config.token, parameters: parameters)
}

fn do_delete_my_commands(
token token: String,
parameters parameters: Option(BotCommandParameters),
) -> Result(Bool, String) {
let parameters =
option.unwrap(parameters, model.new_botcommand_parameters())
Expand All @@ -154,12 +113,11 @@ fn do_delete_my_commands(
let body_json = json.object(parameters)

new_post_request(
token: token,
token: ctx.bot.config.token,
path: "deleteMyCommands",
body: json.to_string(body_json),
query: None,
)
|> api_to_request
|> fetch
|> map_resonse(dynamic.bool)
}
Expand All @@ -169,13 +127,6 @@ fn do_delete_my_commands(
pub fn get_my_commands(
ctx: Context,
parameters parameters: Option(BotCommandParameters),
) -> Result(List(BotCommand), String) {
do_get_my_commands(token: ctx.bot.config.token, parameters: parameters)
}

pub fn do_get_my_commands(
token token: String,
parameters parameters: Option(BotCommandParameters),
) -> Result(List(BotCommand), String) {
let parameters =
option.unwrap(parameters, model.new_botcommand_parameters())
Expand All @@ -184,12 +135,11 @@ pub fn do_get_my_commands(
let body_json = json.object(parameters)

new_post_request(
token: token,
token: ctx.bot.config.token,
path: "getMyCommands",
query: None,
body: json.to_string(body_json),
)
|> api_to_request
|> fetch
|> map_resonse(model.decode_bot_command)
}
Expand All @@ -199,34 +149,32 @@ pub fn do_get_my_commands(
pub fn send_dice(
ctx: Context,
parameters parameters: Option(SendDiceParameters),
) -> Result(Message, String) {
do_send_dice(
token: ctx.bot.config.token,
chat_id: ctx.message.raw.chat.id,
parameters: parameters,
)
}

fn do_send_dice(
token token: String,
chat_id chat_id: Int,
parameters parameters: Option(SendDiceParameters),
) -> Result(Message, String) {
let parameters =
option.unwrap(parameters, model.new_send_dice_parameters(chat_id))
option.unwrap(
parameters,
model.new_send_dice_parameters(ctx.message.raw.chat.id),
)
let body_json = model.encode_send_dice_parameters(parameters)

new_post_request(
token: token,
token: ctx.bot.config.token,
path: "sendDice",
query: None,
body: json.to_string(body_json),
)
|> api_to_request
|> fetch
|> map_resonse(model.decode_message)
}

/// A simple method for testing your bot's authentication token.
/// **Official reference:** https://core.telegram.org/bots/api#getme
pub fn get_me(ctx: Context) -> Result(User, String) {
new_get_request(token: ctx.bot.config.token, path: "getMe", query: None)
|> fetch
|> map_resonse(model.decode_user)
}

fn new_post_request(
token token: String,
path path: String,
Expand Down Expand Up @@ -280,8 +228,8 @@ fn api_to_request(
|> result.map_error(fn(_) { "Failed to convert API request to HTTP request" })
}

fn fetch(api_request: Result(Request(String), String)) {
use api_request <- result.try(api_request)
fn fetch(api_request: TelegramApiRequest) {
use api_request <- result.try(api_to_request(api_request))

httpc.send(api_request)
|> result.map_error(fn(error) {
Expand Down
7 changes: 3 additions & 4 deletions src/telega/message.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import gleam/string
import gleam/list
import gleam/option.{None, Some}
import gleam/dynamic.{type Dynamic}
import gleam/result.{try}
import gleam/result
import telega/model.{type Message as RawMessage, type MessageEntity}

pub type MessageKind {
Expand All @@ -23,9 +23,8 @@ pub type Message {

/// Decode a message from the Telegram API.
pub fn decode(json: Dynamic) -> Result(Message, dynamic.DecodeErrors) {
use update <- try(model.decode_update(json))

Ok(raw_message_to_message(update.message))
model.decode_update(json)
|> result.map(fn(update) { raw_message_to_message(update.message) })
}

fn is_command_message(text: String, raw_message: RawMessage) -> Bool {
Expand Down
14 changes: 6 additions & 8 deletions src/telega/model.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -430,16 +430,14 @@ pub fn encode_botcommand_parameters(
params: BotCommandParameters,
) -> List(#(String, Json)) {
let scope =
params.scope
|> option.map(fn(scope) { [#("scope", bot_command_scope_to_json(scope))] })
|> option.unwrap([])
option_to_json_object_list(params.scope, "scope", bot_command_scope_to_json)

let language_code =
params.language_code
|> option.map(fn(language_code) {
[#("language_code", json.string(language_code))]
})
|> option.unwrap([])
option_to_json_object_list(
params.language_code,
"language_code",
json.string,
)

list.concat([scope, language_code])
}
Expand Down

0 comments on commit 8765d5a

Please sign in to comment.