Packages

Furlex is a structured data extraction tool written in Elixir. It currently supports unfurling oEmbed, Twitter Card, Facebook Open Graph, JSON-LD and plain ole' HTML `<meta />` data out of any url you supply.

Current section

Files

Jump to
furlex lib furlex.ex
Raw

lib/furlex.ex

defmodule Furlex do
@moduledoc """
Furlex is a structured data extraction tool written in Elixir.
It currently supports unfurling oEmbed, Twitter Card, Facebook Open Graph,
JSON-LD and plain ole' HTML `<meta />` data out of any url you supply.
"""
use Application
alias Furlex.{Fetcher, Parser}
alias Furlex.Parser.{Facebook, HTML, JsonLD, Twitter}
defstruct [
:canonical_url, :oembed, :facebook, :twitter, :json_ld, :other, :status_code
]
@type t :: %__MODULE__{
canonical_url: String.t,
oembed: nil | Map.t,
facebook: Map.t,
twitter: Map.t,
json_ld: List.t,
other: Map.t,
status_code: Integer.t,
}
@doc false
def start(_type, _args) do
import Supervisor.Spec
opts = [strategy: :one_for_one, name: Furlex.Supervisor]
children = [
worker(Furlex.Oembed, [[name: Furlex.Oembed]]),
]
Supervisor.start_link(children, opts)
end
@doc """
Unfurls a url
unfurl/1 fetches oembed data if applicable to the given url's host,
in addition to Twitter Card, Open Graph, JSON-LD and other HTML meta tags.
"""
@spec unfurl(String.t) :: {:ok, __MODULE__.t} | {:error, Atom.t}
def unfurl(url) do
with {:ok, {body, status_code}, oembed} <- fetch(url),
{:ok, results} <- parse(body)
do
{:ok, %__MODULE__{
canonical_url: Parser.extract_canonical(body),
oembed: oembed,
facebook: results.facebook,
twitter: results.twitter,
json_ld: results.json_ld,
other: results.other,
status_code: status_code,
}}
end
end
defp fetch(url) do
fetch = Task.async Fetcher, :fetch, [ url ]
fetch_oembed = Task.async Fetcher, :fetch_oembed, [ url ]
yield = Task.yield_many [fetch, fetch_oembed]
with [ fetch, fetch_oembed ] <- yield,
{_fetch, {:ok, {:ok, body, status_code}}} <- fetch,
{_fetch_oembed, {:ok, {:ok, oembed}}} <- fetch_oembed
do
{:ok, {body, status_code}, oembed}
else
_ -> {:error, :fetch_error}
end
end
defp parse(body) do
fun = :parse
args = [ body ]
tasks = Enum.map [Facebook, Twitter, JsonLD, HTML], &Task.async(&1, fun, args)
yield = Task.yield_many(tasks)
with [ facebook, twitter, json_ld, other ] <- yield,
{_facebook, {:ok, {:ok, facebook}}} <- facebook,
{_twitter, {:ok, {:ok, twitter}}} <- twitter,
{_json_ld, {:ok, {:ok, json_ld}}} <- json_ld,
{_other, {:ok, {:ok, other}}} <- other
do
{:ok, %{
facebook: facebook,
twitter: twitter,
json_ld: json_ld,
other: other
}}
else
_ -> {:error, :parse_error}
end
end
end