aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/config.ex13
-rw-r--r--lib/mix/tasks/pleroma/database.ex28
-rw-r--r--lib/mix/tasks/pleroma/digest.ex10
-rw-r--r--lib/mix/tasks/pleroma/emoji.ex49
-rw-r--r--lib/mix/tasks/pleroma/instance.ex31
-rw-r--r--lib/mix/tasks/pleroma/relay.ex19
-rw-r--r--lib/mix/tasks/pleroma/uploads.ex10
-rw-r--r--lib/mix/tasks/pleroma/user.ex80
-rw-r--r--lib/pleroma/application.ex4
-rw-r--r--lib/pleroma/conversation.ex2
-rw-r--r--lib/pleroma/conversation/participation.ex17
-rw-r--r--lib/pleroma/healthcheck.ex8
-rw-r--r--lib/pleroma/job_queue_monitor.ex78
-rw-r--r--lib/pleroma/reverse_proxy/reverse_proxy.ex27
-rw-r--r--lib/pleroma/user.ex58
-rw-r--r--lib/pleroma/user/info.ex1
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex6
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub_controller.ex32
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex67
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex28
-rw-r--r--lib/pleroma/web/activity_pub/views/object_view.ex36
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex2
-rw-r--r--lib/pleroma/web/activity_pub/visibility.ex5
-rw-r--r--lib/pleroma/web/common_api/common_api.ex19
-rw-r--r--lib/pleroma/web/controller_helper.ex12
-rw-r--r--lib/pleroma/web/masto_fe_controller.ex36
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/account_controller.ex45
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/app_controller.ex39
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/auth_controller.ex91
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex11
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/instance_controller.ex17
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex504
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/media_controller.ex42
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/poll_controller.ex53
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/status_controller.ex55
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex63
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex11
-rw-r--r--lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex28
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex35
-rw-r--r--lib/pleroma/web/mastodon_api/views/mastodon_view.ex8
-rw-r--r--lib/pleroma/web/mastodon_api/views/notification_view.ex60
-rw-r--r--lib/pleroma/web/mastodon_api/views/poll_view.ex74
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex74
-rw-r--r--lib/pleroma/web/oauth/oauth_controller.ex26
-rw-r--r--lib/pleroma/web/router.ex312
-rw-r--r--lib/pleroma/web/streamer/ping.ex4
-rw-r--r--lib/pleroma/web/streamer/state.ex4
-rw-r--r--lib/pleroma/web/streamer/streamer_socket.ex4
-rw-r--r--lib/pleroma/web/streamer/supervisor.ex4
-rw-r--r--lib/pleroma/web/streamer/worker.ex12
-rw-r--r--lib/pleroma/web/templates/masto_fe/index.html.eex (renamed from lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex)2
-rw-r--r--lib/pleroma/web/translation_helpers.ex20
-rw-r--r--lib/pleroma/web/views/masto_fe_view.ex102
53 files changed, 1300 insertions, 1078 deletions
diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex
index 462940e7e..11e4fde43 100644
--- a/lib/mix/tasks/pleroma/config.ex
+++ b/lib/mix/tasks/pleroma/config.ex
@@ -8,18 +8,7 @@ defmodule Mix.Tasks.Pleroma.Config do
alias Pleroma.Repo
alias Pleroma.Web.AdminAPI.Config
@shortdoc "Manages the location of the config"
- @moduledoc """
- Manages the location of the config.
-
- ## Transfers config from file to DB.
-
- mix pleroma.config migrate_to_db
-
- ## Transfers config from DB to file `config/env.exported_from_db.secret.exs`
-
- mix pleroma.config migrate_from_db ENV
- """
-
+ @moduledoc File.read!("docs/administration/CLI_tasks/config.md")
def run(["migrate_to_db"]) do
start_pleroma()
diff --git a/lib/mix/tasks/pleroma/database.ex b/lib/mix/tasks/pleroma/database.ex
index 890a383df..cfd9eeada 100644
--- a/lib/mix/tasks/pleroma/database.ex
+++ b/lib/mix/tasks/pleroma/database.ex
@@ -13,34 +13,8 @@ defmodule Mix.Tasks.Pleroma.Database do
use Mix.Task
@shortdoc "A collection of database related tasks"
- @moduledoc """
- A collection of database related tasks
+ @moduledoc File.read!("docs/administration/CLI_tasks/database.md")
- ## Replace embedded objects with their references
-
- Replaces embedded objects with references to them in the `objects` table. Only needs to be ran once. The reason why this is not a migration is because it could significantly increase the database size after being ran, however after this `VACUUM FULL` will be able to reclaim about 20% (really depends on what is in the database, your mileage may vary) of the db size before the migration.
-
- mix pleroma.database remove_embedded_objects
-
- Options:
- - `--vacuum` - run `VACUUM FULL` after the embedded objects are replaced with their references
-
- ## Prune old objects from the database
-
- mix pleroma.database prune_objects
-
- ## Create a conversation for all existing DMs. Can be safely re-run.
-
- mix pleroma.database bump_all_conversations
-
- ## Remove duplicated items from following and update followers count for all users
-
- mix pleroma.database update_users_following_followers_counts
-
- ## Fix the pre-existing "likes" collections for all objects
-
- mix pleroma.database fix_likes_collections
- """
def run(["remove_embedded_objects" | args]) do
{options, [], []} =
OptionParser.parse(
diff --git a/lib/mix/tasks/pleroma/digest.ex b/lib/mix/tasks/pleroma/digest.ex
index 430116a50..7d09e70c5 100644
--- a/lib/mix/tasks/pleroma/digest.ex
+++ b/lib/mix/tasks/pleroma/digest.ex
@@ -2,16 +2,8 @@ defmodule Mix.Tasks.Pleroma.Digest do
use Mix.Task
@shortdoc "Manages digest emails"
- @moduledoc """
- Manages digest emails
+ @moduledoc File.read!("docs/administration/CLI_tasks/digest.md")
- ## Send digest email since given date (user registration date by default)
- ignoring user activity status.
-
- ``mix pleroma.digest test <nickname> <since_date>``
-
- Example: ``mix pleroma.digest test donaldtheduck 2019-05-20``
- """
def run(["test", nickname | opts]) do
Mix.Pleroma.start_pleroma()
diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex
index 881a6f725..6ef0a635d 100644
--- a/lib/mix/tasks/pleroma/emoji.ex
+++ b/lib/mix/tasks/pleroma/emoji.ex
@@ -6,54 +6,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do
use Mix.Task
@shortdoc "Manages emoji packs"
- @moduledoc """
- Manages emoji packs
-
- ## ls-packs
-
- mix pleroma.emoji ls-packs [OPTION...]
-
- Lists the emoji packs and metadata specified in the manifest.
-
- ### Options
-
- - `-m, --manifest PATH/URL` - path to a custom manifest, it can
- either be an URL starting with `http`, in that case the
- manifest will be fetched from that address, or a local path
-
- ## get-packs
-
- mix pleroma.emoji get-packs [OPTION...] PACKS
-
- Fetches, verifies and installs the specified PACKS from the
- manifest into the `STATIC-DIR/emoji/PACK-NAME`
-
- ### Options
-
- - `-m, --manifest PATH/URL` - same as ls-packs
-
- ## gen-pack
-
- mix pleroma.emoji gen-pack PACK-URL
-
- Creates a new manifest entry and a file list from the specified
- remote pack file. Currently, only .zip archives are recognized
- as remote pack files and packs are therefore assumed to be zip
- archives. This command is intended to run interactively and will
- first ask you some basic questions about the pack, then download
- the remote file and generate an SHA256 checksum for it, then
- generate an emoji file list for you.
-
- The manifest entry will either be written to a newly created
- `index.json` file or appended to the existing one, *replacing*
- the old pack with the same name if it was in the file previously.
-
- The file list will be written to the file specified previously,
- *replacing* that file. You _should_ check that the file list doesn't
- contain anything you don't need in the pack, that is, anything that is
- not an emoji (the whole pack is downloaded, but only emoji files
- are extracted).
- """
+ @moduledoc File.read!("docs/administration/CLI_tasks/emoji.md")
def run(["ls-packs" | args]) do
Application.ensure_all_started(:hackney)
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index 1a1634fe9..9af6cda30 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -7,36 +7,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
import Mix.Pleroma
@shortdoc "Manages Pleroma instance"
- @moduledoc """
- Manages Pleroma instance.
-
- ## Generate a new instance config.
-
- mix pleroma.instance gen [OPTION...]
-
- If any options are left unspecified, you will be prompted interactively
-
- ## Options
-
- - `-f`, `--force` - overwrite any output files
- - `-o PATH`, `--output PATH` - the output file for the generated configuration
- - `--output-psql PATH` - the output file for the generated PostgreSQL setup
- - `--domain DOMAIN` - the domain of your instance
- - `--instance-name INSTANCE_NAME` - the name of your instance
- - `--admin-email ADMIN_EMAIL` - the email address of the instance admin
- - `--notify-email NOTIFY_EMAIL` - email address for notifications
- - `--dbhost HOSTNAME` - the hostname of the PostgreSQL database to use
- - `--dbname DBNAME` - the name of the database to use
- - `--dbuser DBUSER` - the user (aka role) to use for the database connection
- - `--dbpass DBPASS` - the password to use for the database connection
- - `--rum Y/N` - Whether to enable RUM indexes
- - `--indexable Y/N` - Allow/disallow indexing site by search engines
- - `--db-configurable Y/N` - Allow/disallow configuring instance from admin part
- - `--uploads-dir` - the directory uploads go in when using a local uploader
- - `--static-dir` - the directory custom public files should be read from (custom emojis, frontend bundle overrides, robots.txt, etc.)
- - `--listen-ip` - the ip the app should listen to, defaults to 127.0.0.1
- - `--listen-port` - the port the app should listen to, defaults to 4000
- """
+ @moduledoc File.read!("docs/administration/CLI_tasks/instance.md")
def run(["gen" | rest]) do
{options, [], []} =
diff --git a/lib/mix/tasks/pleroma/relay.ex b/lib/mix/tasks/pleroma/relay.ex
index 200721163..d7a7b599f 100644
--- a/lib/mix/tasks/pleroma/relay.ex
+++ b/lib/mix/tasks/pleroma/relay.ex
@@ -9,25 +9,8 @@ defmodule Mix.Tasks.Pleroma.Relay do
alias Pleroma.Web.ActivityPub.Relay
@shortdoc "Manages remote relays"
- @moduledoc """
- Manages remote relays
+ @moduledoc File.read!("docs/administration/CLI_tasks/relay.md")
- ## Follow a remote relay
-
- ``mix pleroma.relay follow <relay_url>``
-
- Example: ``mix pleroma.relay follow https://example.org/relay``
-
- ## Unfollow a remote relay
-
- ``mix pleroma.relay unfollow <relay_url>``
-
- Example: ``mix pleroma.relay unfollow https://example.org/relay``
-
- ## List relay subscriptions
-
- ``mix pleroma.relay list``
- """
def run(["follow", target]) do
start_pleroma()
diff --git a/lib/mix/tasks/pleroma/uploads.ex b/lib/mix/tasks/pleroma/uploads.ex
index 95392d81b..3e6fc7ee0 100644
--- a/lib/mix/tasks/pleroma/uploads.ex
+++ b/lib/mix/tasks/pleroma/uploads.ex
@@ -12,16 +12,8 @@ defmodule Mix.Tasks.Pleroma.Uploads do
@log_every 50
@shortdoc "Migrates uploads from local to remote storage"
- @moduledoc """
- Manages uploads
+ @moduledoc File.read!("docs/administration/CLI_tasks/uploads.md")
- ## Migrate uploads from local to remote storage
- mix pleroma.uploads migrate_local TARGET_UPLOADER [OPTIONS...]
- Options:
- - `--delete` - delete local uploads after migrating them to the target uploader
-
- A list of available uploaders can be seen in config.exs
- """
def run(["migrate_local", target_uploader | args]) do
delete? = Enum.member?(args, "--delete")
start_pleroma()
diff --git a/lib/mix/tasks/pleroma/user.ex b/lib/mix/tasks/pleroma/user.ex
index d93ba8dee..134b5bccc 100644
--- a/lib/mix/tasks/pleroma/user.ex
+++ b/lib/mix/tasks/pleroma/user.ex
@@ -10,86 +10,8 @@ defmodule Mix.Tasks.Pleroma.User do
alias Pleroma.Web.OAuth
@shortdoc "Manages Pleroma users"
- @moduledoc """
- Manages Pleroma users.
+ @moduledoc File.read!("docs/administration/CLI_tasks/user.md")
- ## Create a new user.
-
- mix pleroma.user new NICKNAME EMAIL [OPTION...]
-
- Options:
- - `--name NAME` - the user's name (i.e., "Lain Iwakura")
- - `--bio BIO` - the user's bio
- - `--password PASSWORD` - the user's password
- - `--moderator`/`--no-moderator` - whether the user is a moderator
- - `--admin`/`--no-admin` - whether the user is an admin
- - `-y`, `--assume-yes`/`--no-assume-yes` - whether to assume yes to all questions
-
- ## Generate an invite link.
-
- mix pleroma.user invite [OPTION...]
-
- Options:
- - `--expires-at DATE` - last day on which token is active (e.g. "2019-04-05")
- - `--max-use NUMBER` - maximum numbers of token uses
-
- ## List generated invites
-
- mix pleroma.user invites
-
- ## Revoke invite
-
- mix pleroma.user revoke_invite TOKEN OR TOKEN_ID
-
- ## Delete the user's account.
-
- mix pleroma.user rm NICKNAME
-
- ## Delete the user's activities.
-
- mix pleroma.user delete_activities NICKNAME
-
- ## Sign user out from all applications (delete user's OAuth tokens and authorizations).
-
- mix pleroma.user sign_out NICKNAME
-
- ## Deactivate or activate the user's account.
-
- mix pleroma.user toggle_activated NICKNAME
-
- ## Unsubscribe local users from user's account and deactivate it
-
- mix pleroma.user unsubscribe NICKNAME
-
- ## Unsubscribe local users from an entire instance and deactivate all accounts
-
- mix pleroma.user unsubscribe_all_from_instance INSTANCE
-
- ## Create a password reset link.
-
- mix pleroma.user reset_password NICKNAME
-
- ## Set the value of the given user's settings.
-
- mix pleroma.user set NICKNAME [OPTION...]
-
- Options:
- - `--locked`/`--no-locked` - whether the user's account is locked
- - `--moderator`/`--no-moderator` - whether the user is a moderator
- - `--admin`/`--no-admin` - whether the user is an admin
-
- ## Add tags to a user.
-
- mix pleroma.user tag NICKNAME TAGS
-
- ## Delete tags from a user.
-
- mix pleroma.user untag NICKNAME TAGS
-
- ## Toggle confirmation of the user's account.
-
- mix pleroma.user toggle_confirmed NICKNAME
- """
def run(["new", nickname, email | rest]) do
{options, [], []} =
OptionParser.parse(
diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex
index 7aec2c545..0bf218bc7 100644
--- a/lib/pleroma/application.ex
+++ b/lib/pleroma/application.ex
@@ -42,6 +42,7 @@ defmodule Pleroma.Application do
hackney_pool_children() ++
[
Pleroma.Stats,
+ Pleroma.JobQueueMonitor,
{Oban, Pleroma.Config.get(Oban)}
] ++
task_children(@env) ++
@@ -102,7 +103,8 @@ defmodule Pleroma.Application do
build_cachex("scrubber", limit: 2500),
build_cachex("idempotency", expiration: idempotency_expiration(), limit: 2500),
build_cachex("web_resp", limit: 2500),
- build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10)
+ build_cachex("emoji_packs", expiration: emoji_packs_expiration(), limit: 10),
+ build_cachex("failed_proxy_url", limit: 2500)
]
end
diff --git a/lib/pleroma/conversation.ex b/lib/pleroma/conversation.ex
index be5821ad7..098016af2 100644
--- a/lib/pleroma/conversation.ex
+++ b/lib/pleroma/conversation.ex
@@ -67,6 +67,8 @@ defmodule Pleroma.Conversation do
participations =
Enum.map(users, fn user ->
+ User.increment_unread_conversation_count(conversation, user)
+
{:ok, participation} =
Participation.create_for_user_and_conversation(user, conversation, opts)
diff --git a/lib/pleroma/conversation/participation.ex b/lib/pleroma/conversation/participation.ex
index e946f6de2..ab81f3217 100644
--- a/lib/pleroma/conversation/participation.ex
+++ b/lib/pleroma/conversation/participation.ex
@@ -52,6 +52,15 @@ defmodule Pleroma.Conversation.Participation do
participation
|> read_cng(%{read: true})
|> Repo.update()
+ |> case do
+ {:ok, participation} ->
+ participation = Repo.preload(participation, :user)
+ User.set_unread_conversation_count(participation.user)
+ {:ok, participation}
+
+ error ->
+ error
+ end
end
def mark_as_unread(participation) do
@@ -135,4 +144,12 @@ defmodule Pleroma.Conversation.Participation do
{:ok, Repo.preload(participation, :recipients, force: true)}
end
+
+ def unread_conversation_count_for_user(user) do
+ from(p in __MODULE__,
+ where: p.user_id == ^user.id,
+ where: not p.read,
+ select: %{count: count(p.id)}
+ )
+ end
end
diff --git a/lib/pleroma/healthcheck.ex b/lib/pleroma/healthcheck.ex
index 977b78c26..fc2129815 100644
--- a/lib/pleroma/healthcheck.ex
+++ b/lib/pleroma/healthcheck.ex
@@ -14,6 +14,7 @@ defmodule Pleroma.Healthcheck do
active: 0,
idle: 0,
memory_used: 0,
+ job_queue_stats: nil,
healthy: true
@type t :: %__MODULE__{
@@ -21,6 +22,7 @@ defmodule Pleroma.Healthcheck do
active: non_neg_integer(),
idle: non_neg_integer(),
memory_used: number(),
+ job_queue_stats: map(),
healthy: boolean()
}
@@ -30,6 +32,7 @@ defmodule Pleroma.Healthcheck do
memory_used: Float.round(:erlang.memory(:total) / 1024 / 1024, 2)
}
|> assign_db_info()
+ |> assign_job_queue_stats()
|> check_health()
end
@@ -55,6 +58,11 @@ defmodule Pleroma.Healthcheck do
Map.merge(healthcheck, db_info)
end
+ defp assign_job_queue_stats(healthcheck) do
+ stats = Pleroma.JobQueueMonitor.stats()
+ Map.put(healthcheck, :job_queue_stats, stats)
+ end
+
@spec check_health(Healthcheck.t()) :: Healthcheck.t()
def check_health(%{pool_size: pool_size, active: active} = check)
when active >= pool_size do
diff --git a/lib/pleroma/job_queue_monitor.ex b/lib/pleroma/job_queue_monitor.ex
new file mode 100644
index 000000000..3feea8381
--- /dev/null
+++ b/lib/pleroma/job_queue_monitor.ex
@@ -0,0 +1,78 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.JobQueueMonitor do
+ use GenServer
+
+ @initial_state %{workers: %{}, queues: %{}, processed_jobs: 0}
+ @queue %{processed_jobs: 0, success: 0, failure: 0}
+ @operation %{processed_jobs: 0, success: 0, failure: 0}
+
+ def start_link(_) do
+ GenServer.start_link(__MODULE__, @initial_state, name: __MODULE__)
+ end
+
+ @impl true
+ def init(state) do
+ :telemetry.attach("oban-monitor-failure", [:oban, :failure], &handle_event/4, nil)
+ :telemetry.attach("oban-monitor-success", [:oban, :success], &handle_event/4, nil)
+
+ {:ok, state}
+ end
+
+ def stats do
+ GenServer.call(__MODULE__, :stats)
+ end
+
+ def handle_event([:oban, status], %{duration: duration}, meta, _) do
+ GenServer.cast(__MODULE__, {:process_event, status, duration, meta})
+ end
+
+ @impl true
+ def handle_call(:stats, _from, state) do
+ {:reply, state, state}
+ end
+
+ @impl true
+ def handle_cast({:process_event, status, duration, meta}, state) do
+ state =
+ state
+ |> Map.update!(:workers, fn workers ->
+ workers
+ |> Map.put_new(meta.worker, %{})
+ |> Map.update!(meta.worker, &update_worker(&1, status, meta, duration))
+ end)
+ |> Map.update!(:queues, fn workers ->
+ workers
+ |> Map.put_new(meta.queue, @queue)
+ |> Map.update!(meta.queue, &update_queue(&1, status, meta, duration))
+ end)
+ |> Map.update!(:processed_jobs, &(&1 + 1))
+
+ {:noreply, state}
+ end
+
+ defp update_worker(worker, status, meta, duration) do
+ worker
+ |> Map.put_new(meta.args["op"], @operation)
+ |> Map.update!(meta.args["op"], &update_op(&1, status, meta, duration))
+ end
+
+ defp update_op(op, :enqueue, _meta, _duration) do
+ op
+ |> Map.update!(:enqueued, &(&1 + 1))
+ end
+
+ defp update_op(op, status, _meta, _duration) do
+ op
+ |> Map.update!(:processed_jobs, &(&1 + 1))
+ |> Map.update!(status, &(&1 + 1))
+ end
+
+ defp update_queue(queue, status, _meta, _duration) do
+ queue
+ |> Map.update!(:processed_jobs, &(&1 + 1))
+ |> Map.update!(status, &(&1 + 1))
+ end
+end
diff --git a/lib/pleroma/reverse_proxy/reverse_proxy.ex b/lib/pleroma/reverse_proxy/reverse_proxy.ex
index 03efad30a..78144cae3 100644
--- a/lib/pleroma/reverse_proxy/reverse_proxy.ex
+++ b/lib/pleroma/reverse_proxy/reverse_proxy.ex
@@ -15,6 +15,7 @@ defmodule Pleroma.ReverseProxy do
@valid_resp_codes [200, 206, 304]
@max_read_duration :timer.seconds(30)
@max_body_length :infinity
+ @failed_request_ttl :timer.seconds(60)
@methods ~w(GET HEAD)
@moduledoc """
@@ -48,6 +49,8 @@ defmodule Pleroma.ReverseProxy do
* `max_read_duration` (default `#{inspect(@max_read_duration)}` ms): the total time the connection is allowed to
read from the remote upstream.
+ * `failed_request_ttl` (default `#{inspect(@failed_request_ttl)}` ms): the time the failed request is cached and cannot be retried.
+
* `inline_content_types`:
* `true` will not alter `content-disposition` (up to the upstream),
* `false` will add `content-disposition: attachment` to any request,
@@ -83,6 +86,7 @@ defmodule Pleroma.ReverseProxy do
{:keep_user_agent, boolean}
| {:max_read_duration, :timer.time() | :infinity}
| {:max_body_length, non_neg_integer() | :infinity}
+ | {:failed_request_ttl, :timer.time() | :infinity}
| {:http, []}
| {:req_headers, [{String.t(), String.t()}]}
| {:resp_headers, [{String.t(), String.t()}]}
@@ -108,7 +112,8 @@ defmodule Pleroma.ReverseProxy do
opts
end
- with {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
+ with {:ok, nil} <- Cachex.get(:failed_proxy_url_cache, url),
+ {:ok, code, headers, client} <- request(method, url, req_headers, hackney_opts),
:ok <-
header_length_constraint(
headers,
@@ -116,12 +121,18 @@ defmodule Pleroma.ReverseProxy do
) do
response(conn, client, url, code, headers, opts)
else
+ {:ok, true} ->
+ conn
+ |> error_or_redirect(url, 500, "Request failed", opts)
+ |> halt()
+
{:ok, code, headers} ->
head_response(conn, url, code, headers, opts)
|> halt()
{:error, {:invalid_http_response, code}} ->
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed with HTTP status #{code}")
+ track_failed_url(url, code, opts)
conn
|> error_or_redirect(
@@ -134,6 +145,7 @@ defmodule Pleroma.ReverseProxy do
{:error, error} ->
Logger.error("#{__MODULE__}: request to #{inspect(url)} failed: #{inspect(error)}")
+ track_failed_url(url, error, opts)
conn
|> error_or_redirect(url, 500, "Request failed", opts)
@@ -388,4 +400,17 @@ defmodule Pleroma.ReverseProxy do
end
defp client, do: Pleroma.ReverseProxy.Client
+
+ defp track_failed_url(url, code, opts) do
+ code = to_string(code)
+
+ ttl =
+ if code in ["403", "404"] or String.starts_with?(code, "5") do
+ Keyword.get(opts, :failed_request_ttl, @failed_request_ttl)
+ else
+ nil
+ end
+
+ Cachex.put(:failed_proxy_url_cache, url, true, ttl: ttl)
+ end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 4c1cdd042..0d665afa6 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -11,6 +11,7 @@ defmodule Pleroma.User do
alias Comeonin.Pbkdf2
alias Ecto.Multi
alias Pleroma.Activity
+ alias Pleroma.Conversation.Participation
alias Pleroma.Delivery
alias Pleroma.Keys
alias Pleroma.Notification
@@ -583,7 +584,7 @@ defmodule Pleroma.User do
is_integer(nickname_or_id) or FlakeId.flake_id?(nickname_or_id) ->
get_cached_by_id(nickname_or_id) || get_cached_by_nickname(nickname_or_id)
- restrict_to_local == false ->
+ restrict_to_local == false or not String.contains?(nickname_or_id, "@") ->
get_cached_by_nickname(nickname_or_id)
restrict_to_local == :unauthenticated and match?(%User{}, opts[:for]) ->
@@ -842,6 +843,61 @@ defmodule Pleroma.User do
def maybe_update_following_count(user), do: user
+ def set_unread_conversation_count(%User{local: true} = user) do
+ unread_query = Participation.unread_conversation_count_for_user(user)
+
+ User
+ |> join(:inner, [u], p in subquery(unread_query))
+ |> update([u, p],
+ set: [
+ info:
+ fragment(
+ "jsonb_set(?, '{unread_conversation_count}', ?::varchar::jsonb, true)",
+ u.info,
+ p.count
+ )
+ ]
+ )
+ |> where([u], u.id == ^user.id)
+ |> select([u], u)
+ |> Repo.update_all([])
+ |> case do
+ {1, [user]} -> set_cache(user)
+ _ -> {:error, user}
+ end
+ end
+
+ def set_unread_conversation_count(_), do: :noop
+
+ def increment_unread_conversation_count(conversation, %User{local: true} = user) do
+ unread_query =
+ Participation.unread_conversation_count_for_user(user)
+ |> where([p], p.conversation_id == ^conversation.id)
+
+ User
+ |> join(:inner, [u], p in subquery(unread_query))
+ |> update([u, p],
+ set: [
+ info:
+ fragment(
+ "jsonb_set(?, '{unread_conversation_count}', (coalesce((?->>'unread_conversation_count')::int, 0) + 1)::varchar::jsonb, true)",
+ u.info,
+ u.info
+ )
+ ]
+ )
+ |> where([u], u.id == ^user.id)
+ |> where([u, p], p.count == 0)
+ |> select([u], u)
+ |> Repo.update_all([])
+ |> case do
+ {1, [user]} -> set_cache(user)
+ _ -> {:error, user}
+ end
+ end
+
+ def increment_unread_conversation_count(_, _), do: :noop
+
def remove_duplicated_following(%User{following: following} = user) do
uniq_following = Enum.uniq(following)
diff --git a/lib/pleroma/user/info.ex b/lib/pleroma/user/info.ex
index ebd4ddebf..4b5b43d7f 100644
--- a/lib/pleroma/user/info.ex
+++ b/lib/pleroma/user/info.ex
@@ -47,6 +47,7 @@ defmodule Pleroma.User.Info do
field(:hide_followers, :boolean, default: false)
field(:hide_follows, :boolean, default: false)
field(:hide_favorites, :boolean, default: true)
+ field(:unread_conversation_count, :integer, default: 0)
field(:pinned_activities, {:array, :string}, default: [])
field(:email_notifications, :map, default: %{"digest" => false})
field(:mascot, :map, default: nil)
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index 95f994c17..5052d1304 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
alias Pleroma.Web.ActivityPub.Transmogrifier
+ alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.Streamer
alias Pleroma.Web.WebFinger
alias Pleroma.Workers.BackgroundWorker
@@ -291,8 +292,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
- # only accept false as false value
local = !(params[:local] == false)
+ activity_id = params[:activity_id]
with data <- %{
"to" => to,
@@ -301,6 +302,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"actor" => actor,
"object" => object
},
+ data <- Utils.maybe_put(data, "id", activity_id),
{:ok, activity} <- insert(data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
@@ -346,7 +348,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
local \\ true,
public \\ true
) do
- with true <- is_public?(object),
+ with true <- is_announceable?(object, user, public),
announce_data <- make_announce_data(user, object, activity_id, public),
{:ok, activity} <- insert(announce_data, local),
{:ok, object} <- add_announce_to_object(activity, object),
diff --git a/lib/pleroma/web/activity_pub/activity_pub_controller.ex b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
index c349a7048..93fd9e248 100644
--- a/lib/pleroma/web/activity_pub/activity_pub_controller.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub_controller.ex
@@ -87,38 +87,6 @@ defmodule Pleroma.Web.ActivityPub.ActivityPubController do
conn
end
- def object_likes(conn, %{"uuid" => uuid, "page" => page}) do
- with ap_id <- o_status_url(conn, :object, uuid),
- %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(object)},
- likes <- Utils.get_object_likes(object) do
- {page, _} = Integer.parse(page)
-
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(ObjectView)
- |> render("likes.json", %{ap_id: ap_id, likes: likes, page: page})
- else
- {:public?, false} ->
- {:error, :not_found}
- end
- end
-
- def object_likes(conn, %{"uuid" => uuid}) do
- with ap_id <- o_status_url(conn, :object, uuid),
- %Object{} = object <- Object.get_cached_by_ap_id(ap_id),
- {_, true} <- {:public?, Visibility.is_public?(object)},
- likes <- Utils.get_object_likes(object) do
- conn
- |> put_resp_content_type("application/activity+json")
- |> put_view(ObjectView)
- |> render("likes.json", %{ap_id: ap_id, likes: likes})
- else
- {:public?, false} ->
- {:error, :not_found}
- end
- end
-
def activity(conn, %{"uuid" => uuid}) do
with ap_id <- o_status_url(conn, :activity, uuid),
%Activity{} = activity <- Activity.normalize(ap_id),
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 63877248a..872ed0eb2 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -580,7 +580,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
) do
with actor <- Containment.get_actor(data),
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
- {:ok, object} <- get_obj_helper(object_id),
+ {:ok, object} <- get_embedded_obj_helper(object_id, actor),
public <- Visibility.is_public?(data),
{:ok, activity, _object} <- ActivityPub.announce(actor, object, id, false, public) do
{:ok, activity}
@@ -621,7 +621,8 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
to: data["to"] || [],
cc: data["cc"] || [],
object: object,
- actor: actor_id
+ actor: actor_id,
+ activity_id: data["id"]
})
else
e ->
@@ -753,6 +754,24 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
+ # For Undos that don't have the complete object attached, try to find it in our database.
+ def handle_incoming(
+ %{
+ "type" => "Undo",
+ "object" => object
+ } = activity,
+ options
+ )
+ when is_binary(object) do
+ with %Activity{data: data} <- Activity.get_by_ap_id(object) do
+ activity
+ |> Map.put("object", data)
+ |> handle_incoming(options)
+ else
+ _e -> :error
+ end
+ end
+
def handle_incoming(_, _), do: :error
@spec get_obj_helper(String.t(), Keyword.t()) :: {:ok, Object.t()} | nil
@@ -763,6 +782,29 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
end
+ @spec get_embedded_obj_helper(String.t() | Object.t(), User.t()) :: {:ok, Object.t()} | nil
+ def get_embedded_obj_helper(%{"attributedTo" => attributed_to, "id" => object_id} = data, %User{
+ ap_id: ap_id
+ })
+ when attributed_to == ap_id do
+ with {:ok, activity} <-
+ handle_incoming(%{
+ "type" => "Create",
+ "to" => data["to"],
+ "cc" => data["cc"],
+ "actor" => attributed_to,
+ "object" => data
+ }) do
+ {:ok, Object.normalize(activity)}
+ else
+ _ -> get_obj_helper(object_id)
+ end
+ end
+
+ def get_embedded_obj_helper(object_id, _) do
+ get_obj_helper(object_id)
+ end
+
def set_reply_to_uri(%{"inReplyTo" => in_reply_to} = object) when is_binary(in_reply_to) do
with false <- String.starts_with?(in_reply_to, "http"),
{:ok, %{data: replied_to_object}} <- get_obj_helper(in_reply_to) do
@@ -812,6 +854,27 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, data}
end
+ def prepare_outgoing(%{"type" => "Announce", "actor" => ap_id, "object" => object_id} = data) do
+ object =
+ object_id
+ |> Object.normalize()
+
+ data =
+ if Visibility.is_private?(object) && object.data["actor"] == ap_id do
+ data |> Map.put("object", object |> Map.get(:data) |> prepare_object)
+ else
+ data |> maybe_fix_object_url
+ end
+
+ data =
+ data
+ |> strip_internal_fields
+ |> Map.merge(Utils.make_json_ld_header())
+ |> Map.delete("bcc")
+
+ {:ok, data}
+ end
+
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
# because of course it does.
def prepare_outgoing(%{"type" => "Accept"} = data) do
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 2ba182f4e..4ef479f96 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -251,16 +251,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.one()
end
- @doc """
- Returns like activities targeting an object
- """
- def get_object_likes(%{data: %{"id" => id}}) do
- id
- |> Activity.Queries.by_object_id()
- |> Activity.Queries.by_type("Like")
- |> Repo.all()
- end
-
@spec make_like_data(User.t(), map(), String.t()) :: map()
def make_like_data(
%User{ap_id: ap_id} = actor,
@@ -461,14 +451,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
"""
def make_unannounce_data(
%User{ap_id: ap_id} = user,
- %Activity{data: %{"context" => context}} = activity,
+ %Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id
) do
+ object = Object.normalize(object)
+
%{
"type" => "Undo",
"actor" => ap_id,
"object" => activity.data,
- "to" => [user.follower_address, activity.data["actor"]],
+ "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@@ -477,14 +469,16 @@ defmodule Pleroma.Web.ActivityPub.Utils do
def make_unlike_data(
%User{ap_id: ap_id} = user,
- %Activity{data: %{"context" => context}} = activity,
+ %Activity{data: %{"context" => context, "object" => object}} = activity,
activity_id
) do
+ object = Object.normalize(object)
+
%{
"type" => "Undo",
"actor" => ap_id,
"object" => activity.data,
- "to" => [user.follower_address, activity.data["actor"]],
+ "to" => [user.follower_address, object.data["actor"]],
"cc" => [Pleroma.Constants.as_public()],
"context" => context
}
@@ -494,7 +488,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@spec add_announce_to_object(Activity.t(), Object.t()) ::
{:ok, Object.t()} | {:error, Ecto.Changeset.t()}
def add_announce_to_object(
- %Activity{data: %{"actor" => actor, "cc" => [Pleroma.Constants.as_public()]}},
+ %Activity{data: %{"actor" => actor}},
object
) do
announcements = take_announcements(object)
@@ -745,6 +739,6 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|> Repo.all()
end
- defp maybe_put(map, _key, nil), do: map
- defp maybe_put(map, key, value), do: Map.put(map, key, value)
+ def maybe_put(map, _key, nil), do: map
+ def maybe_put(map, key, value), do: Map.put(map, key, value)
end
diff --git a/lib/pleroma/web/activity_pub/views/object_view.ex b/lib/pleroma/web/activity_pub/views/object_view.ex
index 88c55acdd..d8a3ec288 100644
--- a/lib/pleroma/web/activity_pub/views/object_view.ex
+++ b/lib/pleroma/web/activity_pub/views/object_view.ex
@@ -37,40 +37,4 @@ defmodule Pleroma.Web.ActivityPub.ObjectView do
Map.merge(base, additional)
end
-
- def render("likes.json", %{ap_id: ap_id, likes: likes, page: page}) do
- collection(likes, "#{ap_id}/likes", page)
- |> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
- end
-
- def render("likes.json", %{ap_id: ap_id, likes: likes}) do
- %{
- "id" => "#{ap_id}/likes",
- "type" => "OrderedCollection",
- "totalItems" => length(likes),
- "first" => collection(likes, "#{ap_id}/likes", 1)
- }
- |> Map.merge(Pleroma.Web.ActivityPub.Utils.make_json_ld_header())
- end
-
- def collection(collection, iri, page) do
- offset = (page - 1) * 10
- items = Enum.slice(collection, offset, 10)
- items = Enum.map(items, fn object -> Transmogrifier.prepare_object(object.data) end)
- total = length(collection)
-
- map = %{
- "id" => "#{iri}?page=#{page}",
- "type" => "OrderedCollectionPage",
- "partOf" => iri,
- "totalItems" => total,
- "orderedItems" => items
- }
-
- if offset + length(items) < total do
- Map.put(map, "next", "#{iri}?page=#{page + 1}")
- else
- map
- end
- end
end
diff --git a/lib/pleroma/web/activity_pub/views/user_view.ex b/lib/pleroma/web/activity_pub/views/user_view.ex
index c94c5a225..6bc55c85b 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -22,7 +22,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("endpoints.json", %{user: %User{local: true} = _user}) do
%{
"oauthAuthorizationEndpoint" => Helpers.o_auth_url(Endpoint, :authorize),
- "oauthRegistrationEndpoint" => Helpers.mastodon_api_url(Endpoint, :create_app),
+ "oauthRegistrationEndpoint" => Helpers.app_url(Endpoint, :create),
"oauthTokenEndpoint" => Helpers.o_auth_url(Endpoint, :token_exchange),
"sharedInbox" => Helpers.activity_pub_url(Endpoint, :inbox),
"uploadMedia" => Helpers.activity_pub_url(Endpoint, :upload_media)
diff --git a/lib/pleroma/web/activity_pub/visibility.ex b/lib/pleroma/web/activity_pub/visibility.ex
index dfb166b65..270d0fa02 100644
--- a/lib/pleroma/web/activity_pub/visibility.ex
+++ b/lib/pleroma/web/activity_pub/visibility.ex
@@ -27,6 +27,11 @@ defmodule Pleroma.Web.ActivityPub.Visibility do
end
end
+ def is_announceable?(activity, user, public \\ true) do
+ is_public?(activity) ||
+ (!public && is_private?(activity) && activity.data["actor"] == user.ap_id)
+ end
+
def is_direct?(%Activity{data: %{"directMessage" => true}}), do: true
def is_direct?(%Object{data: %{"directMessage" => true}}), do: true
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 2ec017ff8..386408d51 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -16,6 +16,8 @@ defmodule Pleroma.Web.CommonAPI do
import Pleroma.Web.Gettext
import Pleroma.Web.CommonAPI.Utils
+ require Pleroma.Constants
+
def follow(follower, followed) do
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
@@ -76,11 +78,12 @@ defmodule Pleroma.Web.CommonAPI do
end
end
- def repeat(id_or_ap_id, user) do
+ def repeat(id_or_ap_id, user, params \\ %{}) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
object <- Object.normalize(activity),
- nil <- Utils.get_existing_announce(user.ap_id, object) do
- ActivityPub.announce(user, object)
+ nil <- Utils.get_existing_announce(user.ap_id, object),
+ public <- public_announce?(object, params) do
+ ActivityPub.announce(user, object, nil, true, public)
else
_ -> {:error, dgettext("errors", "Could not repeat")}
end
@@ -169,6 +172,14 @@ defmodule Pleroma.Web.CommonAPI do
end
end
+ def public_announce?(_, %{"visibility" => visibility})
+ when visibility in ~w{public unlisted private direct},
+ do: visibility in ~w(public unlisted)
+
+ def public_announce?(object, _) do
+ Visibility.is_public?(object)
+ end
+
def get_visibility(_, _, %Participation{}), do: {"direct", "direct"}
def get_visibility(%{"visibility" => visibility}, in_reply_to, _)
@@ -262,7 +273,7 @@ defmodule Pleroma.Web.CommonAPI do
ActivityPub.update(%{
local: true,
- to: [user.follower_address],
+ to: [Pleroma.Constants.as_public(), user.follower_address],
cc: [],
actor: user.ap_id,
object: Pleroma.Web.ActivityPub.UserView.render("user.json", %{user: user})
diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex
index 83b884ba9..9a4e322c9 100644
--- a/lib/pleroma/web/controller_helper.ex
+++ b/lib/pleroma/web/controller_helper.ex
@@ -75,4 +75,16 @@ defmodule Pleroma.Web.ControllerHelper do
nil -> Pleroma.Web.MastodonAPI.FallbackController.call(conn, {:error, :not_found}) |> halt()
end
end
+
+ def try_render(conn, target, params)
+ when is_binary(target) do
+ case render(conn, target, params) do
+ nil -> render_error(conn, :not_implemented, "Can't display this activity")
+ res -> res
+ end
+ end
+
+ def try_render(conn, _, _) do
+ render_error(conn, :not_implemented, "Can't display this activity")
+ end
end
diff --git a/lib/pleroma/web/masto_fe_controller.ex b/lib/pleroma/web/masto_fe_controller.ex
new file mode 100644
index 000000000..ac9af7502
--- /dev/null
+++ b/lib/pleroma/web/masto_fe_controller.ex
@@ -0,0 +1,36 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastoFEController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.User
+
+ @doc "GET /web/*path"
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ token = get_session(conn, :oauth_token)
+
+ if user && token do
+ conn
+ |> put_layout(false)
+ |> render("index.html", token: token, user: user, custom_emojis: Pleroma.Emoji.get_all())
+ else
+ conn
+ |> put_session(:return_to, conn.request_path)
+ |> redirect(to: "/web/login")
+ end
+ end
+
+ @doc "PUT /api/web/settings"
+ def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
+ with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
+ json(conn, %{})
+ else
+ e ->
+ conn
+ |> put_status(:internal_server_error)
+ |> json(%{error: inspect(e)})
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
index 3bc9ed8ae..e195f56c4 100644
--- a/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/account_controller.ex
@@ -141,6 +141,17 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|> Enum.concat(Emoji.Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()
+ params =
+ if Map.has_key?(params, "fields_attributes") do
+ Map.update!(params, "fields_attributes", fn fields ->
+ fields
+ |> normalize_fields_attributes()
+ |> Enum.filter(fn %{"name" => n} -> n != "" end)
+ end)
+ else
+ params
+ end
+
info_params =
[
:no_rich_text,
@@ -158,12 +169,12 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
add_if_present(acc, params, to_string(key), key, &{:ok, truthy_param?(&1)})
end)
|> add_if_present(params, "default_scope", :default_scope)
- |> add_if_present(params, "fields", :fields, fn fields ->
+ |> add_if_present(params, "fields_attributes", :fields, fn fields ->
fields = Enum.map(fields, fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
{:ok, fields}
end)
- |> add_if_present(params, "fields", :raw_fields)
+ |> add_if_present(params, "fields_attributes", :raw_fields)
|> add_if_present(params, "pleroma_settings_store", :pleroma_settings_store, fn value ->
{:ok, Map.merge(user.info.pleroma_settings_store, value)}
end)
@@ -204,6 +215,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
end
+ defp normalize_fields_attributes(fields) do
+ if Enum.all?(fields, &is_tuple/1) do
+ Enum.map(fields, fn {_, v} -> v end)
+ else
+ fields
+ end
+ end
+
@doc "GET /api/v1/accounts/relationships"
def relationships(%{assigns: %{user: user}} = conn, %{"id" => id}) do
targets = User.get_all_by_ids(List.wrap(id))
@@ -338,6 +357,28 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
end
end
+ @doc "POST /api/v1/follows"
+ def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
+ with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
+ {_, true} <- {:followed, follower.id != followed.id},
+ {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
+ render(conn, "show.json", user: followed, for: follower)
+ else
+ {:followed, _} -> {:error, :not_found}
+ {:error, message} -> json_response(conn, :forbidden, %{error: message})
+ end
+ end
+
+ @doc "GET /api/v1/mutes"
+ def mutes(%{assigns: %{user: user}} = conn, _) do
+ render(conn, "index.json", users: User.muted_users(user), for: user, as: :user)
+ end
+
+ @doc "GET /api/v1/blocks"
+ def blocks(%{assigns: %{user: user}} = conn, _) do
+ render(conn, "index.json", users: User.blocked_users(user), for: user, as: :user)
+ end
+
@doc "GET /api/v1/endorsements"
def endorsements(conn, params),
do: Pleroma.Web.MastodonAPI.MastodonAPIController.empty_array(conn, params)
diff --git a/lib/pleroma/web/mastodon_api/controllers/app_controller.ex b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
new file mode 100644
index 000000000..abbe16a88
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/app_controller.ex
@@ -0,0 +1,39 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AppController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Repo
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Scopes
+ alias Pleroma.Web.OAuth.Token
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @local_mastodon_name "Mastodon-Local"
+
+ @doc "POST /api/v1/apps"
+ def create(conn, params) do
+ scopes = Scopes.fetch_scopes(params, ["read"])
+
+ app_attrs =
+ params
+ |> Map.drop(["scope", "scopes"])
+ |> Map.put("scopes", scopes)
+
+ with cs <- App.register_changeset(%App{}, app_attrs),
+ false <- cs.changes[:client_name] == @local_mastodon_name,
+ {:ok, app} <- Repo.insert(cs) do
+ render(conn, "show.json", app: app)
+ end
+ end
+
+ @doc "GET /api/v1/apps/verify_credentials"
+ def verify_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
+ with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
+ render(conn, "short.json", app: app)
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
new file mode 100644
index 000000000..bfd5120ba
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/auth_controller.ex
@@ -0,0 +1,91 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AuthController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.User
+ alias Pleroma.Web.OAuth.App
+ alias Pleroma.Web.OAuth.Authorization
+ alias Pleroma.Web.OAuth.Token
+ alias Pleroma.Web.TwitterAPI.TwitterAPI
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @local_mastodon_name "Mastodon-Local"
+
+ plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
+
+ @doc "GET /web/login"
+ def login(%{assigns: %{user: %User{}}} = conn, _params) do
+ redirect(conn, to: local_mastodon_root_path(conn))
+ end
+
+ @doc "Local Mastodon FE login init action"
+ def login(conn, %{"code" => auth_token}) do
+ with {:ok, app} <- get_or_make_app(),
+ {:ok, auth} <- Authorization.get_by_token(app, auth_token),
+ {:ok, token} <- Token.exchange_token(app, auth) do
+ conn
+ |> put_session(:oauth_token, token.token)
+ |> redirect(to: local_mastodon_root_path(conn))
+ end
+ end
+
+ @doc "Local Mastodon FE callback action"
+ def login(conn, _) do
+ with {:ok, app} <- get_or_make_app() do
+ path =
+ o_auth_path(conn, :authorize,
+ response_type: "code",
+ client_id: app.client_id,
+ redirect_uri: ".",
+ scope: Enum.join(app.scopes, " ")
+ )
+
+ redirect(conn, to: path)
+ end
+ end
+
+ @doc "DELETE /auth/sign_out"
+ def logout(conn, _) do
+ conn
+ |> clear_session
+ |> redirect(to: "/")
+ end
+
+ @doc "POST /auth/password"
+ def password_reset(conn, params) do
+ nickname_or_email = params["email"] || params["nickname"]
+
+ with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
+ conn
+ |> put_status(:no_content)
+ |> json("")
+ else
+ {:error, "unknown user"} ->
+ send_resp(conn, :not_found, "")
+
+ {:error, _} ->
+ send_resp(conn, :bad_request, "")
+ end
+ end
+
+ defp local_mastodon_root_path(conn) do
+ case get_session(conn, :return_to) do
+ nil ->
+ masto_fe_path(conn, :index, ["getting-started"])
+
+ return_to ->
+ delete_session(conn, :return_to)
+ return_to
+ end
+ end
+
+ @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
+ defp get_or_make_app do
+ %{client_name: @local_mastodon_name, redirect_uris: "."}
+ |> App.get_or_make(["read", "write", "follow", "push"])
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
new file mode 100644
index 000000000..391c0648b
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/custom_emoji_controller.ex
@@ -0,0 +1,11 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.CustomEmojiController do
+ use Pleroma.Web, :controller
+
+ def index(conn, _params) do
+ render(conn, "index.json", custom_emojis: Pleroma.Emoji.get_all())
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
new file mode 100644
index 000000000..a55f60fec
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/instance_controller.ex
@@ -0,0 +1,17 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.InstanceController do
+ use Pleroma.Web, :controller
+
+ @doc "GET /api/v1/instance"
+ def show(conn, _params) do
+ render(conn, "show.json")
+ end
+
+ @doc "GET /api/v1/instance/peers"
+ def peers(conn, _params) do
+ json(conn, Pleroma.Stats.get_peers())
+ end
+end
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 ee644abe3..32077d420 100644
--- a/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/mastodon_api_controller.ex
@@ -5,36 +5,9 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
- import Pleroma.Web.ControllerHelper, only: [add_link_headers: 2]
-
- alias Pleroma.Activity
- alias Pleroma.Bookmark
- alias Pleroma.Config
- alias Pleroma.HTTP
- alias Pleroma.Object
- alias Pleroma.Pagination
- alias Pleroma.Plugs.OAuthScopesPlug
- alias Pleroma.Plugs.RateLimiter
- alias Pleroma.Repo
- alias Pleroma.Stats
- alias Pleroma.User
- alias Pleroma.Web
- alias Pleroma.Web.ActivityPub.ActivityPub
- alias Pleroma.Web.ActivityPub.Visibility
- alias Pleroma.Web.CommonAPI
- alias Pleroma.Web.MastodonAPI.AccountView
- alias Pleroma.Web.MastodonAPI.AppView
- alias Pleroma.Web.MastodonAPI.MastodonView
- alias Pleroma.Web.MastodonAPI.StatusView
- alias Pleroma.Web.MediaProxy
- alias Pleroma.Web.OAuth.App
- alias Pleroma.Web.OAuth.Authorization
- alias Pleroma.Web.OAuth.Scopes
- alias Pleroma.Web.OAuth.Token
- alias Pleroma.Web.TwitterAPI.TwitterAPI
-
require Logger
+ alias Pleroma.Plugs.OAuthScopesPlug
@unauthenticated_access %{fallback: :proceed_unauthenticated, scopes: []}
# Note: :index action handles attempt of unauthenticated access to private instance with redirect
@@ -99,402 +72,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
- def create_app(conn, params) do
- scopes = Scopes.fetch_scopes(params, ["read"])
-
- app_attrs =
- params
- |> Map.drop(["scope", "scopes"])
- |> Map.put("scopes", scopes)
-
- with cs <- App.register_changeset(%App{}, app_attrs),
- false <- cs.changes[:client_name] == @local_mastodon_name,
- {:ok, app} <- Repo.insert(cs) do
- conn
- |> put_view(AppView)
- |> render("show.json", %{app: app})
- end
- end
-
- def verify_app_credentials(%{assigns: %{user: _user, token: token}} = conn, _) do
- with %Token{app: %App{} = app} <- Repo.preload(token, :app) do
- conn
- |> put_view(AppView)
- |> render("short.json", %{app: app})
- end
- end
-
- @mastodon_api_level "2.7.2"
-
- def masto_instance(conn, _params) do
- instance = Config.get(:instance)
-
- response = %{
- uri: Web.base_url(),
- title: Keyword.get(instance, :name),
- description: Keyword.get(instance, :description),
- version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
- email: Keyword.get(instance, :email),
- urls: %{
- streaming_api: Pleroma.Web.Endpoint.websocket_url()
- },
- stats: Stats.get_stats(),
- thumbnail: Web.base_url() <> "/instance/thumbnail.jpeg",
- languages: ["en"],
- registrations: Pleroma.Config.get([:instance, :registrations_open]),
- # Extra (not present in Mastodon):
- max_toot_chars: Keyword.get(instance, :limit),
- poll_limits: Keyword.get(instance, :poll_limits)
- }
-
- json(conn, response)
- end
-
- def peers(conn, _params) do
- json(conn, Stats.get_peers())
- end
-
- defp mastodonized_emoji do
- Pleroma.Emoji.get_all()
- |> Enum.map(fn {shortcode, %Pleroma.Emoji{file: relative_url, tags: tags}} ->
- url = to_string(URI.merge(Web.base_url(), relative_url))
-
- %{
- "shortcode" => shortcode,
- "static_url" => url,
- "visible_in_picker" => true,
- "url" => url,
- "tags" => tags,
- # Assuming that a comma is authorized in the category name
- "category" => (tags -- ["Custom"]) |> Enum.join(",")
- }
- end)
- end
-
- def custom_emojis(conn, _params) do
- mastodon_emoji = mastodonized_emoji()
- json(conn, mastodon_emoji)
- end
-
- def get_poll(%{assigns: %{user: user}} = conn, %{"id" => id}) do
- with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
- true <- Visibility.visible_for_user?(activity, user) do
- conn
- |> put_view(StatusView)
- |> try_render("poll.json", %{object: object, for: user})
- else
- error when is_nil(error) or error == false ->
- render_error(conn, :not_found, "Record not found")
- end
- end
-
- defp get_cached_vote_or_vote(user, object, choices) do
- idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
-
- {_, res} =
- Cachex.fetch(:idempotency_cache, idempotency_key, fn _ ->
- case CommonAPI.vote(user, object, choices) do
- {:error, _message} = res -> {:ignore, res}
- res -> {:commit, res}
- end
- end)
-
- res
- end
-
- def poll_vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
- with %Object{} = object <- Object.get_by_id(id),
- true <- object.data["type"] == "Question",
- %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
- true <- Visibility.visible_for_user?(activity, user),
- {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
- conn
- |> put_view(StatusView)
- |> try_render("poll.json", %{object: object, for: user})
- else
- nil ->
- render_error(conn, :not_found, "Record not found")
-
- false ->
- render_error(conn, :not_found, "Record not found")
-
- {:error, message} ->
- conn
- |> put_status(:unprocessable_entity)
- |> json(%{error: message})
- end
- end
-
- def update_media(
- %{assigns: %{user: user}} = conn,
- %{"id" => id, "description" => description} = _
- )
- when is_binary(description) do
- with %Object{} = object <- Repo.get(Object, id),
- true <- Object.authorize_mutation(object, user),
- {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
- attachment_data = Map.put(data, "id", object.id)
-
- conn
- |> put_view(StatusView)
- |> render("attachment.json", %{attachment: attachment_data})
- end
- end
-
- def update_media(_conn, _data), do: {:error, :bad_request}
-
- def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
- with {:ok, object} <-
- ActivityPub.upload(
- file,
- actor: User.ap_id(user),
- description: Map.get(data, "description")
- ) do
- attachment_data = Map.put(object.data, "id", object.id)
-
- conn
- |> put_view(StatusView)
- |> render("attachment.json", %{attachment: attachment_data})
- end
- end
-
- def follows(%{assigns: %{user: follower}} = conn, %{"uri" => uri}) do
- with {_, %User{} = followed} <- {:followed, User.get_cached_by_nickname(uri)},
- {_, true} <- {:followed, follower.id != followed.id},
- {:ok, follower, followed, _} <- CommonAPI.follow(follower, followed) do
- conn
- |> put_view(AccountView)
- |> render("show.json", %{user: followed, for: follower})
- else
- {:followed, _} ->
- {:error, :not_found}
-
- {:error, message} ->
- conn
- |> put_status(:forbidden)
- |> json(%{error: message})
- end
- end
-
- def mutes(%{assigns: %{user: user}} = conn, _) do
- with muted_accounts <- User.muted_users(user) do
- res = AccountView.render("index.json", users: muted_accounts, for: user, as: :user)
- json(conn, res)
- end
- end
-
- def blocks(%{assigns: %{user: user}} = conn, _) do
- with blocked_accounts <- User.blocked_users(user) do
- res = AccountView.render("index.json", users: blocked_accounts, for: user, as: :user)
- json(conn, res)
- end
- end
-
- def favourites(%{assigns: %{user: user}} = conn, params) do
- params =
- params
- |> Map.put("type", "Create")
- |> Map.put("favorited_by", user.ap_id)
- |> Map.put("blocking_user", user)
-
- activities =
- ActivityPub.fetch_activities([], params)
- |> Enum.reverse()
-
- conn
- |> add_link_headers(activities)
- |> put_view(StatusView)
- |> render("index.json", %{activities: activities, for: user, as: :activity})
- end
-
- def bookmarks(%{assigns: %{user: user}} = conn, params) do
- user = User.get_cached_by_id(user.id)
-
- bookmarks =
- Bookmark.for_user_query(user.id)
- |> Pagination.fetch_paginated(params)
-
- activities =
- bookmarks
- |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
-
- conn
- |> add_link_headers(bookmarks)
- |> put_view(StatusView)
- |> render("index.json", %{activities: activities, for: user, as: :activity})
- end
-
- def index(%{assigns: %{user: user}} = conn, _params) do
- token = get_session(conn, :oauth_token)
-
- if user && token do
- mastodon_emoji = mastodonized_emoji()
-
- limit = Config.get([:instance, :limit])
-
- accounts = Map.put(%{}, user.id, AccountView.render("show.json", %{user: user, for: user}))
-
- initial_state =
- %{
- meta: %{
- streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
- access_token: token,
- locale: "en",
- domain: Pleroma.Web.Endpoint.host(),
- admin: "1",
- me: "#{user.id}",
- unfollow_modal: false,
- boost_modal: false,
- delete_modal: true,
- auto_play_gif: false,
- display_sensitive_media: false,
- reduce_motion: false,
- max_toot_chars: limit,
- mascot: User.get_mascot(user)["url"]
- },
- poll_limits: Config.get([:instance, :poll_limits]),
- rights: %{
- delete_others_notice: present?(user.info.is_moderator),
- admin: present?(user.info.is_admin)
- },
- compose: %{
- me: "#{user.id}",
- default_privacy: user.info.default_scope,
- default_sensitive: false,
- allow_content_types: Config.get([:instance, :allowed_post_formats])
- },
- media_attachments: %{
- accept_content_types: [
- ".jpg",
- ".jpeg",
- ".png",
- ".gif",
- ".webm",
- ".mp4",
- ".m4v",
- "image\/jpeg",
- "image\/png",
- "image\/gif",
- "video\/webm",
- "video\/mp4"
- ]
- },
- settings:
- user.info.settings ||
- %{
- onboarded: true,
- home: %{
- shows: %{
- reblog: true,
- reply: true
- }
- },
- notifications: %{
- alerts: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- },
- shows: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- },
- sounds: %{
- follow: true,
- favourite: true,
- reblog: true,
- mention: true
- }
- }
- },
- push_subscription: nil,
- accounts: accounts,
- custom_emojis: mastodon_emoji,
- char_limit: limit
- }
- |> Jason.encode!()
-
- conn
- |> put_layout(false)
- |> put_view(MastodonView)
- |> render("index.html", %{initial_state: initial_state})
- else
- conn
- |> put_session(:return_to, conn.request_path)
- |> redirect(to: "/web/login")
- end
- end
-
- def put_settings(%{assigns: %{user: user}} = conn, %{"data" => settings} = _params) do
- with {:ok, _} <- User.update_info(user, &User.Info.mastodon_settings_update(&1, settings)) do
- json(conn, %{})
- else
- e ->
- conn
- |> put_status(:internal_server_error)
- |> json(%{error: inspect(e)})
- end
- end
-
- def login(%{assigns: %{user: %User{}}} = conn, _params) do
- redirect(conn, to: local_mastodon_root_path(conn))
- end
-
- @doc "Local Mastodon FE login init action"
- def login(conn, %{"code" => auth_token}) do
- with {:ok, app} <- get_or_make_app(),
- {:ok, auth} <- Authorization.get_by_token(app, auth_token),
- {:ok, token} <- Token.exchange_token(app, auth) do
- conn
- |> put_session(:oauth_token, token.token)
- |> redirect(to: local_mastodon_root_path(conn))
- end
- end
-
- @doc "Local Mastodon FE callback action"
- def login(conn, _) do
- with {:ok, app} <- get_or_make_app() do
- path =
- o_auth_path(conn, :authorize,
- response_type: "code",
- client_id: app.client_id,
- redirect_uri: ".",
- scope: Enum.join(app.scopes, " ")
- )
-
- redirect(conn, to: path)
- end
- end
-
- defp local_mastodon_root_path(conn) do
- case get_session(conn, :return_to) do
- nil ->
- mastodon_api_path(conn, :index, ["getting-started"])
-
- return_to ->
- delete_session(conn, :return_to)
- return_to
- end
- end
-
- @spec get_or_make_app() :: {:ok, App.t()} | {:error, Ecto.Changeset.t()}
- defp get_or_make_app do
- App.get_or_make(
- %{client_name: @local_mastodon_name, redirect_uris: "."},
- ["read", "write", "follow", "push"]
- )
- end
-
- def logout(conn, _) do
- conn
- |> clear_session
- |> redirect(to: "/")
- end
-
# Stubs for unimplemented mastodon api
#
def empty_array(conn, _) do
@@ -506,83 +83,4 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
Logger.debug("Unimplemented, returning an empty object")
json(conn, %{})
end
-
- def suggestions(%{assigns: %{user: user}} = conn, _) do
- suggestions = Config.get(:suggestions)
-
- if Keyword.get(suggestions, :enabled, false) do
- api = Keyword.get(suggestions, :third_party_engine, "")
- timeout = Keyword.get(suggestions, :timeout, 5000)
- limit = Keyword.get(suggestions, :limit, 23)
-
- host = Config.get([Pleroma.Web.Endpoint, :url, :host])
-
- user = user.nickname
-
- url =
- api
- |> String.replace("{{host}}", host)
- |> String.replace("{{user}}", user)
-
- with {:ok, %{status: 200, body: body}} <-
- HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]),
- {:ok, data} <- Jason.decode(body) do
- data =
- data
- |> Enum.slice(0, limit)
- |> Enum.map(fn x ->
- x
- |> Map.put("id", fetch_suggestion_id(x))
- |> Map.put("avatar", MediaProxy.url(x["avatar"]))
- |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
- end)
-
- json(conn, data)
- else
- e ->
- Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
- end
- else
- json(conn, [])
- end
- end
-
- defp fetch_suggestion_id(attrs) do
- case User.get_or_fetch(attrs["acct"]) do
- {:ok, %User{id: id}} -> id
- _ -> 0
- end
- end
-
- def password_reset(conn, params) do
- nickname_or_email = params["email"] || params["nickname"]
-
- with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
- conn
- |> put_status(:no_content)
- |> json("")
- else
- {:error, "unknown user"} ->
- send_resp(conn, :not_found, "")
-
- {:error, _} ->
- send_resp(conn, :bad_request, "")
- end
- end
-
- def try_render(conn, target, params)
- when is_binary(target) do
- case render(conn, target, params) do
- nil -> render_error(conn, :not_implemented, "Can't display this activity")
- res -> res
- end
- end
-
- def try_render(conn, _, _) do
- render_error(conn, :not_implemented, "Can't display this activity")
- end
-
- defp present?(nil), do: false
- defp present?(false), do: false
- defp present?(_), do: true
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/media_controller.ex b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
new file mode 100644
index 000000000..57a5b60fb
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/media_controller.ex
@@ -0,0 +1,42 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.MediaController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Object
+ alias Pleroma.User
+ alias Pleroma.Web.ActivityPub.ActivityPub
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+ plug(:put_view, Pleroma.Web.MastodonAPI.StatusView)
+
+ @doc "POST /api/v1/media"
+ def create(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
+ with {:ok, object} <-
+ ActivityPub.upload(
+ file,
+ actor: User.ap_id(user),
+ description: Map.get(data, "description")
+ ) do
+ attachment_data = Map.put(object.data, "id", object.id)
+
+ render(conn, "attachment.json", %{attachment: attachment_data})
+ end
+ end
+
+ @doc "PUT /api/v1/media/:id"
+ def update(%{assigns: %{user: user}} = conn, %{"id" => id, "description" => description})
+ when is_binary(description) do
+ with %Object{} = object <- Object.get_by_id(id),
+ true <- Object.authorize_mutation(object, user),
+ {:ok, %Object{data: data}} <- Object.update_data(object, %{"name" => description}) do
+ attachment_data = Map.put(data, "id", object.id)
+
+ render(conn, "attachment.json", %{attachment: attachment_data})
+ end
+ end
+
+ def update(_conn, _data), do: {:error, :bad_request}
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
new file mode 100644
index 000000000..fbf7f8673
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/poll_controller.ex
@@ -0,0 +1,53 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.PollController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper, only: [try_render: 3, json_response: 3]
+
+ alias Pleroma.Activity
+ alias Pleroma.Object
+ alias Pleroma.Web.ActivityPub.Visibility
+ alias Pleroma.Web.CommonAPI
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "GET /api/v1/polls/:id"
+ def show(%{assigns: %{user: user}} = conn, %{"id" => id}) do
+ with %Object{} = object <- Object.get_by_id_and_maybe_refetch(id, interval: 60),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+ true <- Visibility.visible_for_user?(activity, user) do
+ try_render(conn, "show.json", %{object: object, for: user})
+ else
+ error when is_nil(error) or error == false ->
+ render_error(conn, :not_found, "Record not found")
+ end
+ end
+
+ @doc "POST /api/v1/polls/:id/votes"
+ def vote(%{assigns: %{user: user}} = conn, %{"id" => id, "choices" => choices}) do
+ with %Object{data: %{"type" => "Question"}} = object <- Object.get_by_id(id),
+ %Activity{} = activity <- Activity.get_create_by_object_ap_id(object.data["id"]),
+ true <- Visibility.visible_for_user?(activity, user),
+ {:ok, _activities, object} <- get_cached_vote_or_vote(user, object, choices) do
+ try_render(conn, "show.json", %{object: object, for: user})
+ else
+ nil -> render_error(conn, :not_found, "Record not found")
+ false -> render_error(conn, :not_found, "Record not found")
+ {:error, message} -> json_response(conn, :unprocessable_entity, %{error: message})
+ end
+ end
+
+ defp get_cached_vote_or_vote(user, object, choices) do
+ idempotency_key = "polls:#{user.id}:#{object.data["id"]}"
+
+ Cachex.fetch!(:idempotency_cache, idempotency_key, fn ->
+ case CommonAPI.vote(user, object, choices) do
+ {:error, _message} = res -> {:ignore, res}
+ res -> {:commit, res}
+ end
+ end)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
index ee9047d1c..2cbf33046 100644
--- a/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/status_controller.ex
@@ -5,7 +5,7 @@
defmodule Pleroma.Web.MastodonAPI.StatusController do
use Pleroma.Web, :controller
- import Pleroma.Web.MastodonAPI.MastodonAPIController, only: [try_render: 3]
+ import Pleroma.Web.ControllerHelper, only: [try_render: 3, add_link_headers: 2]
require Ecto.Query
@@ -176,8 +176,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
end
@doc "POST /api/v1/statuses/:id/reblog"
- def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
- with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user),
+ def reblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id} = params) do
+ with {:ok, announce, _activity} <- CommonAPI.repeat(ap_id_or_id, user, params),
%Activity{} = announce <- Activity.normalize(announce.data) do
try_render(conn, "show.json", %{activity: announce, for: user, as: :activity})
end
@@ -293,7 +293,19 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
def reblogged_by(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
{:visible, true} <- {:visible, Visibility.visible_for_user?(activity, user)},
- %Object{data: %{"announcements" => announces}} <- Object.normalize(activity) do
+ %Object{data: %{"announcements" => announces, "id" => ap_id}} <-
+ Object.normalize(activity) do
+ announces =
+ "Announce"
+ |> Activity.Queries.by_type()
+ |> Ecto.Query.where([a], a.actor in ^announces)
+ # this is to use the index
+ |> Activity.Queries.by_object_id(ap_id)
+ |> Repo.all()
+ |> Enum.filter(&Visibility.visible_for_user?(&1, user))
+ |> Enum.map(& &1.actor)
+ |> Enum.uniq()
+
users =
User
|> Ecto.Query.where([u], u.ap_id in ^announces)
@@ -322,4 +334,39 @@ defmodule Pleroma.Web.MastodonAPI.StatusController do
render(conn, "context.json", activity: activity, activities: activities, user: user)
end
end
+
+ @doc "GET /api/v1/favourites"
+ def favourites(%{assigns: %{user: user}} = conn, params) do
+ params =
+ params
+ |> Map.put("type", "Create")
+ |> Map.put("favorited_by", user.ap_id)
+ |> Map.put("blocking_user", user)
+
+ activities =
+ ActivityPub.fetch_activities([], params)
+ |> Enum.reverse()
+
+ conn
+ |> add_link_headers(activities)
+ |> render("index.json", activities: activities, for: user, as: :activity)
+ end
+
+ @doc "GET /api/v1/bookmarks"
+ def bookmarks(%{assigns: %{user: user}} = conn, params) do
+ user = User.get_cached_by_id(user.id)
+
+ bookmarks =
+ user.id
+ |> Bookmark.for_user_query()
+ |> Pleroma.Pagination.fetch_paginated(params)
+
+ activities =
+ bookmarks
+ |> Enum.map(fn b -> Map.put(b.activity, :bookmark, Map.delete(b, :activity)) end)
+
+ conn
+ |> add_link_headers(bookmarks)
+ |> render("index.json", %{activities: activities, for: user, as: :activity})
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
new file mode 100644
index 000000000..9076bb849
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex
@@ -0,0 +1,63 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.SuggestionController do
+ use Pleroma.Web, :controller
+
+ require Logger
+
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.MediaProxy
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ @doc "GET /api/v1/suggestions"
+ def index(%{assigns: %{user: user}} = conn, _) do
+ if Config.get([:suggestions, :enabled], false) do
+ with {:ok, data} <- fetch_suggestions(user) do
+ limit = Config.get([:suggestions, :limit], 23)
+
+ data =
+ data
+ |> Enum.slice(0, limit)
+ |> Enum.map(fn x ->
+ x
+ |> Map.put("id", fetch_suggestion_id(x))
+ |> Map.put("avatar", MediaProxy.url(x["avatar"]))
+ |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"]))
+ end)
+
+ json(conn, data)
+ end
+ else
+ json(conn, [])
+ end
+ end
+
+ defp fetch_suggestions(user) do
+ api = Config.get([:suggestions, :third_party_engine], "")
+ timeout = Config.get([:suggestions, :timeout], 5000)
+ host = Config.get([Pleroma.Web.Endpoint, :url, :host])
+
+ url =
+ api
+ |> String.replace("{{host}}", host)
+ |> String.replace("{{user}}", user.nickname)
+
+ with {:ok, %{status: 200, body: body}} <-
+ Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do
+ Jason.decode(body)
+ else
+ e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
+ end
+ end
+
+ defp fetch_suggestion_id(attrs) do
+ case User.get_or_fetch(attrs["acct"]) do
+ {:ok, %User{id: id}} -> id
+ _ -> 0
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 99169ef95..2d4976891 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -167,6 +167,7 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
|> maybe_put_chat_token(user, opts[:for], opts)
|> maybe_put_activation_status(user, opts[:for])
|> maybe_put_follow_requests_count(user, opts[:for])
+ |> maybe_put_unread_conversation_count(user, opts[:for])
end
defp username_from_nickname(string) when is_binary(string) do
@@ -248,6 +249,16 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
defp maybe_put_activation_status(data, _, _), do: data
+ defp maybe_put_unread_conversation_count(data, %User{id: user_id} = user, %User{id: user_id}) do
+ data
+ |> Kernel.put_in(
+ [:pleroma, :unread_conversation_count],
+ user.info.unread_conversation_count
+ )
+ end
+
+ defp maybe_put_unread_conversation_count(data, _, _), do: data
+
defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil
end
diff --git a/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
new file mode 100644
index 000000000..cb8688941
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/custom_emoji_view.ex
@@ -0,0 +1,28 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.CustomEmojiView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.Emoji
+ alias Pleroma.Web
+
+ def render("index.json", %{custom_emojis: custom_emojis}) do
+ render_many(custom_emojis, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{custom_emoji: {shortcode, %Emoji{file: relative_url, tags: tags}}}) do
+ url = Web.base_url() |> URI.merge(relative_url) |> to_string()
+
+ %{
+ "shortcode" => shortcode,
+ "static_url" => url,
+ "visible_in_picker" => true,
+ "url" => url,
+ "tags" => tags,
+ # Assuming that a comma is authorized in the category name
+ "category" => tags |> List.delete("Custom") |> Enum.join(",")
+ }
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
new file mode 100644
index 000000000..c4866e510
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -0,0 +1,35 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.InstanceView do
+ use Pleroma.Web, :view
+
+ @mastodon_api_level "2.7.2"
+
+ def render("show.json", _) do
+ instance = Pleroma.Config.get(:instance)
+
+ %{
+ uri: Pleroma.Web.base_url(),
+ title: Keyword.get(instance, :name),
+ description: Keyword.get(instance, :description),
+ version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
+ email: Keyword.get(instance, :email),
+ urls: %{
+ streaming_api: Pleroma.Web.Endpoint.websocket_url()
+ },
+ stats: Pleroma.Stats.get_stats(),
+ thumbnail: Pleroma.Web.base_url() <> "/instance/thumbnail.jpeg",
+ languages: ["en"],
+ registrations: Keyword.get(instance, :registrations_open),
+ # Extra (not present in Mastodon):
+ max_toot_chars: Keyword.get(instance, :limit),
+ poll_limits: Keyword.get(instance, :poll_limits),
+ upload_limit: Keyword.get(instance, :upload_limit),
+ avatar_upload_limit: Keyword.get(instance, :avatar_upload_limit),
+ background_upload_limit: Keyword.get(instance, :background_upload_limit),
+ banner_upload_limit: Keyword.get(instance, :banner_upload_limit)
+ }
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/mastodon_view.ex b/lib/pleroma/web/mastodon_api/views/mastodon_view.ex
deleted file mode 100644
index 33b9a74be..000000000
--- a/lib/pleroma/web/mastodon_api/views/mastodon_view.ex
+++ /dev/null
@@ -1,8 +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.MastodonAPI.MastodonView do
- use Pleroma.Web, :view
- import Phoenix.HTML
-end
diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex
index 60b58dc90..5e3dbe728 100644
--- a/lib/pleroma/web/mastodon_api/views/notification_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex
@@ -25,40 +25,44 @@ defmodule Pleroma.Web.MastodonAPI.NotificationView do
parent_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
mastodon_type = Activity.mastodon_notification_type(activity)
- response = %{
- id: to_string(notification.id),
- type: mastodon_type,
- created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
- account: AccountView.render("show.json", %{user: actor, for: user}),
- pleroma: %{
- is_seen: notification.seen
+ with %{id: _} = account <- AccountView.render("show.json", %{user: actor, for: user}) do
+ response = %{
+ id: to_string(notification.id),
+ type: mastodon_type,
+ created_at: CommonAPI.Utils.to_masto_date(notification.inserted_at),
+ account: account,
+ pleroma: %{
+ is_seen: notification.seen
+ }
}
- }
- case mastodon_type do
- "mention" ->
- response
- |> Map.merge(%{
- status: StatusView.render("show.json", %{activity: activity, for: user})
- })
+ case mastodon_type do
+ "mention" ->
+ response
+ |> Map.merge(%{
+ status: StatusView.render("show.json", %{activity: activity, for: user})
+ })
- "favourite" ->
- response
- |> Map.merge(%{
- status: StatusView.render("show.json", %{activity: parent_activity, for: user})
- })
+ "favourite" ->
+ response
+ |> Map.merge(%{
+ status: StatusView.render("show.json", %{activity: parent_activity, for: user})
+ })
- "reblog" ->
- response
- |> Map.merge(%{
- status: StatusView.render("show.json", %{activity: parent_activity, for: user})
- })
+ "reblog" ->
+ response
+ |> Map.merge(%{
+ status: StatusView.render("show.json", %{activity: parent_activity, for: user})
+ })
- "follow" ->
- response
+ "follow" ->
+ response
- _ ->
- nil
+ _ ->
+ nil
+ end
+ else
+ _ -> nil
end
end
end
diff --git a/lib/pleroma/web/mastodon_api/views/poll_view.ex b/lib/pleroma/web/mastodon_api/views/poll_view.ex
new file mode 100644
index 000000000..753039da3
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/poll_view.ex
@@ -0,0 +1,74 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.PollView do
+ use Pleroma.Web, :view
+
+ alias Pleroma.HTML
+ alias Pleroma.Web.CommonAPI.Utils
+
+ def render("show.json", %{object: object, multiple: multiple, options: options} = params) do
+ {end_time, expired} = end_time_and_expired(object)
+ {options, votes_count} = options_and_votes_count(options)
+
+ %{
+ # 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: end_time,
+ expired: expired,
+ multiple: multiple,
+ votes_count: votes_count,
+ options: options,
+ voted: voted?(params),
+ emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
+ }
+ end
+
+ def render("show.json", %{object: object} = params) do
+ case object.data do
+ %{"anyOf" => options} when is_list(options) ->
+ render(__MODULE__, "show.json", Map.merge(params, %{multiple: true, options: options}))
+
+ %{"oneOf" => options} when is_list(options) ->
+ render(__MODULE__, "show.json", Map.merge(params, %{multiple: false, options: options}))
+
+ _ ->
+ nil
+ end
+ end
+
+ defp end_time_and_expired(object) do
+ case object.data["closed"] || object.data["endTime"] do
+ end_time when is_binary(end_time) ->
+ end_time = NaiveDateTime.from_iso8601!(end_time)
+ expired = NaiveDateTime.compare(end_time, NaiveDateTime.utc_now()) == :lt
+
+ {Utils.to_masto_date(end_time), expired}
+
+ _ ->
+ {nil, false}
+ end
+ end
+
+ defp options_and_votes_count(options) do
+ Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
+ current_count = option["replies"]["totalItems"] || 0
+
+ {%{
+ title: HTML.strip_tags(name),
+ votes_count: current_count
+ }, current_count + count}
+ end)
+ end
+
+ defp voted?(%{object: object} = opts) do
+ if opts[:for] do
+ existing_votes = Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
+ existing_votes != [] or opts[:for].ap_id == object.data["actor"]
+ else
+ false
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index bc527ad1b..9b8dd3086 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -18,6 +18,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.PollView
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.MediaProxy
@@ -124,7 +125,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
pinned: pinned?(activity, user),
sensitive: false,
spoiler_text: "",
- visibility: "public",
+ visibility: get_visibility(activity),
media_attachments: reblogged[:media_attachments] || [],
mentions: mentions,
tags: reblogged[:tags] || [],
@@ -277,7 +278,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
spoiler_text: summary_html,
visibility: get_visibility(object),
media_attachments: attachments,
- poll: render("poll.json", %{object: object, for: opts[:for]}),
+ poll: render(PollView, "show.json", object: object, for: opts[:for]),
mentions: mentions,
tags: build_tags(tags),
application: %{
@@ -389,75 +390,6 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
safe_render_many(opts.activities, StatusView, "listen.json", opts)
end
- def render("poll.json", %{object: object} = opts) do
- {multiple, options} =
- case object.data do
- %{"anyOf" => options} when is_list(options) -> {true, options}
- %{"oneOf" => options} when is_list(options) -> {false, options}
- _ -> {nil, nil}
- end
-
- if options do
- {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 =
- if opts[:for] do
- existing_votes =
- Pleroma.Web.ActivityPub.Utils.get_existing_votes(opts[:for].ap_id, object)
-
- existing_votes != [] or opts[:for].ap_id == object.data["actor"]
- else
- false
- end
-
- {options, votes_count} =
- Enum.map_reduce(options, 0, fn %{"name" => name} = option, count ->
- current_count = option["replies"]["totalItems"] || 0
-
- {%{
- title: HTML.strip_tags(name),
- votes_count: current_count
- }, current_count + count}
- end)
-
- %{
- # 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: end_time,
- expired: expired,
- multiple: multiple,
- votes_count: votes_count,
- options: options,
- voted: voted,
- emojis: build_emojis(object.data["emoji"])
- }
- else
- nil
- end
- end
-
def render("context.json", %{activity: activity, activities: activities, user: user}) do
%{ancestors: ancestors, descendants: descendants} =
activities
diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex
index 2d7b10e73..3c17a2095 100644
--- a/lib/pleroma/web/oauth/oauth_controller.ex
+++ b/lib/pleroma/web/oauth/oauth_controller.ex
@@ -212,13 +212,31 @@ defmodule Pleroma.Web.OAuth.OAuthController do
{:auth_active, false} ->
# Per https://github.com/tootsuite/mastodon/blob/
# 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76
- render_error(conn, :forbidden, "Your login is missing a confirmed e-mail address")
+ render_error(
+ conn,
+ :forbidden,
+ "Your login is missing a confirmed e-mail address",
+ %{},
+ "missing_confirmed_email"
+ )
{:user_active, false} ->
- render_error(conn, :forbidden, "Your account is currently disabled")
+ render_error(
+ conn,
+ :forbidden,
+ "Your account is currently disabled",
+ %{},
+ "account_is_disabled"
+ )
{:password_reset_pending, true} ->
- render_error(conn, :forbidden, "Password reset is required")
+ render_error(
+ conn,
+ :forbidden,
+ "Password reset is required",
+ %{},
+ "password_reset_required"
+ )
_error ->
render_invalid_credentials_error(conn)
@@ -442,7 +460,7 @@ defmodule Pleroma.Web.OAuth.OAuthController do
end
# Special case: Local MastodonFE
- defp redirect_uri(%Plug.Conn{} = conn, "."), do: mastodon_api_url(conn, :login)
+ defp redirect_uri(%Plug.Conn{} = conn, "."), do: auth_url(conn, :login)
defp redirect_uri(%Plug.Conn{}, redirect_uri), do: redirect_uri
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index cc6bcfa1a..a36c40a3b 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -87,6 +87,31 @@ defmodule Pleroma.Web.Router do
plug(Pleroma.Plugs.EnsureUserKeyPlug)
end
+ pipeline :oauth_read_or_public do
+ plug(Pleroma.Plugs.OAuthScopesPlug, %{
+ scopes: ["read"],
+ fallback: :proceed_unauthenticated
+ })
+
+ plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug)
+ end
+
+ pipeline :oauth_read do
+ plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["read"]})
+ end
+
+ pipeline :oauth_write do
+ plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["write"]})
+ end
+
+ pipeline :oauth_follow do
+ plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["follow"]})
+ end
+
+ pipeline :oauth_push do
+ plug(Pleroma.Plugs.OAuthScopesPlug, %{scopes: ["push"]})
+ end
+
pipeline :well_known do
plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
end
@@ -129,7 +154,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api/pleroma/admin", Pleroma.Web.AdminAPI do
- pipe_through(:admin_api)
+ pipe_through([:admin_api, :oauth_write])
post("/users/follow", AdminAPIController, :user_follow)
post("/users/unfollow", AdminAPIController, :user_unfollow)
@@ -188,7 +213,7 @@ defmodule Pleroma.Web.Router do
scope "/api/pleroma/emoji", Pleroma.Web.PleromaAPI do
scope "/packs" do
# Modifying packs
- pipe_through(:admin_api)
+ pipe_through([:admin_api, :oauth_write])
post("/import_from_fs", EmojiAPIController, :import_from_fs)
@@ -212,20 +237,32 @@ defmodule Pleroma.Web.Router do
post("/main/ostatus", UtilController, :remote_subscribe)
get("/ostatus_subscribe", UtilController, :remote_follow)
- post("/ostatus_subscribe", UtilController, :do_remote_follow)
+
+ scope [] do
+ pipe_through(:oauth_follow)
+ post("/ostatus_subscribe", UtilController, :do_remote_follow)
+ end
end
scope "/api/pleroma", Pleroma.Web.TwitterAPI do
pipe_through(:authenticated_api)
- 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)
- post("/disable_account", UtilController, :disable_account)
+ 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)
+ post("/disable_account", UtilController, :disable_account)
+ end
- post("/blocks_import", UtilController, :blocks_import)
- post("/follow_import", UtilController, :follow_import)
+ scope [] do
+ pipe_through(:oauth_follow)
+
+ post("/blocks_import", UtilController, :blocks_import)
+ post("/follow_import", UtilController, :follow_import)
+ end
end
scope "/oauth", Pleroma.Web.OAuth do
@@ -252,14 +289,14 @@ defmodule Pleroma.Web.Router do
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
scope [] do
pipe_through(:authenticated_api)
-
+ pipe_through(:oauth_read)
get("/conversations/:id/statuses", PleromaAPIController, :conversation_statuses)
get("/conversations/:id", PleromaAPIController, :conversation)
end
scope [] do
pipe_through(:authenticated_api)
-
+ pipe_through(:oauth_write)
patch("/conversations/:id", PleromaAPIController, :update_conversation)
post("/notifications/read", PleromaAPIController, :read_notification)
@@ -275,11 +312,13 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:api)
+ pipe_through(:oauth_read_or_public)
get("/accounts/:id/favourites", AccountController, :favourites)
end
scope [] do
pipe_through(:authenticated_api)
+ pipe_through(:oauth_follow)
post("/accounts/:id/subscribe", AccountController, :subscribe)
post("/accounts/:id/unsubscribe", AccountController, :unsubscribe)
@@ -289,7 +328,7 @@ defmodule Pleroma.Web.Router do
end
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
- pipe_through(:api)
+ pipe_through([:api, :oauth_read_or_public])
get("/accounts/:id/scrobbles", ScrobbleController, :user_scrobbles)
end
@@ -297,153 +336,171 @@ defmodule Pleroma.Web.Router do
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api)
- get("/accounts/verify_credentials", AccountController, :verify_credentials)
+ scope [] do
+ pipe_through(:oauth_read)
+
+ get("/accounts/verify_credentials", AccountController, :verify_credentials)
+
+ get("/accounts/relationships", AccountController, :relationships)
+
+ get("/accounts/:id/lists", AccountController, :lists)
+ get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
- get("/accounts/relationships", AccountController, :relationships)
+ get("/follow_requests", FollowRequestController, :index)
+ get("/blocks", AccountController, :blocks)
+ get("/mutes", AccountController, :mutes)
- get("/accounts/:id/lists", AccountController, :lists)
- get("/accounts/:id/identity_proofs", MastodonAPIController, :empty_array)
+ get("/timelines/home", TimelineController, :home)
+ get("/timelines/direct", TimelineController, :direct)
- get("/follow_requests", FollowRequestController, :index)
- get("/blocks", MastodonAPIController, :blocks)
- get("/mutes", MastodonAPIController, :mutes)
+ get("/favourites", StatusController, :favourites)
+ get("/bookmarks", StatusController, :bookmarks)
- get("/timelines/home", TimelineController, :home)
- get("/timelines/direct", TimelineController, :direct)
+ get("/notifications", NotificationController, :index)
+ get("/notifications/:id", NotificationController, :show)
+ post("/notifications/clear", NotificationController, :clear)
+ post("/notifications/dismiss", NotificationController, :dismiss)
+ delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
- get("/favourites", MastodonAPIController, :favourites)
- get("/bookmarks", MastodonAPIController, :bookmarks)
+ get("/scheduled_statuses", ScheduledActivityController, :index)
+ get("/scheduled_statuses/:id", ScheduledActivityController, :show)
- get("/notifications", NotificationController, :index)
- get("/notifications/:id", NotificationController, :show)
- post("/notifications/clear", NotificationController, :clear)
- post("/notifications/dismiss", NotificationController, :dismiss)
- delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
+ get("/lists", ListController, :index)
+ get("/lists/:id", ListController, :show)
+ get("/lists/:id/accounts", ListController, :list_accounts)
- get("/scheduled_statuses", ScheduledActivityController, :index)
- get("/scheduled_statuses/:id", ScheduledActivityController, :show)
+ get("/domain_blocks", DomainBlockController, :index)
- get("/lists", ListController, :index)
- get("/lists/:id", ListController, :show)
- get("/lists/:id/accounts", ListController, :list_accounts)
+ get("/filters", FilterController, :index)
- get("/domain_blocks", DomainBlockController, :index)
+ get("/suggestions", SuggestionController, :index)
- get("/filters", FilterController, :index)
+ get("/conversations", ConversationController, :index)
+ post("/conversations/:id/read", ConversationController, :read)
- get("/suggestions", MastodonAPIController, :suggestions)
+ get("/endorsements", MastodonAPIController, :empty_array)
+ end
- get("/conversations", ConversationController, :index)
- post("/conversations/:id/read", ConversationController, :read)
+ scope [] do
+ pipe_through(:oauth_write)
- get("/endorsements", AccountController, :endorsements)
+ patch("/accounts/update_credentials", AccountController, :update_credentials)
- patch("/accounts/update_credentials", AccountController, :update_credentials)
+ post("/statuses", StatusController, :create)
+ delete("/statuses/:id", StatusController, :delete)
- post("/statuses", StatusController, :create)
- delete("/statuses/:id", StatusController, :delete)
+ post("/statuses/:id/reblog", StatusController, :reblog)
+ post("/statuses/:id/unreblog", StatusController, :unreblog)
+ post("/statuses/:id/favourite", StatusController, :favourite)
+ post("/statuses/:id/unfavourite", StatusController, :unfavourite)
+ post("/statuses/:id/pin", StatusController, :pin)
+ post("/statuses/:id/unpin", StatusController, :unpin)
+ post("/statuses/:id/bookmark", StatusController, :bookmark)
+ post("/statuses/:id/unbookmark", StatusController, :unbookmark)
+ post("/statuses/:id/mute", StatusController, :mute_conversation)
+ post("/statuses/:id/unmute", StatusController, :unmute_conversation)
- post("/statuses/:id/reblog", StatusController, :reblog)
- post("/statuses/:id/unreblog", StatusController, :unreblog)
- post("/statuses/:id/favourite", StatusController, :favourite)
- post("/statuses/:id/unfavourite", StatusController, :unfavourite)
- post("/statuses/:id/pin", StatusController, :pin)
- post("/statuses/:id/unpin", StatusController, :unpin)
- post("/statuses/:id/bookmark", StatusController, :bookmark)
- post("/statuses/:id/unbookmark", StatusController, :unbookmark)
- post("/statuses/:id/mute", StatusController, :mute_conversation)
- post("/statuses/:id/unmute", StatusController, :unmute_conversation)
+ put("/scheduled_statuses/:id", ScheduledActivityController, :update)
+ delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
- put("/scheduled_statuses/:id", ScheduledActivityController, :update)
- delete("/scheduled_statuses/:id", ScheduledActivityController, :delete)
+ post("/polls/:id/votes", PollController, :vote)
- post("/polls/:id/votes", MastodonAPIController, :poll_vote)
+ post("/media", MediaController, :create)
+ put("/media/:id", MediaController, :update)
- post("/media", MastodonAPIController, :upload)
- put("/media/:id", MastodonAPIController, :update_media)
+ delete("/lists/:id", ListController, :delete)
+ post("/lists", ListController, :create)
+ put("/lists/:id", ListController, :update)
- delete("/lists/:id", ListController, :delete)
- post("/lists", ListController, :create)
- put("/lists/:id", ListController, :update)
+ post("/lists/:id/accounts", ListController, :add_to_list)
+ delete("/lists/:id/accounts", ListController, :remove_from_list)
- post("/lists/:id/accounts", ListController, :add_to_list)
- delete("/lists/:id/accounts", ListController, :remove_from_list)
+ post("/filters", FilterController, :create)
+ get("/filters/:id", FilterController, :show)
+ put("/filters/:id", FilterController, :update)
+ delete("/filters/:id", FilterController, :delete)
- post("/filters", FilterController, :create)
- get("/filters/:id", FilterController, :show)
- put("/filters/:id", FilterController, :update)
- delete("/filters/:id", FilterController, :delete)
+ post("/reports", ReportController, :create)
+ end
- post("/reports", ReportController, :create)
+ scope [] do
+ pipe_through(:oauth_follow)
- # To do: POST /api/v1/follows is not present in Mastodon - consider removing
- post("/follows", MastodonAPIController, :follows)
+ post("/follows", AccountController, :follows)
+ post("/accounts/:id/follow", AccountController, :follow)
+ post("/accounts/:id/unfollow", AccountController, :unfollow)
+ post("/accounts/:id/block", AccountController, :block)
+ post("/accounts/:id/unblock", AccountController, :unblock)
+ post("/accounts/:id/mute", AccountController, :mute)
+ post("/accounts/:id/unmute", AccountController, :unmute)
- post("/accounts/:id/follow", AccountController, :follow)
- post("/accounts/:id/unfollow", AccountController, :unfollow)
- post("/accounts/:id/block", AccountController, :block)
- post("/accounts/:id/unblock", AccountController, :unblock)
- post("/accounts/:id/mute", AccountController, :mute)
- post("/accounts/:id/unmute", AccountController, :unmute)
+ post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
+ post("/follow_requests/:id/reject", FollowRequestController, :reject)
- post("/follow_requests/:id/authorize", FollowRequestController, :authorize)
- post("/follow_requests/:id/reject", FollowRequestController, :reject)
+ post("/domain_blocks", DomainBlockController, :create)
+ delete("/domain_blocks", DomainBlockController, :delete)
+ end
- post("/domain_blocks", DomainBlockController, :create)
- delete("/domain_blocks", DomainBlockController, :delete)
+ scope [] do
+ pipe_through(:oauth_push)
- post("/push/subscription", SubscriptionController, :create)
- get("/push/subscription", SubscriptionController, :get)
- put("/push/subscription", SubscriptionController, :update)
- delete("/push/subscription", SubscriptionController, :delete)
+ post("/push/subscription", SubscriptionController, :create)
+ get("/push/subscription", SubscriptionController, :get)
+ put("/push/subscription", SubscriptionController, :update)
+ delete("/push/subscription", SubscriptionController, :delete)
+ end
end
- scope "/api/web", Pleroma.Web.MastodonAPI do
- pipe_through(:authenticated_api)
+ scope "/api/web", Pleroma.Web do
+ pipe_through([:authenticated_api, :oauth_write])
- put("/settings", MastodonAPIController, :put_settings)
+ put("/settings", MastoFEController, :put_settings)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
post("/accounts", AccountController, :create)
+ get("/accounts/search", SearchController, :account_search)
- get("/instance", MastodonAPIController, :masto_instance)
- get("/instance/peers", MastodonAPIController, :peers)
- post("/apps", MastodonAPIController, :create_app)
- get("/apps/verify_credentials", MastodonAPIController, :verify_app_credentials)
- get("/custom_emojis", MastodonAPIController, :custom_emojis)
+ get("/instance", InstanceController, :show)
+ get("/instance/peers", InstanceController, :peers)
- get("/statuses/:id/card", StatusController, :card)
+ post("/apps", AppController, :create)
+ get("/apps/verify_credentials", AppController, :verify_credentials)
+ get("/statuses/:id/card", StatusController, :card)
get("/statuses/:id/favourited_by", StatusController, :favourited_by)
get("/statuses/:id/reblogged_by", StatusController, :reblogged_by)
+ get("/custom_emojis", CustomEmojiController, :index)
+
get("/trends", MastodonAPIController, :empty_array)
- get("/accounts/search", SearchController, :account_search)
+ scope [] do
+ pipe_through(:oauth_read_or_public)
- get("/timelines/public", TimelineController, :public)
- get("/timelines/tag/:tag", TimelineController, :hashtag)
- get("/timelines/list/:list_id", TimelineController, :list)
+ get("/timelines/public", TimelineController, :public)
+ get("/timelines/tag/:tag", TimelineController, :hashtag)
+ get("/timelines/list/:list_id", TimelineController, :list)
- get("/statuses", StatusController, :index)
- get("/statuses/:id", StatusController, :show)
- get("/statuses/:id/context", StatusController, :context)
+ get("/statuses", StatusController, :index)
+ get("/statuses/:id", StatusController, :show)
+ get("/statuses/:id/context", StatusController, :context)
- get("/polls/:id", MastodonAPIController, :get_poll)
+ get("/polls/:id", PollController, :show)
- get("/accounts/:id/statuses", AccountController, :statuses)
- get("/accounts/:id/followers", AccountController, :followers)
- get("/accounts/:id/following", AccountController, :following)
- get("/accounts/:id", AccountController, :show)
+ get("/accounts/:id/statuses", AccountController, :statuses)
+ get("/accounts/:id/followers", AccountController, :followers)
+ get("/accounts/:id/following", AccountController, :following)
+ get("/accounts/:id", AccountController, :show)
- get("/search", SearchController, :search)
+ get("/search", SearchController, :search)
+ end
end
scope "/api/v2", Pleroma.Web.MastodonAPI do
- pipe_through(:api)
+ pipe_through([:api, :oauth_read_or_public])
get("/search", SearchController, :search2)
end
@@ -474,7 +531,11 @@ defmodule Pleroma.Web.Router do
get("/oauth_tokens", TwitterAPI.Controller, :oauth_tokens)
delete("/oauth_tokens/:id", TwitterAPI.Controller, :revoke_token)
- post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
+ scope [] do
+ pipe_through(:oauth_read)
+
+ post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
+ end
end
pipeline :ap_service_actor do
@@ -519,7 +580,6 @@ defmodule Pleroma.Web.Router do
pipe_through(:ostatus)
get("/users/:nickname/outbox", ActivityPubController, :outbox)
- get("/objects/:uuid/likes", ActivityPubController, :object_likes)
end
pipeline :activitypub_client do
@@ -539,14 +599,23 @@ defmodule Pleroma.Web.Router do
scope "/", Pleroma.Web.ActivityPub do
pipe_through([:activitypub_client])
- get("/api/ap/whoami", ActivityPubController, :whoami)
- get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
+ scope [] do
+ pipe_through(:oauth_read)
+ get("/api/ap/whoami", ActivityPubController, :whoami)
+ get("/users/:nickname/inbox", ActivityPubController, :read_inbox)
+ end
- post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
- post("/api/ap/upload_media", ActivityPubController, :upload_media)
+ scope [] do
+ pipe_through(:oauth_write)
+ post("/users/:nickname/outbox", ActivityPubController, :update_outbox)
+ post("/api/ap/upload_media", ActivityPubController, :upload_media)
+ end
- get("/users/:nickname/followers", ActivityPubController, :followers)
- get("/users/:nickname/following", ActivityPubController, :following)
+ scope [] do
+ pipe_through(:oauth_read_or_public)
+ get("/users/:nickname/followers", ActivityPubController, :followers)
+ get("/users/:nickname/following", ActivityPubController, :following)
+ end
end
scope "/", Pleroma.Web.ActivityPub do
@@ -588,15 +657,18 @@ defmodule Pleroma.Web.Router do
get("/:version", Nodeinfo.NodeinfoController, :nodeinfo)
end
- scope "/", Pleroma.Web.MastodonAPI do
+ scope "/", Pleroma.Web do
pipe_through(:mastodon_html)
- get("/web/login", MastodonAPIController, :login)
- delete("/auth/sign_out", MastodonAPIController, :logout)
+ get("/web/login", MastodonAPI.AuthController, :login)
+ delete("/auth/sign_out", MastodonAPI.AuthController, :logout)
- post("/auth/password", MastodonAPIController, :password_reset)
+ post("/auth/password", MastodonAPI.AuthController, :password_reset)
- get("/web/*path", MastodonAPIController, :index)
+ scope [] do
+ pipe_through(:oauth_read)
+ get("/web/*path", MastoFEController, :index)
+ end
end
pipeline :remote_media do
diff --git a/lib/pleroma/web/streamer/ping.ex b/lib/pleroma/web/streamer/ping.ex
index f77cbb95c..db3e68abe 100644
--- a/lib/pleroma/web/streamer/ping.ex
+++ b/lib/pleroma/web/streamer/ping.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Streamer.Ping do
use GenServer
require Logger
diff --git a/lib/pleroma/web/streamer/state.ex b/lib/pleroma/web/streamer/state.ex
index c48752d95..5ce3ebb8a 100644
--- a/lib/pleroma/web/streamer/state.ex
+++ b/lib/pleroma/web/streamer/state.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Streamer.State do
use GenServer
require Logger
diff --git a/lib/pleroma/web/streamer/streamer_socket.ex b/lib/pleroma/web/streamer/streamer_socket.ex
index f006c0306..cf0fa3077 100644
--- a/lib/pleroma/web/streamer/streamer_socket.ex
+++ b/lib/pleroma/web/streamer/streamer_socket.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Streamer.StreamerSocket do
defstruct transport_pid: nil, user: nil
diff --git a/lib/pleroma/web/streamer/supervisor.ex b/lib/pleroma/web/streamer/supervisor.ex
index 6afe19323..ec5985085 100644
--- a/lib/pleroma/web/streamer/supervisor.ex
+++ b/lib/pleroma/web/streamer/supervisor.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Streamer.Supervisor do
use Supervisor
diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex
index 5804508eb..0ea224874 100644
--- a/lib/pleroma/web/streamer/worker.ex
+++ b/lib/pleroma/web/streamer/worker.ex
@@ -1,3 +1,7 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
defmodule Pleroma.Web.Streamer.Worker do
use GenServer
@@ -128,11 +132,14 @@ defmodule Pleroma.Web.Streamer.Worker do
blocks = user.info.blocks || []
mutes = user.info.mutes || []
reblog_mutes = user.info.muted_reblogs || []
+ recipient_blocks = MapSet.new(blocks ++ mutes)
+ recipients = MapSet.new(item.recipients)
domain_blocks = Pleroma.Web.ActivityPub.MRF.subdomains_regex(user.info.domain_blocks)
with parent when not is_nil(parent) <- Object.normalize(item),
true <- Enum.all?([blocks, mutes, reblog_mutes], &(item.actor not in &1)),
true <- Enum.all?([blocks, mutes], &(parent.data["actor"] not in &1)),
+ true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor),
%{host: parent_host} <- URI.parse(parent.data["actor"]),
false <- Pleroma.Web.ActivityPub.MRF.subdomain_match?(domain_blocks, item_host),
@@ -194,11 +201,8 @@ defmodule Pleroma.Web.Streamer.Worker do
# Get the current user so we have up-to-date blocks etc.
if socket_user do
user = User.get_cached_by_ap_id(socket_user.ap_id)
- blocks = user.info.blocks || []
- mutes = user.info.mutes || []
- with true <- Enum.all?([blocks, mutes], &(item.actor not in &1)),
- true <- thread_containment(item, user) do
+ if should_send?(user, item) do
send(transport_pid, {:text, StreamerView.render("update.json", item, user)})
end
else
diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/masto_fe/index.html.eex
index 3325beca1..feff36fae 100644
--- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
+++ b/lib/pleroma/web/templates/masto_fe/index.html.eex
@@ -14,7 +14,7 @@
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/compose.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/home_timeline.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/features/notifications.js'>
-<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
+<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
<script src="/packs/core/common.js"></script>
<link rel="stylesheet" media="all" href="/packs/core/common.css" />
diff --git a/lib/pleroma/web/translation_helpers.ex b/lib/pleroma/web/translation_helpers.ex
index 8f5a43bf6..a104ea6b8 100644
--- a/lib/pleroma/web/translation_helpers.ex
+++ b/lib/pleroma/web/translation_helpers.ex
@@ -3,15 +3,27 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.TranslationHelpers do
- defmacro render_error(conn, status, msgid, bindings \\ Macro.escape(%{})) do
+ defmacro render_error(
+ conn,
+ status,
+ msgid,
+ bindings \\ Macro.escape(%{}),
+ identifier \\ Macro.escape("")
+ ) do
quote do
require Pleroma.Web.Gettext
+ error_map =
+ %{
+ error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings)),
+ identifier: unquote(identifier)
+ }
+ |> Enum.reject(fn {_k, v} -> v == "" end)
+ |> Map.new()
+
unquote(conn)
|> Plug.Conn.put_status(unquote(status))
- |> Phoenix.Controller.json(%{
- error: Pleroma.Web.Gettext.dgettext("errors", unquote(msgid), unquote(bindings))
- })
+ |> Phoenix.Controller.json(error_map)
end
end
end
diff --git a/lib/pleroma/web/views/masto_fe_view.ex b/lib/pleroma/web/views/masto_fe_view.ex
new file mode 100644
index 000000000..21b086d4c
--- /dev/null
+++ b/lib/pleroma/web/views/masto_fe_view.ex
@@ -0,0 +1,102 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastoFEView do
+ use Pleroma.Web, :view
+ alias Pleroma.Config
+ alias Pleroma.User
+ alias Pleroma.Web.MastodonAPI.AccountView
+ alias Pleroma.Web.MastodonAPI.CustomEmojiView
+
+ @default_settings %{
+ onboarded: true,
+ home: %{
+ shows: %{
+ reblog: true,
+ reply: true
+ }
+ },
+ notifications: %{
+ alerts: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ },
+ shows: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ },
+ sounds: %{
+ follow: true,
+ favourite: true,
+ reblog: true,
+ mention: true
+ }
+ }
+ }
+
+ def initial_state(token, user, custom_emojis) do
+ limit = Config.get([:instance, :limit])
+
+ %{
+ meta: %{
+ streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
+ access_token: token,
+ locale: "en",
+ domain: Pleroma.Web.Endpoint.host(),
+ admin: "1",
+ me: "#{user.id}",
+ unfollow_modal: false,
+ boost_modal: false,
+ delete_modal: true,
+ auto_play_gif: false,
+ display_sensitive_media: false,
+ reduce_motion: false,
+ max_toot_chars: limit,
+ mascot: User.get_mascot(user)["url"]
+ },
+ poll_limits: Config.get([:instance, :poll_limits]),
+ rights: %{
+ delete_others_notice: present?(user.info.is_moderator),
+ admin: present?(user.info.is_admin)
+ },
+ compose: %{
+ me: "#{user.id}",
+ default_privacy: user.info.default_scope,
+ default_sensitive: false,
+ allow_content_types: Config.get([:instance, :allowed_post_formats])
+ },
+ media_attachments: %{
+ accept_content_types: [
+ ".jpg",
+ ".jpeg",
+ ".png",
+ ".gif",
+ ".webm",
+ ".mp4",
+ ".m4v",
+ "image\/jpeg",
+ "image\/png",
+ "image\/gif",
+ "video\/webm",
+ "video\/mp4"
+ ]
+ },
+ settings: user.info.settings || @default_settings,
+ push_subscription: nil,
+ accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
+ custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
+ char_limit: limit
+ }
+ |> Jason.encode!()
+ |> Phoenix.HTML.raw()
+ end
+
+ defp present?(nil), do: false
+ defp present?(false), do: false
+ defp present?(_), do: true
+end