Current section

Files

Jump to
telega src telega@keyboard.erl
Raw

src/telega@keyboard.erl

-module(telega@keyboard).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega/keyboard.gleam").
-export([new/1, hear/1, to_markup/1, one_time/1, persistent/1, resized/1, placeholder/2, selected/1, row/1, inline_row/1, grid/2, new_inline/1, inline_grid/2, remove/0, single/1, inline_single/1, builder/0, inline_builder/0, button/1, text/2, contact_button/1, contact/2, location_button/1, location/2, web_app_button/2, web_app/3, next_row/1, build/1, inline_button/2, inline_text/3, inline_url_button/2, inline_url/3, inline_web_app_button/2, inline_web_app/3, inline_switch_query_button/2, inline_switch_query/3, inline_copy_text_button/2, inline_copy_text/3, inline_next_row/1, inline_build/1, inline_to_markup/1, poll_button/2, users_button/8, chat_button/12, to_inline_markup/1, inline_raw_callback_button/2, inline_switch_query_current_chat_button/2, filter_inline_keyboard_query/1, new_callback_data/3, set_callback_data_delimiter/2, pack_callback/2, unpack_callback/2, string_callback_data/1, int_callback_data/1, bool_callback_data/1]).
-export_type([keyboard/0, keyboard_builder/0, inline_keyboard_builder/0, inline_keyboard/0, keyboard_callback_data/1, keyboard_callback/1]).
-if(?OTP_RELEASE >= 27).
-define(MODULEDOC(Str), -moduledoc(Str)).
-define(DOC(Str), -doc(Str)).
-else.
-define(MODULEDOC(Str), -compile([])).
-define(DOC(Str), -compile([])).
-endif.
?MODULEDOC(
" # Keyboard Module\n"
"\n"
" This module provides comprehensive keyboard functionality for Telegram bots, including:\n"
" - Regular reply keyboards with special buttons (contact, location, polls, etc.)\n"
" - Inline keyboards with callback data, URLs, and other interactive elements\n"
" - Helper functions for creating grid layouts and complex keyboard structures\n"
" - Type-safe callback data handling with validation\n"
" - Error handling and validation for Telegram API constraints\n"
"\n"
" ## Quick Start\n"
"\n"
" ### Basic Reply Keyboard\n"
" ```gleam\n"
" // Traditional array-based approach\n"
" let keyboard = keyboard.new([\n"
" [keyboard.button(\"Option 1\"), keyboard.button(\"Option 2\")],\n"
" [keyboard.contact_button(\"📞 Share Contact\"), keyboard.location_button(\"📍 Location\")],\n"
" ])\n"
" |> keyboard.one_time()\n"
" |> keyboard.resized()\n"
"\n"
" // New builder pattern approach (recommended)\n"
" let keyboard = keyboard.builder()\n"
" |> keyboard.text(\"Option 1\")\n"
" |> keyboard.text(\"Option 2\")\n"
" |> keyboard.next_row()\n"
" |> keyboard.contact(\"📞 Share Contact\")\n"
" |> keyboard.location(\"📍 Location\")\n"
" |> keyboard.build()\n"
" |> keyboard.one_time()\n"
" |> keyboard.resized()\n"
" ```\n"
"\n"
" ### Inline Keyboard with Callbacks\n"
" ```gleam\n"
" let callback_data = keyboard.string_callback_data(\"action\")\n"
"\n"
" // Traditional approach\n"
" let assert Ok(button) = keyboard.inline_button(\"Click Me\",\n"
" keyboard.pack_callback(callback_data, \"click\"))\n"
" let keyboard = keyboard.new_inline([[\n"
" button,\n"
" keyboard.inline_url_button(\"Visit\", \"https://example.com\"),\n"
" ]])\n"
"\n"
" // Builder pattern approach (recommended)\n"
" let callback = keyboard.pack_callback(callback_data, \"click\")\n"
" let assert Ok(keyboard) = {\n"
" use builder <- result.try(keyboard.inline_builder()\n"
" |> keyboard.inline_text(\"Click Me\", callback))\n"
" let builder = keyboard.inline_url(builder, \"Visit\", \"https://example.com\")\n"
" Ok(keyboard.inline_build(builder))\n"
" }\n"
"\n"
" // In your handler:\n"
" let assert Ok(filter) = keyboard.filter_inline_keyboard_query(keyboard)\n"
" ```\n"
"\n"
" ### Grid Layouts\n"
" ```gleam\n"
" let buttons = [\n"
" keyboard.button(\"1\"), keyboard.button(\"2\"),\n"
" keyboard.button(\"3\"), keyboard.button(\"4\"),\n"
" ]\n"
" let grid_keyboard = keyboard.grid(buttons, 2) // 2x2 grid\n"
" ```\n"
"\n"
" ## Special Button Types\n"
"\n"
" - **Contact buttons**: Request user's phone number\n"
" - **Location buttons**: Request user's location\n"
" - **Poll buttons**: Allow users to create polls\n"
" - **Web App buttons**: Launch mini-apps\n"
" - **User/Chat request buttons**: Request access to users or chats\n"
"\n"
" ## Inline Button Types\n"
"\n"
" - **Callback buttons**: Execute bot commands with data\n"
" - **URL buttons**: Open external links\n"
" - **Switch inline buttons**: Switch to inline mode\n"
" - **Copy text buttons**: Copy text to clipboard\n"
" - **Web app buttons**: Launch inline web applications\n"
"\n"
" ## Error Handling\n"
"\n"
" The module uses Result types for operations that can fail:\n"
" - Callback data validation (max 64 bytes)\n"
" - Inline button creation with validation\n"
" - Filter creation for callback queries\n"
"\n"
" ## Builder Pattern API\n"
"\n"
" The keyboard module provides a fluent builder API similar to Grammy/GramIO:\n"
"\n"
" ### Reply Keyboard Builder\n"
" ```gleam\n"
" let keyboard = keyboard.builder()\n"
" |> keyboard.text(\"Yes\")\n"
" |> keyboard.text(\"No\")\n"
" |> keyboard.next_row()\n"
" |> keyboard.contact(\"📞 Contact\")\n"
" |> keyboard.location(\"📍 Location\")\n"
" |> keyboard.next_row()\n"
" |> keyboard.web_app(\"Open App\", \"https://example.com\")\n"
" |> keyboard.build()\n"
" |> keyboard.one_time()\n"
" ```\n"
"\n"
" ### Inline Keyboard Builder\n"
" ```gleam\n"
" let callback_data = keyboard.string_callback_data(\"menu\")\n"
"\n"
" let result = keyboard.inline_builder()\n"
" |> keyboard.inline_text(\"Settings\", keyboard.pack_callback(callback_data, \"settings\"))\n"
" |> result.try(keyboard.inline_text(_, \"About\", keyboard.pack_callback(callback_data, \"about\")))\n"
" |> result.try(fn(builder) {\n"
" let builder = keyboard.inline_next_row(builder)\n"
" let builder = keyboard.inline_url(builder, \"Help\", \"https://help.example.com\")\n"
" Ok(keyboard.inline_build(builder))\n"
" })\n"
" ```\n"
"\n"
" ## Best Practices\n"
"\n"
" 1. **Use builder pattern** for complex keyboards (recommended over arrays)\n"
" 2. **Use typed callback data** for better type safety\n"
" 3. **Validate callback data length** before creating buttons\n"
" 4. **Use grid layouts** for better UX with many buttons\n"
" 5. **Handle Result types** properly when using validation functions\n"
" 6. **Use one-time keyboards** for single-use interactions\n"
"\n"
" ## Advanced Usage Patterns\n"
"\n"
" ### Dynamic Keyboards\n"
" ```gleam\n"
" // Create keyboards based on data\n"
" pub fn create_user_list_keyboard(users: List(User)) -> InlineKeyboard {\n"
" let user_callback = keyboard.int_callback_data(\"select_user\")\n"
" let buttons = list.filter_map(users, fn(user) {\n"
" let callback = keyboard.pack_callback(user_callback, user.id)\n"
" case keyboard.inline_button(user.name, callback) {\n"
" Ok(button) -> Some(button)\n"
" Error(_) -> None\n"
" }\n"
" })\n"
" keyboard.inline_grid(buttons, 2) // 2 columns\n"
" }\n"
" ```\n"
"\n"
" ### Pagination Pattern\n"
" ```gleam\n"
" pub fn create_pagination_keyboard(\n"
" current_page: Int,\n"
" total_pages: Int,\n"
" ) -> InlineKeyboard {\n"
" let page_callback = keyboard.int_callback_data(\"page\")\n"
" let buttons = []\n"
"\n"
" // Build buttons list\n"
" let buttons = case current_page > 1 {\n"
" True -> {\n"
" let prev_callback = keyboard.pack_callback(page_callback, current_page - 1)\n"
" case keyboard.inline_button(\"← Previous\", prev_callback) {\n"
" Ok(prev_btn) -> [prev_btn, ..buttons]\n"
" Error(_) -> buttons\n"
" }\n"
" }\n"
" False -> buttons\n"
" }\n"
"\n"
" // Add page indicator\n"
" let page_info = int.to_string(current_page) <> \"/\" <> int.to_string(total_pages)\n"
" let info_btn = keyboard.inline_copy_text_button(page_info, page_info)\n"
" let buttons = [info_btn, ..buttons]\n"
"\n"
" // Add next button\n"
" let buttons = case current_page < total_pages {\n"
" True -> {\n"
" let next_callback = keyboard.pack_callback(page_callback, current_page + 1)\n"
" case keyboard.inline_button(\"Next ->\", next_callback) {\n"
" Ok(next_btn) -> [next_btn, ..buttons]\n"
" Error(_) -> buttons\n"
" }\n"
" }\n"
" False -> buttons\n"
" }\n"
"\n"
" keyboard.new_inline([list.reverse(buttons)])\n"
" }\n"
" ```\n"
"\n"
" ### Error Handling Best Practices\n"
" ```gleam\n"
" // Always handle Result types properly\n"
" case keyboard.inline_button(\"Action\", callback) {\n"
" Ok(button) -> {\n"
" let keyboard = keyboard.inline_single(button)\n"
" reply.with_markup(ctx, \"Choose action:\", keyboard.to_inline_markup(keyboard))\n"
" }\n"
" Error(msg) -> {\n"
" // Log the error and provide fallback\n"
" logging.log(logging.Error, \"Keyboard error: \" <> msg)\n"
" reply.with_text(ctx, \"Sorry, something went wrong.\")\n"
" }\n"
" }\n"
" ```\n"
"\n"
" ### Performance Tips\n"
" - Use `grid()` and `inline_grid()` for better organization\n"
" - Keep callback data under 64 bytes (validated automatically)\n"
" - Use typed callback data for better type safety\n"
" - Cache callback data configurations to avoid recreation\n"
" - Consider using `one_time()` keyboards for single interactions\n"
).
-opaque keyboard() :: {keyboard,
list(list(telega@model@types:keyboard_button())),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(binary()),
gleam@option:option(boolean())}.
-opaque keyboard_builder() :: {keyboard_builder,
list(list(telega@model@types:keyboard_button())),
list(telega@model@types:keyboard_button())}.
-opaque inline_keyboard_builder() :: {inline_keyboard_builder,
list(list(telega@model@types:inline_keyboard_button())),
list(telega@model@types:inline_keyboard_button())}.
-opaque inline_keyboard() :: {inline_keyboard,
list(list(telega@model@types:inline_keyboard_button()))}.
-opaque keyboard_callback_data(AVKV) :: {keyboard_callback_data,
binary(),
fun((AVKV) -> binary()),
fun((binary()) -> AVKV),
binary()}.
-type keyboard_callback(AVKW) :: {keyboard_callback,
AVKW,
binary(),
binary(),
keyboard_callback_data(AVKW)}.
-file("src/telega/keyboard.gleam", 258).
?DOC(
" Create a new reply keyboard from a list of button rows.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let keyboard = keyboard.new([\n"
" [keyboard.button(\"Yes\"), keyboard.button(\"No\")],\n"
" [keyboard.button(\"Cancel\")],\n"
" ])\n"
" ```\n"
).
-spec new(list(list(telega@model@types:keyboard_button()))) -> keyboard().
new(Buttons) ->
{keyboard, Buttons, none, none, none, none, none}.
-file("src/telega/keyboard.gleam", 284).
?DOC(
" Extract button texts from a keyboard for use with `telega.wait_hears()`.\n"
" This is useful for listening to button presses in conversation flows.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let keyboard = keyboard.new([[keyboard.button(\"Yes\"), keyboard.button(\"No\")]])\n"
" let hears = keyboard.hear(keyboard)\n"
"\n"
" use _, text <- telega.wait_hears(ctx:, hears:, or: None, timeout: None)\n"
" case text {\n"
" \"Yes\" -> // handle yes\n"
" \"No\" -> // handle no\n"
" _ -> // handle other\n"
" }\n"
" ```\n"
).
-spec hear(keyboard()) -> telega@bot:hears().
hear(Keyboard) ->
_pipe = erlang:element(2, Keyboard),
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Row) ->
gleam@list:map(Row, fun(Button) -> erlang:element(2, Button) end)
end
),
{hear_texts, _pipe@1}.
-file("src/telega/keyboard.gleam", 291).
?DOC(" Build a reply markup for `Message` from a keyboard\n").
-spec to_markup(keyboard()) -> telega@model@types:send_message_reply_markup_parameters().
to_markup(Keyboard) ->
{send_message_reply_reply_keyboard_markup_parameters,
{reply_keyboard_markup,
erlang:element(2, Keyboard),
erlang:element(3, Keyboard),
erlang:element(4, Keyboard),
erlang:element(5, Keyboard),
erlang:element(6, Keyboard),
erlang:element(7, Keyboard)}}.
-file("src/telega/keyboard.gleam", 303).
?DOC(" Make the keyboard one-time\n").
-spec one_time(keyboard()) -> keyboard().
one_time(Keyboard) ->
{keyboard,
erlang:element(2, Keyboard),
erlang:element(3, Keyboard),
erlang:element(4, Keyboard),
{some, true},
erlang:element(6, Keyboard),
erlang:element(7, Keyboard)}.
-file("src/telega/keyboard.gleam", 308).
?DOC(" Make the keyboard persistent\n").
-spec persistent(keyboard()) -> keyboard().
persistent(Keyboard) ->
{keyboard,
erlang:element(2, Keyboard),
{some, true},
erlang:element(4, Keyboard),
erlang:element(5, Keyboard),
erlang:element(6, Keyboard),
erlang:element(7, Keyboard)}.
-file("src/telega/keyboard.gleam", 313).
?DOC(" Make the keyboard resizable\n").
-spec resized(keyboard()) -> keyboard().
resized(Keyboard) ->
{keyboard,
erlang:element(2, Keyboard),
erlang:element(3, Keyboard),
{some, true},
erlang:element(5, Keyboard),
erlang:element(6, Keyboard),
erlang:element(7, Keyboard)}.
-file("src/telega/keyboard.gleam", 318).
?DOC(" Set the placeholder for the input field\n").
-spec placeholder(keyboard(), binary()) -> keyboard().
placeholder(Keyboard, Text) ->
{keyboard,
erlang:element(2, Keyboard),
erlang:element(3, Keyboard),
erlang:element(4, Keyboard),
erlang:element(5, Keyboard),
{some, Text},
erlang:element(7, Keyboard)}.
-file("src/telega/keyboard.gleam", 324).
?DOC(
" Make the keyboard selective.\n"
" Use this parameter if you want to show the keyboard to specific users only.\n"
).
-spec selected(keyboard()) -> keyboard().
selected(Keyboard) ->
{keyboard,
erlang:element(2, Keyboard),
erlang:element(3, Keyboard),
erlang:element(4, Keyboard),
erlang:element(5, Keyboard),
erlang:element(6, Keyboard),
{some, true}}.
-file("src/telega/keyboard.gleam", 331).
?DOC(" Create a keyboard row from a list of buttons\n").
-spec row(list(telega@model@types:keyboard_button())) -> list(telega@model@types:keyboard_button()).
row(Buttons) ->
Buttons.
-file("src/telega/keyboard.gleam", 336).
?DOC(" Create an inline keyboard row from a list of buttons\n").
-spec inline_row(list(telega@model@types:inline_keyboard_button())) -> list(telega@model@types:inline_keyboard_button()).
inline_row(Buttons) ->
Buttons.
-file("src/telega/keyboard.gleam", 369).
?DOC(" Split a list into chunks of specified size\n").
-spec chunk_list(list(AVLF), integer()) -> list(list(AVLF)).
chunk_list(List, Size) ->
case List of
[] ->
[];
_ ->
{Chunk, Rest} = gleam@list:split(List, Size),
[Chunk | chunk_list(Rest, Size)]
end.
-file("src/telega/keyboard.gleam", 354).
?DOC(
" Create a grid keyboard with buttons arranged in rows of specified width.\n"
" This is useful for creating organized layouts with many buttons.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let buttons = [\n"
" keyboard.button(\"1\"), keyboard.button(\"2\"), keyboard.button(\"3\"),\n"
" keyboard.button(\"4\"), keyboard.button(\"5\"), keyboard.button(\"6\"),\n"
" ]\n"
" let grid_keyboard = keyboard.grid(buttons, 3) // 3 columns per row\n"
" // Results in: [[1, 2, 3], [4, 5, 6]]\n"
" ```\n"
).
-spec grid(list(telega@model@types:keyboard_button()), integer()) -> keyboard().
grid(Buttons, Columns) ->
Rows = chunk_list(Buttons, Columns),
new(Rows).
-file("src/telega/keyboard.gleam", 724).
?DOC(
" Create a new inline keyboard from a list of button rows.\n"
" Inline keyboards appear directly below messages and don't replace the user's keyboard.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let callback_data = keyboard.string_callback_data(\"action\")\n"
" let assert Ok(callback_btn) = keyboard.inline_button(\"Click\",\n"
" keyboard.pack_callback(callback_data, \"click\"))\n"
"\n"
" let keyboard = keyboard.new_inline([\n"
" [callback_btn, keyboard.inline_url_button(\"Visit\", \"https://example.com\")],\n"
" [keyboard.inline_copy_text_button(\"Copy\", \"Hello World!\")],\n"
" ])\n"
" ```\n"
).
-spec new_inline(list(list(telega@model@types:inline_keyboard_button()))) -> inline_keyboard().
new_inline(Buttons) ->
{inline_keyboard, Buttons}.
-file("src/telega/keyboard.gleam", 360).
?DOC(" Create a grid inline keyboard with buttons arranged in rows of specified width\n").
-spec inline_grid(list(telega@model@types:inline_keyboard_button()), integer()) -> inline_keyboard().
inline_grid(Buttons, Columns) ->
Rows = chunk_list(Buttons, Columns),
new_inline(Rows).
-file("src/telega/keyboard.gleam", 380).
?DOC(" Remove keyboard markup - useful for hiding keyboards\n").
-spec remove() -> telega@model@types:send_message_reply_markup_parameters().
remove() ->
{send_message_reply_remove_keyboard_markup_parameters,
{reply_keyboard_remove, true, none}}.
-file("src/telega/keyboard.gleam", 388).
?DOC(" Create a single-button keyboard\n").
-spec single(telega@model@types:keyboard_button()) -> keyboard().
single(Button) ->
new([[Button]]).
-file("src/telega/keyboard.gleam", 393).
?DOC(" Create a single-button inline keyboard\n").
-spec inline_single(telega@model@types:inline_keyboard_button()) -> inline_keyboard().
inline_single(Button) ->
new_inline([[Button]]).
-file("src/telega/keyboard.gleam", 411).
?DOC(
" Create a new keyboard builder for method chaining.\n"
" This provides a more ergonomic API for building complex keyboards.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let keyboard = keyboard.builder()\n"
" |> keyboard.text(\"Option 1\")\n"
" |> keyboard.text(\"Option 2\")\n"
" |> keyboard.next_row()\n"
" |> keyboard.text(\"Cancel\")\n"
" |> keyboard.build()\n"
" ```\n"
).
-spec builder() -> keyboard_builder().
builder() ->
{keyboard_builder, [], []}.
-file("src/telega/keyboard.gleam", 428).
?DOC(
" Create a new inline keyboard builder for method chaining.\n"
" This provides a more ergonomic API for building complex inline keyboards.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let callback_data = keyboard.string_callback_data(\"action\")\n"
" let keyboard = keyboard.inline_builder()\n"
" |> keyboard.inline_text(\"Button 1\", keyboard.pack_callback(callback_data, \"btn1\"))\n"
" |> keyboard.inline_text(\"Button 2\", keyboard.pack_callback(callback_data, \"btn2\"))\n"
" |> keyboard.inline_next_row()\n"
" |> keyboard.inline_url(\"Visit\", \"https://example.com\")\n"
" |> keyboard.inline_build()\n"
" ```\n"
).
-spec inline_builder() -> inline_keyboard_builder().
inline_builder() ->
{inline_keyboard_builder, [], []}.
-file("src/telega/keyboard.gleam", 572).
?DOC(" Create a new keyboard button\n").
-spec button(binary()) -> telega@model@types:keyboard_button().
button(Text) ->
{keyboard_button,
Text,
none,
none,
none,
none,
none,
none,
none,
none,
none}.
-file("src/telega/keyboard.gleam", 433).
?DOC(" Add a text button to the current row of the keyboard builder.\n").
-spec text(keyboard_builder(), binary()) -> keyboard_builder().
text(Builder, Text) ->
Button = button(Text),
{keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 600).
?DOC(
" Create a button that requests the user's contact information.\n"
" When pressed, the user will be prompted to share their phone number.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let contact_btn = keyboard.contact_button(\"📞 Share Contact\")\n"
" let keyboard = keyboard.single(contact_btn)\n"
" ```\n"
).
-spec contact_button(binary()) -> telega@model@types:keyboard_button().
contact_button(Text) ->
_record = button(Text),
{keyboard_button,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
{some, true},
erlang:element(9, _record),
erlang:element(10, _record),
erlang:element(11, _record)}.
-file("src/telega/keyboard.gleam", 439).
?DOC(" Add a contact request button to the current row of the keyboard builder.\n").
-spec contact(keyboard_builder(), binary()) -> keyboard_builder().
contact(Builder, Text) ->
Button = contact_button(Text),
{keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 612).
?DOC(
" Create a button that requests the user's location.\n"
" When pressed, the user will be prompted to share their current location.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let location_btn = keyboard.location_button(\"📍 Share Location\")\n"
" let keyboard = keyboard.single(location_btn)\n"
" ```\n"
).
-spec location_button(binary()) -> telega@model@types:keyboard_button().
location_button(Text) ->
_record = button(Text),
{keyboard_button,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
{some, true},
erlang:element(10, _record),
erlang:element(11, _record)}.
-file("src/telega/keyboard.gleam", 445).
?DOC(" Add a location request button to the current row of the keyboard builder.\n").
-spec location(keyboard_builder(), binary()) -> keyboard_builder().
location(Builder, Text) ->
Button = location_button(Text),
{keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 588).
?DOC(" Create a new web app button\n").
-spec web_app_button(binary(), binary()) -> telega@model@types:keyboard_button().
web_app_button(Text, Url) ->
_record = button(Text),
{keyboard_button,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
erlang:element(9, _record),
erlang:element(10, _record),
{some, {web_app_info, Url}}}.
-file("src/telega/keyboard.gleam", 451).
?DOC(" Add a web app button to the current row of the keyboard builder.\n").
-spec web_app(keyboard_builder(), binary(), binary()) -> keyboard_builder().
web_app(Builder, Text, Url) ->
Button = web_app_button(Text, Url),
{keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 461).
?DOC(" Start a new row in the keyboard builder.\n").
-spec next_row(keyboard_builder()) -> keyboard_builder().
next_row(Builder) ->
case erlang:element(3, Builder) of
[] ->
Builder;
_ ->
New_row = lists:reverse(erlang:element(3, Builder)),
{keyboard_builder, [New_row | erlang:element(2, Builder)], []}
end.
-file("src/telega/keyboard.gleam", 472).
?DOC(" Build the final keyboard from the builder.\n").
-spec build(keyboard_builder()) -> keyboard().
build(Builder) ->
Final_builder = case erlang:element(3, Builder) of
[] ->
Builder;
_ ->
next_row(Builder)
end,
All_rows = lists:reverse(erlang:element(2, Final_builder)),
new(All_rows).
-file("src/telega/keyboard.gleam", 945).
?DOC(" Validate callback data according to Telegram API limits\n").
-spec validate_callback_data(binary()) -> {ok, nil} | {error, binary()}.
validate_callback_data(Data) ->
Byte_length = erlang:byte_size(Data),
case Byte_length > 64 of
true ->
{error,
<<<<"Callback data must be 1-64 bytes long, got "/utf8,
(erlang:integer_to_binary(Byte_length))/binary>>/binary,
" bytes"/utf8>>};
false ->
{ok, nil}
end.
-file("src/telega/keyboard.gleam", 738).
?DOC(" Create a new inline button with callback data\n").
-spec inline_button(binary(), keyboard_callback(any())) -> {ok,
telega@model@types:inline_keyboard_button()} |
{error, binary()}.
inline_button(Text, Callback_data) ->
case validate_callback_data(erlang:element(4, Callback_data)) of
{ok, _} ->
{ok,
{inline_keyboard_button,
Text,
none,
none,
none,
{some, erlang:element(4, Callback_data)},
none,
none,
none,
none,
none,
none,
none,
none}};
{error, Msg} ->
{error, Msg}
end.
-file("src/telega/keyboard.gleam", 482).
?DOC(" Add an inline text button with callback data to the current row.\n").
-spec inline_text(inline_keyboard_builder(), binary(), keyboard_callback(any())) -> {ok,
inline_keyboard_builder()} |
{error, binary()}.
inline_text(Builder, Text, Callback) ->
case inline_button(Text, Callback) of
{ok, Button} ->
{ok,
{inline_keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}};
{error, Msg} ->
{error, Msg}
end.
-file("src/telega/keyboard.gleam", 788).
?DOC(" Create an inline URL button\n").
-spec inline_url_button(binary(), binary()) -> telega@model@types:inline_keyboard_button().
inline_url_button(Text, Url) ->
{inline_keyboard_button,
Text,
none,
none,
{some, Url},
none,
none,
none,
none,
none,
none,
none,
none,
none}.
-file("src/telega/keyboard.gleam", 500).
?DOC(" Add an inline URL button to the current row.\n").
-spec inline_url(inline_keyboard_builder(), binary(), binary()) -> inline_keyboard_builder().
inline_url(Builder, Text, Url) ->
Button = inline_url_button(Text, Url),
{inline_keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 810).
?DOC(" Create an inline web app button\n").
-spec inline_web_app_button(binary(), binary()) -> telega@model@types:inline_keyboard_button().
inline_web_app_button(Text, Url) ->
{inline_keyboard_button,
Text,
none,
none,
none,
none,
{some, {web_app_info, Url}},
none,
none,
none,
none,
none,
none,
none}.
-file("src/telega/keyboard.gleam", 510).
?DOC(" Add an inline web app button to the current row.\n").
-spec inline_web_app(inline_keyboard_builder(), binary(), binary()) -> inline_keyboard_builder().
inline_web_app(Builder, Text, Url) ->
Button = inline_web_app_button(Text, Url),
{inline_keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 832).
?DOC(" Create an inline switch query button\n").
-spec inline_switch_query_button(binary(), binary()) -> telega@model@types:inline_keyboard_button().
inline_switch_query_button(Text, Query) ->
{inline_keyboard_button,
Text,
none,
none,
none,
none,
none,
none,
{some, Query},
none,
none,
none,
none,
none}.
-file("src/telega/keyboard.gleam", 520).
?DOC(" Add an inline switch query button to the current row.\n").
-spec inline_switch_query(inline_keyboard_builder(), binary(), binary()) -> inline_keyboard_builder().
inline_switch_query(Builder, Text, Query) ->
Button = inline_switch_query_button(Text, Query),
{inline_keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 876).
?DOC(" Create an inline copy text button\n").
-spec inline_copy_text_button(binary(), binary()) -> telega@model@types:inline_keyboard_button().
inline_copy_text_button(Text, Copy_text) ->
{inline_keyboard_button,
Text,
none,
none,
none,
none,
none,
none,
none,
none,
none,
{some, {copy_text_button, Copy_text}},
none,
none}.
-file("src/telega/keyboard.gleam", 530).
?DOC(" Add an inline copy text button to the current row.\n").
-spec inline_copy_text(inline_keyboard_builder(), binary(), binary()) -> inline_keyboard_builder().
inline_copy_text(Builder, Text, Copy_text) ->
Button = inline_copy_text_button(Text, Copy_text),
{inline_keyboard_builder,
erlang:element(2, Builder),
[Button | erlang:element(3, Builder)]}.
-file("src/telega/keyboard.gleam", 540).
?DOC(" Start a new row in the inline keyboard builder.\n").
-spec inline_next_row(inline_keyboard_builder()) -> inline_keyboard_builder().
inline_next_row(Builder) ->
case erlang:element(3, Builder) of
[] ->
Builder;
_ ->
New_row = lists:reverse(erlang:element(3, Builder)),
{inline_keyboard_builder,
[New_row | erlang:element(2, Builder)],
[]}
end.
-file("src/telega/keyboard.gleam", 553).
?DOC(" Build the final inline keyboard from the builder.\n").
-spec inline_build(inline_keyboard_builder()) -> inline_keyboard().
inline_build(Builder) ->
Final_builder = case erlang:element(3, Builder) of
[] ->
Builder;
_ ->
inline_next_row(Builder)
end,
All_rows = lists:reverse(erlang:element(2, Final_builder)),
new_inline(All_rows).
-file("src/telega/keyboard.gleam", 563).
?DOC(" Build a reply markup for `Message` from an inline keyboard\n").
-spec inline_to_markup(inline_keyboard()) -> telega@model@types:send_message_reply_markup_parameters().
inline_to_markup(Keyboard) ->
{send_message_reply_inline_keyboard_markup_parameters,
{inline_keyboard_markup, erlang:element(2, Keyboard)}}.
-file("src/telega/keyboard.gleam", 617).
?DOC(" Create a button that requests poll creation\n").
-spec poll_button(binary(), gleam@option:option(binary())) -> telega@model@types:keyboard_button().
poll_button(Text, Poll_type) ->
_record = button(Text),
{keyboard_button,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
erlang:element(9, _record),
{some, {keyboard_button_poll_type, Poll_type}},
erlang:element(11, _record)}.
-file("src/telega/keyboard.gleam", 628).
?DOC(" Create a button that requests users to be shared\n").
-spec users_button(
binary(),
integer(),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(integer()),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(boolean())
) -> telega@model@types:keyboard_button().
users_button(
Text,
Request_id,
User_is_bot,
User_is_premium,
Max_quantity,
Request_name,
Request_username,
Request_photo
) ->
_record = button(Text),
{keyboard_button,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
{some,
{keyboard_button_request_users,
Request_id,
User_is_bot,
User_is_premium,
Max_quantity,
Request_name,
Request_username,
Request_photo}},
erlang:element(6, _record),
erlang:element(7, _record),
erlang:element(8, _record),
erlang:element(9, _record),
erlang:element(10, _record),
erlang:element(11, _record)}.
-file("src/telega/keyboard.gleam", 653).
?DOC(" Create a button that requests a chat to be shared\n").
-spec chat_button(
binary(),
integer(),
boolean(),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(telega@model@types:chat_administrator_rights()),
gleam@option:option(telega@model@types:chat_administrator_rights()),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(boolean())
) -> telega@model@types:keyboard_button().
chat_button(
Text,
Request_id,
Chat_is_channel,
Chat_is_forum,
Chat_has_username,
Chat_is_created,
User_administrator_rights,
Bot_administrator_rights,
Bot_is_member,
Request_title,
Request_username,
Request_photo
) ->
_record = button(Text),
{keyboard_button,
erlang:element(2, _record),
erlang:element(3, _record),
erlang:element(4, _record),
erlang:element(5, _record),
{some,
{keyboard_button_request_chat,
Request_id,
Chat_is_channel,
Chat_is_forum,
Chat_has_username,
Chat_is_created,
User_administrator_rights,
Bot_administrator_rights,
Bot_is_member,
Request_title,
Request_username,
Request_photo}},
erlang:element(7, _record),
erlang:element(8, _record),
erlang:element(9, _record),
erlang:element(10, _record),
erlang:element(11, _record)}.
-file("src/telega/keyboard.gleam", 729).
?DOC(" Build a reply markup for `Message` from an inline keyboard\n").
-spec to_inline_markup(inline_keyboard()) -> telega@model@types:send_message_reply_markup_parameters().
to_inline_markup(Keyboard) ->
{send_message_reply_inline_keyboard_markup_parameters,
{inline_keyboard_markup, erlang:element(2, Keyboard)}}.
-file("src/telega/keyboard.gleam", 766).
?DOC(
" Create a new inline button with raw string callback data.\n"
" Unlike `inline_button`, no typed `KeyboardCallback` packing or validation\n"
" is performed — the string is used as-is.\n"
).
-spec inline_raw_callback_button(binary(), binary()) -> telega@model@types:inline_keyboard_button().
inline_raw_callback_button(Text, Callback_data) ->
{inline_keyboard_button,
Text,
none,
none,
none,
{some, Callback_data},
none,
none,
none,
none,
none,
none,
none,
none}.
-file("src/telega/keyboard.gleam", 854).
?DOC(" Create an inline switch query current chat button\n").
-spec inline_switch_query_current_chat_button(binary(), binary()) -> telega@model@types:inline_keyboard_button().
inline_switch_query_current_chat_button(Text, Query) ->
{inline_keyboard_button,
Text,
none,
none,
none,
none,
none,
none,
none,
{some, Query},
none,
none,
none,
none}.
-file("src/telega/keyboard.gleam", 922).
?DOC(
" Create a filter for inline keyboard callback queries.\n"
" This filter can be used with `telega.wait_callback_query()` to only listen\n"
" for callbacks from buttons in this specific keyboard.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let callback_data = keyboard.string_callback_data(\"action\")\n"
" let assert Ok(button1) = keyboard.inline_button(\"Yes\",\n"
" keyboard.pack_callback(callback_data, \"yes\"))\n"
" let assert Ok(button2) = keyboard.inline_button(\"No\",\n"
" keyboard.pack_callback(callback_data, \"no\"))\n"
"\n"
" let keyboard = keyboard.new_inline([[button1, button2]])\n"
" let assert Ok(filter) = keyboard.filter_inline_keyboard_query(keyboard)\n"
"\n"
" // In your handler:\n"
" use ctx, payload, query_id <- telega.wait_callback_query(\n"
" ctx:, filter:, or: None, timeout: None\n"
" )\n"
" let assert Ok(callback) = keyboard.unpack_callback(payload, callback_data)\n"
" ```\n"
"\n"
" ## Errors\n"
" - Returns `Error` if the keyboard has no callback buttons\n"
" - Returns `Error` if the callback data creates an invalid regex pattern\n"
).
-spec filter_inline_keyboard_query(inline_keyboard()) -> {ok,
telega@bot:callback_query_filter()} |
{error, binary()}.
filter_inline_keyboard_query(Keyboard) ->
Options = begin
_pipe = erlang:element(2, Keyboard),
_pipe@1 = gleam@list:flat_map(
_pipe,
fun(Row) ->
gleam@list:map(
Row,
fun(Button) -> erlang:element(6, Button) end
)
end
),
_pipe@2 = gleam@option:values(_pipe@1),
gleam@string:join(_pipe@2, <<"|"/utf8>>)
end,
case Options of
<<""/utf8>> ->
{error, <<"No callback data found in keyboard"/utf8>>};
_ ->
case gleam@regexp:from_string(
<<<<"^("/utf8, Options/binary>>/binary, ")$"/utf8>>
) of
{ok, Re} ->
{ok, {callback_query_filter, Re}};
{error, _} ->
{error, <<"Invalid regex pattern from callback data"/utf8>>}
end
end.
-file("src/telega/keyboard.gleam", 1013).
?DOC(
" Create a new callback data configuration for inline keyboard buttons.\n"
" This defines how data is serialized/deserialized when buttons are pressed.\n"
"\n"
" ## Parameters\n"
" - `id`: Unique identifier for this callback type\n"
" - `serialize`: Function to convert your data to a string\n"
" - `deserialize`: Function to convert string back to your data\n"
"\n"
" ## Example\n"
" ```gleam\n"
" // For custom enum types\n"
" pub type Action {\n"
" Save\n"
" Delete\n"
" Edit\n"
" }\n"
"\n"
" let action_callback = keyboard.new_callback_data(\n"
" id: \"action\",\n"
" serialize: fn(action) {\n"
" case action {\n"
" Save -> \"save\"\n"
" Delete -> \"delete\"\n"
" Edit -> \"edit\"\n"
" }\n"
" },\n"
" deserialize: fn(str) {\n"
" case str {\n"
" \"save\" -> Save\n"
" \"delete\" -> Delete\n"
" _ -> Edit\n"
" }\n"
" }\n"
" )\n"
" ```\n"
).
-spec new_callback_data(
binary(),
fun((AVMN) -> binary()),
fun((binary()) -> AVMN)
) -> keyboard_callback_data(AVMN).
new_callback_data(Id, Serialize, Deserialize) ->
{keyboard_callback_data, Id, Serialize, Deserialize, <<":"/utf8>>}.
-file("src/telega/keyboard.gleam", 1022).
?DOC(" Change the delimiter for the callback data, useful if you need to use `:` in the id\n").
-spec set_callback_data_delimiter(keyboard_callback_data(AVMP), binary()) -> keyboard_callback_data(AVMP).
set_callback_data_delimiter(Data, Delimiter) ->
{keyboard_callback_data,
erlang:element(2, Data),
erlang:element(3, Data),
erlang:element(4, Data),
Delimiter}.
-file("src/telega/keyboard.gleam", 1030).
?DOC(" Pack callback data into a callback\n").
-spec pack_callback(keyboard_callback_data(AVMS), AVMS) -> keyboard_callback(AVMS).
pack_callback(Callback_data, Data) ->
Payload = <<<<(erlang:element(2, Callback_data))/binary,
(erlang:element(5, Callback_data))/binary>>/binary,
((erlang:element(3, Callback_data))(Data))/binary>>,
{keyboard_callback,
Data,
erlang:element(2, Callback_data),
Payload,
Callback_data}.
-file("src/telega/keyboard.gleam", 1041).
?DOC(" Unpack payload into a callback\n").
-spec unpack_callback(binary(), keyboard_callback_data(AVMV)) -> {ok,
keyboard_callback(AVMV)} |
{error, nil}.
unpack_callback(Payload, Callback_data) ->
gleam@result:'try'(
gleam@string:split_once(Payload, erlang:element(5, Callback_data)),
fun(_use0) ->
{Id, Data} = _use0,
{ok,
{keyboard_callback,
(erlang:element(4, Callback_data))(Data),
Id,
Payload,
Callback_data}}
end
).
-file("src/telega/keyboard.gleam", 1076).
?DOC(
" Create a simple string callback data configuration.\n"
" This is the most common type for simple string-based callbacks.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let callback_data = keyboard.string_callback_data(\"action\")\n"
" let callback = keyboard.pack_callback(callback_data, \"delete_user\")\n"
" let assert Ok(button) = keyboard.inline_button(\"Delete\", callback)\n"
"\n"
" // In your callback handler:\n"
" let assert Ok(unpacked) = keyboard.unpack_callback(payload, callback_data)\n"
" case unpacked.data {\n"
" \"delete_user\" -> // handle deletion\n"
" _ -> // handle other actions\n"
" }\n"
" ```\n"
).
-spec string_callback_data(binary()) -> keyboard_callback_data(binary()).
string_callback_data(Id) ->
new_callback_data(
Id,
fun gleam@function:identity/1,
fun gleam@function:identity/1
).
-file("src/telega/keyboard.gleam", 1097).
?DOC(
" Create an integer callback data configuration.\n"
" Useful for pagination, user IDs, or any numeric data.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let page_callback = keyboard.int_callback_data(\"page\")\n"
" let next_page = keyboard.pack_callback(page_callback, 2)\n"
" let assert Ok(button) = keyboard.inline_button(\"Next ->\", next_page)\n"
"\n"
" // In your callback handler:\n"
" let assert Ok(unpacked) = keyboard.unpack_callback(payload, page_callback)\n"
" let page_number = unpacked.data // Int\n"
" ```\n"
).
-spec int_callback_data(binary()) -> keyboard_callback_data(integer()).
int_callback_data(Id) ->
new_callback_data(
Id,
fun erlang:integer_to_binary/1,
fun(Data) -> case gleam_stdlib:parse_int(Data) of
{ok, I} ->
I;
{error, _} ->
0
end end
).
-file("src/telega/keyboard.gleam", 1140).
-spec deserialize_bool(binary()) -> boolean().
deserialize_bool(Data) ->
Data =:= <<"true"/utf8>>.
-file("src/telega/keyboard.gleam", 1133).
-spec serialize_bool(boolean()) -> binary().
serialize_bool(Data) ->
case Data of
true ->
<<"true"/utf8>>;
false ->
<<"false"/utf8>>
end.
-file("src/telega/keyboard.gleam", 1125).
?DOC(
" Create a boolean callback data configuration.\n"
" Useful for toggle buttons, settings, or yes/no choices.\n"
"\n"
" ## Example\n"
" ```gleam\n"
" let toggle_callback = keyboard.bool_callback_data(\"notifications\")\n"
" let enable_btn = keyboard.pack_callback(toggle_callback, True)\n"
" let disable_btn = keyboard.pack_callback(toggle_callback, False)\n"
"\n"
" let assert Ok(enable) = keyboard.inline_button(\"🔔 Enable\", enable_btn)\n"
" let assert Ok(disable) = keyboard.inline_button(\"🔕 Disable\", disable_btn)\n"
"\n"
" // In your callback handler:\n"
" let assert Ok(unpacked) = keyboard.unpack_callback(payload, toggle_callback)\n"
" case unpacked.data {\n"
" True -> // enable notifications\n"
" False -> // disable notifications\n"
" }\n"
" ```\n"
).
-spec bool_callback_data(binary()) -> keyboard_callback_data(boolean()).
bool_callback_data(Id) ->
new_callback_data(Id, fun serialize_bool/1, fun deserialize_bool/1).