Skip to content

Commit

Permalink
feat: add bot info to context
Browse files Browse the repository at this point in the history
  • Loading branch information
bondiano committed Jul 8, 2024
1 parent fe06a19 commit b4fa6ad
Show file tree
Hide file tree
Showing 4 changed files with 74 additions and 78 deletions.
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,9 @@ A [Gleam](https://gleam.run/) library for the Telegram Bot API.

> If you are new to Telegram bots, read the official [Introduction for Developers](https://core.telegram.org/bots) written by the Telegram team.
First, visit [@BotFather](https://t.me/botfather) and create a new bot. Copy **the token** and save it for later.
First, visit [@BotFather](https://t.me/botfather) to create a new bot. Copy **the token** and save it for later.

Init new gleam project and add `telega` and `wisp` as a dependency:
Initiate a gleam project and add `telega` and `wisp` as a dependencies:

```sh
$ gleam new first_tg_bot
Expand Down Expand Up @@ -78,17 +78,17 @@ pub fn main() {
}
```

Replace `"your bot token from @BotFather"` with the token you got from the BotFather. Set the `url` and `webhook_path` to your server URL and the path you want to use for the webhook. If you don't have a server yet, you can use [ngrok](https://ngrok.com/) or [localtunne](https://localtunnel.me/) to create a tunnel to your local machine.
Replace `"your bot token from @BotFather"` with the token you received from the BotFather. Set the `url` and `webhook_path` to your server's URL and the desired path for the webhook. If you don't have a server yet, you can use [ngrok](https://ngrok.com/) or [localtunne](https://localtunnel.me/) to create a tunnel to your local machine.

Then run the bot:

```sh
$ gleam run
```

and it will echo all received text messages.
And it will echo all received text messages.

Congrats! You just wrote a Telegram bot :)
Congratulations! You just wrote a Telegram bot :)

## Examples

Expand Down
117 changes: 53 additions & 64 deletions src/telega.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import gleam/otp/actor
import gleam/otp/supervisor
import gleam/result
import gleam/string
import telega/api
import telega/bot.{
type CallbackQueryFilter, type Context, type Handler, type Hears,
type RegistryMessage, type SessionSettings, CallbackQueryFilter, Context,
Expand All @@ -13,19 +14,26 @@ import telega/bot.{
}
import telega/internal/config.{type Config}
import telega/log
import telega/model.{type User}
import telega/update.{type Command, type Update}

pub opaque type Telega(session) {
Telega(
config: Config,
bot_info: User,
handlers: List(Handler(session)),
session_settings: Option(SessionSettings(session)),
registry_subject: Option(Subject(RegistryMessage)),
session_settings: SessionSettings(session),
registry_subject: Subject(RegistryMessage),
)
}

pub opaque type TelegaBuilder(session) {
TelegaBuilder(telega: Telega(session))
TelegaBuilder(
config: Config,
handlers: List(Handler(session)),
session_settings: Option(SessionSettings(session)),
registry_subject: Option(Subject(RegistryMessage)),
)
}

/// Check if a path is the webhook path for the bot.
Expand All @@ -49,7 +57,7 @@ pub fn new(
webhook_path webhook_path: String,
secret_token secret_token: Option(String),
) -> TelegaBuilder(session) {
TelegaBuilder(Telega(
TelegaBuilder(
handlers: [],
config: config.new(
token: token,
Expand All @@ -59,20 +67,15 @@ pub fn new(
),
registry_subject: None,
session_settings: None,
))
)
}

/// Handles all messages.
pub fn handle_all(
bot builder: TelegaBuilder(session),
handler handler: fn(Context(session)) -> Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
handlers: [HandleAll(handler), ..builder.telega.handlers],
),
)
TelegaBuilder(..builder, handlers: [HandleAll(handler), ..builder.handlers])
}

/// Stops bot message handling and waits for any message.
Expand All @@ -90,10 +93,8 @@ pub fn handle_command(
handler handler: fn(Context(session), Command) -> Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
handlers: [HandleCommand(command, handler), ..builder.telega.handlers],
),
..builder,
handlers: [HandleCommand(command, handler), ..builder.handlers],
)
}

Expand All @@ -112,10 +113,8 @@ pub fn handle_commands(
handler handler: fn(Context(session), Command) -> Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
handlers: [HandleCommands(commands, handler), ..builder.telega.handlers],
),
..builder,
handlers: [HandleCommands(commands, handler), ..builder.handlers],
)
}

