defmodule Nadia.Examples.MediaFiles do @moduledoc """ Explicit helpers for choosing between Telegram file IDs, URLs, and local uploads. `Nadia.InputFile` makes source intent explicit and reports malformed URLs or local path errors before a request is sent. """ alias Nadia.Client alias Nadia.InputFile alias Nadia.InputContactMessageContent alias Nadia.InputInvoiceMessageContent alias Nadia.InputLocationMessageContent alias Nadia.InputMedia alias Nadia.InputPaidMedia alias Nadia.InputPollOption alias Nadia.InputPollMedia alias Nadia.InputProfilePhoto alias Nadia.InputRichMessage alias Nadia.InputRichMessageContent alias Nadia.InputSticker alias Nadia.InputStoryContent alias Nadia.InputTextMessageContent alias Nadia.InputVenueMessageContent alias Nadia.LabeledPrice alias Nadia.Model.InlineQueryResult alias Nadia.StoryArea @type source :: {:file_id, binary} | {:url, binary} | {:path, Path.t()} @spec send_document(Client.t(), integer | binary, source, keyword) :: term def send_document(client, chat_id, source, options \\ []) def send_document(%Client{} = client, chat_id, {:file_id, file_id}, options) when is_binary(file_id) do Nadia.send_document(client, chat_id, InputFile.file_id(file_id), options) end def send_document(%Client{} = client, chat_id, {:url, url}, options) when is_binary(url) do case URI.parse(url) do %URI{scheme: scheme, host: host} when scheme in ["http", "https"] and is_binary(host) -> Nadia.send_document(client, chat_id, InputFile.url(url), options) _ -> {:error, {:invalid_file_url, url}} end end def send_document(%Client{} = client, chat_id, {:path, path}, options) when is_binary(path) do Nadia.send_document(client, chat_id, InputFile.path(path), options) end def send_document(%Client{}, _chat_id, source, _options), do: {:error, {:invalid_file_source, source}} @doc """ Uploads bounded iodata directly without flattening it or copying it to disk. `:max_bytes` defaults to 10 MB and is consumed by this helper rather than sent to Telegram. The application still owns the original in-memory data. """ @spec upload_bytes(Client.t(), integer | binary, iodata, binary, keyword) :: term def upload_bytes(%Client{} = client, chat_id, bytes, filename, options \\ []) when is_binary(filename) do {max_bytes, options} = Keyword.pop(options, :max_bytes, 10_000_000) {content_type, options} = Keyword.pop(options, :content_type) input_file = InputFile.bytes(bytes, filename, max_bytes: max_bytes, content_type: content_type ) Nadia.send_document(client, chat_id, input_file, options) end @doc """ Sends a typed two-item album with a reusable photo and a bounded path upload. """ @spec send_album(Client.t(), integer | binary, binary, Path.t()) :: term def send_album(%Client{} = client, chat_id, photo_file_id, video_path) do media = [ InputMedia.photo(InputFile.file_id(photo_file_id)), InputMedia.video(InputFile.path(video_path, max_bytes: 50_000_000), supports_streaming: true ) ] Nadia.send_media_group(client, chat_id, media) end @doc """ Sends paid media with a reusable photo and a bounded video upload. """ @spec send_paid_media(Client.t(), integer | binary, binary, Path.t()) :: term def send_paid_media(%Client{} = client, chat_id, photo_file_id, video_path) do media = [ InputPaidMedia.photo(InputFile.file_id(photo_file_id)), InputPaidMedia.video( InputFile.path(video_path, max_bytes: 50_000_000), supports_streaming: true ) ] Nadia.send_paid_media(client, chat_id, 25, media) end @doc """ Sets a static bot profile photo from a new bounded JPG upload. """ @spec set_profile_photo(Client.t(), Path.t()) :: term def set_profile_photo(%Client{} = client, path) do photo = path |> InputFile.path(max_bytes: 10_000_000) |> InputProfilePhoto.static() Nadia.set_my_profile_photo(client, photo) end @doc """ Posts a business story from a new bounded 1080x1920 photo upload. """ @spec post_photo_story(Client.t(), binary, Path.t(), integer) :: term def post_photo_story(%Client{} = client, business_connection_id, path, active_period) do content = path |> InputFile.path(max_bytes: 10_000_000) |> InputStoryContent.photo() position = StoryArea.position(50, 85, 40, 10, 0, 2) areas = [StoryArea.link(position, "https://hexdocs.pm/nadia")] Nadia.post_story(client, business_connection_id, content, active_period, areas: areas) end @doc """ Sends a poll with location description media and a link on one option. """ @spec send_media_poll(Client.t(), integer | binary) :: term def send_media_poll(%Client{} = client, chat_id) do Nadia.send_poll(client, chat_id, "Where should we read the guide?", options: [ InputPollOption.new( "Documentation", media: InputPollMedia.link("https://hexdocs.pm/nadia") ), InputPollOption.new("At the office") ], media: InputPollMedia.location(35.6762, 139.6503), allows_revoting: false ) end @doc """ Sends a typed rich message using Telegram's rich HTML formatting. Telegram parses and validates the HTML grammar and media permissions. """ @spec send_rich_message(Client.t(), integer | binary) :: term def send_rich_message(%Client{} = client, chat_id) do rich_message = InputRichMessage.html( "
Typed Telegram Bot API helpers.
", skip_entity_detection: false ) Nadia.send_rich_message(client, chat_id, rich_message) end @doc """ Streams a rich-message draft while content is still being generated. Telegram allows `Almost ready.
" ) Nadia.send_rich_message_draft(client, chat_id, draft_id, rich_message) end @doc """ Edits an existing message with typed rich Markdown content. """ @spec edit_rich_message(Client.t(), integer | binary, integer) :: term def edit_rich_message(%Client{} = client, chat_id, message_id) do rich_message = InputRichMessage.markdown( "## Updated\nThe example guide is ready.", skip_entity_detection: true ) Nadia.edit_message_text(client, chat_id, message_id, nil, nil, rich_message: rich_message) end @doc """ Answers an inline query with a rich-message inline result. """ @spec answer_rich_inline_query(Client.t(), binary) :: term def answer_rich_inline_query(%Client{} = client, inline_query_id) do result = %InlineQueryResult.Article{ id: "rich-guide", title: "Rich guide", input_message_content: "Inline Nadia result.
" |> InputRichMessage.html() |> InputRichMessageContent.new() } Nadia.answer_inline_query(client, inline_query_id, [result], cache_time: 60) end @doc """ Answers an inline query with typed text, invoice, location, venue, and contact content. """ @spec answer_inline_content_query(Client.t(), binary) :: term def answer_inline_content_query(%Client{} = client, inline_query_id) do results = [ %InlineQueryResult.Article{ id: "docs-text", title: "Docs link", input_message_content: InputTextMessageContent.new( "Open Nadia docs: https://hexdocs.pm/nadia", link_preview_options: [is_disabled: true] ) }, %InlineQueryResult.Article{ id: "support-invoice", title: "Support invoice", input_message_content: InputInvoiceMessageContent.stars( "Nadia supporter", "A compact invoice content example.", "docs-inline-invoice", LabeledPrice.new("Example star", 1) ) }, %InlineQueryResult.Location{ id: "tokyo-location", latitude: 35.6762, longitude: 139.6503, title: "Tokyo", input_message_content: InputLocationMessageContent.new(35.6762, 139.6503, horizontal_accuracy: 25, live_period: 60 ) }, %InlineQueryResult.Venue{ id: "docs-venue", latitude: 35.6762, longitude: 139.6503, title: "Nadia Docs", address: "https://hexdocs.pm/nadia", input_message_content: InputVenueMessageContent.new( 35.6762, 139.6503, "Nadia Docs", "https://hexdocs.pm/nadia", google_place_id: "docs-place" ) }, %InlineQueryResult.Contact{ id: "maintainer-contact", phone_number: "+15550123", first_name: "Nadia", input_message_content: InputContactMessageContent.new("+15550123", "Nadia", last_name: "Bot") } ] Nadia.answer_inline_query(client, inline_query_id, results, is_personal: true) end @doc """ Adds a typed static sticker from a bounded local upload. """ @spec add_static_sticker(Client.t(), integer, binary, Path.t(), binary) :: term def add_static_sticker(%Client{} = client, user_id, set_name, path, emoji) do sticker = InputSticker.static( InputFile.path(path, max_bytes: 512_000), [emoji] ) Nadia.add_sticker_to_set(client, user_id, set_name, sticker) end @doc """ Downloads a Telegram file to a destination under a mandatory byte limit. Nadia streams to a same-directory temporary file, refuses an existing destination, and publishes the completed file atomically where supported. """ @spec download_file(Client.t(), binary, Path.t(), non_neg_integer) :: {:ok, Path.t()} | {:error, term} def download_file(%Client{} = client, file_id, destination, max_bytes) when is_binary(file_id) do Nadia.download_file(client, file_id, destination, max_bytes) end end