EDA.ImageData (EDA v0.5.0-beta.2)

Copy Markdown View Source

Builds the data URIs Discord calls image data.

Avatars, banners, guild icons, emoji and stickers are not uploaded as files: the endpoint takes a base64 data URI in the JSON body. Discord accepts PNG, JPEG and GIF there, and the media type in the URI has to match the bytes.

EDA.ImageData.from_path("avatar.png")
#=> "data:image/png;base64,iVBORw0KGgoAAAANSUhEUg..."

EDA.API.Member.modify_me(guild_id, avatar: EDA.ImageData.from_path("avatar.png"))

The media type comes from the bytes, not the filename

A JPEG saved as photo.png is a file Discord rejects with an opaque error, and it is an easy mistake to make when the image was produced by something else. So the type is read from the file's magic number and the extension is ignored:

iex> EDA.ImageData.type(<<0xFF, 0xD8, 0xFF, 0xE0>>)
{:ok, :jpeg}

iex> EDA.ImageData.type("GIF89a" <> <<0>>)
{:ok, :gif}

A format Discord does not take — WebP most often, since it is what screenshot tools and image editors now default to — is refused here rather than at the API:

iex> EDA.ImageData.type("RIFF" <> <<0, 0, 0, 0>> <> "WEBP")
{:error, :webp}

Where this is accepted

Anywhere Discord's reference says image data. EDA.API.Member.modify_me/2 and EDA.API.User.modify_me/1 also take a raw binary or a path for :avatar and :banner and convert it themselves, so the helper is only needed when building a payload map by hand.

Summary

Types

An image format Discord accepts as image data.

Functions

Coerces whatever an API caller passed for an image field into a data URI.

Returns true if the string is already a data URI.

Returns the data URI for image bytes, detecting the format.

Returns the data URI for image bytes, stating the format rather than detecting it.

Reads an image file and returns its data URI.

Identifies image bytes by their magic number.

Types

format()

@type format() :: :png | :jpeg | :gif

An image format Discord accepts as image data.

Functions

coerce(value)

@spec coerce(nil | String.t() | binary()) :: nil | String.t()

Coerces whatever an API caller passed for an image field into a data URI.

This is what lets :avatar and :banner take a path, raw bytes or a ready-made URI. A data URI is returned as is, and nil passes through because Discord uses it to clear an avatar or banner.

A binary is ambiguous — it could be a path or the image itself — so it is read as image bytes when it carries a recognisable header, and as a path only when it still looks like one. Neither, and it raises here rather than sending something Discord cannot use.

Examples

iex> EDA.ImageData.coerce(nil)
nil

iex> EDA.ImageData.coerce("data:image/png;base64,AQID")
"data:image/png;base64,AQID"

iex> EDA.ImageData.coerce("GIF89a" <> <<1, 2, 3>>)
"data:image/gif;base64,R0lGODlhAQID"

data_uri?(arg1)

@spec data_uri?(term()) :: boolean()

Returns true if the string is already a data URI.

Examples

iex> EDA.ImageData.data_uri?("data:image/png;base64,AQID")
true

iex> EDA.ImageData.data_uri?("avatar.png")
false

from_binary(data)

@spec from_binary(binary()) :: String.t()

Returns the data URI for image bytes, detecting the format.

Raises ArgumentError if the bytes are not PNG, JPEG or GIF.

Examples

iex> EDA.ImageData.from_binary("GIF89a" <> <<1, 2, 3>>)
"data:image/gif;base64,R0lGODlhAQID"

from_binary(data, format)

@spec from_binary(binary(), format()) :: String.t()

Returns the data URI for image bytes, stating the format rather than detecting it.

Use this only when the bytes genuinely carry no recognisable header; a mismatch between the stated type and the data is rejected by Discord, not here.

Examples

iex> EDA.ImageData.from_binary(<<1, 2, 3>>, :png)
"data:image/png;base64,AQID"

from_path(path)

@spec from_path(String.t()) :: String.t()

Reads an image file and returns its data URI.

Raises if the file does not exist or is not a format Discord accepts.

type(data)

@spec type(binary()) :: {:ok, format()} | {:error, :webp | :unknown}

Identifies image bytes by their magic number.

Returns {:ok, format}, or {:error, :webp} / {:error, :unknown} for something Discord will not take.

Examples

iex> EDA.ImageData.type(<<0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A>>)
{:ok, :png}

iex> EDA.ImageData.type("not an image")
{:error, :unknown}