Expand All @@ -132,12 +131,7 @@ pub fn handle_text(
bot builder: TelegaBuilder(session),
handler handler: fn(Context(session), String) -> Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
handlers: [HandleText(handler), ..builder.telega.handlers],
),
)
TelegaBuilder(..builder, handlers: [HandleText(handler), ..builder.handlers])
}

pub fn wait_text(
Expand All @@ -154,10 +148,8 @@ pub fn handle_hears(
handler handler: fn(Context(session), String) -> Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
handlers: [HandleHears(hears, handler), ..builder.telega.handlers],
),
..builder,
handlers: [HandleHears(hears, handler), ..builder.handlers],
)
}

Expand All @@ -177,13 +169,8 @@ pub fn handle_callback_query(
Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
handlers: [
HandleCallbackQuery(filter, handler),
..builder.telega.handlers
],
),
..builder,
handlers: [HandleCallbackQuery(filter, handler), ..builder.handlers],
)
}

Expand Down Expand Up @@ -221,26 +208,21 @@ pub fn with_session_settings(
get_session get_session: fn(String) -> Result(session, String),
) -> TelegaBuilder(session) {
TelegaBuilder(
Telega(
..builder.telega,
session_settings: Some(SessionSettings(
persist_session: persist_session,
get_session: get_session,
)),
),
..builder,
session_settings: Some(SessionSettings(
persist_session: persist_session,
get_session: get_session,
)),
)
}

fn nil_session_settings(builder: TelegaBuilder(Nil)) -> TelegaBuilder(Nil) {
TelegaBuilder(
Telega(
..builder.telega,
session_settings: Some(
SessionSettings(
persist_session: fn(_, _) { Ok(Nil) },
get_session: fn(_) { Ok(Nil) },
),
),
..builder,
session_settings: Some(
SessionSettings(persist_session: fn(_, _) { Ok(Nil) }, get_session: fn(_) {
Ok(Nil)
}),
),
)
}
Expand All @@ -259,13 +241,14 @@ pub fn init_nil_session(
/// This function should be called after all handlers are added.
/// It will set the webhook and start the `Registry`.
pub fn init(builder: TelegaBuilder(session)) -> Result(Telega(session), String) {
let TelegaBuilder(telega) = builder
use is_ok <- result.try(bot.set_webhook(telega.config))
use is_ok <- result.try(bot.set_webhook(builder.config))
use <- bool.guard(!is_ok, Error("Failed to set webhook"))

use bot_info <- result.try(api.get_me(builder.config.api))

let session_settings =
option.to_result(
telega.session_settings,
builder.session_settings,
"Session settings not initialized",
)

Expand All @@ -275,10 +258,11 @@ pub fn init(builder: TelegaBuilder(session)) -> Result(Telega(session), String)
let registry_actor =
supervisor.supervisor(fn(_) {
bot.start_registry(
telega.config,
telega.handlers,
session_settings,
telega_subject,
config: builder.config,
handlers: builder.handlers,
session_settings: session_settings,
root_subject: telega_subject,
bot_info: bot_info,
)
})

Expand All @@ -295,17 +279,22 @@ pub fn init(builder: TelegaBuilder(session)) -> Result(Telega(session), String)
}),
)

Ok(Telega(..telega, registry_subject: Some(registry_subject)))
Ok(Telega(
config: builder.config,
handlers: builder.handlers,
bot_info: bot_info,
registry_subject: registry_subject,
session_settings: session_settings,
))
}

