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

Copy Markdown View Source

The file type filters Discord shows the user when picking a file.

An ATTACHMENT command option — and, once it exists, the File Upload component — takes up to ten filters. Each is one of the group names image, video or audio, or a dot-prefixed extension such as .pdf.

import EDA.Command.Option

attachment("receipt", "Your receipt", required: true, file_types: [:image, ".pdf"])

This is not validation

Discord matches the filename's extension and never looks inside the file. Renaming payload.exe to payload.png passes the filter. Discord says so outright: "You are still responsible for validating the actual contents of the file."

Treat it as a convenience that narrows the file picker, and check the bytes yourself when it matters — EDA.ImageData.type/1 does that for images.

Discord reorders them

A registered command does not read back in the order it was sent: ["image", ".pdf"] comes back as [".pdf", "image"]. Comparing the two lists directly — when diffing a deployed command against its definition to decide whether to re-register — therefore says "changed" forever. Compare expand/1 of each instead, which is sorted and deduplicated.

Checking a file yourself

expand/1 turns the filters into the concrete extensions Discord would have allowed, and matches?/2 answers whether a filename is one of them — for re-checking an upload against the same rule the picker used.

iex> EDA.FileType.matches?("holiday.JPG", [:image])
true

iex> EDA.FileType.matches?("notes.txt", [:image, ".pdf"])
false

Summary

Types

A group name, or a dot-prefixed extension such as ".pdf".

Functions

Returns true if two filter lists mean the same thing.

Expands filters into the extensions Discord would accept, lowercased and deduplicated.

The group names Discord accepts, and the extensions each stands for.

Returns true if a filename passes the filters.

The most filters Discord accepts on one option.

Validates a list of filters and returns what should go on the wire.

Types

filter()

@type filter() :: :image | :video | :audio | String.t()

A group name, or a dot-prefixed extension such as ".pdf".

Functions

equivalent?(left, right)

@spec equivalent?([filter()], [filter()]) :: boolean()

Returns true if two filter lists mean the same thing.

Order-insensitive and duplicate-insensitive, because Discord returns filters in an order of its own. This is the comparison to use when deciding whether a registered command still matches its definition.

Examples

iex> EDA.FileType.equivalent?(["image", ".pdf"], [".pdf", :image])
true

iex> EDA.FileType.equivalent?([:image], [:image, ".pdf"])
false

expand(filters)

@spec expand([filter()]) :: [String.t()]

Expands filters into the extensions Discord would accept, lowercased and deduplicated.

Examples

iex> EDA.FileType.expand([:audio])
[".flac", ".m4a", ".mp3", ".ogg", ".opus", ".wav"]

iex> EDA.FileType.expand([".PDF", ".pdf"])
[".pdf"]

iex> EDA.FileType.expand([])
[]

groups()

@spec groups() :: %{required(atom()) => [String.t()]}

The group names Discord accepts, and the extensions each stands for.

Examples

iex> EDA.FileType.groups()[:video]
[".mp4", ".mov", ".qt", ".webm"]

matches?(filename, filters)

@spec matches?(String.t(), [filter()]) :: boolean()

Returns true if a filename passes the filters.

Compares the extension only, case-insensitively — the same shallow check Discord makes, so this re-applies the picker's rule rather than adding one. An empty filter list accepts everything, which is what an option without :file_types means.

Remember that a matching extension says nothing about the contents.

Examples

iex> EDA.FileType.matches?("clip.MP4", [:video])
true

iex> EDA.FileType.matches?("report.pdf", [:image, ".pdf"])
true

iex> EDA.FileType.matches?("README", [:image])
false

iex> EDA.FileType.matches?("anything.xyz", [])
true

max_filters()

@spec max_filters() :: pos_integer()

The most filters Discord accepts on one option.

normalize!(filters)

@spec normalize!([filter()]) :: [String.t()]

Validates a list of filters and returns what should go on the wire.

Group names may be given as atoms or strings; extensions are lowercased, since Discord treats .PDF and .pdf alike.

Raises ArgumentError for more than 10 filters, for a name that is not a group, or for an extension written without its dot — "pdf" is not a group Discord knows and would silently filter nothing.

Examples

iex> EDA.FileType.normalize!([:image, ".PDF"])
["image", ".pdf"]

iex> EDA.FileType.normalize!(["audio"])
["audio"]