Packages
indieweb
0.0.14
0.0.66
0.0.65
0.0.64
0.0.63
0.0.62
0.0.61
0.0.60
0.0.59
0.0.58
0.0.56
0.0.55
0.0.54
0.0.53
0.0.51
0.0.50
0.0.49
0.0.48
0.0.47
0.0.46
0.0.42
0.0.41
0.0.40
0.0.39
0.0.38
0.0.37
0.0.36
0.0.35
0.0.34
0.0.33
0.0.32
0.0.31
0.0.30
0.0.29
0.0.28
0.0.27
0.0.26
0.0.25
0.0.24
0.0.23
0.0.22
0.0.21
0.0.20
0.0.19
0.0.17
0.0.16
0.0.15
0.0.14
0.0.13
0.0.12
0.0.10
0.0.9
0.0.8
0.0.7
0.0.6
0.0.5
0.0.3
0.0.2
0.0.1
Collection of common IndieWeb utilites like authorship resolution, Webmention, post type discovery and IndieAuth.
Current section
Files
Jump to
Current section
Files
lib/indieweb/webmention.ex
defmodule IndieWeb.Webmention do
@moduledoc """
Handles Webmention interoperability for a site.
"""
defmodule URIAdapter do
@moduledoc """
Facility for handling URI generation of Webmention logic.
"""
@doc "Defines a means of generating a URI from the provided value."
@callback to_source_url(object :: any()) :: {:ok, URI.t()} | {:error, any()}
@doc "Defines a means of obtaining a target from a URI."
@callback from_source_url(uri :: URI.t()) :: {:ok, any()} | {:error, any()}
end
defmodule SendResponse do
@enforce_keys ~w(target source code)a
defstruct ~w(target source code status)a
end
@doc "Defines the adpater to use to resolve URI and source content."
@spec url_adapter() :: __MODULE__.URIAdapter.t()
def url_adapter, do: Application.get_env(:indieweb, :webmention_url_adapter)
def resolve_target_from_url(target_url) do
if url_adapter() != nil do
case url_adapter().from_source_url(target_url) do
nil -> {:error, :no_target}
target -> {:ok, target}
end
else
{:error, :no_adapter}
end
end
def resolve_source_url(source) do
if url_adapter() != nil do
case url_adapter().to_source_url(source) do
nil -> {:error, :no_source}
%URI{} = url -> {:ok, url}
_ -> {:error, :invalid_url}
end
else
{:error, :no_adapter}
end
end
@doc """
Finds the Webmention endpoint of the provided URI.
This employs the [Webmention endpoint discovery algorithm][1] to find
the proper endpoint to send Webmentions for the URI in question.
[1]: https://www.w3.org/TR/webmention/#sender-discovers-receiver-webmention-endpoint
TODO: Add User-Agent information (by allowing to pass in header options)
"""
@spec discover_endpoint(binary) :: {:ok, binary()} | {:error, any()}
def discover_endpoint(page_url) do
case IndieWeb.LinkRel.find(page_url, "webmention") do
list when length(list) != 0 ->
{:ok, List.first(list)}
_ ->
{:error, :no_endpoint_found}
end
end
@doc """
Sends a Webmention to the provided URI.
This determines the endpoint to send [Webmentions][1] to (using `discover_endpoint/1`) and
sends the request using the HTTP client provided.
[1]: https://www.w3.org/TR/webmention
"""
@spec send(binary(), any()) ::
{:ok, IndieWeb.Webmention.SendResponse.t()} | {:error, any()}
def send(target_url, source) do
with(
{:ok, source_url} <- resolve_source_url(source),
{:ok, endpoint_url} <- discover_endpoint(target_url),
{:ok, resp} <-
IndieWeb.Http.post(endpoint_url,
body: %{"source" => source_url, "target" => target_url},
headers: %{"Content-Type" => "application/x-www-form-urlencoded"}
)
) do
send_resp = %SendResponse{
target: target_url,
source: source_url,
code: resp.code,
status: :accepted
}
{:ok, send_resp}
else
{:error, error} -> {:error, :webmention_send_failure, reason: error}
end
end
@doc """
Parses properties of an incoming Webmention.
This aims to resolve the target of an incoming Webmention and determine if there's
a valid action to take from it.
"""
@spec receive(map()) :: {:ok, [action: atom(), args: map()]} | {:error, any()}
def receive([source: source_url, target: target_url] = _args) do
case resolve_target_from_url(target_url) do
{:ok, target} ->
{:ok, [from: source_url, target: target]}
{:error, error} ->
{:error, :webmention_receive_failure, reason: error}
end
end
end