diff options
Diffstat (limited to 'lib/pleroma/web')
-rw-r--r-- | lib/pleroma/web/activity_pub/activity_pub.ex | 30 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/transmogrifier.ex | 10 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/utils.ex | 9 | ||||
-rw-r--r-- | lib/pleroma/web/activity_pub/views/user_view.ex | 12 | ||||
-rw-r--r-- | lib/pleroma/web/common_api/common_api.ex | 22 | ||||
-rw-r--r-- | lib/pleroma/web/common_api/utils.ex | 35 | ||||
-rw-r--r-- | lib/pleroma/web/controller_helper.ex | 76 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/mastodon_api_controller.ex | 82 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/views/account_view.ex | 21 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/views/conversation_view.ex | 6 | ||||
-rw-r--r-- | lib/pleroma/web/mastodon_api/views/status_view.ex | 22 | ||||
-rw-r--r-- | lib/pleroma/web/pleroma_api/pleroma_api_controller.ex | 73 | ||||
-rw-r--r-- | lib/pleroma/web/router.ex | 15 | ||||
-rw-r--r-- | lib/pleroma/web/streamer.ex | 8 | ||||
-rw-r--r-- | lib/pleroma/web/templates/email/digest.html.eex | 588 | ||||
-rw-r--r-- | lib/pleroma/web/twitter_api/views/user_view.ex | 13 | ||||
-rw-r--r-- | lib/pleroma/web/views/email_view.ex | 10 | ||||
-rw-r--r-- | lib/pleroma/web/web.ex | 18 |
18 files changed, 910 insertions, 140 deletions
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 2e8cbe13d..172c952d4 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -388,7 +388,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do def follow(follower, followed, activity_id \\ nil, local \\ true) do with data <- make_follow_data(follower, followed, activity_id), {:ok, activity} <- insert(data, local), - :ok <- maybe_federate(activity) do + :ok <- maybe_federate(activity), + _ <- User.set_follow_state_cache(follower.ap_id, followed.ap_id, activity.data["state"]) do {:ok, activity} end end @@ -790,14 +791,20 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp restrict_muted(query, %{"with_muted" => val}) when val in [true, "true", "1"], do: query - defp restrict_muted(query, %{"muting_user" => %User{info: info}}) do + defp restrict_muted(query, %{"muting_user" => %User{info: info}} = opts) do mutes = info.mutes - from( - activity in query, - where: fragment("not (? = ANY(?))", activity.actor, ^mutes), - where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes) - ) + query = + from([activity] in query, + where: fragment("not (? = ANY(?))", activity.actor, ^mutes), + where: fragment("not (?->'to' \\?| ?)", activity.data, ^mutes) + ) + + unless opts["skip_preload"] do + from([thread_mute: tm] in query, where: is_nil(tm)) + else + query + end end defp restrict_muted(query, _), do: query @@ -898,7 +905,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do defp maybe_set_thread_muted_field(query, opts) do query - |> Activity.with_set_thread_muted_field(opts["user"]) + |> Activity.with_set_thread_muted_field(opts["muting_user"] || opts["user"]) end defp maybe_order(query, %{order: :desc}) do @@ -1016,6 +1023,12 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do "url" => [%{"href" => data["image"]["url"]}] } + fields = + data + |> Map.get("attachment", []) + |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) + |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) + locked = data["manuallyApprovesFollowers"] || false data = Transmogrifier.maybe_fix_user_object(data) @@ -1025,6 +1038,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do ap_enabled: true, source_data: data, banner: banner, + fields: fields, locked: locked }, avatar: avatar, diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 0fcc81bf3..36340a3a1 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -598,14 +598,20 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do banner = new_user_data[:info][:banner] locked = new_user_data[:info][:locked] || false + attachment = get_in(new_user_data, [:info, :source_data, "attachment"]) || [] + + fields = + attachment + |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) + |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) update_data = new_user_data |> Map.take([:name, :bio, :avatar]) - |> Map.put(:info, %{banner: banner, locked: locked}) + |> Map.put(:info, %{banner: banner, locked: locked, fields: fields}) actor - |> User.upgrade_changeset(update_data) + |> User.upgrade_changeset(update_data, true) |> User.update_and_set_cache() ActivityPub.update(%{ diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index fc5305c58..1c3058658 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -374,6 +374,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do [state, actor, object] ) + User.set_follow_state_cache(actor, object, state) activity = Activity.get_by_id(activity.id) {:ok, activity} rescue @@ -382,12 +383,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do end end - def update_follow_state(%Activity{} = activity, state) do + def update_follow_state( + %Activity{data: %{"actor" => actor, "object" => object}} = activity, + state + ) do with new_data <- activity.data |> Map.put("state", state), changeset <- Changeset.change(activity, data: new_data), - {:ok, activity} <- Repo.update(changeset) do + {:ok, activity} <- Repo.update(changeset), + _ <- User.set_follow_state_cache(actor, object, state) do {:ok, activity} end end diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex index 06c9e1c71..7be734b26 100644 --- a/lib/pleroma/web/activity_pub/views/user_view.ex +++ b/lib/pleroma/web/activity_pub/views/user_view.ex @@ -80,6 +80,17 @@ defmodule Pleroma.Web.ActivityPub.UserView do |> Transmogrifier.add_emoji_tags() |> Map.get("tag", []) + fields = + user.info + |> User.Info.fields() + |> Enum.map(fn %{"name" => name, "value" => value} -> + %{ + "name" => Pleroma.HTML.strip_tags(name), + "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) + } + end) + |> Enum.map(&Map.put(&1, "type", "PropertyValue")) + %{ "id" => user.ap_id, "type" => "Person", @@ -98,6 +109,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do "publicKeyPem" => public_key }, "endpoints" => endpoints, + "attachment" => fields, "tag" => (user.info.source_data["tag"] || []) ++ user_tags } |> Map.merge(maybe_make_image(&User.avatar_url/2, "icon", user)) diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex index 2db58324b..72da46263 100644 --- a/lib/pleroma/web/common_api/common_api.ex +++ b/lib/pleroma/web/common_api/common_api.ex @@ -4,6 +4,7 @@ defmodule Pleroma.Web.CommonAPI do alias Pleroma.Activity + alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.ThreadMute @@ -171,21 +172,25 @@ defmodule Pleroma.Web.CommonAPI do end) end - def get_visibility(%{"visibility" => visibility}, in_reply_to) + def get_visibility(_, _, %Participation{}) do + {"direct", "direct"} + end + + def get_visibility(%{"visibility" => visibility}, in_reply_to, _) when visibility in ~w{public unlisted private direct}, do: {visibility, get_replied_to_visibility(in_reply_to)} - def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to) do + def get_visibility(%{"visibility" => "list:" <> list_id}, in_reply_to, _) do visibility = {:list, String.to_integer(list_id)} {visibility, get_replied_to_visibility(in_reply_to)} end - def get_visibility(_, in_reply_to) when not is_nil(in_reply_to) do + def get_visibility(_, in_reply_to, _) when not is_nil(in_reply_to) do visibility = get_replied_to_visibility(in_reply_to) {visibility, visibility} end - def get_visibility(_, in_reply_to), do: {"public", get_replied_to_visibility(in_reply_to)} + def get_visibility(_, in_reply_to, _), do: {"public", get_replied_to_visibility(in_reply_to)} def get_replied_to_visibility(nil), do: nil @@ -201,7 +206,9 @@ defmodule Pleroma.Web.CommonAPI do with status <- String.trim(status), attachments <- attachments_from_ids(data), in_reply_to <- get_replied_to_activity(data["in_reply_to_status_id"]), - {visibility, in_reply_to_visibility} <- get_visibility(data, in_reply_to), + in_reply_to_conversation <- Participation.get(data["in_reply_to_conversation_id"]), + {visibility, in_reply_to_visibility} <- + get_visibility(data, in_reply_to, in_reply_to_conversation), {_, false} <- {:private_to_public, in_reply_to_visibility == "direct" && visibility != "direct"}, {content_html, mentions, tags} <- @@ -214,8 +221,9 @@ defmodule Pleroma.Web.CommonAPI do mentioned_users <- for({_, mentioned_user} <- mentions, do: mentioned_user.ap_id), addressed_users <- get_addressed_users(mentioned_users, data["to"]), {poll, poll_emoji} <- make_poll_data(data), - {to, cc} <- get_to_and_cc(user, addressed_users, in_reply_to, visibility), - context <- make_context(in_reply_to), + {to, cc} <- + get_to_and_cc(user, addressed_users, in_reply_to, visibility, in_reply_to_conversation), + context <- make_context(in_reply_to, in_reply_to_conversation), cw <- data["spoiler_text"] || "", sensitive <- data["sensitive"] || Enum.member?(tags, {"#nsfw", "nsfw"}), full_payload <- String.trim(status <> cw), diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex index 22c44a0a3..61b96aba9 100644 --- a/lib/pleroma/web/common_api/utils.ex +++ b/lib/pleroma/web/common_api/utils.ex @@ -8,6 +8,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do alias Calendar.Strftime alias Pleroma.Activity alias Pleroma.Config + alias Pleroma.Conversation.Participation alias Pleroma.Formatter alias Pleroma.Object alias Pleroma.Plugs.AuthenticationPlug @@ -86,9 +87,21 @@ defmodule Pleroma.Web.CommonAPI.Utils do |> Enum.filter(& &1) end - @spec get_to_and_cc(User.t(), list(String.t()), Activity.t() | nil, String.t()) :: + @spec get_to_and_cc( + User.t(), + list(String.t()), + Activity.t() | nil, + String.t(), + Participation.t() | nil + ) :: {list(String.t()), list(String.t())} - def get_to_and_cc(user, mentioned_users, inReplyTo, "public") do + + def get_to_and_cc(_, _, _, _, %Participation{} = participation) do + participation = Repo.preload(participation, :recipients) + {Enum.map(participation.recipients, & &1.ap_id), []} + end + + def get_to_and_cc(user, mentioned_users, inReplyTo, "public", _) do to = [Pleroma.Constants.as_public() | mentioned_users] cc = [user.follower_address] @@ -99,7 +112,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted") do + def get_to_and_cc(user, mentioned_users, inReplyTo, "unlisted", _) do to = [user.follower_address | mentioned_users] cc = [Pleroma.Constants.as_public()] @@ -110,12 +123,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - def get_to_and_cc(user, mentioned_users, inReplyTo, "private") do - {to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct") + def get_to_and_cc(user, mentioned_users, inReplyTo, "private", _) do + {to, cc} = get_to_and_cc(user, mentioned_users, inReplyTo, "direct", nil) {[user.follower_address | to], cc} end - def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct") do + def get_to_and_cc(_user, mentioned_users, inReplyTo, "direct", _) do if inReplyTo do {Enum.uniq([inReplyTo.data["actor"] | mentioned_users]), []} else @@ -123,7 +136,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do end end - def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}), do: {mentions, []} + def get_to_and_cc(_user, mentions, _inReplyTo, {:list, _}, _), do: {mentions, []} def get_addressed_users(_, to) when is_list(to) do User.get_ap_ids_by_nicknames(to) @@ -253,8 +266,12 @@ defmodule Pleroma.Web.CommonAPI.Utils do defp maybe_add_nsfw_tag(data, _), do: data - def make_context(%Activity{data: %{"context" => context}}), do: context - def make_context(_), do: Utils.generate_context_id() + def make_context(_, %Participation{} = participation) do + Repo.preload(participation, :conversation).conversation.ap_id + end + + def make_context(%Activity{data: %{"context" => context}}, _), do: context + def make_context(_, _), do: Utils.generate_context_id() def maybe_add_attachments(parsed, _attachments, true = _no_links), do: parsed diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 8a753bb4f..eeac9f503 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -33,4 +33,80 @@ defmodule Pleroma.Web.ControllerHelper do end defp param_to_integer(_, default), do: default + + def add_link_headers( + conn, + method, + activities, + param \\ nil, + params \\ %{}, + func3 \\ nil, + func4 \\ nil + ) do + params = + conn.params + |> Map.drop(["since_id", "max_id", "min_id"]) + |> Map.merge(params) + + last = List.last(activities) + + func3 = func3 || (&mastodon_api_url/3) + func4 = func4 || (&mastodon_api_url/4) + + if last do + max_id = last.id + + limit = + params + |> Map.get("limit", "20") + |> String.to_integer() + + min_id = + if length(activities) <= limit do + activities + |> List.first() + |> Map.get(:id) + else + activities + |> Enum.at(limit * -1) + |> Map.get(:id) + end + + {next_url, prev_url} = + if param do + { + func4.( + Pleroma.Web.Endpoint, + method, + param, + Map.merge(params, %{max_id: max_id}) + ), + func4.( + Pleroma.Web.Endpoint, + method, + param, + Map.merge(params, %{min_id: min_id}) + ) + } + else + { + func3.( + Pleroma.Web.Endpoint, + method, + Map.merge(params, %{max_id: max_id}) + ), + func3.( + Pleroma.Web.Endpoint, + method, + Map.merge(params, %{min_id: min_id}) + ) + } + end + + conn + |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") + else + conn + end + end end diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex index 7ce2b5b06..53cf95fbb 100644 --- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex @@ -5,7 +5,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do use Pleroma.Web, :controller - import Pleroma.Web.ControllerHelper, only: [json_response: 3] + import Pleroma.Web.ControllerHelper, + only: [json_response: 3, add_link_headers: 5, add_link_headers: 4, add_link_headers: 3] alias Ecto.Changeset alias Pleroma.Activity @@ -137,7 +138,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do emojis_text = (user_params["display_name"] || "") <> (user_params["note"] || "") user_info_emojis = - ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text)) + user.info + |> Map.get(:emoji, []) + |> Enum.concat(Formatter.get_emoji_map(emojis_text)) |> Enum.dedup() info_params = @@ -156,6 +159,12 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do end) end) |> add_if_present(params, "default_scope", :default_scope) + |> add_if_present(params, "fields", :fields, fn fields -> + fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end) + + {:ok, fields} + end) + |> add_if_present(params, "fields", :raw_fields) |> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value -> {:ok, Map.merge(user.info.pleroma_settings_store, value)} end) @@ -342,71 +351,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do json(conn, mastodon_emoji) end - defp add_link_headers(conn, method, activities, param \\ nil, params \\ %{}) do - params = - conn.params - |> Map.drop(["since_id", "max_id", "min_id"]) - |> Map.merge(params) - - last = List.last(activities) - - if last do - max_id = last.id - - limit = - params - |> Map.get("limit", "20") - |> String.to_integer() - - min_id = - if length(activities) <= limit do - activities - |> List.first() - |> Map.get(:id) - else - activities - |> Enum.at(limit * -1) - |> Map.get(:id) - end - - {next_url, prev_url} = - if param do - { - mastodon_api_url( - Pleroma.Web.Endpoint, - method, - param, - Map.merge(params, %{max_id: max_id}) - ), - mastodon_api_url( - Pleroma.Web.Endpoint, - method, - param, - Map.merge(params, %{min_id: min_id}) - ) - } - else - { - mastodon_api_url( - Pleroma.Web.Endpoint, - method, - Map.merge(params, %{max_id: max_id}) - ), - mastodon_api_url( - Pleroma.Web.Endpoint, - method, - Map.merge(params, %{min_id: min_id}) - ) - } - end - - conn - |> put_resp_header("link", "<#{next_url}>; rel=\"next\", <#{prev_url}>; rel=\"prev\"") - else - conn - end - end - def home_timeline(%{assigns: %{user: user}} = conn, params) do params = params @@ -1797,7 +1741,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do conversations = Enum.map(participations, fn participation -> - ConversationView.render("participation.json", %{participation: participation, user: user}) + ConversationView.render("participation.json", %{participation: participation, for: user}) end) conn @@ -1810,7 +1754,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do Repo.get_by(Participation, id: participation_id, user_id: user.id), {:ok, participation} <- Participation.mark_as_read(participation) do participation_view = - ConversationView.render("participation.json", %{participation: participation, user: user}) + ConversationView.render("participation.json", %{participation: participation, for: user}) conn |> json(participation_view) diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex index 72c092f25..169116d0d 100644 --- a/lib/pleroma/web/mastodon_api/views/account_view.ex +++ b/lib/pleroma/web/mastodon_api/views/account_view.ex @@ -37,11 +37,11 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do end def render("relationship.json", %{user: %User{} = user, target: %User{} = target}) do - follow_activity = Pleroma.Web.ActivityPub.Utils.fetch_latest_follow(user, target) + follow_state = User.get_cached_follow_state(user, target) requested = - if follow_activity && !User.following?(target, user) do - follow_activity.data["state"] == "pending" + if follow_state && !User.following?(user, target) do + follow_state == "pending" else false end @@ -94,12 +94,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do end) fields = - (user.info.source_data["attachment"] || []) - |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) - |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) + user.info + |> User.Info.fields() + |> Enum.map(fn %{"name" => name, "value" => value} -> + %{ + "name" => Pleroma.HTML.strip_tags(name), + "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) + } + end) - bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) + raw_fields = Map.get(user.info, :raw_fields, []) + bio = HTML.filter_tags(user.bio, User.html_filter_policy(opts[:for])) relationship = render("relationship.json", %{user: opts[:for], target: user}) %{ @@ -124,6 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do source: %{ note: HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), sensitive: false, + fields: raw_fields, pleroma: %{} }, diff --git a/lib/pleroma/web/mastodon_api/views/conversation_view.ex b/lib/pleroma/web/mastodon_api/views/conversation_view.ex index 38bdec737..40acc07b3 100644 --- a/lib/pleroma/web/mastodon_api/views/conversation_view.ex +++ b/lib/pleroma/web/mastodon_api/views/conversation_view.ex @@ -11,8 +11,8 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do alias Pleroma.Web.MastodonAPI.AccountView alias Pleroma.Web.MastodonAPI.StatusView - def render("participation.json", %{participation: participation, user: user}) do - participation = Repo.preload(participation, conversation: :users) + def render("participation.json", %{participation: participation, for: user}) do + participation = Repo.preload(participation, conversation: [], recipients: []) last_activity_id = with nil <- participation.last_activity_id do @@ -28,7 +28,7 @@ defmodule Pleroma.Web.MastodonAPI.ConversationView do # Conversations return all users except the current user. users = - participation.conversation.users + participation.recipients |> Enum.reject(&(&1.id == user.id)) accounts = diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 492af1702..42fbdf51b 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -8,6 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do require Pleroma.Constants alias Pleroma.Activity + alias Pleroma.Conversation + alias Pleroma.Conversation.Participation alias Pleroma.HTML alias Pleroma.Object alias Pleroma.Repo @@ -70,12 +72,14 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do def render("index.json", opts) do replied_to_activities = get_replied_to_activities(opts.activities) + parallel = unless is_nil(opts[:parallel]), do: opts[:parallel], else: true opts.activities |> safe_render_many( StatusView, "status.json", - Map.put(opts, :replied_to_activities, replied_to_activities) + Map.put(opts, :replied_to_activities, replied_to_activities), + parallel ) end @@ -233,6 +237,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do object.data["url"] || object.data["external_url"] || object.data["id"] end + direct_conversation_id = + with {_, true} <- {:include_id, opts[:with_direct_conversation_id]}, + {_, %User{} = for_user} <- {:for_user, opts[:for]}, + %{data: %{"context" => context}} when is_binary(context) <- activity, + %Conversation{} = conversation <- Conversation.get_for_ap_id(context), + %Participation{id: participation_id} <- + Participation.for_user_and_conversation(for_user, conversation) do + participation_id + else + _e -> + nil + end + %{ id: to_string(activity.id), uri: object.data["id"], @@ -270,7 +287,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do conversation_id: get_context_id(activity), in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, content: %{"text/plain" => content_plaintext}, - spoiler_text: %{"text/plain" => summary_plaintext} + spoiler_text: %{"text/plain" => summary_plaintext}, + direct_conversation_id: direct_conversation_id } } end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex new file mode 100644 index 000000000..b6d2bf86b --- /dev/null +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -0,0 +1,73 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do + use Pleroma.Web, :controller + + import Pleroma.Web.ControllerHelper, only: [add_link_headers: 7] + + alias Pleroma.Conversation.Participation + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.MastodonAPI.ConversationView + alias Pleroma.Web.MastodonAPI.StatusView + + def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id}) do + with %Participation{} = participation <- Participation.get(participation_id), + true <- user.id == participation.user_id do + conn + |> put_view(ConversationView) + |> render("participation.json", %{participation: participation, for: user}) + end + end + + def conversation_statuses( + %{assigns: %{user: user}} = conn, + %{"id" => participation_id} = params + ) do + params = + params + |> Map.put("blocking_user", user) + |> Map.put("muting_user", user) + |> Map.put("user", user) + + participation = + participation_id + |> Participation.get(preload: [:conversation]) + + if user.id == participation.user_id do + activities = + participation.conversation.ap_id + |> ActivityPub.fetch_activities_for_context(params) + |> Enum.reverse() + + conn + |> add_link_headers( + :conversation_statuses, + activities, + participation_id, + params, + nil, + &pleroma_api_url/4 + ) + |> put_view(StatusView) + |> render("index.json", %{activities: activities, for: user, as: :activity}) + end + end + + def update_conversation( + %{assigns: %{user: user}} = conn, + %{"id" => participation_id, "recipients" => recipients} + ) do + participation = + participation_id + |> Participation.get() + + with true <- user.id == participation.user_id, + {:ok, participation} <- Participation.set_recipients(participation, recipients) do + conn + |> put_view(ConversationView) + |> render("participation.json", %{participation: participation, for: user}) + end + end +end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index c8c1c22dd..1eb6f7b9d 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -259,6 +259,21 @@ defmodule Pleroma.Web.Router do end end + scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do + pipe_through(:authenticated_api) + + scope [] do + pipe_through(:oauth_read) + get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses) + get("/conversations/:id", PleromaAPIController, :conversation) + end + + scope [] do + pipe_through(:oauth_write) + patch("/conversations/:id", PleromaAPIController, :update_conversation) + end + end + scope "/api/v1", Pleroma.Web.MastodonAPI do pipe_through(:authenticated_api) diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex index bbaddd852..587c43f40 100644 --- a/lib/pleroma/web/streamer.ex +++ b/lib/pleroma/web/streamer.ex @@ -113,8 +113,7 @@ defmodule Pleroma.Web.Streamer do |> Map.get("#{topic}:#{item.user_id}", []) |> Enum.each(fn socket -> with %User{} = user <- User.get_cached_by_ap_id(socket.assigns[:user].ap_id), - true <- should_send?(user, item), - false <- CommonAPI.thread_muted?(user, item.activity) do + true <- should_send?(user, item) do send( socket.transport_pid, {:text, represent_notification(socket.assigns[:user], item)} @@ -202,7 +201,7 @@ defmodule Pleroma.Web.Streamer do payload: Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ participation: participation, - user: participation.user + for: participation.user }) |> Jason.encode!() } @@ -236,7 +235,8 @@ defmodule Pleroma.Web.Streamer do %{host: parent_host} <- URI.parse(parent.data["actor"]), false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host), false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, parent_host), - true <- thread_containment(item, user) do + true <- thread_containment(item, user), + false <- CommonAPI.thread_muted?(user, item) do true else _ -> false diff --git a/lib/pleroma/web/templates/email/digest.html.eex b/lib/pleroma/web/templates/email/digest.html.eex index c9dd699fd..860df5f9c 100644 --- a/lib/pleroma/web/templates/email/digest.html.eex +++ b/lib/pleroma/web/templates/email/digest.html.eex @@ -1,20 +1,568 @@ -<h1>Hey <%= @user.nickname %>, here is what you've missed!</h1> - -<h2>New Mentions:</h2> -<ul> -<%= for %{data: mention, object: object, from: from} <- @mentions do %> - <li><%= link from.nickname, to: mention.activity.actor %>: <%= raw object.data["content"] %></li> -<% end %> -</ul> - -<%= if @followers != [] do %> -<h2><%= length(@followers) %> New Followers:</h2> -<ul> -<%= for %{data: follow, from: from} <- @followers do %> - <li><%= link from.nickname, to: follow.activity.actor %></li> -<% end %> -</ul> -<% end %> - -<p>You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</p> -<p>The email address you are subscribed as is <%= @user.email %>. To unsubscribe, please go <%= link "here", to: @unsubscribe_link %>.</p>
\ No newline at end of file +<!DOCTYPE html + PUBLIC "-//W3C//DTD XHTML 1.0 Transitional //EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> + +<html xmlns="http://www.w3.org/1999/xhtml" xmlns:o="urn:schemas-microsoft-com:office:office" + xmlns:v="urn:schemas-microsoft-com:vml"> + +<head> + <!--[if gte mso 9]><xml><o:OfficeDocumentSettings><o:AllowPNG/><o:PixelsPerInch>96</o:PixelsPerInch></o:OfficeDocumentSettings></xml><![endif]--> + <meta content="text/html; charset=utf-8" http-equiv="Content-Type" /> + <meta content="width=device-width" name="viewport" /> + <!--[if !mso]><!--> + <meta content="IE=edge" http-equiv="X-UA-Compatible" /> + <!--<![endif]--> + <title><%= @email.subject %><</title> + <!--[if !mso]><!--> + <!--<![endif]--> + <style type="text/css"> + body { + margin: 0; + padding: 0; + } + + a { + + color: <%= @styling.link_color %>; + text-decoration: none; + } + + table, + td, + tr { + vertical-align: top; + border-collapse: collapse; + } + + * { + line-height: inherit; + } + + a[x-apple-data-detectors=true] { + color: inherit !important; + text-decoration: none !important; + } + </style> + <style id="media-query" type="text/css"> + @media (max-width: 610px) { + + .block-grid, + .col { + min-width: 320px !important; + max-width: 100% !important; + display: block !important; + } + + .block-grid { + width: 100% !important; + } + + .col { + width: 100% !important; + } + + .col>div { + margin: 0 auto; + } + + .no-stack .col { + min-width: 0 !important; + display: table-cell !important; + } + + .no-stack.two-up .col { + width: 50% !important; + } + + .no-stack .col.num4 { + width: 33% !important; + } + + .no-stack .col.num8 { + width: 66% !important; + } + + .no-stack .col.num4 { + width: 33% !important; + } + + .no-stack .col.num3 { + width: 25% !important; + } + + .no-stack .col.num6 { + width: 50% !important; + } + + .no-stack .col.num9 { + width: 75% !important; + } + + } + </style> +</head> + +<body class="clean-body" style="margin: 0; padding: 0; -webkit-text-size-adjust: 100%; background-color: <%= @styling.background_color %>;"> + <!--[if IE]><div class="ie-browser"><![endif]--> + <table bgcolor="<%= @styling.background_color %>" cellpadding="0" cellspacing="0" class="nl-container" role="presentation" + style="table-layout: fixed; vertical-align: top; min-width: 320px; Margin: 0 auto; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; background-color: <%= @styling.background_color %>; width: 100%;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td style="word-break: break-word; vertical-align: top;" valign="top"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td align="center" style="background-color:<%= @styling.background_color %>"><![endif]--> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <div align="center" class="img-container center" + style="padding-right: 0px;padding-left: 0px;"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="center"><![endif]--><img + align="center" alt="Image" border="0" class="center" src="cid:logo.png" + style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: 80px; width: auto; max-height: 80px; display: block;" + title="Image" height="80" /> + <!--[if mso]></td></tr></table><![endif]--> + </div> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height: 14px; color: <%= @styling.header_color %>;"> + <p style="line-height: 36px; text-align: center; margin: 0;"><span + style="font-size: 30px; color: <%= @styling.header_color %>;">Hey <%= @user.nickname %>, here is what you've missed!</span></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;"> + <!--<![endif]--> + <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td class="divider_inner" + style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;" + valign="top"> + <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" + height="0" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td height="0" + style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top"><span></span></td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <p + style="font-size: 12px; line-height: 24px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;"> + <span style="font-size: 20px;">Mentions</span></p> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + + <%= for %{data: mention, object: object, from: from} <- @mentions do %> + <%# mention START %> + <%# user card START %> + <div style="background-color:transparent;"> + <div class="block-grid mixed-two-up no-stack" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num3" + style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;"> + <!--<![endif]--> + <div align="left" class="img-container left " + style="padding-right: 0px;padding-left: 0px;"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img + alt="<%= from.name %>" border="0" class="left " src="<%= avatar_url(from) %>" + style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;" + title="<%= from.name %>" width="76" /> + <!--[if mso]></td></tr></table><![endif]--> + </div> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num9" + style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px; color: <%= @styling.text_color %>;"><%= from.name %></span></p> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px;"><%= link "@" <> from.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: mention.activity.actor %></span></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <%# user card END %> + + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> + <span style="font-size: 16px; line-height: 19px;"><%= raw object.data["content"] %></span></div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 15px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_muted_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:15px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_muted_color %>;"> + <p style="font-size: 14px; line-height: 16px; margin: 0;"><%= format_date object.data["published"] %></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <%# mention END %> + <% end %> + + <%= if @followers != [] do %> + + <%# new followers header START %> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 15px; padding-left: 15px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 15px; padding-left: 15px;"> + <!--<![endif]--> + <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td class="divider_inner" + style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;" + valign="top"> + <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" + height="0" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td height="0" + style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top"><span></span></td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> + <p style="font-size: 12px; line-height: 24px; text-align: center; margin: 0;"><span + style="font-size: 20px;"><%= length(@followers) %> New Followers</span><span + style="font-size: 20px; line-height: 24px;"></span></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <%# new followers header END %> + + <%= for %{data: follow, from: from} <- @followers do %> + <%# user card START %> + <div style="background-color:transparent;"> + <div class="block-grid mixed-two-up no-stack" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="147" style="background-color:<%= @styling.content_background_color%>;width:76px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 20px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num3" + style="display: table-cell; vertical-align: top; max-width: 320px; min-width: 76px; width: 76px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 20px;"> + <!--<![endif]--> + <div align="left" class="img-container left " + style="padding-right: 0px;padding-left: 0px;"> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr style="line-height:0px"><td style="padding-right: 0px;padding-left: 0px;" align="left"><![endif]--><img + alt="<%= from.name %>" border="0" class="left " src="<%= avatar_url(from) %>" + style="text-decoration: none; -ms-interpolation-mode: bicubic; border: 0; height: auto; width: 100%; max-width: 76px; display: block;" + title="<%= from.name %>" width="76" /> + <!--[if mso]></td></tr></table><![endif]--> + </div> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td><td align="center" width="442" style="background-color:<%= @styling.content_background_color%>;width:442px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num9" + style="display: table-cell; vertical-align: top; min-width: 320px; max-width: 441px; width: 442px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <div + style="font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; font-size: 12px; line-height: 14px; color: <%= @styling.text_color %>;"> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px; color: <%= @styling.text_color %>;"><%= from.name %></span></p> + <p style="font-size: 14px; line-height: 19px; margin: 0;"><span + style="font-size: 16px;"><%= link "@" <> from.nickname, style: "color: #{@styling.link_color};text-decoration: none;", to: follow.activity.actor %></span></p> + </div> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <%# user card END %> + <% end %> + + + <% end %> + + <%# divider start %> + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <table border="0" cellpadding="0" cellspacing="0" class="divider" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td class="divider_inner" + style="word-break: break-word; vertical-align: top; min-width: 100%; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%; padding-top: 10px; padding-right: 10px; padding-bottom: 10px; padding-left: 10px;" + valign="top"> + <table align="center" border="0" cellpadding="0" cellspacing="0" class="divider_content" + height="0" role="presentation" + style="table-layout: fixed; vertical-align: top; border-spacing: 0; border-collapse: collapse; mso-table-lspace: 0pt; mso-table-rspace: 0pt; width: 100%; border-top: 1px solid <%= @styling.text_color %>; height: 0px;" + valign="top" width="100%"> + <tbody> + <tr style="vertical-align: top;" valign="top"> + <td height="0" + style="word-break: break-word; vertical-align: top; -ms-text-size-adjust: 100%; -webkit-text-size-adjust: 100%;" + valign="top"><span></span></td> + </tr> + </tbody> + </table> + </td> + </tr> + </tbody> + </table> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + + <%# divider end %> + + + <div style="background-color:transparent;"> + <div class="block-grid" + style="Margin: 0 auto; min-width: 320px; max-width: 590px; overflow-wrap: break-word; word-wrap: break-word; word-break: break-word; background-color: <%= @styling.content_background_color%>;"> + <div style="border-collapse: collapse;display: table;width: 100%;background-color:<%= @styling.content_background_color%>;"> + <!--[if (mso)|(IE)]><table width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color:transparent;"><tr><td align="center"><table cellpadding="0" cellspacing="0" border="0" style="width:590px"><tr class="layout-full-width" style="background-color:<%= @styling.content_background_color%>"><![endif]--> + <!--[if (mso)|(IE)]><td align="center" width="590" style="background-color:<%= @styling.content_background_color%>;width:590px; border-top: 0px solid transparent; border-left: 0px solid transparent; border-bottom: 0px solid transparent; border-right: 0px solid transparent;" valign="top"><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 0px; padding-left: 0px; padding-top:5px; padding-bottom:5px;"><![endif]--> + <div class="col num12" + style="min-width: 320px; max-width: 590px; display: table-cell; vertical-align: top; width: 590px;"> + <div style="width:100% !important;"> + <!--[if (!mso)&(!IE)]><!--> + <div + style="border-top:0px solid transparent; border-left:0px solid transparent; border-bottom:0px solid transparent; border-right:0px solid transparent; padding-top:5px; padding-bottom:5px; padding-right: 0px; padding-left: 0px;"> + <!--<![endif]--> + <!--[if mso]><table width="100%" cellpadding="0" cellspacing="0" border="0"><tr><td style="padding-right: 10px; padding-left: 10px; padding-top: 10px; padding-bottom: 10px; font-family: Arial, sans-serif"><![endif]--> + <div + style="color:<%= @styling.text_color %>;font-family:Arial, 'Helvetica Neue', Helvetica, sans-serif;line-height:120%;padding-top:10px;padding-right:10px;padding-bottom:10px;padding-left:10px;"> + <p + style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;"> + <span style="font-size: 14px;">You have received this email because you have signed up to receive digest emails from <b><%= @instance %></b> Pleroma instance.</span></p> + <p + style="font-size: 12px; line-height: 14px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;"> + </p> + <p + style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;"> + <span style="font-size: 14px;">The email address you are subscribed as is <a href="mailto:<%= @user.email %>" style="color: <%= @styling.link_color %>;text-decoration: none;"><%= @user.email %></a>. </span></p> + <p + style="font-size: 12px; line-height: 16px; text-align: center; color: <%= @styling.text_color %>; font-family: Arial, 'Helvetica Neue', Helvetica, sans-serif; margin: 0;"> + <span style="font-size: 14px;">To unsubscribe, please go <%= link "here", style: "color: #{@styling.link_color};text-decoration: none;", to: @unsubscribe_link %>.</span></p> + </div> + <!--[if mso]></td></tr></table><![endif]--> + <!--[if (!mso)&(!IE)]><!--> + </div> + <!--<![endif]--> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + <!--[if (mso)|(IE)]></td></tr></table></td></tr></table><![endif]--> + </div> + </div> + </div> + <!--[if (mso)|(IE)]></td></tr></table><![endif]--> + </td> + </tr> + </tbody> + </table> + <!--[if (IE)]></div><![endif]--> +</body> + +</html> diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex index 8d8892068..8a7d2fc72 100644 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ b/lib/pleroma/web/twitter_api/views/user_view.ex @@ -74,12 +74,15 @@ defmodule Pleroma.Web.TwitterAPI.UserView do |> HTML.filter_tags(User.html_filter_policy(for_user)) |> Formatter.emojify(emoji) - # ``fields`` is an array of mastodon profile field, containing ``{"name": "…", "value": "…"}``. - # For example: [{"name": "Pronoun", "value": "she/her"}, …] fields = - (user.info.source_data["attachment"] || []) - |> Enum.filter(fn %{"type" => t} -> t == "PropertyValue" end) - |> Enum.map(fn fields -> Map.take(fields, ["name", "value"]) end) + user.info + |> User.Info.fields() + |> Enum.map(fn %{"name" => name, "value" => value} -> + %{ + "name" => Pleroma.HTML.strip_tags(name), + "value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly) + } + end) data = %{ diff --git a/lib/pleroma/web/views/email_view.ex b/lib/pleroma/web/views/email_view.ex index b63eb162c..b506a234b 100644 --- a/lib/pleroma/web/views/email_view.ex +++ b/lib/pleroma/web/views/email_view.ex @@ -2,4 +2,14 @@ defmodule Pleroma.Web.EmailView do use Pleroma.Web, :view import Phoenix.HTML import Phoenix.HTML.Link + + def avatar_url(user) do + Pleroma.User.avatar_url(user) + end + + def format_date(date) when is_binary(date) do + date + |> Timex.parse!("{ISO:Extended:Z}") + |> Timex.format!("{Mshort} {D}, {YYYY} {h24}:{m}") + end end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index 687346554..bfb6c7287 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -66,9 +66,23 @@ defmodule Pleroma.Web do end @doc """ - Same as `render_many/4` but wrapped in rescue block. + Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument). """ - def safe_render_many(collection, view, template, assigns \\ %{}) do + def safe_render_many(collection, view, template, assigns \\ %{}, parallel \\ true) + + def safe_render_many(collection, view, template, assigns, true) do + Enum.map(collection, fn resource -> + Task.async(fn -> + as = Map.get(assigns, :as) || view.__resource__ + assigns = Map.put(assigns, as, resource) + safe_render(view, template, assigns) + end) + end) + |> Enum.map(&Task.await(&1, :infinity)) + |> Enum.filter(& &1) + end + + def safe_render_many(collection, view, template, assigns, false) do Enum.map(collection, fn resource -> as = Map.get(assigns, :as) || view.__resource__ assigns = Map.put(assigns, as, resource) |