Current section
Files
Jump to
Current section
Files
src/telega@media_group.erl
-module(telega@media_group).
-compile([no_auto_import, nowarn_unused_vars, nowarn_unused_function, nowarn_nomatch, inline]).
-define(FILEPATH, "src/telega/media_group.gleam").
-export([new/0, add_photo/3, add_video/3, add_audio/3, add_document/3, add_animation/3, build/1, validate_and_build/1, default_photo_options/0, default_video_options/0, default_audio_options/0, default_document_options/0, default_animation_options/0, photo_with_caption/2, video_with_caption/2, add_photo_file/3, add_photo_url/3, add_photo_with_caption/3, add_photo_url_with_caption/3, add_video_file/3, add_video_url/3, add_video_with_caption/3, add_video_url_with_caption/3, add_document_file/3, add_document_with_caption/3, add_audio_with_caption/3, requires_multipart/1, from_photo_urls/1, from_photos_with_captions/1, from_video_urls/1, count/1, is_empty/1, first/1, last/1]).
-export_type([media_group_builder/0, media_group_error/0, photo_options/0, video_options/0, audio_options/0, document_options/0, animation_options/0]).
-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(
" Media Group Builder module for creating and sending groups of photos, videos, documents, and audio files.\n"
"\n"
" This module provides a fluent builder API for constructing media groups (albums) that can be sent\n"
" via the Telegram Bot API. Media groups allow you to send multiple media files as a single message,\n"
" appearing as an album in the Telegram client.\n"
"\n"
" ## Features\n"
"\n"
" - **Builder Pattern**: Chain method calls to construct complex media groups\n"
" - **Type Safety**: Validates media compatibility at compile time\n"
" - **Flexible Input**: Supports URLs, file IDs, local files, and raw bytes\n"
" - **Rich Formatting**: Add captions with HTML/Markdown formatting\n"
" - **Validation**: Ensures media groups meet Telegram's requirements\n"
"\n"
" ## Telegram API Constraints\n"
"\n"
" - Media groups must contain 2-10 items\n"
" - Documents and audio files can only be grouped with media of the same type\n"
" - Photos and videos can be mixed in the same group\n"
" - Only the first media item's caption is prominently displayed\n"
"\n"
" ## Examples\n"
"\n"
" ### Simple Photo Album\n"
" ```gleam\n"
" let album = media_group.new()\n"
" |> media_group.add_photo_url(\"https://example.com/photo1.jpg\", None)\n"
" |> media_group.add_photo_url_with_caption(\n"
" \"https://example.com/photo2.jpg\",\n"
" \"Beautiful sunset\"\n"
" )\n"
" |> media_group.validate_and_build()\n"
" ```\n"
"\n"
" ### Mixed Media (Photos + Videos)\n"
" ```gleam\n"
" let mixed = media_group.new()\n"
" |> media_group.add_photo_url(\"https://example.com/photo.jpg\", None)\n"
" |> media_group.add_video_url_with_caption(\n"
" \"https://example.com/video.mp4\",\n"
" \"Amazing video\"\n"
" )\n"
" |> media_group.validate_and_build()\n"
" ```\n"
"\n"
" ### Document Group\n"
" ```gleam\n"
" let docs = media_group.new()\n"
" |> media_group.add_document(file.Url(\"https://example.com/doc1.pdf\"), None)\n"
" |> media_group.add_document_with_caption(\n"
" file.Url(\"https://example.com/doc2.pdf\"),\n"
" \"Important document\"\n"
" )\n"
" |> media_group.validate_and_build()\n"
" ```\n"
"\n"
" ## Error Handling\n"
"\n"
" The `validate_and_build` function returns a `Result` with possible errors:\n"
" - `InvalidMediaCount`: Less than 2 or more than 10 items\n"
" - `IncompatibleMediaTypes`: Mixing incompatible media types\n"
" - `EmptyMediaGroup`: No media items added\n"
"\n"
" ## See Also\n"
"\n"
" - [Telegram Bot API - sendMediaGroup](https://core.telegram.org/bots/api#sendmediagroup)\n"
" - [InputMedia types](https://core.telegram.org/bots/api#inputmedia)\n"
).
-type media_group_builder() :: {media_group_builder,
list(telega@model@types:input_media())}.
-type media_group_error() :: {invalid_media_count, integer()} |
incompatible_media_types |
empty_media_group.
-type photo_options() :: {photo_options,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(list(telega@model@types:message_entity())),
gleam@option:option(boolean()),
gleam@option:option(boolean())}.
-type video_options() :: {video_options,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(list(telega@model@types:message_entity())),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(binary()),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(boolean())}.
-type audio_options() :: {audio_options,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(list(telega@model@types:message_entity())),
gleam@option:option(integer()),
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(binary())}.
-type document_options() :: {document_options,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(list(telega@model@types:message_entity())),
gleam@option:option(boolean()),
gleam@option:option(binary())}.
-type animation_options() :: {animation_options,
gleam@option:option(binary()),
gleam@option:option(binary()),
gleam@option:option(list(telega@model@types:message_entity())),
gleam@option:option(boolean()),
gleam@option:option(boolean()),
gleam@option:option(binary()),
gleam@option:option(integer()),
gleam@option:option(integer()),
gleam@option:option(integer())}.
-file("src/telega/media_group.gleam", 167).
?DOC(
" Creates a new empty media group builder.\n"
"\n"
" This is the starting point for building a media group. Chain other methods\n"
" to add media items, then call `validate_and_build()` to get the final result.\n"
).
-spec new() -> media_group_builder().
new() ->
{media_group_builder, []}.
-file("src/telega/media_group.gleam", 190).
?DOC(
" Adds a photo to the media group with optional formatting options.\n"
"\n"
" ## Parameters\n"
" - `media`: The photo to add (URL, file ID, local file, or bytes)\n"
" - `options`: Optional formatting settings (caption, parse mode, spoiler, etc.)\n"
"\n"
" ## Example\n"
" ```gleam\n"
" media_group.new()\n"
" |> media_group.add_photo(\n"
" file.Url(\"https://example.com/photo.jpg\"),\n"
" Some(PhotoOptions(\n"
" caption: Some(\"Beautiful landscape\"),\n"
" parse_mode: Some(\"HTML\"),\n"
" has_spoiler: Some(False),\n"
" ..default_photo_options()\n"
" ))\n"
" )\n"
" ```\n"
).
-spec add_photo(
media_group_builder(),
telega@file:media_input(),
gleam@option:option(photo_options())
) -> media_group_builder().
add_photo(Builder, Media, Options) ->
Photo = case Options of
none ->
{input_media_photo,
<<"photo"/utf8>>,
telega@file:to_json_value(Media),
none,
none,
none,
none,
none};
{some, Opts} ->
{input_media_photo,
<<"photo"/utf8>>,
telega@file:to_json_value(Media),
erlang:element(2, Opts),
erlang:element(3, Opts),
erlang:element(4, Opts),
erlang:element(5, Opts),
erlang:element(6, Opts)}
end,
{media_group_builder,
lists:append(
erlang:element(2, Builder),
[{input_media_photo_input_media, Photo}]
)}.
-file("src/telega/media_group.gleam", 232).
?DOC(
" Adds a video to the media group with optional formatting options.\n"
"\n"
" Videos can be mixed with photos in the same media group.\n"
"\n"
" ## Parameters\n"
" - `media`: The video to add (URL, file ID, local file, or bytes)\n"
" - `options`: Optional video settings (caption, dimensions, duration, etc.)\n"
).
-spec add_video(
media_group_builder(),
telega@file:media_input(),
gleam@option:option(video_options())
) -> media_group_builder().
add_video(Builder, Media, Options) ->
Video = case Options of
none ->
{input_media_video,
<<"video"/utf8>>,
telega@file:to_json_value(Media),
none,
none,
none,
none,
none,
none,
none,
none,
none,
none,
none,
none};
{some, Opts} ->
{input_media_video,
<<"video"/utf8>>,
telega@file:to_json_value(Media),
erlang:element(7, Opts),
none,
none,
erlang:element(2, Opts),
erlang:element(3, Opts),
erlang:element(4, Opts),
erlang:element(5, Opts),
erlang:element(8, Opts),
erlang:element(9, Opts),
erlang:element(10, Opts),
erlang:element(11, Opts),
erlang:element(6, Opts)}
end,
{media_group_builder,
lists:append(
erlang:element(2, Builder),
[{input_media_video_input_media, Video}]
)}.
-file("src/telega/media_group.gleam", 282).
?DOC(" Adds an audio file to the media group\n").
-spec add_audio(
media_group_builder(),
telega@file:media_input(),
gleam@option:option(audio_options())
) -> media_group_builder().
add_audio(Builder, Media, Options) ->
Audio = case Options of
none ->
{input_media_audio,
<<"audio"/utf8>>,
telega@file:to_json_value(Media),
none,
none,
none,
none,
none,
none,
none};
{some, Opts} ->
{input_media_audio,
<<"audio"/utf8>>,
telega@file:to_json_value(Media),
erlang:element(8, Opts),
erlang:element(2, Opts),
erlang:element(3, Opts),
erlang:element(4, Opts),
erlang:element(5, Opts),
erlang:element(6, Opts),
erlang:element(7, Opts)}
end,
{media_group_builder,
lists:append(
erlang:element(2, Builder),
[{input_media_audio_input_media, Audio}]
)}.
-file("src/telega/media_group.gleam", 322).
?DOC(" Adds a document to the media group\n").
-spec add_document(
media_group_builder(),
telega@file:media_input(),
gleam@option:option(document_options())
) -> media_group_builder().
add_document(Builder, Media, Options) ->
Document = case Options of
none ->
{input_media_document,
<<"document"/utf8>>,
telega@file:to_json_value(Media),
none,
none,
none,
none,
none};
{some, Opts} ->
{input_media_document,
<<"document"/utf8>>,
telega@file:to_json_value(Media),
erlang:element(6, Opts),
erlang:element(2, Opts),
erlang:element(3, Opts),
erlang:element(4, Opts),
erlang:element(5, Opts)}
end,
{media_group_builder,
lists:append(
erlang:element(2, Builder),
[{input_media_document_input_media, Document}]
)}.
-file("src/telega/media_group.gleam", 358).
?DOC(" Adds an animation to the media group\n").
-spec add_animation(
media_group_builder(),
telega@file:media_input(),
gleam@option:option(animation_options())
) -> media_group_builder().
add_animation(Builder, Media, Options) ->
Animation = case Options of
none ->
{input_media_animation,
<<"animation"/utf8>>,
telega@file:to_json_value(Media),
none,
none,
none,
none,
none,
none,
none,
none,
none};
{some, Opts} ->
{input_media_animation,
<<"animation"/utf8>>,
telega@file:to_json_value(Media),
erlang:element(7, Opts),
erlang:element(2, Opts),
erlang:element(3, Opts),
erlang:element(4, Opts),
erlang:element(5, Opts),
erlang:element(8, Opts),
erlang:element(9, Opts),
erlang:element(10, Opts),
erlang:element(6, Opts)}
end,
{media_group_builder,
lists:append(
erlang:element(2, Builder),
[{input_media_animation_input_media, Animation}]
)}.
-file("src/telega/media_group.gleam", 402).
?DOC(" Builds the media group and returns the list of InputMedia\n").
-spec build(media_group_builder()) -> list(telega@model@types:input_media()).
build(Builder) ->
erlang:element(2, Builder).
-file("src/telega/media_group.gleam", 448).
?DOC(" Gets the type of an InputMedia item\n").
-spec get_media_type(telega@model@types:input_media()) -> binary().
get_media_type(Media) ->
case Media of
{input_media_photo_input_media, _} ->
<<"photo"/utf8>>;
{input_media_video_input_media, _} ->
<<"video"/utf8>>;
{input_media_audio_input_media, _} ->
<<"audio"/utf8>>;
{input_media_document_input_media, _} ->
<<"document"/utf8>>;
{input_media_animation_input_media, _} ->
<<"animation"/utf8>>;
{input_media_live_photo_input_media, _} ->
<<"live_photo"/utf8>>;
{input_media_location_input_media, _} ->
<<"location"/utf8>>;
{input_media_sticker_input_media, _} ->
<<"sticker"/utf8>>;
{input_media_venue_input_media, _} ->
<<"venue"/utf8>>
end.
-file("src/telega/media_group.gleam", 433).
?DOC(
" Checks if media types in the group are compatible\n"
" Photos and videos can be mixed, but audio and documents must be grouped separately\n"
).
-spec is_compatible_media_types(list(telega@model@types:input_media())) -> boolean().
is_compatible_media_types(Media) ->
Types = gleam@list:map(Media, fun get_media_type/1),
Unique_types = gleam@list:unique(Types),
case Unique_types of
[_] ->
true;
[<<"photo"/utf8>>, <<"video"/utf8>>] ->
true;
[<<"video"/utf8>>, <<"photo"/utf8>>] ->
true;
_ ->
false
end.
-file("src/telega/media_group.gleam", 412).
?DOC(
" Validates and builds the final media group.\n"
"\n"
" This function checks that the media group meets Telegram's requirements:\n"
" - Contains 2-10 items\n"
" - Uses compatible media types\n"
" - Is not empty\n"
).
-spec validate_and_build(media_group_builder()) -> {ok,
list(telega@model@types:input_media())} |
{error, media_group_error()}.
validate_and_build(Builder) ->
Count = erlang:length(erlang:element(2, Builder)),
case Count of
0 ->
{error, empty_media_group};
1 ->
{error, {invalid_media_count, Count}};
N when N > 10 ->
{error, {invalid_media_count, Count}};
_ ->
case is_compatible_media_types(erlang:element(2, Builder)) of
true ->
{ok, erlang:element(2, Builder)};
false ->
{error, incompatible_media_types}
end
end.
-file("src/telega/media_group.gleam", 463).
?DOC(" Helper function to create default photo options\n").
-spec default_photo_options() -> photo_options().
default_photo_options() ->
{photo_options, none, none, none, none, none}.
-file("src/telega/media_group.gleam", 474).
?DOC(" Helper function to create default video options\n").
-spec default_video_options() -> video_options().
default_video_options() ->
{video_options, none, none, none, none, none, none, none, none, none, none}.
-file("src/telega/media_group.gleam", 490).
?DOC(" Helper function to create default audio options\n").
-spec default_audio_options() -> audio_options().
default_audio_options() ->
{audio_options, none, none, none, none, none, none, none}.
-file("src/telega/media_group.gleam", 503).
?DOC(" Helper function to create default document options\n").
-spec default_document_options() -> document_options().
default_document_options() ->
{document_options, none, none, none, none, none}.
-file("src/telega/media_group.gleam", 514).
?DOC(" Helper function to create default animation options\n").
-spec default_animation_options() -> animation_options().
default_animation_options() ->
{animation_options, none, none, none, none, none, none, none, none, none}.
-file("src/telega/media_group.gleam", 532).
?DOC(
" Creates a standalone photo InputMedia with a caption.\n"
"\n"
" Useful when you need to create a single photo for direct use\n"
" without the builder pattern.\n"
).
-spec photo_with_caption(telega@file:media_input(), binary()) -> telega@model@types:input_media().
photo_with_caption(Media, Caption) ->
{input_media_photo_input_media,
{input_media_photo,
<<"photo"/utf8>>,
telega@file:to_json_value(Media),
{some, Caption},
none,
none,
none,
none}}.
-file("src/telega/media_group.gleam", 545).
?DOC(" Creates a video with caption\n").
-spec video_with_caption(telega@file:media_input(), binary()) -> telega@model@types:input_media().
video_with_caption(Media, Caption) ->
{input_media_video_input_media,
{input_media_video,
<<"video"/utf8>>,
telega@file:to_json_value(Media),
none,
none,
none,
{some, Caption},
none,
none,
none,
none,
none,
none,
none,
none}}.
-file("src/telega/media_group.gleam", 565).
?DOC(" Add a photo from a file path\n").
-spec add_photo_file(
media_group_builder(),
binary(),
gleam@option:option(photo_options())
) -> media_group_builder().
add_photo_file(Builder, Path, Options) ->
add_photo(Builder, telega@file:from_file(Path), Options).
-file("src/telega/media_group.gleam", 576).
?DOC(
" Convenience function to add a photo from a URL.\n"
"\n"
" This is a shorthand for `add_photo(builder, file.Url(url), options)`.\n"
).
-spec add_photo_url(
media_group_builder(),
binary(),
gleam@option:option(photo_options())
) -> media_group_builder().
add_photo_url(Builder, Url, Options) ->
add_photo(Builder, {url, Url}, Options).
-file("src/telega/media_group.gleam", 587).
?DOC(
" Convenience function to add a photo with just a caption.\n"
"\n"
" Use this when you only need to set a caption without other formatting options.\n"
).
-spec add_photo_with_caption(
media_group_builder(),
telega@file:media_input(),
binary()
) -> media_group_builder().
add_photo_with_caption(Builder, Media, Caption) ->
add_photo(
Builder,
Media,
{some, {photo_options, {some, Caption}, none, none, none, none}}
).
-file("src/telega/media_group.gleam", 608).
?DOC(
" Convenience function to add a photo from URL with a caption.\n"
"\n"
" Combines URL input and caption setting in a single call.\n"
).
-spec add_photo_url_with_caption(media_group_builder(), binary(), binary()) -> media_group_builder().
add_photo_url_with_caption(Builder, Url, Caption) ->
add_photo_with_caption(Builder, {url, Url}, Caption).
-file("src/telega/media_group.gleam", 617).
?DOC(" Add a video from a file path\n").
-spec add_video_file(
media_group_builder(),
binary(),
gleam@option:option(video_options())
) -> media_group_builder().
add_video_file(Builder, Path, Options) ->
add_video(Builder, telega@file:from_file(Path), Options).
-file("src/telega/media_group.gleam", 626).
?DOC(" Add a video from a URL\n").
-spec add_video_url(
media_group_builder(),
binary(),
gleam@option:option(video_options())
) -> media_group_builder().
add_video_url(Builder, Url, Options) ->
add_video(Builder, {url, Url}, Options).
-file("src/telega/media_group.gleam", 635).
?DOC(" Add a video with caption (convenience function)\n").
-spec add_video_with_caption(
media_group_builder(),
telega@file:media_input(),
binary()
) -> media_group_builder().
add_video_with_caption(Builder, Media, Caption) ->
add_video(
Builder,
Media,
{some,
{video_options,
{some, Caption},
none,
none,
none,
none,
none,
none,
none,
none,
none}}
).
-file("src/telega/media_group.gleam", 659).
?DOC(" Add a video URL with caption (convenience function)\n").
-spec add_video_url_with_caption(media_group_builder(), binary(), binary()) -> media_group_builder().
add_video_url_with_caption(Builder, Url, Caption) ->
add_video_with_caption(Builder, {url, Url}, Caption).
-file("src/telega/media_group.gleam", 668).
?DOC(" Add a document from a file path\n").
-spec add_document_file(
media_group_builder(),
binary(),
gleam@option:option(document_options())
) -> media_group_builder().
add_document_file(Builder, Path, Options) ->
add_document(Builder, telega@file:from_file(Path), Options).
-file("src/telega/media_group.gleam", 677).
?DOC(" Add a document with caption (convenience function)\n").
-spec add_document_with_caption(
media_group_builder(),
telega@file:media_input(),
binary()
) -> media_group_builder().
add_document_with_caption(Builder, Media, Caption) ->
add_document(
Builder,
Media,
{some, {document_options, {some, Caption}, none, none, none, none}}
).
-file("src/telega/media_group.gleam", 696).
?DOC(" Add an audio file with caption (convenience function)\n").
-spec add_audio_with_caption(
media_group_builder(),
telega@file:media_input(),
binary()
) -> media_group_builder().
add_audio_with_caption(Builder, Media, Caption) ->
add_audio(
Builder,
Media,
{some,
{audio_options, {some, Caption}, none, none, none, none, none, none}}
).
-file("src/telega/media_group.gleam", 717).
?DOC(" Checks if any media in the group requires multipart upload\n").
-spec requires_multipart(media_group_builder()) -> boolean().
requires_multipart(Builder) ->
gleam@list:any(erlang:element(2, Builder), fun(Media) -> case Media of
{input_media_photo_input_media, Photo} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Photo))
);
{input_media_video_input_media, Video} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Video))
);
{input_media_audio_input_media, Audio} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Audio))
);
{input_media_document_input_media, Document} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Document))
);
{input_media_animation_input_media, Animation} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Animation))
);
{input_media_live_photo_input_media, Live_photo} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Live_photo))
)
orelse telega@file:requires_multipart(
telega@file:from_string(erlang:element(4, Live_photo))
);
{input_media_sticker_input_media, Sticker} ->
telega@file:requires_multipart(
telega@file:from_string(erlang:element(3, Sticker))
);
{input_media_location_input_media, _} ->
false;
{input_media_venue_input_media, _} ->
false
end end).
-file("src/telega/media_group.gleam", 744).
?DOC(
" Creates a media group from a list of photo URLs.\n"
"\n"
" Convenient helper for quickly creating a photo album from URLs.\n"
).
-spec from_photo_urls(list(binary())) -> media_group_builder().
from_photo_urls(Urls) ->
gleam@list:fold(
Urls,
new(),
fun(Builder, Url) -> add_photo_url(Builder, Url, none) end
).
-file("src/telega/media_group.gleam", 749).
?DOC(" Creates a media group from a list of photo URLs with captions\n").
-spec from_photos_with_captions(list({binary(), binary()})) -> media_group_builder().
from_photos_with_captions(Photos) ->
gleam@list:fold(
Photos,
new(),
fun(Builder, Photo) ->
{Url, Caption} = Photo,
add_photo_url(
Builder,
Url,
{some, {photo_options, {some, Caption}, none, none, none, none}}
)
end
).
-file("src/telega/media_group.gleam", 769).
?DOC(" Creates a media group from a list of video URLs\n").
-spec from_video_urls(list(binary())) -> media_group_builder().
from_video_urls(Urls) ->
gleam@list:fold(
Urls,
new(),
fun(Builder, Url) -> add_video_url(Builder, Url, none) end
).
-file("src/telega/media_group.gleam", 776).
?DOC(
" Gets the count of media items in the builder.\n"
"\n"
" Useful for checking if you're within the 2-10 item limit.\n"
).
-spec count(media_group_builder()) -> integer().
count(Builder) ->
erlang:length(erlang:element(2, Builder)).
-file("src/telega/media_group.gleam", 781).
?DOC(" Checks if the builder is empty\n").
-spec is_empty(media_group_builder()) -> boolean().
is_empty(Builder) ->
gleam@list:is_empty(erlang:element(2, Builder)).
-file("src/telega/media_group.gleam", 786).
?DOC(" Gets the first media item if it exists\n").
-spec first(media_group_builder()) -> gleam@option:option(telega@model@types:input_media()).
first(Builder) ->
_pipe = gleam@list:first(erlang:element(2, Builder)),
gleam@option:from_result(_pipe).
-file("src/telega/media_group.gleam", 791).
?DOC(" Gets the last media item if it exists\n").
-spec last(media_group_builder()) -> gleam@option:option(telega@model@types:input_media()).
last(Builder) ->
_pipe = gleam@list:last(erlang:element(2, Builder)),
gleam@option:from_result(_pipe).