new parser
This commit is contained in:
188
lib/something_erlang/awful_api/awful_api.ex
Normal file
188
lib/something_erlang/awful_api/awful_api.ex
Normal file
@ -0,0 +1,188 @@
|
||||
defmodule SomethingErlang.AwfulApi do
|
||||
require Logger
|
||||
|
||||
alias SomethingErlang.AwfulApi.Client
|
||||
alias SomethingErlang.AwfulApi.Thread
|
||||
|
||||
def parse_thread_userinfo(thread) do
|
||||
{_, userinfos} =
|
||||
Floki.find(thread, "td.userinfo dl.userinfo")
|
||||
|> Floki.traverse_and_update([], fn
|
||||
{"dt", [{"class", _class} | _rest], children}, acc ->
|
||||
{nil, [{:name, Floki.text(children)} | acc]}
|
||||
|
||||
{"dd", [{"class", "registered"} | _rest], children}, acc ->
|
||||
{nil, [{:regdate, Floki.text(children)} | acc]}
|
||||
|
||||
{"dd", [{"class", "title"} | _rest], children}, acc ->
|
||||
{nil, [{:title, Floki.raw_html(children)} | acc]}
|
||||
|
||||
other, acc ->
|
||||
{other, acc}
|
||||
end)
|
||||
|
||||
Enum.chunk_every(userinfos, 3)
|
||||
|> Enum.map(&Map.new/1)
|
||||
|> Enum.reverse()
|
||||
end
|
||||
|
||||
def parse_thread_postdate(thread) do
|
||||
{[], postdates} =
|
||||
Floki.find(thread, "td.postdate")
|
||||
|> Floki.traverse_and_update([], fn
|
||||
{"td", [{"class", "postdate"}], children}, acc ->
|
||||
{nil, [Floki.text(children) | acc]}
|
||||
|
||||
other, acc ->
|
||||
{other, acc}
|
||||
end)
|
||||
|
||||
Enum.reverse(postdates)
|
||||
end
|
||||
|
||||
def parse_thread_postbody(thread) do
|
||||
{[], postbodies} =
|
||||
Floki.find(thread, "td.postbody")
|
||||
|> Floki.traverse_and_update(fn
|
||||
{"img", attrs, []} -> transform(:img, attrs)
|
||||
{"a", attrs, children} -> transform(:a, attrs, children)
|
||||
{:comment, _} -> nil
|
||||
other -> other
|
||||
end)
|
||||
# TODO: use Floki find or smth?
|
||||
|> Floki.traverse_and_update([], fn
|
||||
{"td", [{"class", "postbody"}], children}, acc ->
|
||||
{nil, [Floki.raw_html(children) | acc]}
|
||||
|
||||
other, acc ->
|
||||
{other, acc}
|
||||
end)
|
||||
|
||||
Enum.reverse(postbodies)
|
||||
end
|
||||
|
||||
defp transform(elem, attr, children \\ [])
|
||||
|
||||
defp transform(:img, attrs, _children) do
|
||||
{"class", class} = List.keyfind(attrs, "class", 0, {"class", ""})
|
||||
if class == "sa-smilie" do
|
||||
{"img", attrs, []}
|
||||
else
|
||||
t_attrs = List.keyreplace(attrs, "class", 0, {"class", "img-responsive"})
|
||||
{"img", [{"loading", "lazy"} | t_attrs], []}
|
||||
end
|
||||
end
|
||||
|
||||
defp transform(:a, attrs, children) do
|
||||
{"href", href} = List.keyfind(attrs, "href", 0, {"href", ""})
|
||||
cond do
|
||||
# skip internal links
|
||||
String.starts_with?(href, "/") ->
|
||||
{"a", [{"href", href}], children}
|
||||
|
||||
# mp4
|
||||
String.ends_with?(href, ".mp4") ->
|
||||
transform_link(:mp4, href)
|
||||
|
||||
# gifv
|
||||
String.ends_with?(href, ".gifv") ->
|
||||
transform_link(:gifv, href)
|
||||
|
||||
# youtube
|
||||
String.starts_with?(href, "https://www.youtube.com/watch") ->
|
||||
transform_link(:ytlong, href)
|
||||
|
||||
String.starts_with?(href, "https://youtu.be/") ->
|
||||
transform_link(:ytshort, href)
|
||||
|
||||
true ->
|
||||
Logger.debug "no transform for #{href}"
|
||||
{"a", [{"href", href}], children}
|
||||
end
|
||||
end
|
||||
|
||||
defp transform_link(:mp4, href),
|
||||
do: {"div", [{"class", "responsive-embed"}],
|
||||
[{"video", [{"class", "img-responsive"}, {"controls", ""}],
|
||||
[{"source", [{"src", href}, {"type", "video/mp4"}], []}]
|
||||
}]
|
||||
}
|
||||
|
||||
defp transform_link(:gifv, href),
|
||||
do: {"div", [{"class", "responsive-embed"}],
|
||||
[{"video", [{"class", "img-responsive"}, {"controls", ""}],
|
||||
[{"source", [{"src", String.replace(href, ".gifv", ".webm")},
|
||||
{"type", "video/webm"}], []},
|
||||
{"source", [{"src", String.replace(href, ".gifv", ".mp4")},
|
||||
{"type", "video/mp4"}], []}]
|
||||
}]
|
||||
}
|
||||
|
||||
defp transform_link(:ytlong, href) do
|
||||
String.replace(href, "/watch?v=", "/embed/")
|
||||
|> youtube_iframe()
|
||||
end
|
||||
|
||||
defp transform_link(:ytshort, href) do
|
||||
String.replace(href, "youtu.be/", "www.youtube.com/embed/")
|
||||
|> youtube_iframe()
|
||||
end
|
||||
|
||||
defp youtube_iframe(src),
|
||||
do: {"div", [{"class", "responsive-embed"}],
|
||||
[{"iframe",
|
||||
[
|
||||
{"class", "youtube-player"},
|
||||
{"loading", "lazy"},
|
||||
{"allow", "fullscreen"},
|
||||
{"src", src}
|
||||
], []}
|
||||
]}
|
||||
|
||||
@doc """
|
||||
Returns a list of all posts on page of a thread.
|
||||
|
||||
## Examples
|
||||
|
||||
iex> t = AwfulApi.parsed_thread(3945300, 1)
|
||||
iex> length(t.posts)
|
||||
42
|
||||
iex> t.page_count
|
||||
12
|
||||
"""
|
||||
def parsed_thread(id, page, user) do
|
||||
Thread.compile(id, page, user)
|
||||
end
|
||||
|
||||
def parsed_thread(id, page, user, :deprecated) do
|
||||
doc = Client.thread_doc(id, page, user)
|
||||
html = Floki.parse_document!(doc)
|
||||
thread = Floki.find(html, "#thread") |> Floki.filter_out("table.post.ignored")
|
||||
|> tap(&IO.inspect(Floki.find(&1, "table.post") |> Enum.take(2)))
|
||||
|
||||
title = Floki.find(html, "title") |> Floki.text()
|
||||
title = title |> String.replace(" - The Something Awful Forums", "")
|
||||
|
||||
page_count =
|
||||
case Floki.find(html, "#content .pages.top option:last-of-type") |> Floki.text() do
|
||||
"" -> 1
|
||||
s -> String.to_integer(s)
|
||||
end
|
||||
|
||||
posts =
|
||||
Enum.zip([
|
||||
parse_thread_userinfo(thread),
|
||||
parse_thread_postdate(thread),
|
||||
parse_thread_postbody(thread)
|
||||
])
|
||||
|> Enum.map(fn {ui, pd, pb} ->
|
||||
%{:userinfo => ui, :postdate => pd, :postbody => pb}
|
||||
end)
|
||||
|
||||
%{id: id,
|
||||
title: title,
|
||||
page: page,
|
||||
page_count: page_count,
|
||||
posts: posts}
|
||||
end
|
||||
end
|
25
lib/something_erlang/awful_api/client.ex
Normal file
25
lib/something_erlang/awful_api/client.ex
Normal file
@ -0,0 +1,25 @@
|
||||
defmodule SomethingErlang.AwfulApi.Client do
|
||||
@base_url "https://forums.somethingawful.com/"
|
||||
|
||||
def thread_doc(id, page, user) do
|
||||
resp = new_request(user) |> get_thread(id, page)
|
||||
:unicode.characters_to_binary(resp.body, :latin1)
|
||||
end
|
||||
|
||||
defp cookies(args) when is_map(args) do
|
||||
Enum.map_join(args, "; ", fn {k, v} -> "#{k}=#{v}" end)
|
||||
end
|
||||
|
||||
defp get_thread(req, id, page \\ 1) do
|
||||
url = "showthread.php"
|
||||
params = [threadid: id, pagenumber: page]
|
||||
Req.get!(req, url: url, params: params)
|
||||
end
|
||||
|
||||
defp new_request(user) do
|
||||
Req.new(
|
||||
base_url: @base_url,
|
||||
headers: [cookie: [cookies(%{bbuserid: user.id, bbpassword: user.hash})]]
|
||||
)
|
||||
end
|
||||
end
|
155
lib/something_erlang/awful_api/thread.ex
Normal file
155
lib/something_erlang/awful_api/thread.ex
Normal file
@ -0,0 +1,155 @@
|
||||
defmodule SomethingErlang.AwfulApi.Thread do
|
||||
require Logger
|
||||
|
||||
alias SomethingErlang.AwfulApi.Client
|
||||
|
||||
def compile(id, page, user) do
|
||||
doc = Client.thread_doc(id, page, user)
|
||||
html = Floki.parse_document!(doc)
|
||||
thread = Floki.find(html, "#thread") |> Floki.filter_out("table.post.ignored")
|
||||
|
||||
|
||||
title = Floki.find(html, "title") |> Floki.text()
|
||||
title = title |> String.replace(" - The Something Awful Forums", "")
|
||||
|
||||
page_count =
|
||||
case Floki.find(html, "#content .pages.top option:last-of-type") |> Floki.text() do
|
||||
"" -> 1
|
||||
s -> String.to_integer(s)
|
||||
end
|
||||
|
||||
posts = for post <- Floki.find(thread, "table.post") do
|
||||
%{
|
||||
userinfo: post |> userinfo(),
|
||||
postdate: post |> postdate(),
|
||||
postbody: post |> postbody()
|
||||
}
|
||||
end
|
||||
|
||||
%{id: id,
|
||||
title: title,
|
||||
page: page,
|
||||
page_count: page_count,
|
||||
posts: posts}
|
||||
end
|
||||
|
||||
def userinfo(post) do
|
||||
|
||||
|
||||
user = Floki.find(post, "dl.userinfo")
|
||||
user |> IO.inspect()
|
||||
|
||||
name = user |> Floki.find("dt") |> Floki.text() |> IO.inspect()
|
||||
regdate = user |> Floki.find("dd.registered") |> Floki.text() |> IO.inspect()
|
||||
title = user |> Floki.find_and_update("dd.title", fn {"dd", attrs} -> {"div", attrs} end) |> Floki.raw_html()
|
||||
|
||||
%{
|
||||
name: name,
|
||||
regdate: regdate,
|
||||
title: title
|
||||
}
|
||||
end
|
||||
|
||||
def postdate(post) do
|
||||
_date =
|
||||
Floki.find(post, "td.postdate")
|
||||
|> Floki.find("td.postdate") |> Floki.children() |> Floki.text()
|
||||
end
|
||||
|
||||
def postbody(post) do
|
||||
body = Floki.find(post, "td.postbody")
|
||||
|
||||
Floki.traverse_and_update(body, fn
|
||||
{"img", attrs, []} -> transform(:img, attrs)
|
||||
{"a", attrs, children} -> transform(:a, attrs, children)
|
||||
{:comment, _} -> nil
|
||||
other -> other
|
||||
end)
|
||||
|> Floki.traverse_and_update([], fn
|
||||
{"td", [{"class", "postbody"}], children}, acc ->
|
||||
{nil, [Floki.raw_html(children) | acc]}
|
||||
|
||||
other, acc ->
|
||||
{other, acc}
|
||||
end)
|
||||
end
|
||||
|
||||
defp transform(elem, attr, children \\ [])
|
||||
|
||||
defp transform(:img, attrs, _children) do
|
||||
{"class", class} = List.keyfind(attrs, "class", 0, {"class", ""})
|
||||
if class == "sa-smilie" do
|
||||
{"img", attrs, []}
|
||||
else
|
||||
t_attrs = List.keyreplace(attrs, "class", 0, {"class", "img-responsive"})
|
||||
{"img", [{"loading", "lazy"} | t_attrs], []}
|
||||
end
|
||||
end
|
||||
|
||||
defp transform(:a, attrs, children) do
|
||||
{"href", href} = List.keyfind(attrs, "href", 0, {"href", ""})
|
||||
cond do
|
||||
# skip internal links
|
||||
String.starts_with?(href, "/") ->
|
||||
{"a", [{"href", href}], children}
|
||||
|
||||
# mp4
|
||||
String.ends_with?(href, ".mp4") ->
|
||||
transform_link(:mp4, href)
|
||||
|
||||
# gifv
|
||||
String.ends_with?(href, ".gifv") ->
|
||||
transform_link(:gifv, href)
|
||||
|
||||
# youtube
|
||||
String.starts_with?(href, "https://www.youtube.com/watch") ->
|
||||
transform_link(:ytlong, href)
|
||||
|
||||
String.starts_with?(href, "https://youtu.be/") ->
|
||||
transform_link(:ytshort, href)
|
||||
|
||||
true ->
|
||||
Logger.debug "no transform for #{href}"
|
||||
{"a", [{"href", href}], children}
|
||||
end
|
||||
end
|
||||
|
||||
defp transform_link(:mp4, href),
|
||||
do: {"div", [{"class", "responsive-embed"}],
|
||||
[{"video", [{"class", "img-responsive"}, {"controls", ""}],
|
||||
[{"source", [{"src", href}, {"type", "video/mp4"}], []}]
|
||||
}]
|
||||
}
|
||||
|
||||
defp transform_link(:gifv, href),
|
||||
do: {"div", [{"class", "responsive-embed"}],
|
||||
[{"video", [{"class", "img-responsive"}, {"controls", ""}],
|
||||
[{"source", [{"src", String.replace(href, ".gifv", ".webm")},
|
||||
{"type", "video/webm"}], []},
|
||||
{"source", [{"src", String.replace(href, ".gifv", ".mp4")},
|
||||
{"type", "video/mp4"}], []}]
|
||||
}]
|
||||
}
|
||||
|
||||
defp transform_link(:ytlong, href) do
|
||||
String.replace(href, "/watch?v=", "/embed/")
|
||||
|> youtube_iframe()
|
||||
end
|
||||
|
||||
defp transform_link(:ytshort, href) do
|
||||
String.replace(href, "youtu.be/", "www.youtube.com/embed/")
|
||||
|> youtube_iframe()
|
||||
end
|
||||
|
||||
defp youtube_iframe(src),
|
||||
do: {"div", [{"class", "responsive-embed"}],
|
||||
[{"iframe",
|
||||
[
|
||||
{"class", "youtube-player"},
|
||||
{"loading", "lazy"},
|
||||
{"allow", "fullscreen"},
|
||||
{"src", src}
|
||||
], []}
|
||||
]}
|
||||
|
||||
end
|
@ -1,6 +1,7 @@
|
||||
defmodule SomethingErlang.Grover do
|
||||
use GenServer
|
||||
|
||||
alias SomethingErlang.AwfulApi
|
||||
require Logger
|
||||
|
||||
def mount(user, thread_id) do
|
||||
|
@ -43,7 +43,7 @@ defmodule SomethingErlangWeb.ThreadLive.Show do
|
||||
<aside class="userinfo bg-base-100 shrink-0 sm:w-[13em]">
|
||||
<h3 class="mb-4"><%= @info.name %></h3>
|
||||
<div class="title hidden sm:flex flex-col text-sm pr-4">
|
||||
<%= raw @info.title %>
|
||||
<%= raw @info.title %>
|
||||
</div>
|
||||
</aside>
|
||||
"""
|
||||
|
@ -12,6 +12,6 @@
|
||||
<%= link "Register", class: "link",
|
||||
to: Routes.user_registration_path(@conn, :new) %>
|
||||
<%= button "Log in", class: "btn btn-sm",
|
||||
to: Routes.user_session_path(@conn, :new) %>
|
||||
to: Routes.user_session_path(@conn, :new), method: :get %>
|
||||
<% end %>
|
||||
</div>
|
||||
|
2
mix.exs
2
mix.exs
@ -52,7 +52,7 @@ defmodule SomethingErlang.MixProject do
|
||||
{:plug_cowboy, "~> 2.5"},
|
||||
{:tailwind, "~> 0.1", runtime: Mix.env() == :dev},
|
||||
{:credo, "~> 1.6", only: [:dev, :test], runtime: false},
|
||||
{:awful_api, path: "../awful_api"}
|
||||
{:req, "~> 0.3.0"}
|
||||
]
|
||||
end
|
||||
|
||||
|
6
mix.lock
6
mix.lock
@ -17,9 +17,11 @@
|
||||
"elixir_make": {:hex, :elixir_make, "0.6.3", "bc07d53221216838d79e03a8019d0839786703129599e9619f4ab74c8c096eac", [:mix], [], "hexpm", "f5cbd651c5678bcaabdbb7857658ee106b12509cd976c2c2fca99688e1daf716"},
|
||||
"esbuild": {:hex, :esbuild, "0.5.0", "d5bb08ff049d7880ee3609ed5c4b864bd2f46445ea40b16b4acead724fb4c4a3", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "f183a0b332d963c4cfaf585477695ea59eef9a6f2204fdd0efa00e099694ffe5"},
|
||||
"file_system": {:hex, :file_system, "0.2.10", "fb082005a9cd1711c05b5248710f8826b02d7d1784e7c3451f9c1231d4fc162d", [:mix], [], "hexpm", "41195edbfb562a593726eda3b3e8b103a309b733ad25f3d642ba49696bf715dc"},
|
||||
"finch": {:hex, :finch, "0.13.0", "c881e5460ec563bf02d4f4584079e62201db676ed4c0ef3e59189331c4eddf7b", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: false]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.3", [hex: :mint, repo: "hexpm", optional: false]}, {:nimble_options, "~> 0.4.0", [hex: :nimble_options, repo: "hexpm", optional: false]}, {:nimble_pool, "~> 0.2.6", [hex: :nimble_pool, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "49957dcde10dcdc042a123a507a9c5ec5a803f53646d451db2f7dea696fba6cc"},
|
||||
"floki": {:hex, :floki, "0.33.1", "f20f1eb471e726342b45ccb68edb9486729e7df94da403936ea94a794f072781", [:mix], [{:html_entities, "~> 0.5.0", [hex: :html_entities, repo: "hexpm", optional: false]}], "hexpm", "461035fd125f13fdf30f243c85a0b1e50afbec876cbf1ceefe6fddd2e6d712c6"},
|
||||
"gettext": {:hex, :gettext, "0.19.1", "564953fd21f29358e68b91634799d9d26989f8d039d7512622efb3c3b1c97892", [:mix], [], "hexpm", "10c656c0912b8299adba9b061c06947511e3f109ab0d18b44a866a4498e77222"},
|
||||
"hackney": {:hex, :hackney, "1.18.1", "f48bf88f521f2a229fc7bae88cf4f85adc9cd9bcf23b5dc8eb6a1788c662c4f6", [:rebar3], [{:certifi, "~>2.9.0", [hex: :certifi, repo: "hexpm", optional: false]}, {:idna, "~>6.1.0", [hex: :idna, repo: "hexpm", optional: false]}, {:metrics, "~>1.0.0", [hex: :metrics, repo: "hexpm", optional: false]}, {:mimerl, "~>1.1", [hex: :mimerl, repo: "hexpm", optional: false]}, {:parse_trans, "3.3.1", [hex: :parse_trans, repo: "hexpm", optional: false]}, {:ssl_verify_fun, "~>1.1.0", [hex: :ssl_verify_fun, repo: "hexpm", optional: false]}, {:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "a4ecdaff44297e9b5894ae499e9a070ea1888c84afdd1fd9b7b2bc384950128e"},
|
||||
"hpax": {:hex, :hpax, "0.1.1", "2396c313683ada39e98c20a75a82911592b47e5c24391363343bde74f82396ca", [:mix], [], "hexpm", "0ae7d5a0b04a8a60caf7a39fcf3ec476f35cc2cc16c05abea730d3ce6ac6c826"},
|
||||
"html_entities": {:hex, :html_entities, "0.5.2", "9e47e70598da7de2a9ff6af8758399251db6dbb7eebe2b013f2bbd2515895c3c", [:mix], [], "hexpm", "c53ba390403485615623b9531e97696f076ed415e8d8058b1dbaa28181f4fdcc"},
|
||||
"httpoison": {:hex, :httpoison, "1.8.1", "df030d96de89dad2e9983f92b0c506a642d4b1f4a819c96ff77d12796189c63e", [:mix], [{:hackney, "~> 1.17", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "35156a6d678d6d516b9229e208942c405cf21232edd632327ecfaf4fd03e79e0"},
|
||||
"idna": {:hex, :idna, "6.1.1", "8a63070e9f7d0c62eb9d9fcb360a7de382448200fbbd1b106cc96d3d8099df8d", [:rebar3], [{:unicode_util_compat, "~>0.7.0", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "92376eb7894412ed19ac475e4a86f7b413c1b9fbb5bd16dccd57934157944cea"},
|
||||
@ -27,6 +29,9 @@
|
||||
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
|
||||
"mime": {:hex, :mime, "2.0.2", "0b9e1a4c840eafb68d820b0e2158ef5c49385d17fb36855ac6e7e087d4b1dcc5", [:mix], [], "hexpm", "e6a3f76b4c277739e36c2e21a2c640778ba4c3846189d5ab19f97f126df5f9b7"},
|
||||
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
|
||||
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
|
||||
"nimble_options": {:hex, :nimble_options, "0.4.0", "c89babbab52221a24b8d1ff9e7d838be70f0d871be823165c94dd3418eea728f", [:mix], [], "hexpm", "e6701c1af326a11eea9634a3b1c62b475339ace9456c1a23ec3bc9a847bca02d"},
|
||||
"nimble_pool": {:hex, :nimble_pool, "0.2.6", "91f2f4c357da4c4a0a548286c84a3a28004f68f05609b4534526871a22053cde", [:mix], [], "hexpm", "1c715055095d3f2705c4e236c18b618420a35490da94149ff8b580a2144f653f"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.1", "16328ab840cc09919bd10dab29e431da3af9e9e7e7e6f0089dd5a2d2820011d8", [:rebar3], [], "hexpm", "07cd9577885f56362d414e8c4c4e6bdf10d43a8767abb92d24cbe8b24c54888b"},
|
||||
"phoenix": {:hex, :phoenix, "1.6.11", "29f3c0fd12fa1fc4d4b05e341578e55bc78d96ea83a022587a7e276884d397e4", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 2.0", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:phoenix_view, "~> 1.0", [hex: :phoenix_view, repo: "hexpm", optional: false]}, {:plug, "~> 1.10", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 2.2", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:plug_crypto, "~> 1.2", [hex: :plug_crypto, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "1664e34f80c25ea4918fbadd957f491225ef601c0e00b4e644b1a772864bfbc2"},
|
||||
"phoenix_ecto": {:hex, :phoenix_ecto, "4.4.0", "0672ed4e4808b3fbed494dded89958e22fb882de47a97634c0b13e7b0b5f7720", [:mix], [{:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:phoenix_html, "~> 2.14.2 or ~> 3.0", [hex: :phoenix_html, repo: "hexpm", optional: true]}, {:plug, "~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "09864e558ed31ee00bd48fcc1d4fc58ae9678c9e81649075431e69dbabb43cc1"},
|
||||
@ -41,6 +46,7 @@
|
||||
"plug_crypto": {:hex, :plug_crypto, "1.2.2", "05654514ac717ff3a1843204b424477d9e60c143406aa94daf2274fdd280794d", [:mix], [], "hexpm", "87631c7ad914a5a445f0a3809f99b079113ae4ed4b867348dd9eec288cecb6db"},
|
||||
"postgrex": {:hex, :postgrex, "0.16.3", "fac79a81a9a234b11c44235a4494d8565303fa4b9147acf57e48978a074971db", [:mix], [{:connection, "~> 1.1", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:table, "~> 0.1.0", [hex: :table, repo: "hexpm", optional: true]}], "hexpm", "aeaae1d2d1322da4e5fe90d241b0a564ce03a3add09d7270fb85362166194590"},
|
||||
"ranch": {:hex, :ranch, "1.8.0", "8c7a100a139fd57f17327b6413e4167ac559fbc04ca7448e9be9057311597a1d", [:make, :rebar3], [], "hexpm", "49fbcfd3682fab1f5d109351b61257676da1a2fdbe295904176d5e521a2ddfe5"},
|
||||
"req": {:hex, :req, "0.3.0", "45944bfa0ea21294ad269e2025b9983dd084cc89125c4fc0a8de8a4e7869486b", [:mix], [{:brotli, "~> 0.3.1", [hex: :brotli, repo: "hexpm", optional: true]}, {:ezstd, "~> 1.0", [hex: :ezstd, repo: "hexpm", optional: true]}, {:finch, "~> 0.9", [hex: :finch, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mime, "~> 1.6 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:nimble_csv, "~> 1.0", [hex: :nimble_csv, repo: "hexpm", optional: true]}, {:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: true]}], "hexpm", "1212a3e047eede0fa7eeb84c30d08206d44bb120df98b6f6b9a9e04910954a71"},
|
||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||
"swoosh": {:hex, :swoosh, "1.7.3", "febb47c8c3ce76747eb9e3ea25ed694c815f72069127e3bb039b7724082ec670", [:mix], [{:cowboy, "~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:ex_aws, "~> 2.1", [hex: :ex_aws, repo: "hexpm", optional: true]}, {:finch, "~> 0.6", [hex: :finch, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13 or ~> 1.0", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: true]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.2 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "76abac313f95b6825baa8ceec269d597e8395950c928742fc6451d3456ca256d"},
|
||||
"tailwind": {:hex, :tailwind, "0.1.8", "3762defebc8e328fb19ff1afb8c37723e53b52be5ca74f0b8d0a02d1f3f432cf", [:mix], [{:castore, ">= 0.0.0", [hex: :castore, repo: "hexpm", optional: false]}], "hexpm", "40061d1bf2c0505c6b87be7a3ed05243fc10f6e1af4bac3336db8358bc84d4cc"},
|
||||
|
Reference in New Issue
Block a user