Compare commits
No commits in common. "a2ddde09f7ac7c8fc750178707c91b743fb05c6c" and "0afe2d3b30baab8daf6f5d8f08c19844c35a3939" have entirely different histories.
a2ddde09f7
...
0afe2d3b30
|
@ -40,8 +40,6 @@ config :clacks, Oban,
|
|||
prune: {:maxlen, 10_000},
|
||||
queues: [federate: 10]
|
||||
|
||||
config :floki, :html_parser, Floki.HTMLParser.FastHtml
|
||||
|
||||
# Import environment specific config. This must remain at the bottom
|
||||
# of this file so it overrides the configuration defined above.
|
||||
import_config "#{Mix.env()}.exs"
|
||||
|
|
|
@ -33,7 +33,6 @@ defmodule Clacks.ActivityPub do
|
|||
html :: String.t(),
|
||||
context :: String.t() | nil,
|
||||
in_reply_to :: String.t() | nil,
|
||||
mentions :: [{String.t(), Clacks.Actor.t()}],
|
||||
id :: String.t() | nil,
|
||||
published :: DateTime.t(),
|
||||
to :: [String.t()],
|
||||
|
@ -44,7 +43,6 @@ defmodule Clacks.ActivityPub do
|
|||
html,
|
||||
context \\ nil,
|
||||
in_reply_to \\ nil,
|
||||
mentions \\ [],
|
||||
id \\ nil,
|
||||
published \\ DateTime.utc_now(),
|
||||
to \\ [@public],
|
||||
|
@ -66,15 +64,7 @@ defmodule Clacks.ActivityPub do
|
|||
"conversation" => context,
|
||||
"context" => context,
|
||||
"inReplyTo" => in_reply_to,
|
||||
"published" => published |> DateTime.to_iso8601(),
|
||||
"tag" =>
|
||||
Enum.map(mentions, fn {name, actor} ->
|
||||
%{
|
||||
"href" => actor.ap_id,
|
||||
"name" => name,
|
||||
"type" => "Mention"
|
||||
}
|
||||
end)
|
||||
"published" => published |> DateTime.to_iso8601()
|
||||
}
|
||||
end
|
||||
|
||||
|
|
|
@ -38,8 +38,9 @@ defmodule Clacks.ActivityPub.Fetcher do
|
|||
Logger.debug("Attempting to fetch AP object at #{uri}")
|
||||
|
||||
headers = [Accept: "application/activity+json, application/ld+json"]
|
||||
opts = [hackney: Application.get_env(:clacks, :hackney_opts, [])]
|
||||
|
||||
with {:ok, %HTTPoison.Response{body: body}} <- Clacks.HTTP.get(uri, headers),
|
||||
with {:ok, %HTTPoison.Response{body: body}} <- Clacks.HTTP.get(uri, headers, opts),
|
||||
{:ok, data} <- Jason.decode(body) do
|
||||
data
|
||||
else
|
||||
|
|
|
@ -1,22 +1,8 @@
|
|||
defmodule Clacks.HTTP do
|
||||
require Logger
|
||||
|
||||
@spec get(url :: String.t(), headers :: [{String.t(), String.t()}]) ::
|
||||
{:ok, HTTPoison.Response.t()} | {:error, String.t()}
|
||||
def get(url, headers \\ []) do
|
||||
fetch(:get, url, headers)
|
||||
end
|
||||
|
||||
@spec head(url :: String.t(), headers :: [{String.t(), String.t()}]) ::
|
||||
{:ok, HTTPoison.Response.t()} | {:error, String.t()}
|
||||
def head(url, headers \\ []) do
|
||||
fetch(:head, url, headers)
|
||||
end
|
||||
|
||||
defp fetch(method, url, headers) do
|
||||
opts = [hackney: Application.get_env(:clacks, :hackney_opts, [])]
|
||||
|
||||
case HTTPoison.request(method, url, "", headers, opts) do
|
||||
def get(url, headers \\ [], options \\ []) do
|
||||
case HTTPoison.get(url, headers, options) do
|
||||
{:ok, %HTTPoison.Response{status_code: status_code} = response}
|
||||
when status_code in 200..299 ->
|
||||
{:ok, response}
|
||||
|
@ -27,10 +13,18 @@ defmodule Clacks.HTTP do
|
|||
|> Enum.find(fn {name, _value} -> String.downcase(name) == "location" end)
|
||||
|> case do
|
||||
{_, new_url} ->
|
||||
new_url = URI.merge(URI.parse(url), URI.parse(new_url))
|
||||
new_url =
|
||||
case URI.parse(new_url) do
|
||||
%URI{host: nil, path: path} ->
|
||||
# relative path
|
||||
%URI{URI.parse(url) | path: path} |> URI.to_string()
|
||||
|
||||
uri ->
|
||||
uri
|
||||
end
|
||||
|
||||
Logger.debug("Got 301 redirect from #{url} to #{new_url}")
|
||||
fetch(method, new_url, headers)
|
||||
get(new_url, headers, options)
|
||||
|
||||
_ ->
|
||||
{:error, "Missing Location header for redirect"}
|
||||
|
@ -46,7 +40,7 @@ defmodule Clacks.HTTP do
|
|||
{:error, "HTTP #{status_code}"}
|
||||
|
||||
{:error, %HTTPoison.Error{reason: reason}} ->
|
||||
{:error, inspect(reason)}
|
||||
{:error, reason}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,35 +1,26 @@
|
|||
defmodule Clacks.UserActionsHelper do
|
||||
alias Clacks.{User, Repo, Activity, Object, ActivityPub, Actor}
|
||||
alias Clacks.{User, Repo, Activity, Object, ActivityPub}
|
||||
|
||||
@public "https://www.w3.org/ns/activitystreams#Public"
|
||||
|
||||
@spec post_status(
|
||||
author :: User.t(),
|
||||
content :: String.t(),
|
||||
content_type :: String.t(),
|
||||
in_reply_to :: String.t() | Activity.t() | nil
|
||||
) :: {:ok, Activity.t()} | {:error, any()}
|
||||
|
||||
def post_status(_, _, content_type, _)
|
||||
when not (content_type in ["text/plain", "text/markdown", "text/html"]) do
|
||||
{:error, "invalid content type, must be text/plain, text/markdown, or text/html"}
|
||||
end
|
||||
|
||||
def post_status(author, content, content_type, in_reply_to_ap_id)
|
||||
when is_binary(in_reply_to_ap_id) do
|
||||
def post_status(author, content, in_reply_to_ap_id) when is_binary(in_reply_to_ap_id) do
|
||||
case Activity.get_by_ap_id(in_reply_to_ap_id) do
|
||||
nil ->
|
||||
{:error, "Could find post to reply to with AP ID '#{in_reply_to_ap_id}'"}
|
||||
|
||||
in_reply_to ->
|
||||
post_status(author, content, content_type, in_reply_to)
|
||||
post_status(author, content, in_reply_to)
|
||||
end
|
||||
end
|
||||
|
||||
def post_status(author, content, content_type, in_reply_to) do
|
||||
{content, mentions} = convert_to_html(content, content_type)
|
||||
|
||||
note = note_for_posting(author, content, mentions, in_reply_to)
|
||||
def post_status(author, content, in_reply_to) do
|
||||
note = note_for_posting(author, content, in_reply_to)
|
||||
note_changeset = Object.changeset_for_creating(note)
|
||||
{:ok, _object} = Repo.insert(note_changeset)
|
||||
|
||||
|
@ -44,48 +35,18 @@ defmodule Clacks.UserActionsHelper do
|
|||
end
|
||||
end
|
||||
|
||||
@spec get_addressed(String.t(), [{String.t(), Actor.t()}], String.t() | nil) ::
|
||||
{[String.t()], [String.t()]}
|
||||
defp get_addressed(_author, mentions, in_reply_to_actor) do
|
||||
to = [@public | Enum.map(mentions, fn {_, actor} -> actor.ap_id end)]
|
||||
defp note_for_posting(author, content, %Activity{
|
||||
data: %{"id" => in_reply_to_ap_id, "context" => context, "actor" => in_reply_to_actor}
|
||||
}) do
|
||||
to = [in_reply_to_actor, @public]
|
||||
# todo: followers
|
||||
cc = []
|
||||
|
||||
to =
|
||||
case in_reply_to_actor do
|
||||
nil -> to
|
||||
in_reply_to_actor -> [in_reply_to_actor | to]
|
||||
end
|
||||
|
||||
{
|
||||
Enum.uniq(to),
|
||||
Enum.uniq(cc)
|
||||
}
|
||||
end
|
||||
|
||||
@spec note_for_posting(User.t(), String.t(), [{String.t(), Actor.t()}], Activity.t() | nil) ::
|
||||
map()
|
||||
|
||||
defp note_for_posting(author, content, mentions, in_reply_to) do
|
||||
{context, in_reply_to_ap_id, in_reply_to_actor} =
|
||||
case in_reply_to do
|
||||
nil ->
|
||||
{nil, nil, nil}
|
||||
|
||||
%Activity{
|
||||
data: %{"id" => in_reply_to_ap_id, "context" => context, "actor" => in_reply_to_actor}
|
||||
} ->
|
||||
{context, in_reply_to_ap_id, in_reply_to_actor}
|
||||
end
|
||||
|
||||
{to, cc} = get_addressed(author, mentions, in_reply_to_actor)
|
||||
|
||||
ActivityPub.note(
|
||||
author.actor.ap_id,
|
||||
content,
|
||||
context,
|
||||
in_reply_to_ap_id,
|
||||
mentions,
|
||||
nil,
|
||||
DateTime.utc_now(),
|
||||
to,
|
||||
|
@ -93,76 +54,7 @@ defmodule Clacks.UserActionsHelper do
|
|||
)
|
||||
end
|
||||
|
||||
@spec convert_to_html(String.t(), String.t()) :: String.t()
|
||||
|
||||
defp convert_to_html(content, "text/html") do
|
||||
content
|
||||
|> replace_mentions()
|
||||
end
|
||||
|
||||
defp convert_to_html(content, "text/markdown") do
|
||||
content
|
||||
|> Earmark.as_html!()
|
||||
|> replace_mentions()
|
||||
end
|
||||
|
||||
defp convert_to_html(content, "text/plain") do
|
||||
{content, mentions} =
|
||||
content
|
||||
|> HtmlEntities.encode()
|
||||
|> replace_links()
|
||||
|> replace_mentions()
|
||||
|
||||
{String.replace(content, ["\r\n", "\n"], "<br>"), mentions}
|
||||
end
|
||||
|
||||
@link_regex ~r/\bhttps?\S+\b/i
|
||||
|
||||
defp replace_links(content) do
|
||||
Regex.replace(@link_regex, content, "<a href=\"\\1\">\\1</a>")
|
||||
end
|
||||
|
||||
@mention_regex ~r/@(([a-z0-9_]+)(?:@[a-z0-9_\-.]+)?)/i
|
||||
|
||||
@spec replace_mentions(String.t()) :: {String.t(), [{String.t(), Actor.t()}]}
|
||||
|
||||
defp replace_mentions(content) do
|
||||
Regex.scan(@mention_regex, content, return: :index)
|
||||
|> Enum.reverse()
|
||||
|> Enum.reduce({content, []}, fn [
|
||||
{match_start, match_length},
|
||||
{nickname_start, nickname_length},
|
||||
{username_start, username_length}
|
||||
],
|
||||
{content, mentions} ->
|
||||
nickname = String.slice(content, nickname_start, nickname_length)
|
||||
|
||||
Actor.get_by_nickname(nickname)
|
||||
|> case do
|
||||
%Actor{} = actor ->
|
||||
actor
|
||||
|
||||
nil ->
|
||||
# todo: fetch by webfinger
|
||||
raise "unimplemented"
|
||||
end
|
||||
|> case do
|
||||
nil ->
|
||||
{content, mentions}
|
||||
|
||||
actor ->
|
||||
username = String.slice(content, username_start, username_length)
|
||||
|
||||
html =
|
||||
"<span class=\"h-card\"><a href=\"#{actor.ap_id}\" class=\"u-url\">@#{username}</a></span>"
|
||||
|
||||
new_content =
|
||||
String.slice(content, 0, match_start) <>
|
||||
html <>
|
||||
String.slice(content, (match_start + match_length)..String.length(content))
|
||||
|
||||
{new_content, [{nickname, actor} | mentions]}
|
||||
end
|
||||
end)
|
||||
defp note_for_posting(author, content, _in_reply_to) do
|
||||
ActivityPub.note(author.actor.ap_id, content)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,119 +0,0 @@
|
|||
defmodule Clacks.Webmention.Endpoint do
|
||||
require Logger
|
||||
|
||||
@spec find_endpoint(url :: String.t()) :: URI.t() | nil
|
||||
def find_endpoint(url) do
|
||||
case find_endpoint_by_header(url) do
|
||||
nil ->
|
||||
find_endpoint_by_html(url)
|
||||
|
||||
:error ->
|
||||
:error
|
||||
|
||||
endpoint ->
|
||||
endpoint
|
||||
end
|
||||
end
|
||||
|
||||
defp find_endpoint_by_header(url) do
|
||||
case Clacks.HTTP.head(url) do
|
||||
{:ok, %HTTPoison.Response{headers: headers, request: %HTTPoison.Request{url: final_url}}} ->
|
||||
headers
|
||||
|> Enum.filter(fn {name, _} -> String.downcase(name) == "link" end)
|
||||
|> webmention_link()
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
str when is_binary(str) ->
|
||||
URI.merge(final_url, str)
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warn("Unable to find Webmention endpoint for '#{url}': #{reason}")
|
||||
:error
|
||||
end
|
||||
end
|
||||
|
||||
defp webmention_link([]), do: nil
|
||||
|
||||
defp webmention_link([{_, value} | rest]) do
|
||||
String.split(value, ",")
|
||||
|> Enum.map(&parse_link_header/1)
|
||||
|> Enum.find(fn {rels, _} -> "webmention" in rels end)
|
||||
|> case do
|
||||
nil ->
|
||||
webmention_link(rest)
|
||||
|
||||
{_, res} ->
|
||||
res
|
||||
end
|
||||
end
|
||||
|
||||
defp parse_link_header(value) do
|
||||
[value | params] = String.split(value, ";")
|
||||
|
||||
uri_reference =
|
||||
value
|
||||
|> String.trim()
|
||||
|> String.slice(1..-1)
|
||||
|
||||
{_, rel} =
|
||||
params
|
||||
|> Enum.map(fn str ->
|
||||
str = String.trim(str)
|
||||
[name | rest] = String.split(str, "=")
|
||||
rest = Enum.join(rest, "=")
|
||||
|
||||
value =
|
||||
if String.starts_with?(rest, "\"") do
|
||||
{_, rest} = String.split_at(rest, 1)
|
||||
|
||||
if String.ends_with?(rest, "\"") do
|
||||
{rest, _} = String.split_at(rest, -1)
|
||||
rest
|
||||
else
|
||||
rest
|
||||
end
|
||||
else
|
||||
rest
|
||||
end
|
||||
|
||||
{name, value}
|
||||
end)
|
||||
|> Enum.find(fn {name, _} -> String.downcase(name) == "rel" end)
|
||||
|
||||
rels = String.split(rel, ~r/\s+/) |> Enum.map(&String.downcase/1)
|
||||
|
||||
{rels, uri_reference}
|
||||
end
|
||||
|
||||
defp find_endpoint_by_html(url) do
|
||||
case Clacks.HTTP.get(url) do
|
||||
{:ok, %HTTPoison.Response{body: body, request: %HTTPoison.Request{url: final_url}}} ->
|
||||
{:ok, doc} = Floki.parse_document(body)
|
||||
|
||||
Floki.find(doc, "link[rel~=webmention], a[rel~=webmention]")
|
||||
|> Enum.reduce_while(nil, fn el, _acc ->
|
||||
case Floki.attribute(el, "href") do
|
||||
[href] when is_binary(href) ->
|
||||
{:halt, href}
|
||||
|
||||
_ ->
|
||||
{:cont, nil}
|
||||
end
|
||||
end)
|
||||
|> case do
|
||||
nil ->
|
||||
nil
|
||||
|
||||
str when is_binary(str) ->
|
||||
URI.merge(final_url, str)
|
||||
end
|
||||
|
||||
{:error, reason} ->
|
||||
Logger.warn("Unable to find Webmention endpoint for '#{url}': #{reason}")
|
||||
:error
|
||||
end
|
||||
end
|
||||
end
|
|
@ -254,12 +254,7 @@ defmodule ClacksWeb.FrontendController do
|
|||
def post_status(conn, %{"content" => content} = params) do
|
||||
current_user = conn.assigns[:user] |> Repo.preload(:actor)
|
||||
|
||||
UserActionsHelper.post_status(
|
||||
current_user,
|
||||
content,
|
||||
"text/plain",
|
||||
Map.get(params, "in_reply_to")
|
||||
)
|
||||
UserActionsHelper.post_status(current_user, content, Map.get(params, "in_reply_to"))
|
||||
|> case do
|
||||
{:ok, activity} ->
|
||||
path = Map.get(params, "continue", Routes.frontend_path(Endpoint, :status, activity.id))
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<div class="status h-entry">
|
||||
<div class="status">
|
||||
<div class="status-meta">
|
||||
<h2 class="status-author-nickname">
|
||||
<a href="<%= local_actor_link(@author) %>" class="p-author">
|
||||
<a href="<%= local_actor_link(@author) %>">
|
||||
<%= @author.data["preferredUsername"] %>
|
||||
</a>
|
||||
</h2>
|
||||
|
@ -11,12 +11,12 @@
|
|||
</a>
|
||||
</h3>
|
||||
<p class="status-meta-right">
|
||||
<time datetime="<%= @note["published"] %>" class="dt-published"><%= display_timestamp(@note["published"]) %></time>
|
||||
<a href="<%= @note["url"] || @note["id"] %>" class="status-permalink u-url">Permalink</a>
|
||||
<span><%= display_timestamp(@note["published"]) %></span>
|
||||
<a href="<%= @note["url"] || @note["id"] %>" class="status-permalink">Permalink</a>
|
||||
</p>
|
||||
</div>
|
||||
<div class="status-content e-content">
|
||||
<%= raw(@note["content"]) %>
|
||||
<div class="status-content">
|
||||
<%= @note["content"] %>
|
||||
</div>
|
||||
<div class="status-actions">
|
||||
<a href="<%= Routes.frontend_path(@conn, :reply, @status.id) %>">Reply</a>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
<div class="h-card">
|
||||
<h1 class="p-name"><%= @actor.data["preferredUsername"] %></h1>
|
||||
<h1><%= @actor.data["preferredUsername"] %></h1>
|
||||
<h2>
|
||||
<a href="<%= @actor.ap_id %>" class="p-nickname u-url">
|
||||
<%= if @actor.local do %>
|
||||
<%= display_username(@actor) %>
|
||||
<% else %>
|
||||
<a href="<%= @actor.ap_id %>">
|
||||
<%= display_username(@actor) %>
|
||||
</a>
|
||||
<% end %>
|
||||
</h2>
|
||||
<p><%= @actor.data["summary"] %></p>
|
||||
</div>
|
||||
|
||||
<div class="actor-actions">
|
||||
<%= unless @current_user.actor.ap_id == @actor.ap_id do %>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
|
||||
<%= form_tag Routes.frontend_path(@conn, :post_status), method: :post, class: "compose-status" do %>
|
||||
<input type="hidden" name="in_reply_to" value="<%= @status.data["object"]["id"] %>">
|
||||
<textarea id="content" name="content" rows="5" placeholder="Reply" required><%= mentions_for_replying_to(@conn, @status) %></textarea>
|
||||
<textarea id="content" name="content" rows="5" placeholder="Reply" required></textarea>
|
||||
<%= submit "Post" %>
|
||||
<hr>
|
||||
<% end %>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
defmodule ClacksWeb.FrontendView do
|
||||
use ClacksWeb, :view
|
||||
alias Clacks.{Actor, Activity, Repo}
|
||||
alias Clacks.{Actor, Activity}
|
||||
alias ClacksWeb.Router.Helpers, as: Routes
|
||||
alias ClacksWeb.Endpoint
|
||||
|
||||
|
@ -88,36 +88,4 @@ defmodule ClacksWeb.FrontendView do
|
|||
|
||||
defp activity_id(%Activity{id: id}), do: id
|
||||
defp activity_id({%Activity{id: id}, _}), do: id
|
||||
|
||||
@spec mentions_for_replying_to(Activity.t()) :: String.t()
|
||||
defp mentions_for_replying_to(conn, %Activity{
|
||||
data: %{"object" => %{"actor" => actor, "tag" => tags}}
|
||||
}) do
|
||||
current_user = conn.assigns[:user] |> Repo.preload(:actor)
|
||||
|
||||
tag_actors =
|
||||
tags
|
||||
|> Enum.filter(fn
|
||||
%{"type" => "Mention"} -> true
|
||||
_ -> false
|
||||
end)
|
||||
|> Enum.map(fn %{"href" => ap_id} -> ap_id end)
|
||||
|
||||
actors =
|
||||
[actor | tag_actors]
|
||||
|> List.delete(current_user.actor.ap_id)
|
||||
|> Enum.uniq()
|
||||
|
||||
mentions =
|
||||
actors
|
||||
|> Enum.map(fn ap_id ->
|
||||
actor = Actor.get_cached_by_ap_id(ap_id)
|
||||
"@#{actor.nickname}"
|
||||
end)
|
||||
|> Enum.join(" ")
|
||||
|
||||
"#{mentions} "
|
||||
end
|
||||
|
||||
defp mentions_for_replying_to(_), do: ""
|
||||
end
|
||||
|
|
6
mix.exs
6
mix.exs
|
@ -52,11 +52,7 @@ defmodule Clacks.MixProject do
|
|||
{:bcrypt_elixir, "~> 2.0"},
|
||||
{:oban, "~> 1.2.0"},
|
||||
{:fast_sanitize, "~> 0.1.7"},
|
||||
{:fast_html, "~> 1.0.3"},
|
||||
{:dialyxir, "~> 1.0", only: [:dev], runtime: false},
|
||||
{:floki, "~> 0.26.0"},
|
||||
{:earmark, "~> 1.4.4"},
|
||||
{:html_entities, "~> 0.5.1"}
|
||||
{:dialyxir, "~> 1.0", only: [:dev], runtime: false}
|
||||
]
|
||||
end
|
||||
|
||||
|
|
3
mix.lock
3
mix.lock
|
@ -11,7 +11,6 @@
|
|||
"db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm", "5a0e8c1c722dbcd31c0cbd1906b1d1074c863d335c295e4b994849b65a1fbe47"},
|
||||
"decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm", "52694ef56e60108e5012f8af9673874c66ed58ac1c4fae9b5b7ded31786663f5"},
|
||||
"dialyxir": {:hex, :dialyxir, "1.0.0", "6a1fa629f7881a9f5aaf3a78f094b2a51a0357c843871b8bc98824e7342d00a5", [:mix], [{:erlex, ">= 0.2.6", [hex: :erlex, repo: "hexpm", optional: false]}], "hexpm", "aeb06588145fac14ca08d8061a142d52753dbc2cf7f0d00fc1013f53f8654654"},
|
||||
"earmark": {:hex, :earmark, "1.4.4", "4821b8d05cda507189d51f2caeef370cf1e18ca5d7dfb7d31e9cafe6688106a4", [:mix], [], "hexpm", "1f93aba7340574847c0f609da787f0d79efcab51b044bb6e242cae5aca9d264d"},
|
||||
"ecto": {:hex, :ecto, "3.2.1", "a0f9af0fb50b19d3bb6237e512ac0ba56ea222c2bbea92e7c6c94897932c76ba", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "e1a1a1d72514b880d6bdd9fe9423d13a800ec1fb041c7239d885e5407b1fabce"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.2.0", "751cea597e8deb616084894dd75cbabfdbe7255ff01e8c058ca13f0353a3921b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "a2e23cf761668126252418cae07eff7967ad0152fbc5e2d0dc3de487a5ec774c"},
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.0", "38349f3e29aff4864352084fc736fa7fa0f2995a819a737554f7ebd28b85aaab", [:mix], [], "hexpm", "d522695b93b7f0b4c0fcb2dfe73a6b905b1c301226a5a55cb42e5b14d509e050"},
|
||||
|
@ -20,10 +19,8 @@
|
|||
"fast_sanitize": {:hex, :fast_sanitize, "0.1.7", "2a7cd8734c88a2de6de55022104f8a3b87f1fdbe8bbf131d9049764b53d50d0d", [:mix], [{:fast_html, "~> 1.0", [hex: :fast_html, repo: "hexpm", optional: false]}, {:plug, "~> 1.8", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "f39fe8ea08fbac17487c30bf09b7d9f3e12472e51fb07a88ffeb8fd17da8ab67"},
|
||||
"file_system": {:hex, :file_system, "0.2.7", "e6f7f155970975789f26e77b8b8d8ab084c59844d8ecfaf58cbda31c494d14aa", [:mix], [], "hexpm", "b4cfa2d69c7f0b18fd06db222b2398abeef743a72504e6bd7df9c52f171b047f"},
|
||||
"flake_id": {:hex, :flake_id, "0.1.0", "7716b086d2e405d09b647121a166498a0d93d1a623bead243e1f74216079ccb3", [:mix], [{:base62, "~> 1.2", [hex: :base62, repo: "hexpm", optional: false]}, {:ecto, ">= 2.0.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm", "31fc8090fde1acd267c07c36ea7365b8604055f897d3a53dd967658c691bd827"},
|
||||
"floki": {:hex, :floki, "0.26.0", "4df88977e2e357c6720e1b650f613444bfb48c5acfc6a0c646ab007d08ad13bf", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "e7b66ce7feef5518a9cd9fc7b52dd62a64028bd9cb6d6ad282a0f0fc90a4ae52"},
|
||||
"gettext": {:hex, :gettext, "0.17.0", "abe21542c831887a2b16f4c94556db9c421ab301aee417b7c4fbde7fbdbe01ec", [:mix], [], "hexpm", "e0b8598e802676c81e66b061a2148c37c03886b24a3ca86a1f98ed40693b94b3"},
|
||||
"hackney": {:hex, :hackney, "1.15.2", "07e33c794f8f8964ee86cebec1a8ed88db5070e52e904b8f12209773c1036085", [:rebar3], [{:certifi, "2.5.1", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "6.0.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "1.0.1", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "1.1.5", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}], "hexpm", "e0100f8ef7d1124222c11ad362c857d3df7cb5f4204054f9f0f4a728666591fc"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.1", "1c9715058b42c35a2ab65edc5b36d0ea66dd083767bef6e3edb57870ef556549", [:mix], [], "hexpm", "30efab070904eb897ff05cd52fa61c1025d7f8ef3a9ca250bc4e6513d16c32de"},
|
||||
"http_signatures": {:git, "https://git.pleroma.social/pleroma/http_signatures.git", "293d77bb6f4a67ac8bde1428735c3b42f22cbb30", [ref: "293d77bb6f4a67ac8bde1428735c3b42f22cbb30"]},
|
||||
"httpoison": {:hex, :httpoison, "1.5.1", "0f55b5b673b03c5c327dac7015a67cb571b99b631acc0bc1b0b98dcd6b9f2104", [:mix], [{:hackney, "~> 1.8", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "191a3b6329c917de4e7ca68431919a59bf19e60694b313a69bc1f56a4cb160bf"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
|
||||
|
|
Loading…
Reference in New Issue