Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion lib/builders/document.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defmodule TelegramEx.Builder.Document do
"""

alias TelegramEx.API
alias TelegramEx.MimeType

@doc """
Sets the document from a URL.
Expand Down Expand Up @@ -65,7 +66,7 @@ defmodule TelegramEx.Builder.Document do
content = File.read!(path)

Map.get(ctx, :payload, %{})
|> Map.put(:document, {content, filename: filename, content_type: "application/octet-stream"})
|> Map.put(:document, {content, filename: filename, content_type: MimeType.from_path(path)})
|> then(&Map.put(ctx, :payload, &1))
end

Expand Down
3 changes: 2 additions & 1 deletion lib/builders/photo.ex
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ defmodule TelegramEx.Builder.Photo do
"""

alias TelegramEx.API
alias TelegramEx.MimeType

@doc """
Sets the photo from a URL.
Expand Down Expand Up @@ -77,7 +78,7 @@ defmodule TelegramEx.Builder.Photo do
content = File.read!(path)

Map.get(ctx, :payload, %{})
|> Map.put(:photo, {content, filename: filename, content_type: "image/jpeg"})
|> Map.put(:photo, {content, filename: filename, content_type: MimeType.from_path(path)})
|> then(&Map.put(ctx, :payload, &1))
end

Expand Down
3 changes: 2 additions & 1 deletion lib/builders/sticker.ex
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ defmodule TelegramEx.Builder.Sticker do
"""

alias TelegramEx.API
alias TelegramEx.MimeType

@doc """
Sets the sticker by Telegram file ID.
Expand Down Expand Up @@ -81,7 +82,7 @@ defmodule TelegramEx.Builder.Sticker do
content = File.read!(path)

Map.get(ctx, :payload, %{})
|> Map.put(:sticker, {content, filename: filename, content_type: "image/webp"})
|> Map.put(:sticker, {content, filename: filename, content_type: MimeType.from_path(path)})
|> then(&Map.put(ctx, :payload, &1))
end

Expand Down
5 changes: 3 additions & 2 deletions lib/builders/video.ex
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ defmodule TelegramEx.Builder.Video do
"""

alias TelegramEx.API
alias TelegramEx.MimeType

@doc """
Sets the video by Telegram file ID.
Expand Down Expand Up @@ -83,7 +84,7 @@ defmodule TelegramEx.Builder.Video do
content = File.read!(path)

Map.get(ctx, :payload, %{})
|> Map.put(:video, {content, filename: filename, content_type: "video/mp4"})
|> Map.put(:video, {content, filename: filename, content_type: MimeType.from_path(path)})
|> then(&Map.put(ctx, :payload, &1))
end

Expand Down Expand Up @@ -124,7 +125,7 @@ defmodule TelegramEx.Builder.Video do
content = File.read!(path)

Map.get(ctx, :payload, %{})
|> Map.put(:cover, {content, filename: filename, content_type: "image/jpeg"})
|> Map.put(:cover, {content, filename: filename, content_type: MimeType.from_path(path)})
|> then(&Map.put(ctx, :payload, &1))
end

Expand Down
77 changes: 77 additions & 0 deletions lib/mime_type.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
defmodule TelegramEx.MimeType do
@moduledoc """
Resolves MIME content types for local files based on their extension.

This is used by the file-based builders (`Photo`, `Video`, `Sticker`,
`Document`) so that uploaded media carries the correct `Content-Type`
header instead of a hardcoded value.
"""

@types %{
".jpg" => "image/jpeg",
".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".webp" => "image/webp",
".bmp" => "image/bmp",
".tif" => "image/tiff",
".tiff" => "image/tiff",
".svg" => "image/svg+xml",
".mp4" => "video/mp4",
".webm" => "video/webm",
".mov" => "video/quicktime",
".avi" => "video/x-msvideo",
".mkv" => "video/x-matroska",
".mpeg" => "video/mpeg",
".mpg" => "video/mpeg",
".mp3" => "audio/mpeg",
".ogg" => "audio/ogg",
".wav" => "audio/wav",
".m4a" => "audio/mp4",
".pdf" => "application/pdf",
".zip" => "application/zip",
".json" => "application/json",
".txt" => "text/plain",
".csv" => "text/csv",
".html" => "text/html"
}

@default "application/octet-stream"

@doc """
Returns the MIME content type for the given file path.

The lookup is based on the lowercased file extension. Unknown extensions
fall back to `#{@default}`.

## Parameters

- `path` - Path or filename of the file

## Returns

A MIME content type string.

## Examples

iex> TelegramEx.MimeType.from_path("photo.PNG")
"image/png"

iex> TelegramEx.MimeType.from_path("clip.webm")
"video/webm"

iex> TelegramEx.MimeType.from_path("archive.unknown")
"application/octet-stream"
"""
@spec from_path(String.t()) :: String.t()
def from_path(path) do
extension = path |> Path.extname() |> String.downcase()
Map.get(@types, extension, @default)
end

@doc """
Returns the fallback MIME content type used for unknown extensions.
"""
@spec default() :: String.t()
def default, do: @default
end
34 changes: 34 additions & 0 deletions test/mime_type_test.exs
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
defmodule TelegramEx.MimeTypeTest do
use ExUnit.Case

doctest TelegramEx.MimeType

alias TelegramEx.MimeType

test "resolves image extensions to their MIME types" do
assert MimeType.from_path("avatar.png") == "image/png"
assert MimeType.from_path("animation.gif") == "image/gif"
assert MimeType.from_path("sticker.webp") == "image/webp"
assert MimeType.from_path("photo.jpg") == "image/jpeg"
end

test "resolves video extensions to their MIME types" do
assert MimeType.from_path("clip.webm") == "video/webm"
assert MimeType.from_path("movie.mp4") == "video/mp4"
end

test "lookup is case insensitive" do
assert MimeType.from_path("PHOTO.PNG") == "image/png"
assert MimeType.from_path("Clip.WebM") == "video/webm"
end

test "falls back to application/octet-stream for unknown extensions" do
assert MimeType.from_path("archive.unknown") == "application/octet-stream"
assert MimeType.from_path("noextension") == "application/octet-stream"
assert MimeType.from_path("report.pdf") == "application/pdf"
end

test "default/0 returns the safe fallback type" do
assert MimeType.default() == "application/octet-stream"
end
end
Loading