diff options
| author | lain <lain@soykaf.club> | 2019-09-13 16:31:27 +0200 | 
|---|---|---|
| committer | lain <lain@soykaf.club> | 2019-09-13 16:31:27 +0200 | 
| commit | a7f31bf06cfe3f8e2549393f34d6573854d783c0 (patch) | |
| tree | f53d6747ee73044f7b342d3fdae3d30b5b5b3c90 /lib | |
| parent | f649a2e972b70dfefb7bfc110b27a0194cda51c5 (diff) | |
| parent | 0d9609894f4f4557da2db62a33da1b8995c9e1d7 (diff) | |
| download | pleroma-a7f31bf06cfe3f8e2549393f34d6573854d783c0.tar.gz pleroma-a7f31bf06cfe3f8e2549393f34d6573854d783c0.zip  | |
Merge remote-tracking branch 'origin/develop' into reactions
Diffstat (limited to 'lib')
34 files changed, 685 insertions, 1847 deletions
diff --git a/lib/mix/tasks/pleroma/benchmark.ex b/lib/mix/tasks/pleroma/benchmark.ex index 4cc634727..84dccf7f3 100644 --- a/lib/mix/tasks/pleroma/benchmark.ex +++ b/lib/mix/tasks/pleroma/benchmark.ex @@ -27,7 +27,7 @@ defmodule Mix.Tasks.Pleroma.Benchmark do      })    end -  def run(["render_timeline", nickname]) do +  def run(["render_timeline", nickname | _] = args) do      start_pleroma()      user = Pleroma.User.get_by_nickname(nickname) @@ -37,33 +37,37 @@ defmodule Mix.Tasks.Pleroma.Benchmark do        |> Map.put("blocking_user", user)        |> Map.put("muting_user", user)        |> Map.put("user", user) -      |> Map.put("limit", 80) +      |> Map.put("limit", 4096)        |> Pleroma.Web.ActivityPub.ActivityPub.fetch_public_activities()        |> Enum.reverse()      inputs = %{ -      "One activity" => Enum.take_random(activities, 1), -      "Ten activities" => Enum.take_random(activities, 10), -      "Twenty activities" => Enum.take_random(activities, 20), -      "Forty activities" => Enum.take_random(activities, 40), -      "Eighty activities" => Enum.take_random(activities, 80) +      "1 activity" => Enum.take_random(activities, 1), +      "10 activities" => Enum.take_random(activities, 10), +      "20 activities" => Enum.take_random(activities, 20), +      "40 activities" => Enum.take_random(activities, 40), +      "80 activities" => Enum.take_random(activities, 80)      } +    inputs = +      if Enum.at(args, 2) == "extended" do +        Map.merge(inputs, %{ +          "200 activities" => Enum.take_random(activities, 200), +          "500 activities" => Enum.take_random(activities, 500), +          "2000 activities" => Enum.take_random(activities, 2000), +          "4096 activities" => Enum.take_random(activities, 4096) +        }) +      else +        inputs +      end +      Benchee.run(        %{ -        "Parallel rendering" => fn activities -> -          Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{ -            activities: activities, -            for: user, -            as: :activity -          }) -        end,          "Standart rendering" => fn activities ->            Pleroma.Web.MastodonAPI.StatusView.render("index.json", %{              activities: activities,              for: user, -            as: :activity, -            parallel: false +            as: :activity            })          end        }, diff --git a/lib/mix/tasks/pleroma/docs.ex b/lib/mix/tasks/pleroma/docs.ex new file mode 100644 index 000000000..0d2663648 --- /dev/null +++ b/lib/mix/tasks/pleroma/docs.ex @@ -0,0 +1,42 @@ +defmodule Mix.Tasks.Pleroma.Docs do +  use Mix.Task +  import Mix.Pleroma + +  @shortdoc "Generates docs from descriptions.exs" +  @moduledoc """ +  Generates docs from `descriptions.exs`. + +  Supports two formats: `markdown` and `json`. + +  ## Generate Markdown docs + +  `mix pleroma.docs` + +  ## Generate JSON docs + +  `mix pleroma.docs json` +  """ + +  def run(["json"]) do +    do_run(Pleroma.Docs.JSON) +  end + +  def run(_) do +    do_run(Pleroma.Docs.Markdown) +  end + +  defp do_run(implementation) do +    start_pleroma() + +    with {descriptions, _paths} <- Mix.Config.eval!("config/description.exs"), +         {:ok, file_path} <- +           Pleroma.Docs.Generator.process( +             implementation, +             descriptions[:pleroma][:config_description] +           ) do +      type = if implementation == Pleroma.Docs.Markdown, do: "Markdown", else: "JSON" + +      Mix.shell().info([:green, "#{type} docs successfully generated to #{file_path}."]) +    end +  end +end diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index 2d4e9da0c..44f1e3011 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -173,6 +173,13 @@ defmodule Pleroma.Activity do      |> Repo.one()    end +  def all_by_ids_with_object(ids) do +    Activity +    |> where([a], a.id in ^ids) +    |> with_preloaded_object() +    |> Repo.all() +  end +    def by_object_ap_id(ap_id) do      from(        activity in Activity, @@ -308,10 +315,19 @@ defmodule Pleroma.Activity do        %{data: %{"type" => "Create", "object" => %{"id" => ap_id}}} -> ap_id == id        _ -> nil      end) +    |> purge_web_resp_cache()    end    def delete_by_ap_id(_), do: nil +  defp purge_web_resp_cache(%Activity{} = activity) do +    %{path: path} = URI.parse(activity.data["id"]) +    Cachex.del(:web_resp_cache, path) +    activity +  end + +  defp purge_web_resp_cache(nil), do: nil +    for {ap_type, type} <- @mastodon_notification_types do      def mastodon_notification_type(%Activity{data: %{"type" => unquote(ap_type)}}),        do: unquote(type) @@ -362,12 +378,12 @@ defmodule Pleroma.Activity do    end    def restrict_deactivated_users(query) do +    deactivated_users = +      from(u in User.Query.build(deactivated: true), select: u.ap_id) +      |> Repo.all() +      from(activity in query, -      where: -        fragment( -          "? not in (SELECT ap_id FROM users WHERE info->'deactivated' @> 'true')", -          activity.actor -        ) +      where: activity.actor not in ^deactivated_users      )    end diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index 483ac1f39..1d46925f8 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -116,7 +116,8 @@ defmodule Pleroma.Application do        build_cachex("object", default_ttl: 25_000, ttl_interval: 1000, limit: 2500),        build_cachex("rich_media", default_ttl: :timer.minutes(120), limit: 5000),        build_cachex("scrubber", limit: 2500), -      build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500) +      build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500), +      build_cachex("web_resp", limit: 2500)      ]    end diff --git a/lib/pleroma/docs/generator.ex b/lib/pleroma/docs/generator.ex new file mode 100644 index 000000000..aa578eee2 --- /dev/null +++ b/lib/pleroma/docs/generator.ex @@ -0,0 +1,73 @@ +defmodule Pleroma.Docs.Generator do +  @callback process(keyword()) :: {:ok, String.t()} + +  @spec process(module(), keyword()) :: {:ok, String.t()} +  def process(implementation, descriptions) do +    implementation.process(descriptions) +  end + +  @spec uploaders_list() :: [module()] +  def uploaders_list do +    {:ok, modules} = :application.get_key(:pleroma, :modules) + +    Enum.filter(modules, fn module -> +      name_as_list = Module.split(module) + +      List.starts_with?(name_as_list, ["Pleroma", "Uploaders"]) and +        List.last(name_as_list) != "Uploader" +    end) +  end + +  @spec filters_list() :: [module()] +  def filters_list do +    {:ok, modules} = :application.get_key(:pleroma, :modules) + +    Enum.filter(modules, fn module -> +      name_as_list = Module.split(module) + +      List.starts_with?(name_as_list, ["Pleroma", "Upload", "Filter"]) +    end) +  end + +  @spec mrf_list() :: [module()] +  def mrf_list do +    {:ok, modules} = :application.get_key(:pleroma, :modules) + +    Enum.filter(modules, fn module -> +      name_as_list = Module.split(module) + +      List.starts_with?(name_as_list, ["Pleroma", "Web", "ActivityPub", "MRF"]) and +        length(name_as_list) > 4 +    end) +  end + +  @spec richmedia_parsers() :: [module()] +  def richmedia_parsers do +    {:ok, modules} = :application.get_key(:pleroma, :modules) + +    Enum.filter(modules, fn module -> +      name_as_list = Module.split(module) + +      List.starts_with?(name_as_list, ["Pleroma", "Web", "RichMedia", "Parsers"]) and +        length(name_as_list) == 5 +    end) +  end +end + +defimpl Jason.Encoder, for: Tuple do +  def encode(tuple, opts) do +    Jason.Encode.list(Tuple.to_list(tuple), opts) +  end +end + +defimpl Jason.Encoder, for: [Regex, Function] do +  def encode(term, opts) do +    Jason.Encode.string(inspect(term), opts) +  end +end + +defimpl String.Chars, for: Regex do +  def to_string(term) do +    inspect(term) +  end +end diff --git a/lib/pleroma/docs/json.ex b/lib/pleroma/docs/json.ex new file mode 100644 index 000000000..18ba01d58 --- /dev/null +++ b/lib/pleroma/docs/json.ex @@ -0,0 +1,20 @@ +defmodule Pleroma.Docs.JSON do +  @behaviour Pleroma.Docs.Generator + +  @spec process(keyword()) :: {:ok, String.t()} +  def process(descriptions) do +    config_path = "docs/generate_config.json" + +    with {:ok, file} <- File.open(config_path, [:write]), +         json <- generate_json(descriptions), +         :ok <- IO.write(file, json), +         :ok <- File.close(file) do +      {:ok, config_path} +    end +  end + +  @spec generate_json([keyword()]) :: String.t() +  def generate_json(descriptions) do +    Jason.encode!(descriptions) +  end +end diff --git a/lib/pleroma/docs/markdown.ex b/lib/pleroma/docs/markdown.ex new file mode 100644 index 000000000..24930cc9f --- /dev/null +++ b/lib/pleroma/docs/markdown.ex @@ -0,0 +1,78 @@ +defmodule Pleroma.Docs.Markdown do +  @behaviour Pleroma.Docs.Generator + +  @spec process(keyword()) :: {:ok, String.t()} +  def process(descriptions) do +    config_path = "docs/config.md" +    {:ok, file} = File.open(config_path, [:utf8, :write]) +    IO.write(file, "# Configuration\n") +    IO.write(file, "Date of generation: #{Date.utc_today()}\n\n") + +    IO.write( +      file, +      "This file describe the configuration, it is recommended to edit the relevant `*.secret.exs` file instead of the others founds in the ``config`` directory.\n\n" <> +        "If you run Pleroma with ``MIX_ENV=prod`` the file is ``prod.secret.exs``, otherwise it is ``dev.secret.exs``.\n\n" +    ) + +    for group <- descriptions do +      if is_nil(group[:key]) do +        IO.write(file, "## #{inspect(group[:group])}\n") +      else +        IO.write(file, "## #{inspect(group[:key])}\n") +      end + +      IO.write(file, "#{group[:description]}\n") + +      for child <- group[:children] do +        print_child_header(file, child) + +        print_suggestions(file, child[:suggestions]) + +        if child[:children] do +          for subchild <- child[:children] do +            print_child_header(file, subchild) + +            print_suggestions(file, subchild[:suggestions]) +          end +        end +      end + +      IO.write(file, "\n") +    end + +    :ok = File.close(file) +    {:ok, config_path} +  end + +  defp print_suggestion(file, suggestion) when is_list(suggestion) do +    IO.write(file, "  `#{inspect(suggestion)}`\n") +  end + +  defp print_suggestion(file, suggestion) when is_function(suggestion) do +    IO.write(file, "  `#{inspect(suggestion.())}`\n") +  end + +  defp print_suggestion(file, suggestion, as_list \\ false) do +    list_mark = if as_list, do: "- ", else: "" +    IO.write(file, "  #{list_mark}`#{inspect(suggestion)}`\n") +  end + +  defp print_suggestions(_file, nil), do: nil + +  defp print_suggestions(file, suggestions) do +    IO.write(file, "Suggestions:\n") + +    if length(suggestions) > 1 do +      for suggestion <- suggestions do +        print_suggestion(file, suggestion, true) +      end +    else +      print_suggestion(file, List.first(suggestions)) +    end +  end + +  defp print_child_header(file, child) do +    IO.write(file, "- `#{inspect(child[:key])}` -`#{inspect(child[:type])}`  \n") +    IO.write(file, "#{child[:description]}  \n") +  end +end diff --git a/lib/healthcheck.ex b/lib/pleroma/healthcheck.ex index f97d14432..977b78c26 100644 --- a/lib/healthcheck.ex +++ b/lib/pleroma/healthcheck.ex @@ -9,6 +9,7 @@ defmodule Pleroma.Healthcheck do    alias Pleroma.Healthcheck    alias Pleroma.Repo +  @derive Jason.Encoder    defstruct pool_size: 0,              active: 0,              idle: 0, diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 5d29af853..b7c880c51 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -102,15 +102,33 @@ defmodule Pleroma.Notification do          n in Notification,          where: n.user_id == ^user_id,          where: n.id <= ^id, +        where: n.seen == false,          update: [            set: [              seen: true,              updated_at: ^NaiveDateTime.utc_now()            ] -        ] +        ], +        # Ideally we would preload object and activities here +        # but Ecto does not support preloads in update_all +        select: n.id        ) -    Repo.update_all(query, []) +    {_, notification_ids} = Repo.update_all(query, []) + +    Notification +    |> where([n], n.id in ^notification_ids) +    |> join(:inner, [n], activity in assoc(n, :activity)) +    |> join(:left, [n, a], object in Object, +      on: +        fragment( +          "(?->>'id') = COALESCE((? -> 'object'::text) ->> 'id'::text)", +          object.data, +          a.data +        ) +    ) +    |> preload([n, a, o], activity: {a, object: o}) +    |> Repo.all()    end    def read_one(%User{} = user, notification_id) do diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index d58eb7f7d..5033798ae 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -130,14 +130,16 @@ defmodule Pleroma.Object do    def delete(%Object{data: %{"id" => id}} = object) do      with {:ok, _obj} = swap_object_with_tombstone(object),           deleted_activity = Activity.delete_by_ap_id(id), -         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do +         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), +         {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do        {:ok, object, deleted_activity}      end    end    def prune(%Object{data: %{"id" => id}} = object) do      with {:ok, object} <- Repo.delete(object), -         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}") do +         {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), +         {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do        {:ok, object}      end    end diff --git a/lib/pleroma/pagination.ex b/lib/pleroma/pagination.ex index 2b869ccdc..b55379c4a 100644 --- a/lib/pleroma/pagination.ex +++ b/lib/pleroma/pagination.ex @@ -16,6 +16,15 @@ defmodule Pleroma.Pagination do    def fetch_paginated(query, params, type \\ :keyset) +  def fetch_paginated(query, %{"total" => true} = params, :keyset) do +    total = Repo.aggregate(query, :count, :id) + +    %{ +      total: total, +      items: fetch_paginated(query, Map.drop(params, ["total"]), :keyset) +    } +  end +    def fetch_paginated(query, params, :keyset) do      options = cast_params(params) @@ -25,6 +34,15 @@ defmodule Pleroma.Pagination do      |> enforce_order(options)    end +  def fetch_paginated(query, %{"total" => true} = params, :offset) do +    total = Repo.aggregate(query, :count, :id) + +    %{ +      total: total, +      items: fetch_paginated(query, Map.drop(params, ["total"]), :offset) +    } +  end +    def fetch_paginated(query, params, :offset) do      options = cast_params(params) diff --git a/lib/pleroma/plugs/cache.ex b/lib/pleroma/plugs/cache.ex new file mode 100644 index 000000000..a81a861d0 --- /dev/null +++ b/lib/pleroma/plugs/cache.ex @@ -0,0 +1,122 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.Cache do +  @moduledoc """ +  Caches successful GET responses. + +  To enable the cache add the plug to a router pipeline or controller: + +      plug(Pleroma.Plugs.Cache) + +  ## Configuration + +  To configure the plug you need to pass settings as the second argument to the `plug/2` macro: + +      plug(Pleroma.Plugs.Cache, [ttl: nil, query_params: true]) + +  Available options: + +  - `ttl`:  An expiration time (time-to-live). This value should be in milliseconds or `nil` to disable expiration. Defaults to `nil`. +  - `query_params`: Take URL query string into account (`true`), ignore it (`false`) or limit to specific params only (list). Defaults to `true`. + +  Additionally, you can overwrite the TTL inside a controller action by assigning `cache_ttl` to the connection struct: + +      def index(conn, _params) do +        ttl = 60_000 # one minute + +        conn +        |> assign(:cache_ttl, ttl) +        |> render("index.html") +      end + +  """ + +  import Phoenix.Controller, only: [current_path: 1, json: 2] +  import Plug.Conn + +  @behaviour Plug + +  @defaults %{ttl: nil, query_params: true} + +  @impl true +  def init([]), do: @defaults + +  def init(opts) do +    opts = Map.new(opts) +    Map.merge(@defaults, opts) +  end + +  @impl true +  def call(%{method: "GET"} = conn, opts) do +    key = cache_key(conn, opts) + +    case Cachex.get(:web_resp_cache, key) do +      {:ok, nil} -> +        cache_resp(conn, opts) + +      {:ok, record} -> +        send_cached(conn, record) + +      {atom, message} when atom in [:ignore, :error] -> +        render_error(conn, message) +    end +  end + +  def call(conn, _), do: conn + +  # full path including query params +  defp cache_key(conn, %{query_params: true}), do: current_path(conn) + +  # request path without query params +  defp cache_key(conn, %{query_params: false}), do: conn.request_path + +  # request path with specific query params +  defp cache_key(conn, %{query_params: query_params}) when is_list(query_params) do +    query_string = +      conn.params +      |> Map.take(query_params) +      |> URI.encode_query() + +    conn.request_path <> "?" <> query_string +  end + +  defp cache_resp(conn, opts) do +    register_before_send(conn, fn +      %{status: 200, resp_body: body} = conn -> +        ttl = Map.get(conn.assigns, :cache_ttl, opts.ttl) +        key = cache_key(conn, opts) +        content_type = content_type(conn) +        record = {content_type, body} + +        Cachex.put(:web_resp_cache, key, record, ttl: ttl) + +        put_resp_header(conn, "x-cache", "MISS from Pleroma") + +      conn -> +        conn +    end) +  end + +  defp content_type(conn) do +    conn +    |> Plug.Conn.get_resp_header("content-type") +    |> hd() +  end + +  defp send_cached(conn, {content_type, body}) do +    conn +    |> put_resp_content_type(content_type, nil) +    |> put_resp_header("x-cache", "HIT from Pleroma") +    |> send_resp(:ok, body) +    |> halt() +  end + +  defp render_error(conn, message) do +    conn +    |> put_status(:internal_server_error) +    |> json(%{error: message}) +    |> halt() +  end +end diff --git a/lib/pleroma/plugs/trailing_format_plug.ex b/lib/pleroma/plugs/trailing_format_plug.ex new file mode 100644 index 000000000..ce366b218 --- /dev/null +++ b/lib/pleroma/plugs/trailing_format_plug.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Plugs.TrailingFormatPlug do +  @moduledoc "Calls TrailingFormatPlug for specific paths. Ideally we would just do this in the router, but TrailingFormatPlug needs to be called before Plug.Parsers." + +  @behaviour Plug +  @paths [ +    "/api/statusnet", +    "/api/statuses", +    "/api/qvitter", +    "/api/search", +    "/api/account", +    "/api/friends", +    "/api/mutes", +    "/api/media", +    "/api/favorites", +    "/api/blocks", +    "/api/friendships", +    "/api/users", +    "/users", +    "/nodeinfo", +    "/api/help", +    "/api/externalprofile", +    "/notice", +    "/api/pleroma/emoji" +  ] + +  def init(opts) do +    TrailingFormatPlug.init(opts) +  end + +  for path <- @paths do +    def call(%{request_path: unquote(path) <> _} = conn, opts) do +      TrailingFormatPlug.call(conn, opts) +    end +  end + +  def call(conn, _opts), do: conn +end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 29fd6d2ea..1f6a75d03 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -569,8 +569,22 @@ defmodule Pleroma.User do      end)    end -  def get_cached_by_nickname_or_id(nickname_or_id) do -    get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) +  def get_cached_by_nickname_or_id(nickname_or_id, opts \\ []) do +    restrict_to_local = Pleroma.Config.get([:instance, :limit_to_local_content]) + +    cond do +      is_integer(nickname_or_id) or Pleroma.FlakeId.is_flake_id?(nickname_or_id) -> +        get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id) + +      restrict_to_local == false -> +        get_cached_by_nickname(nickname_or_id) + +      restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) -> +        get_cached_by_nickname(nickname_or_id) + +      true -> +        nil +    end    end    def get_by_nickname(nickname) do @@ -1610,4 +1624,13 @@ defmodule Pleroma.User do    def is_internal_user?(%User{nickname: nil}), do: true    def is_internal_user?(%User{local: true, nickname: "internal." <> _}), do: true    def is_internal_user?(_), do: false + +  def change_email(user, email) do +    user +    |> cast(%{email: email}, [:email]) +    |> validate_required([:email]) +    |> unique_constraint(:email) +    |> validate_format(:email, @email_regex) +    |> update_and_set_cache() +  end  end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 4ee9b1885..74663ac97 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -807,7 +807,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do        )      unless opts["skip_preload"] do -      from([thread_mute: tm] in query, where: is_nil(tm)) +      from([thread_mute: tm] in query, where: is_nil(tm.user_id))      else        query      end diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex index 08bf1c752..705dbc1c2 100644 --- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex +++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex @@ -23,6 +23,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    action_fallback(:errors) +  plug(Pleroma.Plugs.Cache, [query_params: false] when action in [:activity, :object])    plug(Pleroma.Web.FederatingPlug when action in [:inbox, :relay])    plug(:set_requester_reachable when action in [:inbox])    plug(:relay_active? when action in [:relay]) @@ -53,8 +54,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do           %Object{} = object <- Object.get_cached_by_ap_id(ap_id),           {_, true} <- {:public?, Visibility.is_public?(object)} do        conn +      |> set_cache_ttl_for(object)        |> put_resp_content_type("application/activity+json") -      |> json(ObjectView.render("object.json", %{object: object})) +      |> put_view(ObjectView) +      |> render("object.json", object: object)      else        {:public?, false} ->          {:error, :not_found} @@ -96,14 +99,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do           %Activity{} = activity <- Activity.normalize(ap_id),           {_, true} <- {:public?, Visibility.is_public?(activity)} do        conn +      |> set_cache_ttl_for(activity)        |> put_resp_content_type("application/activity+json") -      |> json(ObjectView.render("object.json", %{object: activity})) +      |> put_view(ObjectView) +      |> render("object.json", object: activity)      else -      {:public?, false} -> -        {:error, :not_found} +      {:public?, false} -> {:error, :not_found} +      nil -> {:error, :not_found}      end    end +  defp set_cache_ttl_for(conn, %Activity{object: object}) do +    set_cache_ttl_for(conn, object) +  end + +  defp set_cache_ttl_for(conn, entity) do +    ttl = +      case entity do +        %Object{data: %{"type" => "Question"}} -> +          Pleroma.Config.get([:web_cache_ttl, :activity_pub_question]) + +        %Object{} -> +          Pleroma.Config.get([:web_cache_ttl, :activity_pub]) + +        _ -> +          nil +      end + +    assign(conn, :cache_ttl, ttl) +  end +    # GET /relay/following    def following(%{assigns: %{relay: true}} = conn, _params) do      conn @@ -251,22 +276,36 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do    def whoami(_conn, _params), do: {:error, :not_found} -  def read_inbox(%{assigns: %{user: user}} = conn, %{"nickname" => nickname} = params) do -    if nickname == user.nickname do -      conn -      |> put_resp_content_type("application/activity+json") -      |> json(UserView.render("inbox.json", %{user: user, max_id: params["max_id"]})) -    else -      err = -        dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}", -          nickname: nickname, -          as_nickname: user.nickname -        ) +  def read_inbox( +        %{assigns: %{user: %{nickname: nickname} = user}} = conn, +        %{"nickname" => nickname} = params +      ) do +    conn +    |> put_resp_content_type("application/activity+json") +    |> put_view(UserView) +    |> render("inbox.json", user: user, max_id: params["max_id"]) +  end -      conn -      |> put_status(:forbidden) -      |> json(err) -    end +  def read_inbox(%{assigns: %{user: nil}} = conn, %{"nickname" => nickname}) do +    err = dgettext("errors", "can't read inbox of %{nickname}", nickname: nickname) + +    conn +    |> put_status(:forbidden) +    |> json(err) +  end + +  def read_inbox(%{assigns: %{user: %{nickname: as_nickname}}} = conn, %{ +        "nickname" => nickname +      }) do +    err = +      dgettext("errors", "can't read inbox of %{nickname} as %{as_nickname}", +        nickname: nickname, +        as_nickname: as_nickname +      ) + +    conn +    |> put_status(:forbidden) +    |> json(err)    end    def handle_user_activity(user, %{"type" => "Create"} = params) do diff --git a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex index de1eb4aa5..b3547ecd4 100644 --- a/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex +++ b/lib/pleroma/web/activity_pub/mrf/anti_followbot_policy.ex @@ -25,11 +25,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.AntiFollowbotPolicy do    defp score_displayname(_), do: 0.0    defp determine_if_followbot(%User{nickname: nickname, name: displayname}) do -    # nickname will always be a binary string because it's generated by Pleroma. +    # nickname will be a binary string except when following a relay      nick_score = -      nickname -      |> String.downcase() -      |> score_nickname() +      if is_binary(nickname) do +        nickname +        |> String.downcase() +        |> score_nickname() +      else +        0.0 +      end      # displayname will either be a binary string or nil, if a displayname isn't set.      name_score = diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex index 0b9cc4499..cb5755ccc 100644 --- a/lib/pleroma/web/activity_pub/transmogrifier.ex +++ b/lib/pleroma/web/activity_pub/transmogrifier.ex @@ -185,12 +185,12 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do              |> Map.put("context", replied_object.data["context"] || object["conversation"])            else              e -> -              Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") +              Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")                object            end          e -> -          Logger.error("Couldn't fetch \"#{inspect(in_reply_to_id)}\", error: #{inspect(e)}") +          Logger.error("Couldn't fetch #{inspect(in_reply_to_id)}, error: #{inspect(e)}")            object        end      else @@ -464,8 +464,10 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do          %{"type" => "Follow", "object" => followed, "actor" => follower, "id" => id} = data,          _options        ) do -    with %User{local: true} = followed <- User.get_cached_by_ap_id(followed), -         {:ok, %User{} = follower} <- User.get_or_fetch_by_ap_id(follower), +    with %User{local: true} = followed <- +           User.get_cached_by_ap_id(Containment.get_actor(%{"actor" => followed})), +         {:ok, %User{} = follower} <- +           User.get_or_fetch_by_ap_id(Containment.get_actor(%{"actor" => follower})),           {:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do        with deny_follow_blocked <- Pleroma.Config.get([:user, :deny_follow_blocked]),             {_, false} <- {:user_blocked, User.blocks?(followed, follower) && deny_follow_blocked}, diff --git a/lib/pleroma/web/admin_api/config.ex b/lib/pleroma/web/admin_api/config.ex index a10cc779b..1917a5580 100644 --- a/lib/pleroma/web/admin_api/config.ex +++ b/lib/pleroma/web/admin_api/config.ex @@ -90,6 +90,8 @@ defmodule Pleroma.Web.AdminAPI.Config do      for v <- entity, into: [], do: do_convert(v)    end +  defp do_convert(%Regex{} = entity), do: inspect(entity) +    defp do_convert(entity) when is_map(entity) do      for {k, v} <- entity, into: %{}, do: {do_convert(k), do_convert(v)}    end @@ -122,7 +124,7 @@ defmodule Pleroma.Web.AdminAPI.Config do    def transform(entity), do: :erlang.term_to_binary(entity) -  defp do_transform(%Regex{} = entity) when is_map(entity), do: entity +  defp do_transform(%Regex{} = entity), do: entity    defp do_transform(%{"tuple" => [":dispatch", [entity]]}) do      {dispatch_settings, []} = do_eval(entity) @@ -154,8 +156,15 @@ defmodule Pleroma.Web.AdminAPI.Config do    defp do_transform(entity), do: entity    defp do_transform_string("~r/" <> pattern) do -    pattern = String.trim_trailing(pattern, "/") -    ~r/#{pattern}/ +    modificator = String.split(pattern, "/") |> List.last() +    pattern = String.trim_trailing(pattern, "/" <> modificator) + +    case modificator do +      "" -> ~r/#{pattern}/ +      "i" -> ~r/#{pattern}/i +      "u" -> ~r/#{pattern}/u +      "s" -> ~r/#{pattern}/s +    end    end    defp do_transform_string(":" <> atom), do: String.to_atom(atom) diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex index c123530dc..eb805e853 100644 --- a/lib/pleroma/web/endpoint.ex +++ b/lib/pleroma/web/endpoint.ex @@ -57,7 +57,7 @@ defmodule Pleroma.Web.Endpoint do      plug(Phoenix.CodeReloader)    end -  plug(TrailingFormatPlug) +  plug(Pleroma.Plugs.TrailingFormatPlug)    plug(Plug.RequestId)    plug(Plug.Logger) diff --git a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex index 83e877c0e..c54462bb3 100644 --- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex @@ -290,7 +290,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def user(%{assigns: %{user: for_user}} = conn, %{"id" => nickname_or_id}) do -    with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id), +    with %User{} = user <- User.get_cached_by_nickname_or_id(nickname_or_id, for: for_user),           true <- User.auth_active?(user) || user.id == for_user.id || User.superuser?(for_user) do        account = AccountView.render("account.json", %{user: user, for: for_user})        json(conn, account) @@ -390,7 +390,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do    end    def user_statuses(%{assigns: %{user: reading_user}} = conn, params) do -    with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"]) do +    with %User{} = user <- User.get_cached_by_nickname_or_id(params["id"], for: reading_user) do        params =          params          |> Map.put("tag", params["tagged"]) @@ -427,6 +427,20 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do      |> render("index.json", %{activities: activities, for: user, as: :activity})    end +  def get_statuses(%{assigns: %{user: user}} = conn, %{"ids" => ids}) do +    limit = 100 + +    activities = +      ids +      |> Enum.take(limit) +      |> Activity.all_by_ids_with_object() +      |> Enum.filter(&Visibility.visible_for_user?(&1, user)) + +    conn +    |> put_view(StatusView) +    |> render("index.json", activities: activities, for: user, as: :activity) +  end +    def get_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do      with %Activity{} = activity <- Activity.get_by_id_with_object(id),           true <- Visibility.visible_for_user?(activity, user) do diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index 27e9cab06..ec8eadcaa 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -14,7 +14,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do    alias Pleroma.Web.MastodonAPI.StatusView    def render("index.json", %{notifications: notifications, for: user}) do -    render_many(notifications, NotificationView, "show.json", %{for: user}) +    safe_render_many(notifications, NotificationView, "show.json", %{for: user})    end    def render("show.json", %{ diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index a4ee0b5dd..ef796cddd 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -73,14 +73,12 @@ 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), -      parallel +      Map.put(opts, :replied_to_activities, replied_to_activities)      )    end @@ -299,7 +297,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          content: %{"text/plain" => content_plaintext},          spoiler_text: %{"text/plain" => summary_plaintext},          expires_at: expires_at, -        direct_conversation_id: direct_conversation_id +        direct_conversation_id: direct_conversation_id, +        thread_muted: thread_muted?        }      }    end @@ -384,16 +383,27 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do        end      if options do -      end_time = -        (object.data["closed"] || object.data["endTime"]) -        |> NaiveDateTime.from_iso8601!() - -      expired = -        end_time -        |> NaiveDateTime.compare(NaiveDateTime.utc_now()) -        |> case do -          :lt -> true -          _ -> false +      {end_time, expired} = +        case object.data["closed"] || object.data["endTime"] do +          end_time when is_binary(end_time) -> +            end_time = +              (object.data["closed"] || object.data["endTime"]) +              |> NaiveDateTime.from_iso8601!() + +            expired = +              end_time +              |> NaiveDateTime.compare(NaiveDateTime.utc_now()) +              |> case do +                :lt -> true +                _ -> false +              end + +            end_time = Utils.to_masto_date(end_time) + +            {end_time, expired} + +          _ -> +            {nil, false}          end        voted = @@ -420,7 +430,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do          # Mastodon uses separate ids for polls, but an object can't have          # more than one poll embedded so object id is fine          id: to_string(object.id), -        expires_at: Utils.to_masto_date(end_time), +        expires_at: end_time,          expired: expired,          multiple: multiple,          votes_count: votes_count, @@ -487,7 +497,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do      object_tags = for tag when is_binary(tag) <- object_tags, do: tag      Enum.reduce(object_tags, [], fn tag, tags -> -      tags ++ [%{name: tag, url: "/tag/#{tag}"}] +      tags ++ [%{name: tag, url: "/tag/#{URI.encode(tag)}"}]      end)    end diff --git a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex index bb090d37f..110240115 100644 --- a/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/pleroma_api_controller.ex @@ -11,10 +11,12 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do    alias Pleroma.Object    alias Pleroma.User    alias Pleroma.Conversation.Participation +  alias Pleroma.Notification    alias Pleroma.Web.ActivityPub.ActivityPub    alias Pleroma.Web.CommonAPI    alias Pleroma.Web.MastodonAPI.AccountView    alias Pleroma.Web.MastodonAPI.ConversationView +  alias Pleroma.Web.MastodonAPI.NotificationView    alias Pleroma.Web.MastodonAPI.StatusView    def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do @@ -109,4 +111,27 @@ defmodule Pleroma.Web.PleromaAPI.PleromaAPIController do        |> render("participation.json", %{participation: participation, for: user})      end    end + +  def read_notification(%{assigns: %{user: user}} = conn, %{"id" => notification_id}) do +    with {:ok, notification} <- Notification.read_one(user, notification_id) do +      conn +      |> put_view(NotificationView) +      |> render("show.json", %{notification: notification, for: user}) +    else +      {:error, message} -> +        conn +        |> put_status(:bad_request) +        |> json(%{"error" => message}) +    end +  end + +  def read_notification(%{assigns: %{user: user}} = conn, %{"max_id" => max_id}) do +    with notifications <- Notification.set_read_up_to(user, max_id) do +      notifications = Enum.take(notifications, 80) + +      conn +      |> put_view(NotificationView) +      |> render("index.json", %{notifications: notifications, for: user}) +    end +  end  end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ec6179420..6354510cf 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -224,6 +224,7 @@ defmodule Pleroma.Web.Router do      scope [] do        pipe_through(:oauth_write) +      post("/change_email", UtilController, :change_email)        post("/change_password", UtilController, :change_password)        post("/delete_account", UtilController, :delete_account)        put("/notification_settings", UtilController, :update_notificaton_settings) @@ -236,12 +237,6 @@ defmodule Pleroma.Web.Router do        post("/blocks_import", UtilController, :blocks_import)        post("/follow_import", UtilController, :follow_import)      end - -    scope [] do -      pipe_through(:oauth_read) - -      post("/notifications/read", UtilController, :notifications_read) -    end    end    scope "/oauth", Pleroma.Web.OAuth do @@ -284,6 +279,7 @@ defmodule Pleroma.Web.Router do        pipe_through(:oauth_write)        patch("/conversations/:id", PleromaAPIController, :update_conversation)        post("/statuses/:id/react_with_emoji", PleromaAPIController, :react_with_emoji) +      post("/notifications/read", PleromaAPIController, :read_notification)      end    end @@ -455,6 +451,7 @@ defmodule Pleroma.Web.Router do        get("/timelines/tag/:tag", MastodonAPIController, :hashtag_timeline)        get("/timelines/list/:list_id", MastodonAPIController, :list_timeline) +      get("/statuses", MastodonAPIController, :get_statuses)        get("/statuses/:id", MastodonAPIController, :get_status)        get("/statuses/:id/context", MastodonAPIController, :get_context) @@ -489,53 +486,12 @@ defmodule Pleroma.Web.Router do    scope "/api", Pleroma.Web do      pipe_through(:api) -    post("/account/register", TwitterAPI.Controller, :register) -    post("/account/password_reset", TwitterAPI.Controller, :password_reset) - -    post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email) -      get(        "/account/confirm_email/:user_id/:token",        TwitterAPI.Controller,        :confirm_email,        as: :confirm_email      ) - -    scope [] do -      pipe_through(:oauth_read_or_public) - -      get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline) -      get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline) -      get("/users/show", TwitterAPI.Controller, :show_user) - -      get("/statuses/followers", TwitterAPI.Controller, :followers) -      get("/statuses/friends", TwitterAPI.Controller, :friends) -      get("/statuses/blocks", TwitterAPI.Controller, :blocks) -      get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status) -      get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation) - -      get("/search", TwitterAPI.Controller, :search) -      get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline) -    end -  end - -  scope "/api", Pleroma.Web do -    pipe_through([:api, :oauth_read_or_public]) - -    get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline) - -    get( -      "/statuses/public_and_external_timeline", -      TwitterAPI.Controller, -      :public_and_external_timeline -    ) - -    get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline) -  end - -  scope "/api", Pleroma.Web, as: :twitter_api_search do -    pipe_through([:api, :oauth_read_or_public]) -    get("/pleroma/search_user", TwitterAPI.Controller, :search_user)    end    scope "/api", Pleroma.Web, as: :authenticated_twitter_api do @@ -547,67 +503,8 @@ defmodule Pleroma.Web.Router do      scope [] do        pipe_through(:oauth_read) -      get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials) -      post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials) - -      get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline) -      get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline) -      get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline) -      get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline) -      get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline) -      get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications) - -      get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests) - -      get("/friends/ids", TwitterAPI.Controller, :friends_ids) -      get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array) - -      get("/mutes/users/ids", TwitterAPI.Controller, :empty_array) -      get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array) - -      get("/externalprofile/show", TwitterAPI.Controller, :external_profile) -        post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)      end - -    scope [] do -      pipe_through(:oauth_write) - -      post("/account/update_profile", TwitterAPI.Controller, :update_profile) -      post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner) -      post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background) - -      post("/statuses/update", TwitterAPI.Controller, :status_update) -      post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet) -      post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet) -      post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post) - -      post("/statuses/pin/:id", TwitterAPI.Controller, :pin) -      post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin) - -      post("/statusnet/media/upload", TwitterAPI.Controller, :upload) -      post("/media/upload", TwitterAPI.Controller, :upload_json) -      post("/media/metadata/create", TwitterAPI.Controller, :update_media) - -      post("/favorites/create/:id", TwitterAPI.Controller, :favorite) -      post("/favorites/create", TwitterAPI.Controller, :favorite) -      post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite) - -      post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar) -    end - -    scope [] do -      pipe_through(:oauth_follow) - -      post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request) -      post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request) - -      post("/friendships/create", TwitterAPI.Controller, :follow) -      post("/friendships/destroy", TwitterAPI.Controller, :unfollow) - -      post("/blocks/create", TwitterAPI.Controller, :block) -      post("/blocks/destroy", TwitterAPI.Controller, :unblock) -    end    end    pipeline :ap_service_actor do diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex index 3405bd3b7..867787c57 100644 --- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex +++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex @@ -314,6 +314,25 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do      end    end +  def change_email(%{assigns: %{user: user}} = conn, params) do +    case CommonAPI.Utils.confirm_current_password(user, params["password"]) do +      {:ok, user} -> +        with {:ok, _user} <- User.change_email(user, params["email"]) do +          json(conn, %{status: "success"}) +        else +          {:error, changeset} -> +            {_, {error, _}} = Enum.at(changeset.errors, 0) +            json(conn, %{error: "Email #{error}."}) + +          _ -> +            json(conn, %{error: "Unable to change email."}) +        end + +      {:error, msg} -> +        json(conn, %{error: msg}) +    end +  end +    def delete_account(%{assigns: %{user: user}} = conn, params) do      case CommonAPI.Utils.confirm_current_password(user, params["password"]) do        {:ok, user} -> diff --git a/lib/pleroma/web/twitter_api/representers/base_representer.ex b/lib/pleroma/web/twitter_api/representers/base_representer.ex deleted file mode 100644 index 3d31e6079..000000000 --- a/lib/pleroma/web/twitter_api/representers/base_representer.ex +++ /dev/null @@ -1,38 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do -  defmacro __using__(_opts) do -    quote do -      def to_json(object) do -        to_json(object, %{}) -      end - -      def to_json(object, options) do -        object -        |> to_map(options) -        |> Jason.encode!() -      end - -      def enum_to_list(enum, options) do -        mapping = fn el -> to_map(el, options) end -        Enum.map(enum, mapping) -      end - -      def to_map(object) do -        to_map(object, %{}) -      end - -      def enum_to_json(enum) do -        enum_to_json(enum, %{}) -      end - -      def enum_to_json(enum, options) do -        enum -        |> enum_to_list(options) -        |> Jason.encode!() -      end -    end -  end -end diff --git a/lib/pleroma/web/twitter_api/representers/object_representer.ex b/lib/pleroma/web/twitter_api/representers/object_representer.ex deleted file mode 100644 index 47130ba06..000000000 --- a/lib/pleroma/web/twitter_api/representers/object_representer.ex +++ /dev/null @@ -1,39 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do -  use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter -  alias Pleroma.Object - -  def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do -    data = object.data - -    %{ -      url: url["href"] |> Pleroma.Web.MediaProxy.url(), -      mimetype: url["mediaType"] || url["mimeType"], -      id: data["uuid"], -      oembed: false, -      description: data["name"] -    } -  end - -  def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do -    %{ -      url: url |> Pleroma.Web.MediaProxy.url(), -      mimetype: data["mediaType"] || data["mimeType"], -      id: data["uuid"], -      oembed: false, -      description: data["name"] -    } -  end - -  def to_map(%Object{}, _opts) do -    %{} -  end - -  # If we only get the naked data, wrap in an object -  def to_map(%{} = data, opts) do -    to_map(%Object{data: data}, opts) -  end -end diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex index 80082ea84..8eda762c7 100644 --- a/lib/pleroma/web/twitter_api/twitter_api.ex +++ b/lib/pleroma/web/twitter_api/twitter_api.ex @@ -3,133 +3,14 @@  # SPDX-License-Identifier: AGPL-3.0-only  defmodule Pleroma.Web.TwitterAPI.TwitterAPI do -  alias Pleroma.Activity    alias Pleroma.Emails.Mailer    alias Pleroma.Emails.UserEmail    alias Pleroma.Repo    alias Pleroma.User    alias Pleroma.UserInviteToken -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.TwitterAPI.UserView - -  import Ecto.Query    require Pleroma.Constants -  def create_status(%User{} = user, %{"status" => _} = data) do -    CommonAPI.post(user, data) -  end - -  def delete(%User{} = user, id) do -    with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id), -         {:ok, activity} <- CommonAPI.delete(id, user) do -      {:ok, activity} -    end -  end - -  def follow(%User{} = follower, params) do -    with {:ok, %User{} = followed} <- get_user(params) do -      CommonAPI.follow(follower, followed) -    end -  end - -  def unfollow(%User{} = follower, params) do -    with {:ok, %User{} = unfollowed} <- get_user(params), -         {:ok, follower} <- CommonAPI.unfollow(follower, unfollowed) do -      {:ok, follower, unfollowed} -    end -  end - -  def block(%User{} = blocker, params) do -    with {:ok, %User{} = blocked} <- get_user(params), -         {:ok, blocker} <- User.block(blocker, blocked), -         {:ok, _activity} <- ActivityPub.block(blocker, blocked) do -      {:ok, blocker, blocked} -    else -      err -> err -    end -  end - -  def unblock(%User{} = blocker, params) do -    with {:ok, %User{} = blocked} <- get_user(params), -         {:ok, blocker} <- User.unblock(blocker, blocked), -         {:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do -      {:ok, blocker, blocked} -    else -      err -> err -    end -  end - -  def repeat(%User{} = user, ap_id_or_id) do -    with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do -      {:ok, activity} -    end -  end - -  def unrepeat(%User{} = user, ap_id_or_id) do -    with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do -      {:ok, activity} -    end -  end - -  def pin(%User{} = user, ap_id_or_id) do -    CommonAPI.pin(ap_id_or_id, user) -  end - -  def unpin(%User{} = user, ap_id_or_id) do -    CommonAPI.unpin(ap_id_or_id, user) -  end - -  def fav(%User{} = user, ap_id_or_id) do -    with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do -      {:ok, activity} -    end -  end - -  def unfav(%User{} = user, ap_id_or_id) do -    with {:ok, _unfav, _fav, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user), -         %Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do -      {:ok, activity} -    end -  end - -  def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do -    {:ok, object} = ActivityPub.upload(file, actor: User.ap_id(user)) - -    url = List.first(object.data["url"]) -    href = url["href"] -    type = url["mediaType"] - -    case format do -      "xml" -> -        # Fake this as good as possible... -        """ -        <?xml version="1.0" encoding="UTF-8"?> -        <rsp stat="ok" xmlns:atom="http://www.w3.org/2005/Atom"> -        <mediaid>#{object.id}</mediaid> -        <media_id>#{object.id}</media_id> -        <media_id_string>#{object.id}</media_id_string> -        <media_url>#{href}</media_url> -        <mediaurl>#{href}</mediaurl> -        <atom:link rel="enclosure" href="#{href}" type="#{type}"></atom:link> -        </rsp> -        """ - -      "json" -> -        %{ -          media_id: object.id, -          media_id_string: "#{object.id}}", -          media_url: href, -          size: 0 -        } -        |> Jason.encode!() -    end -  end -    def register_user(params, opts \\ []) do      token = params["token"] @@ -236,80 +117,4 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do          {:error, "unknown user"}      end    end - -  def get_user(user \\ nil, params) do -    case params do -      %{"user_id" => user_id} -> -        case User.get_cached_by_nickname_or_id(user_id) do -          nil -> -            {:error, "No user with such user_id"} - -          %User{info: %{deactivated: true}} -> -            {:error, "User has been disabled"} - -          user -> -            {:ok, user} -        end - -      %{"screen_name" => nickname} -> -        case User.get_cached_by_nickname(nickname) do -          nil -> {:error, "No user with such screen_name"} -          target -> {:ok, target} -        end - -      _ -> -        if user do -          {:ok, user} -        else -          {:error, "You need to specify screen_name or user_id"} -        end -    end -  end - -  defp parse_int(string, default) - -  defp parse_int(string, default) when is_binary(string) do -    with {n, _} <- Integer.parse(string) do -      n -    else -      _e -> default -    end -  end - -  defp parse_int(_, default), do: default - -  # TODO: unify the search query with MastoAPI one and do only pagination here -  def search(_user, %{"q" => query} = params) do -    limit = parse_int(params["rpp"], 20) -    page = parse_int(params["page"], 1) -    offset = (page - 1) * limit - -    q = -      from( -        [a, o] in Activity.with_preloaded_object(Activity), -        where: fragment("?->>'type' = 'Create'", a.data), -        where: ^Pleroma.Constants.as_public() in a.recipients, -        where: -          fragment( -            "to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)", -            o.data, -            ^query -          ), -        limit: ^limit, -        offset: ^offset, -        # this one isn't indexed so psql won't take the wrong index. -        order_by: [desc: :inserted_at] -      ) - -    _activities = Repo.all(q) -  end - -  def get_external_profile(for_user, uri) do -    with {:ok, %User{} = user} <- User.get_or_fetch(uri) do -      {:ok, UserView.render("show.json", %{user: user, for: for_user})} -    else -      _e -> -        {:error, "Couldn't find user"} -    end -  end  end diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex index 5dfab6a6c..42234ae09 100644 --- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex +++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex @@ -5,448 +5,16 @@  defmodule Pleroma.Web.TwitterAPI.Controller do    use Pleroma.Web, :controller -  import Pleroma.Web.ControllerHelper, only: [json_response: 3] -    alias Ecto.Changeset -  alias Pleroma.Activity -  alias Pleroma.Formatter    alias Pleroma.Notification -  alias Pleroma.Object -  alias Pleroma.Repo    alias Pleroma.User -  alias Pleroma.Web.ActivityPub.ActivityPub -  alias Pleroma.Web.ActivityPub.Visibility -  alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.CommonAPI.Utils    alias Pleroma.Web.OAuth.Token -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.TwitterAPI.NotificationView    alias Pleroma.Web.TwitterAPI.TokenView -  alias Pleroma.Web.TwitterAPI.TwitterAPI -  alias Pleroma.Web.TwitterAPI.UserView    require Logger -  plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset) -  plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])    action_fallback(:errors) -  def verify_credentials(%{assigns: %{user: user}} = conn, _params) do -    token = Phoenix.Token.sign(conn, "user socket", user.id) - -    conn -    |> put_view(UserView) -    |> render("show.json", %{user: user, token: token, for: user}) -  end - -  def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do -    with media_ids <- extract_media_ids(status_data), -         {:ok, activity} <- -           TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do -      conn -      |> json(ActivityView.render("activity.json", activity: activity, for: user)) -    else -      _ -> empty_status_reply(conn) -    end -  end - -  def status_update(conn, _status_data) do -    empty_status_reply(conn) -  end - -  defp empty_status_reply(conn) do -    bad_request_reply(conn, "Client must provide a 'status' parameter with a value.") -  end - -  defp extract_media_ids(status_data) do -    with media_ids when not is_nil(media_ids) <- status_data["media_ids"], -         split_ids <- String.split(media_ids, ","), -         clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do -      clean_ids -    else -      _e -> [] -    end -  end - -  def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do -    params = -      params -      |> Map.put("type", ["Create", "Announce"]) -      |> Map.put("blocking_user", user) - -    activities = ActivityPub.fetch_public_activities(params) - -    conn -    |> put_view(ActivityView) -    |> render("index.json", %{activities: activities, for: user}) -  end - -  def public_timeline(%{assigns: %{user: user}} = conn, params) do -    params = -      params -      |> Map.put("type", ["Create", "Announce"]) -      |> Map.put("local_only", true) -      |> Map.put("blocking_user", user) - -    activities = ActivityPub.fetch_public_activities(params) - -    conn -    |> put_view(ActivityView) -    |> render("index.json", %{activities: activities, for: user}) -  end - -  def friends_timeline(%{assigns: %{user: user}} = conn, params) do -    params = -      params -      |> Map.put("type", ["Create", "Announce", "Follow", "Like"]) -      |> Map.put("blocking_user", user) -      |> Map.put("user", user) - -    activities = ActivityPub.fetch_activities([user.ap_id | user.following], params) - -    conn -    |> put_view(ActivityView) -    |> render("index.json", %{activities: activities, for: user}) -  end - -  def show_user(conn, params) do -    for_user = conn.assigns.user - -    with {:ok, shown} <- TwitterAPI.get_user(params), -         true <- -           User.auth_active?(shown) || -             (for_user && (for_user.id == shown.id || User.superuser?(for_user))) do -      params = -        if for_user do -          %{user: shown, for: for_user} -        else -          %{user: shown} -        end - -      conn -      |> put_view(UserView) -      |> render("show.json", params) -    else -      {:error, msg} -> -        bad_request_reply(conn, msg) - -      false -> -        conn -        |> put_status(404) -        |> json(%{error: "Unconfirmed user"}) -    end -  end - -  def user_timeline(%{assigns: %{user: user}} = conn, params) do -    case TwitterAPI.get_user(user, params) do -      {:ok, target_user} -> -        # Twitter and ActivityPub use a different name and sense for this parameter. -        {include_rts, params} = Map.pop(params, "include_rts") - -        params = -          case include_rts do -            x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true") -            _ -> params -          end - -        activities = ActivityPub.fetch_user_activities(target_user, user, params) - -        conn -        |> put_view(ActivityView) -        |> render("index.json", %{activities: activities, for: user}) - -      {:error, msg} -> -        bad_request_reply(conn, msg) -    end -  end - -  def mentions_timeline(%{assigns: %{user: user}} = conn, params) do -    params = -      params -      |> Map.put("type", ["Create", "Announce", "Follow", "Like"]) -      |> Map.put("blocking_user", user) -      |> Map.put(:visibility, ~w[unlisted public private]) - -    activities = ActivityPub.fetch_activities([user.ap_id], params) - -    conn -    |> put_view(ActivityView) -    |> render("index.json", %{activities: activities, for: user}) -  end - -  def dm_timeline(%{assigns: %{user: user}} = conn, params) do -    params = -      params -      |> Map.put("type", "Create") -      |> Map.put("blocking_user", user) -      |> Map.put("user", user) -      |> Map.put(:visibility, "direct") -      |> Map.put(:order, :desc) - -    activities = -      ActivityPub.fetch_activities_query([user.ap_id], params) -      |> Repo.all() - -    conn -    |> put_view(ActivityView) -    |> render("index.json", %{activities: activities, for: user}) -  end - -  def notifications(%{assigns: %{user: user}} = conn, params) do -    params = -      if Map.has_key?(params, "with_muted") do -        Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"]) -      else -        params -      end - -    notifications = Notification.for_user(user, params) - -    conn -    |> put_view(NotificationView) -    |> render("notification.json", %{notifications: notifications, for: user}) -  end - -  def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do -    Notification.set_read_up_to(user, latest_id) - -    notifications = Notification.for_user(user, params) - -    conn -    |> put_view(NotificationView) -    |> render("notification.json", %{notifications: notifications, for: user}) -  end - -  def notifications_read(%{assigns: %{user: _user}} = conn, _) do -    bad_request_reply(conn, "You need to specify latest_id") -  end - -  def follow(%{assigns: %{user: user}} = conn, params) do -    case TwitterAPI.follow(user, params) do -      {:ok, user, followed, _activity} -> -        conn -        |> put_view(UserView) -        |> render("show.json", %{user: followed, for: user}) - -      {:error, msg} -> -        forbidden_json_reply(conn, msg) -    end -  end - -  def block(%{assigns: %{user: user}} = conn, params) do -    case TwitterAPI.block(user, params) do -      {:ok, user, blocked} -> -        conn -        |> put_view(UserView) -        |> render("show.json", %{user: blocked, for: user}) - -      {:error, msg} -> -        forbidden_json_reply(conn, msg) -    end -  end - -  def unblock(%{assigns: %{user: user}} = conn, params) do -    case TwitterAPI.unblock(user, params) do -      {:ok, user, blocked} -> -        conn -        |> put_view(UserView) -        |> render("show.json", %{user: blocked, for: user}) - -      {:error, msg} -> -        forbidden_json_reply(conn, msg) -    end -  end - -  def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.delete(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    end -  end - -  def unfollow(%{assigns: %{user: user}} = conn, params) do -    case TwitterAPI.unfollow(user, params) do -      {:ok, user, unfollowed} -> -        conn -        |> put_view(UserView) -        |> render("show.json", %{user: unfollowed, for: user}) - -      {:error, msg} -> -        forbidden_json_reply(conn, msg) -    end -  end - -  def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with %Activity{} = activity <- Activity.get_by_id(id), -         true <- Visibility.visible_for_user?(activity, user) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    end -  end - -  def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with context when is_binary(context) <- Utils.conversation_id_to_context(id), -         activities <- -           ActivityPub.fetch_activities_for_context(context, %{ -             "blocking_user" => user, -             "user" => user -           }) do -      conn -      |> put_view(ActivityView) -      |> render("index.json", %{activities: activities, for: user}) -    end -  end - -  @doc """ -  Updates metadata of uploaded media object. -  Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create). -  """ -  def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do -    object = Repo.get(Object, id) -    description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"] - -    {conn, status, response_body} = -      cond do -        !object -> -          {halt(conn), :not_found, ""} - -        !Object.authorize_mutation(object, user) -> -          {halt(conn), :forbidden, "You can only update your own uploads."} - -        !is_binary(description) -> -          {conn, :not_modified, ""} - -        true -> -          new_data = Map.put(object.data, "name", description) - -          {:ok, _} = -            object -            |> Object.change(%{data: new_data}) -            |> Repo.update() - -          {conn, :no_content, ""} -      end - -    conn -    |> put_status(status) -    |> json(response_body) -  end - -  def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do -    response = TwitterAPI.upload(media, user) - -    conn -    |> put_resp_content_type("application/atom+xml") -    |> send_resp(200, response) -  end - -  def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do -    response = TwitterAPI.upload(media, user, "json") - -    conn -    |> json_reply(200, response) -  end - -  def get_by_id_or_ap_id(id) do -    activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id) - -    if activity.data["type"] == "Create" do -      activity -    else -      Activity.get_create_by_object_ap_id(activity.data["object"]) -    end -  end - -  def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.fav(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    else -      _ -> json_reply(conn, 400, Jason.encode!(%{})) -    end -  end - -  def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.unfav(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    else -      _ -> json_reply(conn, 400, Jason.encode!(%{})) -    end -  end - -  def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.repeat(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    else -      _ -> json_reply(conn, 400, Jason.encode!(%{})) -    end -  end - -  def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    else -      _ -> json_reply(conn, 400, Jason.encode!(%{})) -    end -  end - -  def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.pin(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    else -      {:error, message} -> bad_request_reply(conn, message) -      err -> err -    end -  end - -  def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do -    with {:ok, activity} <- TwitterAPI.unpin(user, id) do -      conn -      |> put_view(ActivityView) -      |> render("activity.json", %{activity: activity, for: user}) -    else -      {:error, message} -> bad_request_reply(conn, message) -      err -> err -    end -  end - -  def register(conn, params) do -    with {:ok, user} <- TwitterAPI.register_user(params) do -      conn -      |> put_view(UserView) -      |> render("show.json", %{user: user}) -    else -      {:error, errors} -> -        conn -        |> json_reply(400, Jason.encode!(errors)) -    end -  end - -  def password_reset(conn, params) do -    nickname_or_email = params["email"] || params["nickname"] - -    with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do -      json_response(conn, :no_content, "") -    else -      {:error, "unknown user"} -> -        send_resp(conn, :not_found, "") - -      {:error, _} -> -        send_resp(conn, :bad_request, "") -    end -  end -    def confirm_email(conn, %{"user_id" => uid, "token" => token}) do      with %User{} = user <- User.get_cached_by_id(uid),           true <- user.local, @@ -460,147 +28,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      end    end -  def resend_confirmation_email(conn, params) do -    nickname_or_email = params["email"] || params["nickname"] - -    with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email), -         {:ok, _} <- User.try_send_confirmation_email(user) do -      conn -      |> json_response(:no_content, "") -    end -  end - -  def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do -    change = Changeset.change(user, %{avatar: nil}) -    {:ok, user} = User.update_and_set_cache(change) -    CommonAPI.update(user) - -    conn -    |> put_view(UserView) -    |> render("show.json", %{user: user, for: user}) -  end - -  def update_avatar(%{assigns: %{user: user}} = conn, params) do -    {:ok, object} = ActivityPub.upload(params, type: :avatar) -    change = Changeset.change(user, %{avatar: object.data}) -    {:ok, user} = User.update_and_set_cache(change) -    CommonAPI.update(user) - -    conn -    |> put_view(UserView) -    |> render("show.json", %{user: user, for: user}) -  end - -  def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do -    with new_info <- %{"banner" => %{}}, -         info_cng <- User.Info.profile_update(user.info, new_info), -         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), -         {:ok, user} <- User.update_and_set_cache(changeset) do -      CommonAPI.update(user) -      response = %{url: nil} |> Jason.encode!() - -      conn -      |> json_reply(200, response) -    end -  end - -  def update_banner(%{assigns: %{user: user}} = conn, params) do -    with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner), -         new_info <- %{"banner" => object.data}, -         info_cng <- User.Info.profile_update(user.info, new_info), -         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), -         {:ok, user} <- User.update_and_set_cache(changeset) do -      CommonAPI.update(user) -      %{"url" => [%{"href" => href} | _]} = object.data -      response = %{url: href} |> Jason.encode!() - -      conn -      |> json_reply(200, response) -    end -  end - -  def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do -    with new_info <- %{"background" => %{}}, -         info_cng <- User.Info.profile_update(user.info, new_info), -         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), -         {:ok, _user} <- User.update_and_set_cache(changeset) do -      response = %{url: nil} |> Jason.encode!() - -      conn -      |> json_reply(200, response) -    end -  end - -  def update_background(%{assigns: %{user: user}} = conn, params) do -    with {:ok, object} <- ActivityPub.upload(params, type: :background), -         new_info <- %{"background" => object.data}, -         info_cng <- User.Info.profile_update(user.info, new_info), -         changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng), -         {:ok, _user} <- User.update_and_set_cache(changeset) do -      %{"url" => [%{"href" => href} | _]} = object.data -      response = %{url: href} |> Jason.encode!() - -      conn -      |> json_reply(200, response) -    end -  end - -  def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do -    with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri), -         response <- Jason.encode!(user_map) do -      conn -      |> json_reply(200, response) -    else -      _e -> -        conn -        |> put_status(404) -        |> json(%{error: "Can't find user"}) -    end -  end - -  def followers(%{assigns: %{user: for_user}} = conn, params) do -    {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1) - -    with {:ok, user} <- TwitterAPI.get_user(for_user, params), -         {:ok, followers} <- User.get_followers(user, page) do -      followers = -        cond do -          for_user && user.id == for_user.id -> followers -          user.info.hide_followers -> [] -          true -> followers -        end - -      conn -      |> put_view(UserView) -      |> render("index.json", %{users: followers, for: conn.assigns[:user]}) -    else -      _e -> bad_request_reply(conn, "Can't get followers") -    end -  end - -  def friends(%{assigns: %{user: for_user}} = conn, params) do -    {:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1) -    {:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false) - -    page = if export, do: nil, else: page - -    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), -         {:ok, friends} <- User.get_friends(user, page) do -      friends = -        cond do -          for_user && user.id == for_user.id -> friends -          user.info.hide_follows -> [] -          true -> friends -        end - -      conn -      |> put_view(UserView) -      |> render("index.json", %{users: friends, for: conn.assigns[:user]}) -    else -      _e -> bad_request_reply(conn, "Can't get friends") -    end -  end -    def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do      with oauth_tokens <- Token.get_user_tokens(user) do        conn @@ -615,160 +42,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      json_reply(conn, 201, "")    end -  def blocks(%{assigns: %{user: user}} = conn, _params) do -    with blocked_users <- User.blocked_users(user) do -      conn -      |> put_view(UserView) -      |> render("index.json", %{users: blocked_users, for: user}) -    end -  end - -  def friend_requests(conn, params) do -    with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params), -         {:ok, friend_requests} <- User.get_follow_requests(user) do -      conn -      |> put_view(UserView) -      |> render("index.json", %{users: friend_requests, for: conn.assigns[:user]}) -    else -      _e -> bad_request_reply(conn, "Can't get friend requests") -    end -  end - -  def approve_friend_request(conn, %{"user_id" => uid} = _params) do -    with followed <- conn.assigns[:user], -         %User{} = follower <- User.get_cached_by_id(uid), -         {:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do -      conn -      |> put_view(UserView) -      |> render("show.json", %{user: follower, for: followed}) -    else -      e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}") -    end -  end - -  def deny_friend_request(conn, %{"user_id" => uid} = _params) do -    with followed <- conn.assigns[:user], -         %User{} = follower <- User.get_cached_by_id(uid), -         {:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do -      conn -      |> put_view(UserView) -      |> render("show.json", %{user: follower, for: followed}) -    else -      e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}") -    end -  end - -  def friends_ids(%{assigns: %{user: user}} = conn, _params) do -    with {:ok, friends} <- User.get_friends(user) do -      ids = -        friends -        |> Enum.map(fn x -> x.id end) -        |> Jason.encode!() - -      json(conn, ids) -    else -      _e -> bad_request_reply(conn, "Can't get friends") -    end -  end - -  def empty_array(conn, _params) do -    json(conn, Jason.encode!([])) -  end - -  def raw_empty_array(conn, _params) do -    json(conn, []) -  end - -  defp build_info_cng(user, params) do -    info_params = -      [ -        "no_rich_text", -        "locked", -        "hide_followers", -        "hide_follows", -        "hide_favorites", -        "show_role", -        "skip_thread_containment" -      ] -      |> Enum.reduce(%{}, fn key, res -> -        if value = params[key] do -          Map.put(res, key, value == "true") -        else -          res -        end -      end) - -    info_params = -      if value = params["default_scope"] do -        Map.put(info_params, "default_scope", value) -      else -        info_params -      end - -    User.Info.profile_update(user.info, info_params) -  end - -  defp parse_profile_bio(user, params) do -    if bio = params["description"] do -      emojis_text = (params["description"] || "") <> " " <> (params["name"] || "") - -      emojis = -        ((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text)) -        |> Enum.dedup() - -      user_info = -        user.info -        |> Map.put( -          "emoji", -          emojis -        ) - -      params -      |> Map.put("bio", User.parse_bio(bio, user)) -      |> Map.put("info", user_info) -    else -      params -    end -  end - -  def update_profile(%{assigns: %{user: user}} = conn, params) do -    params = parse_profile_bio(user, params) -    info_cng = build_info_cng(user, params) - -    with changeset <- User.update_changeset(user, params), -         changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng), -         {:ok, user} <- User.update_and_set_cache(changeset) do -      CommonAPI.update(user) - -      conn -      |> put_view(UserView) -      |> render("user.json", %{user: user, for: user}) -    else -      error -> -        Logger.debug("Can't update user: #{inspect(error)}") -        bad_request_reply(conn, "Can't update user") -    end -  end - -  def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do -    activities = TwitterAPI.search(user, params) - +  def errors(conn, {:param_cast, _}) do      conn -    |> put_view(ActivityView) -    |> render("index.json", %{activities: activities, for: user}) +    |> put_status(400) +    |> json("Invalid parameters")    end -  def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do -    users = User.search(query, resolve: true, for_user: user) - +  def errors(conn, _) do      conn -    |> put_view(UserView) -    |> render("index.json", %{users: users, for: user}) -  end - -  defp bad_request_reply(conn, error_message) do -    json = error_json(conn, error_message) -    json_reply(conn, 400, json) +    |> put_status(500) +    |> json("Something went wrong")    end    defp json_reply(conn, status, json) do @@ -777,36 +60,27 @@ defmodule Pleroma.Web.TwitterAPI.Controller do      |> send_resp(status, json)    end -  defp forbidden_json_reply(conn, error_message) do -    json = error_json(conn, error_message) -    json_reply(conn, 403, json) -  end +  def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do +    Notification.set_read_up_to(user, latest_id) -  def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn +    notifications = Notification.for_user(user, params) -  def only_if_public_instance(conn, _) do -    if Pleroma.Config.get([:instance, :public]) do -      conn -    else -      conn -      |> forbidden_json_reply("Invalid credentials.") -      |> halt() -    end +    conn +    # XXX: This is a hack because pleroma-fe still uses that API. +    |> put_view(Pleroma.Web.MastodonAPI.NotificationView) +    |> render("index.json", %{notifications: notifications, for: user})    end -  defp error_json(conn, error_message) do -    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!() +  def notifications_read(%{assigns: %{user: _user}} = conn, _) do +    bad_request_reply(conn, "You need to specify latest_id")    end -  def errors(conn, {:param_cast, _}) do -    conn -    |> put_status(400) -    |> json("Invalid parameters") +  defp bad_request_reply(conn, error_message) do +    json = error_json(conn, error_message) +    json_reply(conn, 400, json)    end -  def errors(conn, _) do -    conn -    |> put_status(500) -    |> json("Something went wrong") +  defp error_json(conn, error_message) do +    %{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()    end  end diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex deleted file mode 100644 index abae63877..000000000 --- a/lib/pleroma/web/twitter_api/views/activity_view.ex +++ /dev/null @@ -1,366 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.ActivityView do -  use Pleroma.Web, :view -  alias Pleroma.Activity -  alias Pleroma.Formatter -  alias Pleroma.HTML -  alias Pleroma.Object -  alias Pleroma.Repo -  alias Pleroma.User -  alias Pleroma.Web.CommonAPI -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.MastodonAPI.StatusView -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter -  alias Pleroma.Web.TwitterAPI.UserView - -  import Ecto.Query -  require Logger -  require Pleroma.Constants - -  defp query_context_ids([]), do: [] - -  defp query_context_ids(contexts) do -    query = from(o in Object, where: fragment("(?)->>'id' = ANY(?)", o.data, ^contexts)) - -    Repo.all(query) -  end - -  defp query_users([]), do: [] - -  defp query_users(user_ids) do -    query = from(user in User, where: user.ap_id in ^user_ids) - -    Repo.all(query) -  end - -  defp collect_context_ids(activities) do -    _contexts = -      activities -      |> Enum.reject(& &1.data["context_id"]) -      |> Enum.map(fn %{data: data} -> -        data["context"] -      end) -      |> Enum.filter(& &1) -      |> query_context_ids() -      |> Enum.reduce(%{}, fn %{data: %{"id" => ap_id}, id: id}, acc -> -        Map.put(acc, ap_id, id) -      end) -  end - -  defp collect_users(activities) do -    activities -    |> Enum.map(fn activity -> -      case activity.data do -        data = %{"type" => "Follow"} -> -          [data["actor"], data["object"]] - -        data -> -          [data["actor"]] -      end ++ activity.recipients -    end) -    |> List.flatten() -    |> Enum.uniq() -    |> query_users() -    |> Enum.reduce(%{}, fn user, acc -> -      Map.put(acc, user.ap_id, user) -    end) -  end - -  defp get_context_id(%{data: %{"context_id" => context_id}}, _) when not is_nil(context_id), -    do: context_id - -  defp get_context_id(%{data: %{"context" => nil}}, _), do: nil - -  defp get_context_id(%{data: %{"context" => context}}, options) do -    cond do -      id = options[:context_ids][context] -> id -      true -> Utils.context_to_conversation_id(context) -    end -  end - -  defp get_context_id(_, _), do: nil - -  defp get_user(ap_id, opts) do -    cond do -      user = opts[:users][ap_id] -> -        user - -      String.ends_with?(ap_id, "/followers") -> -        nil - -      ap_id == Pleroma.Constants.as_public() -> -        nil - -      user = User.get_cached_by_ap_id(ap_id) -> -        user - -      user = User.get_by_guessed_nickname(ap_id) -> -        user - -      true -> -        User.error_user(ap_id) -    end -  end - -  def render("index.json", opts) do -    context_ids = collect_context_ids(opts.activities) -    users = collect_users(opts.activities) - -    opts = -      opts -      |> Map.put(:context_ids, context_ids) -      |> Map.put(:users, users) - -    safe_render_many( -      opts.activities, -      ActivityView, -      "activity.json", -      opts -    ) -  end - -  def render("activity.json", %{activity: %{data: %{"type" => "Delete"}} = activity} = opts) do -    user = get_user(activity.data["actor"], opts) -    created_at = activity.data["published"] |> Utils.date_to_asctime() - -    %{ -      "id" => activity.id, -      "uri" => activity.data["object"], -      "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), -      "attentions" => [], -      "statusnet_html" => "deleted notice {{tag", -      "text" => "deleted notice {{tag", -      "is_local" => activity.local, -      "is_post_verb" => false, -      "created_at" => created_at, -      "in_reply_to_status_id" => nil, -      "external_url" => activity.data["id"], -      "activity_type" => "delete" -    } -  end - -  def render("activity.json", %{activity: %{data: %{"type" => "Follow"}} = activity} = opts) do -    user = get_user(activity.data["actor"], opts) -    created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at) -    created_at = created_at |> Utils.date_to_asctime() - -    followed = get_user(activity.data["object"], opts) -    text = "#{user.nickname} started following #{followed.nickname}" - -    %{ -      "id" => activity.id, -      "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), -      "attentions" => [], -      "statusnet_html" => text, -      "text" => text, -      "is_local" => activity.local, -      "is_post_verb" => false, -      "created_at" => created_at, -      "in_reply_to_status_id" => nil, -      "external_url" => activity.data["id"], -      "activity_type" => "follow" -    } -  end - -  def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do -    user = get_user(activity.data["actor"], opts) -    created_at = activity.data["published"] |> Utils.date_to_asctime() -    announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) - -    text = "#{user.nickname} repeated a status." - -    retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity})) - -    %{ -      "id" => activity.id, -      "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), -      "statusnet_html" => text, -      "text" => text, -      "is_local" => activity.local, -      "is_post_verb" => false, -      "uri" => "tag:#{activity.data["id"]}:objectType=note", -      "created_at" => created_at, -      "retweeted_status" => retweeted_status, -      "statusnet_conversation_id" => get_context_id(announced_activity, opts), -      "external_url" => activity.data["id"], -      "activity_type" => "repeat" -    } -  end - -  def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do -    user = get_user(activity.data["actor"], opts) -    liked_activity = Activity.get_create_by_object_ap_id(activity.data["object"]) -    liked_activity_id = if liked_activity, do: liked_activity.id, else: nil - -    created_at = -      activity.data["published"] -      |> Utils.date_to_asctime() - -    text = "#{user.nickname} favorited a status." - -    favorited_status = -      if liked_activity, -        do: render("activity.json", Map.merge(opts, %{activity: liked_activity})), -        else: nil - -    %{ -      "id" => activity.id, -      "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), -      "statusnet_html" => text, -      "text" => text, -      "is_local" => activity.local, -      "is_post_verb" => false, -      "uri" => "tag:#{activity.data["id"]}:objectType=Favourite", -      "created_at" => created_at, -      "favorited_status" => favorited_status, -      "in_reply_to_status_id" => liked_activity_id, -      "external_url" => activity.data["id"], -      "activity_type" => "like" -    } -  end - -  def render( -        "activity.json", -        %{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts -      ) do -    user = get_user(activity.data["actor"], opts) - -    object = Object.normalize(object_id) - -    created_at = object.data["published"] |> Utils.date_to_asctime() -    like_count = object.data["like_count"] || 0 -    announcement_count = object.data["announcement_count"] || 0 -    favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || []) -    repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || []) -    pinned = activity.id in user.info.pinned_activities - -    attentions = -      [] -      |> Utils.maybe_notify_to_recipients(activity) -      |> Utils.maybe_notify_mentioned_recipients(activity) -      |> Enum.map(fn ap_id -> get_user(ap_id, opts) end) -      |> Enum.filter(& &1) -      |> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end) - -    conversation_id = get_context_id(activity, opts) - -    tags = object.data["tag"] || [] -    possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw") - -    tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags - -    {summary, content} = render_content(object.data) - -    html = -      content -      |> HTML.get_cached_scrubbed_html_for_activity( -        User.html_filter_policy(opts[:for]), -        activity, -        "twitterapi:content" -      ) -      |> Formatter.emojify(object.data["emoji"]) - -    text = -      if content do -        content -        |> String.replace(~r/<br\s?\/?>/, "\n") -        |> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content") -      else -        "" -      end - -    reply_parent = Activity.get_in_reply_to_activity(activity) - -    reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor) - -    summary = HTML.strip_tags(summary) - -    card = -      StatusView.render( -        "card.json", -        Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity) -      ) - -    thread_muted? = -      case activity.thread_muted? do -        thread_muted? when is_boolean(thread_muted?) -> thread_muted? -        nil -> CommonAPI.thread_muted?(user, activity) -      end - -    %{ -      "id" => activity.id, -      "uri" => object.data["id"], -      "user" => UserView.render("show.json", %{user: user, for: opts[:for]}), -      "statusnet_html" => html, -      "text" => text, -      "is_local" => activity.local, -      "is_post_verb" => true, -      "created_at" => created_at, -      "in_reply_to_status_id" => reply_parent && reply_parent.id, -      "in_reply_to_screen_name" => reply_user && reply_user.nickname, -      "in_reply_to_profileurl" => User.profile_url(reply_user), -      "in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id, -      "in_reply_to_user_id" => reply_user && reply_user.id, -      "statusnet_conversation_id" => conversation_id, -      "attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts), -      "attentions" => attentions, -      "fave_num" => like_count, -      "repeat_num" => announcement_count, -      "favorited" => !!favorited, -      "repeated" => !!repeated, -      "pinned" => pinned, -      "external_url" => object.data["external_url"] || object.data["id"], -      "tags" => tags, -      "activity_type" => "post", -      "possibly_sensitive" => possibly_sensitive, -      "visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object), -      "summary" => summary, -      "summary_html" => summary |> Formatter.emojify(object.data["emoji"]), -      "card" => card, -      "muted" => thread_muted? || User.mutes?(opts[:for], user) -    } -  end - -  def render("activity.json", %{activity: unhandled_activity}) do -    Logger.warn("#{__MODULE__} unhandled activity: #{inspect(unhandled_activity)}") -    nil -  end - -  def render_content(%{"type" => "Note"} = object) do -    summary = object["summary"] - -    content = -      if !!summary and summary != "" do -        "<p>#{summary}</p>#{object["content"]}" -      else -        object["content"] -      end - -    {summary, content} -  end - -  def render_content(%{"type" => object_type} = object) -      when object_type in ["Article", "Page", "Video"] do -    summary = object["name"] || object["summary"] - -    content = -      if !!summary and summary != "" and is_bitstring(object["url"]) do -        "<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}" -      else -        object["content"] -      end - -    {summary, content} -  end - -  def render_content(object) do -    summary = object["summary"] || "Unhandled activity type: #{object["type"]}" -    content = "<p>#{summary}</p>#{object["content"]}" - -    {summary, content} -  end -end diff --git a/lib/pleroma/web/twitter_api/views/notification_view.ex b/lib/pleroma/web/twitter_api/views/notification_view.ex deleted file mode 100644 index 085cd5aa3..000000000 --- a/lib/pleroma/web/twitter_api/views/notification_view.ex +++ /dev/null @@ -1,71 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.NotificationView do -  use Pleroma.Web, :view -  alias Pleroma.Notification -  alias Pleroma.User -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.TwitterAPI.ActivityView -  alias Pleroma.Web.TwitterAPI.UserView - -  require Pleroma.Constants - -  defp get_user(ap_id, opts) do -    cond do -      user = opts[:users][ap_id] -> -        user - -      String.ends_with?(ap_id, "/followers") -> -        nil - -      ap_id == Pleroma.Constants.as_public() -> -        nil - -      true -> -        User.get_cached_by_ap_id(ap_id) -    end -  end - -  def render("notification.json", %{notifications: notifications, for: user}) do -    render_many( -      notifications, -      Pleroma.Web.TwitterAPI.NotificationView, -      "notification.json", -      for: user -    ) -  end - -  def render( -        "notification.json", -        %{ -          notification: %Notification{ -            id: id, -            seen: seen, -            activity: activity, -            inserted_at: created_at -          }, -          for: user -        } = opts -      ) do -    ntype = -      case activity.data["type"] do -        "Create" -> "mention" -        "Like" -> "like" -        "Announce" -> "repeat" -        "Follow" -> "follow" -      end - -    from = get_user(activity.data["actor"], opts) - -    %{ -      "id" => id, -      "ntype" => ntype, -      "notice" => ActivityView.render("activity.json", %{activity: activity, for: user}), -      "from_profile" => UserView.render("show.json", %{user: from, for: user}), -      "is_seen" => if(seen, do: 1, else: 0), -      "created_at" => created_at |> Utils.format_naive_asctime() -    } -  end -end diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex deleted file mode 100644 index 8a7d2fc72..000000000 --- a/lib/pleroma/web/twitter_api/views/user_view.ex +++ /dev/null @@ -1,191 +0,0 @@ -# Pleroma: A lightweight social networking server -# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/> -# SPDX-License-Identifier: AGPL-3.0-only - -defmodule Pleroma.Web.TwitterAPI.UserView do -  use Pleroma.Web, :view -  alias Pleroma.Formatter -  alias Pleroma.HTML -  alias Pleroma.User -  alias Pleroma.Web.CommonAPI.Utils -  alias Pleroma.Web.MediaProxy - -  def render("show.json", %{user: user = %User{}} = assigns) do -    render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns) -  end - -  def render("index.json", %{users: users, for: user}) do -    users -    |> render_many(Pleroma.Web.TwitterAPI.UserView, "user.json", for: user) -    |> Enum.filter(&Enum.any?/1) -  end - -  def render("user.json", %{user: user = %User{}} = assigns) do -    if User.visible_for?(user, assigns[:for]), -      do: do_render("user.json", assigns), -      else: %{} -  end - -  def render("short.json", %{ -        user: %User{ -          nickname: nickname, -          id: id, -          ap_id: ap_id, -          name: name -        } -      }) do -    %{ -      "fullname" => name, -      "id" => id, -      "ostatus_uri" => ap_id, -      "profile_url" => ap_id, -      "screen_name" => nickname -    } -  end - -  defp do_render("user.json", %{user: user = %User{}} = assigns) do -    for_user = assigns[:for] -    image = User.avatar_url(user) |> MediaProxy.url() - -    {following, follows_you, statusnet_blocking} = -      if for_user do -        { -          User.following?(for_user, user), -          User.following?(user, for_user), -          User.blocks?(for_user, user) -        } -      else -        {false, false, false} -      end - -    user_info = User.get_cached_user_info(user) - -    emoji = -      (user.info.source_data["tag"] || []) -      |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end) -      |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} -> -        {String.trim(name, ":"), url} -      end) - -    emoji = Enum.dedup(emoji ++ user.info.emoji) - -    description_html = -      (user.bio || "") -      |> HTML.filter_tags(User.html_filter_policy(for_user)) -      |> Formatter.emojify(emoji) - -    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) - -    data = -      %{ -        "created_at" => user.inserted_at |> Utils.format_naive_asctime(), -        "description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")), -        "description_html" => description_html, -        "favourites_count" => 0, -        "followers_count" => user_info[:follower_count], -        "following" => following, -        "follows_you" => follows_you, -        "statusnet_blocking" => statusnet_blocking, -        "friends_count" => user_info[:following_count], -        "id" => user.id, -        "name" => user.name || user.nickname, -        "name_html" => -          if(user.name, -            do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji), -            else: user.nickname -          ), -        "profile_image_url" => image, -        "profile_image_url_https" => image, -        "profile_image_url_profile_size" => image, -        "profile_image_url_original" => image, -        "screen_name" => user.nickname, -        "statuses_count" => user_info[:note_count], -        "statusnet_profile_url" => user.ap_id, -        "cover_photo" => User.banner_url(user) |> MediaProxy.url(), -        "background_image" => image_url(user.info.background) |> MediaProxy.url(), -        "is_local" => user.local, -        "locked" => user.info.locked, -        "hide_followers" => user.info.hide_followers, -        "hide_follows" => user.info.hide_follows, -        "fields" => fields, - -        # Pleroma extension -        "pleroma" => -          %{ -            "confirmation_pending" => user_info.confirmation_pending, -            "tags" => user.tags, -            "skip_thread_containment" => user.info.skip_thread_containment -          } -          |> maybe_with_activation_status(user, for_user) -          |> with_notification_settings(user, for_user) -      } -      |> maybe_with_user_settings(user, for_user) -      |> maybe_with_role(user, for_user) - -    if assigns[:token] do -      Map.put(data, "token", token_string(assigns[:token])) -    else -      data -    end -  end - -  defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do -    Map.put(data, "notification_settings", user.info.notification_settings) -  end - -  defp with_notification_settings(data, _, _), do: data - -  defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do -    Map.put(data, "deactivated", user.info.deactivated) -  end - -  defp maybe_with_activation_status(data, _, _), do: data - -  defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do -    Map.merge(data, %{ -      "role" => role(user), -      "show_role" => user.info.show_role, -      "rights" => %{ -        "delete_others_notice" => !!user.info.is_moderator, -        "admin" => !!user.info.is_admin -      } -    }) -  end - -  defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do -    Map.merge(data, %{ -      "role" => role(user), -      "rights" => %{ -        "delete_others_notice" => !!user.info.is_moderator, -        "admin" => !!user.info.is_admin -      } -    }) -  end - -  defp maybe_with_role(data, _, _), do: data - -  defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do -    data -    |> Kernel.put_in(["default_scope"], info.default_scope) -    |> Kernel.put_in(["no_rich_text"], info.no_rich_text) -  end - -  defp maybe_with_user_settings(data, _, _), do: data -  defp role(%User{info: %{:is_admin => true}}), do: "admin" -  defp role(%User{info: %{:is_moderator => true}}), do: "moderator" -  defp role(_), do: "member" - -  defp image_url(%{"url" => [%{"href" => href} | _]}), do: href -  defp image_url(_), do: nil - -  defp token_string(%Pleroma.Web.OAuth.Token{token: token_str}), do: token_str -  defp token_string(token), do: token -end diff --git a/lib/pleroma/web/web.ex b/lib/pleroma/web/web.ex index bfb6c7287..687346554 100644 --- a/lib/pleroma/web/web.ex +++ b/lib/pleroma/web/web.ex @@ -66,23 +66,9 @@ defmodule Pleroma.Web do        end        @doc """ -      Same as `render_many/4` but wrapped in rescue block and parallelized (unless disabled by passing false as a fifth argument). +      Same as `render_many/4` but wrapped in rescue block.        """ -      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 +      def safe_render_many(collection, view, template, assigns \\ %{}) do          Enum.map(collection, fn resource ->            as = Map.get(assigns, :as) || view.__resource__            assigns = Map.put(assigns, as, resource)  | 