/// Handle an update from the Telegram API.
pub fn handle_update(
telega: Telega(session),
update: Update,
) -> Result(Nil, String) {
let registry_subject =
option.to_result(telega.registry_subject, "Registry not initialized")
use registry_subject <- result.try(registry_subject)

Ok(actor.send(registry_subject, HandleBotRegistryMessage(update: update)))
Ok(actor.send(
telega.registry_subject,
HandleBotRegistryMessage(update: update),
))
}
17 changes: 12 additions & 5 deletions src/telega/bot.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import gleam/string
import telega/api
import telega/internal/config.{type Config}
import telega/log
import telega/model
import telega/model.{type User}
import telega/update.{
type Command, type Update, CallbackQueryUpdate, CommandUpdate, TextUpdate,
UnknownUpdate,
Expand All @@ -32,6 +32,7 @@ type Registry(session) {
Registry(
bots: Dict(String, RegistryItem(session)),
config: Config,
bot_info: User,
session_settings: SessionSettings(session),
handlers: List(Handler(session)),
registry_subject: RegestrySubject,
Expand Down Expand Up @@ -143,10 +144,11 @@ pub fn set_webhook(config config: Config) -> Result(Bool, String) {
}

pub fn start_registry(
config: Config,
handlers: List(Handler(session)),
session_settings: SessionSettings(session),
root_subject: Subject(RegestrySubject),
config config: Config,
handlers handlers: List(Handler(session)),
session_settings session_settings: SessionSettings(session),
root_subject root_subject: Subject(RegestrySubject),
bot_info bot_info: User,
) -> Result(RegestrySubject, actor.StartError) {
actor.start_spec(actor.Spec(
init: fn() {
Expand All @@ -165,6 +167,7 @@ pub fn start_registry(
handlers: handlers,
registry_subject: registry_subject,
bot_instances_subject: bot_instances_subject,
bot_info: bot_info,
)
|> actor.Ready(selector)
},
Expand All @@ -188,6 +191,7 @@ fn new_context(bot: BotInstanse(session), update: Update) -> Context(session) {
config: bot.config,
session: bot.session,
bot_subject: bot.own_subject,
bot_info: bot.bot_info,
)
}

Expand Down Expand Up @@ -241,6 +245,7 @@ fn start_bot_instanse(
BotInstanse(
key: session_key,
session: session,
bot_info: registry.bot_info,
config: registry.config,
handlers: registry.handlers,
session_settings: registry.session_settings,
Expand All @@ -263,6 +268,7 @@ pub type Context(session) {
Context(
key: String,
update: Update,
bot_info: User,
config: Config,
session: session,
bot_subject: BotInstanseSubject(session),
Expand All @@ -281,6 +287,7 @@ type BotInstanse(session) {
session: session,
config: Config,
handlers: List(Handler(session)),
bot_info: User,
session_settings: SessionSettings(session),
active_handler: Option(Handler(session)),
own_subject: BotInstanseSubject(session),
Expand Down
8 changes: 4 additions & 4 deletions src/telega/model.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -946,17 +946,17 @@ pub type KeyboardButton {
KeyboardButton(
/// Text of the button. If none of the optional fields are used, it will be sent as a message when the button is pressed
text: String,
/// If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a users_shared service message. Available in private chats only.
/// If specified, pressing the button will open a list of suitable users. Identifiers of selected users will be sent to the bot in a "users_shared" service message. Available in private chats only.
request_users: Option(KeyboardButtonRequestUsers),
/// If specified, pressing the button will open a list of suitable chats. Tapping on a chat will send its identifier to the bot in a chat_shared service message. Available in private chats only.
/// If specified, pressing the button will open a list of suitable chats. Tapping on a chat will send its identifier to the bot in a "chat_shared" service message. Available in private chats only.
request_chat: Option(KeyboardButtonRequestChat),
/// If _True_, the user's phone number will be sent as a contact when the button is pressed. Available in private chats only.
request_contact: Option(Bool),
/// If _True_, the user's current location will be sent when the button is pressed. Available in private chats only.
request_location: Option(Bool),
/// If specified, the user will be asked to create a poll and send it to the bot when the button is pressed. Available in private chats only.
request_poll: Option(KeyboardButtonPollType),
/// If specified, the described [Web App](https://core.telegram.org/bots/webapps) will be launched when the button is pressed. The Web App will be able to send a web_app_data service message. Available in private chats only.
/// If specified, the described [Web App](https://core.telegram.org/bots/webapps) will be launched when the button is pressed. The Web App will be able to send a "web_app_data" service message. Available in private chats only.
web_app: Option(WebAppInfo),
)
}
Expand Down Expand Up @@ -2124,7 +2124,7 @@ pub type SetWebhookParameters {
allowed_updates: Option(List(String)),
/// Pass _True_ to drop all pending updates
drop_pending_updates: Option(Bool),
/// A secret token to be sent in a header X-Telegram-Bot-Api-Secret-Token in every webhook request, 1-256 characters. Only characters A-Z, a-z, 0-9, _ and - are allowed. The header is useful to ensure that the request comes from a webhook set by you.
/// A secret token to be sent in a header "X-Telegram-Bot-Api-Secret-Token" in every webhook request, 1-256 characters. Only characters A-Z, a-z, 0-9, _ and - are allowed. The header is useful to ensure that the request comes from a webhook set by you.
secret_token: Option(String),
)
}
Expand Down

0 comments on commit b4fa6ad

Please sign in to comment.