defmodule Nola.Plugins.Link.Twitter do @behaviour Nola.Plugins.Link @moduledoc """ # Twitter Link Preview Configuration: needs an API key and auth tokens: ``` config :extwitter, :oauth, [ consumer_key: "zzzzz", consumer_secret: "xxxxxxx", access_token: "yyyyyy", access_token_secret: "ssshhhhhh" ] ``` options: * `expand_quoted`: Add the quoted tweet instead of its URL. Default: true. """ def match(uri = %URI{host: twitter, path: path}, _opts) when twitter in ["twitter.com", "m.twitter.com", "mobile.twitter.com"] do case String.split(path, "/", parts: 4) do ["", _username, "status", status_id] -> {status_id, _} = Integer.parse(status_id) {true, %{status_id: status_id}} _ -> false end end def match(_, _), do: false @impl true def post_match(_, _, _, _), do: false def expand(_uri, %{status_id: status_id}, opts) do expand_tweet(ExTwitter.show(status_id, tweet_mode: "extended"), opts) end defp expand_tweet(nil, _opts) do :error end defp link_tweet(tweet_or_screen_id_tuple, opts, force_twitter_com \\ false) defp link_tweet({screen_name, id}, opts, force_twitter_com) do path = "/#{screen_name}/status/#{id}" nitter = Keyword.get(opts, :nitter) host = if !force_twitter_com && nitter, do: nitter, else: "twitter.com" "https://#{host}/#{screen_name}/status/#{id}" end defp link_tweet(tweet, opts, force_twitter_com) do link_tweet({tweet.user.screen_name, tweet.id}, opts, force_twitter_com) end defp expand_tweet(tweet, opts) do head = format_tweet_header(tweet, opts) # Format tweet text text = expand_twitter_text(tweet, opts) text = if tweet.quoted_status do quote_url = link_tweet(tweet.quoted_status, opts, true) String.replace(text, quote_url, "") else text end text = Nola.Irc.Message.splitlong(text) reply_to = if tweet.in_reply_to_status_id do reply_url = link_tweet({tweet.in_reply_to_screen_name, tweet.in_reply_to_status_id}, opts) text = if tweet.in_reply_to_screen_name == tweet.user.screen_name, do: "continued from", else: "replying to" <<3, 15, " ↪ ", text::binary, " ", reply_url::binary, 3>> end quoted = if tweet.quoted_status do full_text = tweet.quoted_status |> expand_twitter_text(opts) |> Nola.Irc.Message.splitlong_with_prefix(">") head = format_tweet_header(tweet.quoted_status, opts, details: false, prefix: "↓ quoting") [head | full_text] else [] end #<<2, "#{tweet.user.name} (@#{tweet.user.screen_name})", 2, " ", 3, 61, "#{foot} #{nitter_link}", 3>>, reply_to] ++ text ++ quoted text = [head, reply_to | text] ++ quoted |> Enum.filter(& &1) {:ok, text} end defp expand_twitter_text(tweet, _opts) do text = Enum.reduce(tweet.entities.urls, tweet.full_text, fn(entity, text) -> String.replace(text, entity.url, entity.expanded_url) end) extended = tweet.extended_entities || %{media: []} text = Enum.reduce(extended.media, text, fn(entity, text) -> url = Enum.filter(extended.media, fn(e) -> entity.url == e.url end) |> Enum.map(fn(e) -> cond do e.type == "video" -> e.expanded_url true -> e.media_url_https end end) |> Enum.join(" ") String.replace(text, entity.url, url) end) |> HtmlEntities.decode() end defp format_tweet_header(tweet, opts, format_opts \\ []) do prefix = Keyword.get(format_opts, :prefix, nil) details = Keyword.get(format_opts, :details, true) padded_prefix = if prefix, do: "#{prefix} ", else: "" author = <> link = link_tweet(tweet, opts) {:ok, at} = Timex.parse(tweet.created_at, "%a %b %e %H:%M:%S %z %Y", :strftime) {:ok, formatted_time} = Timex.format(at, "{relative}", :relative) nsfw = if tweet.possibly_sensitive, do: <<3, 52, "NSFW", 3>> rts = if tweet.retweet_count && tweet.retweet_count > 0, do: "#{tweet.retweet_count} RT" likes = if tweet.favorite_count && tweet.favorite_count > 0, do: "#{tweet.favorite_count} ❤︎" qrts = if tweet.quote_count && tweet.quote_count > 0, do: "#{tweet.quote_count} QRT" replies = if tweet.reply_count && tweet.reply_count > 0, do: "#{tweet.reply_count} Reps" dmcad = if tweet.withheld_copyright, do: <<3, 52, "DMCA", 3>> withheld_local = if tweet.withheld_in_countries && length(tweet.withheld_in_countries) > 0 do "Withheld in #{length(tweet.withheld_in_countries)} countries" end verified = if tweet.user.verified, do: <<3, 51, "✔", 3>> meta = if details do [verified, nsfw, formatted_time, dmcad, withheld_local, rts, qrts, likes, replies] else [verified, nsfw, formatted_time, dmcad, withheld_local] end meta = meta |> Enum.filter(& &1) |> Enum.join(" - ") meta = <<3, 15, meta::binary, " → #{link}", 3>> <> end end