summaryrefslogtreecommitdiff
path: root/lib/nola_plugins/link_plugin.ex
diff options
context:
space:
mode:
authorJordan Bracco <href@random.sh>2022-12-20 02:13:47 +0000
committerJordan Bracco <href@random.sh>2022-12-20 19:29:41 +0100
commit70b9bba56f5319361ce5a7df5c489b9c0d6905ce (patch)
treef9b4438965f4c5e3e1f3a6129904cbb9a37047f2 /lib/nola_plugins/link_plugin.ex
parentUpdate repo URL, refs T77. (diff)
Rename to Nola
Summary: Nola rename cont. pt. 2. Refs T77. `find lib -name "*.ex" -type f | xargs sed -i '' 's/LSG/Nola/g'` Nola rename, cont. pt. 3. Refs T77. `s/:lsg/:nola/g` Nola rename, cont. pt. 4. Refs T77. Nola rename, cont. pt. 5. Refs T77. Configs. find config -type f | xargs sed -i '' 's/LSG/Nola/g' find config -type f | xargs sed -i '' 's/lsg/nola/g' BREAKING CHANGE: Config keys switch from `:lsg` to `:nola` Nola rename, the end. pt 6. Refs T77. Nola rename: The Big Move, Refs T77 Update repo URL, refs T77. Nola rename: Nola.Plugins, refs T77 Maniphest Tasks: T77 Differential Revision: https://phab.random.sh/D3
Diffstat (limited to 'lib/nola_plugins/link_plugin.ex')
-rw-r--r--lib/nola_plugins/link_plugin.ex271
1 files changed, 0 insertions, 271 deletions
diff --git a/lib/nola_plugins/link_plugin.ex b/lib/nola_plugins/link_plugin.ex
deleted file mode 100644
index dee78e8..0000000
--- a/lib/nola_plugins/link_plugin.ex
+++ /dev/null
@@ -1,271 +0,0 @@
-defmodule Nola.IRC.LinkPlugin do
- @moduledoc """
- # Link Previewer
-
- An extensible link previewer for IRC.
-
- To extend the supported sites, create a new handler implementing the callbacks.
-
- See `link_plugin/` directory for examples. The first in list handler that returns true to the `match/2` callback will be used,
- and if the handler returns `:error` or crashes, will fallback to the default preview.
-
- Unsupported websites will use the default link preview method, which is for html document the title, otherwise it'll use
- the mimetype and size.
-
- ## Configuration:
-
- ```
- config :nola, Nola.IRC.LinkPlugin,
- handlers: [
- Nola.IRC.LinkPlugin.Youtube: [
- invidious: true
- ],
- Nola.IRC.LinkPlugin.Twitter: [],
- Nola.IRC.LinkPlugin.Imgur: [],
- ]
- ```
-
- """
-
- @ircdoc """
- # Link preview
-
- Previews links (just post a link!).
-
- Announces real URL after redirections and provides extended support for YouTube, Twitter and Imgur.
- """
- def short_irc_doc, do: false
- def irc_doc, do: @ircdoc
- require Logger
-
- def start_link() do
- GenServer.start_link(__MODULE__, [], name: __MODULE__)
- end
-
- @callback match(uri :: URI.t, options :: Keyword.t) :: {true, params :: Map.t} | false
- @callback expand(uri :: URI.t, params :: Map.t, options :: Keyword.t) :: {:ok, lines :: [] | String.t} | :error
- @callback post_match(uri :: URI.t, content_type :: binary, headers :: [], opts :: Keyword.t) :: {:body | :file, params :: Map.t} | false
- @callback post_expand(uri :: URI.t, body :: binary() | Path.t, params :: Map.t, options :: Keyword.t) :: {:ok, lines :: [] | String.t} | :error
-
- @optional_callbacks [expand: 3, post_expand: 4]
-
- defstruct [:client]
-
- def init([]) do
- {:ok, _} = Registry.register(IRC.PubSub, "messages", [plugin: __MODULE__])
- #{:ok, _} = Registry.register(IRC.PubSub, "messages:telegram", [plugin: __MODULE__])
- Logger.info("Link handler started")
- {:ok, %__MODULE__{}}
- end
-
- def handle_info({:irc, :text, message = %{text: text}}, state) do
- String.split(text)
- |> Enum.map(fn(word) ->
- if String.starts_with?(word, "http://") || String.starts_with?(word, "https://") do
- uri = URI.parse(word)
- if uri.scheme && uri.host do
- spawn(fn() ->
- :timer.kill_after(:timer.seconds(30))
- case expand_link([uri]) do
- {:ok, uris, text} ->
- text = case uris do
- [uri] -> text
- [luri | _] ->
- if luri.host == uri.host && luri.path == luri.path do
- text
- else
- ["-> #{URI.to_string(luri)}", text]
- end
- end
- if is_list(text) do
- for line <- text, do: message.replyfun.(line)
- else
- message.replyfun.(text)
- end
- _ -> nil
- end
- end)
- end
- end
- end)
- {:noreply, state}
- end
-
- def handle_info(msg, state) do
- {:noreply, state}
- end
-
- def terminate(_reason, state) do
- :ok
- end
-
- # 1. Match the first valid handler
- # 2. Try to run the handler
- # 3. If :error or crash, default link.
- # If :skip, nothing
- # 4. ?
-
- # Over five redirections: cancel.
- def expand_link(acc = [_, _, _, _, _ | _]) do
- {:ok, acc, "link redirects more than five times"}
- end
-
- def expand_link(acc=[uri | _]) do
- Logger.debug("link: expanding: #{inspect uri}")
- handlers = Keyword.get(Application.get_env(:nola, __MODULE__, [handlers: []]), :handlers)
- handler = Enum.reduce_while(handlers, nil, fn({module, opts}, acc) ->
- Logger.debug("link: attempt expanding: #{inspect module} for #{inspect uri}")
- module = Module.concat([module])
- case module.match(uri, opts) do
- {true, params} -> {:halt, {module, params, opts}}
- false -> {:cont, acc}
- end
- end)
- run_expand(acc, handler)
- end
-
- def run_expand(acc, nil) do
- expand_default(acc)
- end
-
- def run_expand(acc=[uri|_], {module, params, opts}) do
- Logger.debug("link: expanding #{inspect uri} with #{inspect module}")
- case module.expand(uri, params, opts) do
- {:ok, data} -> {:ok, acc, data}
- :error -> expand_default(acc)
- :skip -> nil
- end
- rescue
- e ->
- Logger.error("link: rescued #{inspect uri} with #{inspect module}: #{inspect e}")
- Logger.error(Exception.format(:error, e, __STACKTRACE__))
- expand_default(acc)
- catch
- e, b ->
- Logger.error("link: catched #{inspect uri} with #{inspect module}: #{inspect {e, b}}")
- expand_default(acc)
- end
-
- defp get(url, headers \\ [], options \\ []) do
- get_req(url, :hackney.get(url, headers, <<>>, options))
- end
-
- defp get_req(_, {:error, reason}) do
- {:error, reason}
- end
-
- defp get_req(url, {:ok, 200, headers, client}) do
- headers = Enum.reduce(headers, %{}, fn({key, value}, acc) ->
- Map.put(acc, String.downcase(key), value)
- end)
- content_type = Map.get(headers, "content-type", "application/octect-stream")
- length = Map.get(headers, "content-length", "0")
- {length, _} = Integer.parse(length)
-
- handlers = Keyword.get(Application.get_env(:nola, __MODULE__, [handlers: []]), :handlers)
- handler = Enum.reduce_while(handlers, false, fn({module, opts}, acc) ->
- module = Module.concat([module])
- try do
- case module.post_match(url, content_type, headers, opts) do
- {mode, params} when mode in [:body, :file] -> {:halt, {module, params, opts, mode}}
- false -> {:cont, acc}
- end
- rescue
- e ->
- Logger.error(inspect(e))
- {:cont, false}
- catch
- e, b ->
- Logger.error(inspect({b}))
- {:cont, false}
- end
- end)
-
- cond do
- handler != false and length <= 30_000_000 ->
- case get_body(url, 30_000_000, client, handler, <<>>) do
- {:ok, _} = ok -> ok
- :error ->
- {:ok, "file: #{content_type}, size: #{human_size(length)}"}
- end
- #String.starts_with?(content_type, "text/html") && length <= 30_000_000 ->
- # get_body(url, 30_000_000, client, <<>>)
- true ->
- :hackney.close(client)
- {:ok, "file: #{content_type}, size: #{human_size(length)}"}
- end
- end
-
- defp get_req(_, {:ok, redirect, headers, client}) when redirect in 300..399 do
- headers = Enum.reduce(headers, %{}, fn({key, value}, acc) ->
- Map.put(acc, String.downcase(key), value)
- end)
- location = Map.get(headers, "location")
-
- :hackney.close(client)
- {:redirect, location}
- end
-
- defp get_req(_, {:ok, status, headers, client}) do
- :hackney.close(client)
- {:error, status, headers}
- end
-
- defp get_body(url, len, client, {handler, params, opts, mode} = h, acc) when len >= byte_size(acc) do
- case :hackney.stream_body(client) do
- {:ok, data} ->
- get_body(url, len, client, h, << acc::binary, data::binary >>)
- :done ->
- body = case mode do
- :body -> acc
- :file ->
- {:ok, tmpfile} = Plug.Upload.random_file("linkplugin")
- File.write!(tmpfile, acc)
- tmpfile
- end
- handler.post_expand(url, body, params, opts)
- {:error, reason} ->
- {:ok, "failed to fetch body: #{inspect reason}"}
- end
- end
-
- defp get_body(_, len, client, h, _acc) do
- :hackney.close(client)
- IO.inspect(h)
- {:ok, "Error: file over 30"}
- end
-
- def expand_default(acc = [uri = %URI{scheme: scheme} | _]) when scheme in ["http", "https"] do
- Logger.debug("link: expanding #{uri} with default")
- headers = [{"user-agent", "DmzBot (like TwitterBot)"}]
- options = [follow_redirect: false, max_body_length: 30_000_000]
- case get(URI.to_string(uri), headers, options) do
- {:ok, text} ->
- {:ok, acc, text}
- {:redirect, link} ->
- new_uri = URI.parse(link)
- #new_uri = %URI{new_uri | scheme: scheme, authority: uri.authority, host: uri.host, port: uri.port}
- expand_link([new_uri | acc])
- {:error, status, _headers} ->
- text = Plug.Conn.Status.reason_phrase(status)
- {:ok, acc, "Error: HTTP #{text} (#{status})"}
- {:error, {:tls_alert, {:handshake_failure, err}}} ->
- {:ok, acc, "TLS Error: #{to_string(err)}"}
- {:error, reason} ->
- {:ok, acc, "Error: #{to_string(reason)}"}
- end
- end
-
- # Unsupported scheme, came from a redirect.
- def expand_default(acc = [uri | _]) do
- {:ok, [uri], "-> #{URI.to_string(uri)}"}
- end
-
-
- defp human_size(bytes) do
- bytes
- |> FileSize.new(:b)
- |> FileSize.scale()
- |> FileSize.format()
- end
-end