summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/pleroma/instance.ex44
-rw-r--r--lib/pleroma/announcement.ex160
-rw-r--r--lib/pleroma/announcement_read_relationship.ex55
-rw-r--r--lib/pleroma/application_requirements.ex3
-rw-r--r--lib/pleroma/config/deprecation_warnings.ex40
-rw-r--r--lib/pleroma/constants.ex6
-rw-r--r--lib/pleroma/docs/translator.ex10
-rw-r--r--lib/pleroma/docs/translator/compiler.ex119
-rw-r--r--lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex25
-rw-r--r--lib/pleroma/upload.ex27
-rw-r--r--lib/pleroma/upload/filter/exiftool/read_description.ex49
-rw-r--r--lib/pleroma/upload/filter/exiftool/strip_location.ex (renamed from lib/pleroma/upload/filter/exiftool.ex)2
-rw-r--r--lib/pleroma/user.ex43
-rw-r--r--lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex12
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex6
-rw-r--r--lib/pleroma/web/admin_api/controllers/announcement_controller.ex83
-rw-r--r--lib/pleroma/web/admin_api/controllers/config_controller.ex50
-rw-r--r--lib/pleroma/web/admin_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex165
-rw-r--r--lib/pleroma/web/api_spec/operations/announcement_operation.ex57
-rw-r--r--lib/pleroma/web/api_spec/operations/notification_operation.ex6
-rw-r--r--lib/pleroma/web/api_spec/schemas/announcement.ex45
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex60
-rw-r--r--lib/pleroma/web/mastodon_api/controllers/notification_controller.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api.ex6
-rw-r--r--lib/pleroma/web/mastodon_api/views/announcement_view.ex15
-rw-r--r--lib/pleroma/web/mastodon_api/views/instance_view.ex1
-rw-r--r--lib/pleroma/web/router.ex14
-rw-r--r--lib/pleroma/web/static_fe/static_fe_controller.ex9
-rw-r--r--lib/pleroma/web/twitter_api/views/remote_follow_view.ex6
30 files changed, 1073 insertions, 62 deletions
diff --git a/lib/mix/tasks/pleroma/instance.ex b/lib/mix/tasks/pleroma/instance.ex
index f292fc762..5c93f19ff 100644
--- a/lib/mix/tasks/pleroma/instance.ex
+++ b/lib/mix/tasks/pleroma/instance.ex
@@ -34,7 +34,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
static_dir: :string,
listen_ip: :string,
listen_port: :string,
- strip_uploads: :string,
+ strip_uploads_location: :string,
+ read_uploads_description: :string,
anonymize_uploads: :string,
dedupe_uploads: :string
],
@@ -161,7 +162,7 @@ defmodule Mix.Tasks.Pleroma.Instance do
)
|> Path.expand()
- {strip_uploads_message, strip_uploads_default} =
+ {strip_uploads_location_message, strip_uploads_location_default} =
if Pleroma.Utils.command_available?("exiftool") do
{"Do you want to strip location (GPS) data from uploaded images? This requires exiftool, it was detected as installed. (y/n)",
"y"}
@@ -170,12 +171,29 @@ defmodule Mix.Tasks.Pleroma.Instance do
"n"}
end
- strip_uploads =
+ strip_uploads_location =
get_option(
options,
- :strip_uploads,
- strip_uploads_message,
- strip_uploads_default
+ :strip_uploads_location,
+ strip_uploads_location_message,
+ strip_uploads_location_default
+ ) === "y"
+
+ {read_uploads_description_message, read_uploads_description_default} =
+ if Pleroma.Utils.command_available?("exiftool") do
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as installed. (y/n)",
+ "y"}
+ else
+ {"Do you want to read data from uploaded files so clients can use it to prefill fields like image description? This requires exiftool, it was detected as not installed, please install it if you answer yes. (y/n)",
+ "n"}
+ end
+
+ read_uploads_description =
+ get_option(
+ options,
+ :read_uploads_description,
+ read_uploads_description_message,
+ read_uploads_description_default
) === "y"
anonymize_uploads =
@@ -229,7 +247,8 @@ defmodule Mix.Tasks.Pleroma.Instance do
listen_port: listen_port,
upload_filters:
upload_filters(%{
- strip: strip_uploads,
+ strip_location: strip_uploads_location,
+ read_description: read_uploads_description,
anonymize: anonymize_uploads,
dedupe: dedupe_uploads
})
@@ -297,13 +316,20 @@ defmodule Mix.Tasks.Pleroma.Instance do
defp upload_filters(filters) when is_map(filters) do
enabled_filters =
- if filters.strip do
- [Pleroma.Upload.Filter.Exiftool]
+ if filters.strip_location do
+ [Pleroma.Upload.Filter.Exiftool.StripLocation]
else
[]
end
enabled_filters =
+ if filters.read_description do
+ enabled_filters ++ [Pleroma.Upload.Filter.Exiftool.ReadDescription]
+ else
+ enabled_filters
+ end
+
+ enabled_filters =
if filters.anonymize do
enabled_filters ++ [Pleroma.Upload.Filter.AnonymizeFilename]
else
diff --git a/lib/pleroma/announcement.ex b/lib/pleroma/announcement.ex
new file mode 100644
index 000000000..d97c5e728
--- /dev/null
+++ b/lib/pleroma/announcement.ex
@@ -0,0 +1,160 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Announcement do
+ use Ecto.Schema
+
+ import Ecto.Changeset, only: [cast: 3, validate_required: 2]
+ import Ecto.Query
+
+ alias Pleroma.AnnouncementReadRelationship
+ alias Pleroma.Repo
+
+ @type t :: %__MODULE__{}
+ @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true}
+
+ schema "announcements" do
+ field(:data, :map)
+ field(:starts_at, :utc_datetime)
+ field(:ends_at, :utc_datetime)
+ field(:rendered, :map)
+
+ timestamps(type: :utc_datetime)
+ end
+
+ def change(struct, params \\ %{}) do
+ struct
+ |> cast(validate_params(struct, params), [:data, :starts_at, :ends_at, :rendered])
+ |> validate_required([:data])
+ end
+
+ defp validate_params(struct, params) do
+ base_data =
+ %{
+ "content" => "",
+ "all_day" => false
+ }
+ |> Map.merge((struct && struct.data) || %{})
+
+ merged_data =
+ Map.merge(base_data, params.data)
+ |> Map.take(["content", "all_day"])
+
+ params
+ |> Map.merge(%{data: merged_data})
+ |> add_rendered_properties()
+ end
+
+ def add_rendered_properties(params) do
+ {content_html, _, _} =
+ Pleroma.Web.CommonAPI.Utils.format_input(params.data["content"], "text/plain",
+ mentions_format: :full
+ )
+
+ rendered = %{
+ "content" => content_html
+ }
+
+ params
+ |> Map.put(:rendered, rendered)
+ end
+
+ def add(params) do
+ changeset = change(%__MODULE__{}, params)
+
+ Repo.insert(changeset)
+ end
+
+ def update(announcement, params) do
+ changeset = change(announcement, params)
+
+ Repo.update(changeset)
+ end
+
+ def list_all do
+ __MODULE__
+ |> Repo.all()
+ end
+
+ def list_paginated(%{limit: limited_number, offset: offset_number}) do
+ __MODULE__
+ |> limit(^limited_number)
+ |> offset(^offset_number)
+ |> Repo.all()
+ end
+
+ def get_by_id(id) do
+ Repo.get_by(__MODULE__, id: id)
+ end
+
+ def delete_by_id(id) do
+ with announcement when not is_nil(announcement) <- get_by_id(id),
+ {:ok, _} <- Repo.delete(announcement) do
+ :ok
+ else
+ _ ->
+ :error
+ end
+ end
+
+ def read_by?(announcement, user) do
+ AnnouncementReadRelationship.exists?(user, announcement)
+ end
+
+ def mark_read_by(announcement, user) do
+ AnnouncementReadRelationship.mark_read(user, announcement)
+ end
+
+ def render_json(announcement, opts \\ []) do
+ extra_params =
+ case Keyword.fetch(opts, :for) do
+ {:ok, user} when not is_nil(user) ->
+ %{read: read_by?(announcement, user)}
+
+ _ ->
+ %{}
+ end
+
+ admin_extra_params =
+ case Keyword.fetch(opts, :admin) do
+ {:ok, true} ->
+ %{pleroma: %{raw_content: announcement.data["content"]}}
+
+ _ ->
+ %{}
+ end
+
+ base = %{
+ id: announcement.id,
+ content: announcement.rendered["content"],
+ starts_at: announcement.starts_at,
+ ends_at: announcement.ends_at,
+ all_day: announcement.data["all_day"],
+ published_at: announcement.inserted_at,
+ updated_at: announcement.updated_at,
+ mentions: [],
+ statuses: [],
+ tags: [],
+ emojis: [],
+ reactions: []
+ }
+
+ base
+ |> Map.merge(extra_params)
+ |> Map.merge(admin_extra_params)
+ end
+
+ # "visible" means:
+ # starts_at < time < ends_at
+ def list_all_visible_when(time) do
+ __MODULE__
+ |> where([a], is_nil(a.starts_at) or a.starts_at < ^time)
+ |> where([a], is_nil(a.ends_at) or a.ends_at > ^time)
+ |> Repo.all()
+ end
+
+ def list_all_visible do
+ list_all_visible_when(DateTime.now("Etc/UTC") |> elem(1))
+ end
+end
diff --git a/lib/pleroma/announcement_read_relationship.ex b/lib/pleroma/announcement_read_relationship.ex
new file mode 100644
index 000000000..9b64404ce
--- /dev/null
+++ b/lib/pleroma/announcement_read_relationship.ex
@@ -0,0 +1,55 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.AnnouncementReadRelationship do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias FlakeId.Ecto.CompatType
+ alias Pleroma.Announcement
+ alias Pleroma.Repo
+ alias Pleroma.User
+
+ @type t :: %__MODULE__{}
+
+ schema "announcement_read_relationships" do
+ belongs_to(:user, User, type: CompatType)
+ belongs_to(:announcement, Announcement, type: CompatType)
+
+ timestamps(updated_at: false)
+ end
+
+ def mark_read(user, announcement) do
+ %__MODULE__{}
+ |> cast(%{user_id: user.id, announcement_id: announcement.id}, [:user_id, :announcement_id])
+ |> validate_required([:user_id, :announcement_id])
+ |> foreign_key_constraint(:user_id)
+ |> foreign_key_constraint(:announcement_id)
+ |> unique_constraint([:user_id, :announcement_id])
+ |> Repo.insert()
+ end
+
+ def mark_unread(user, announcement) do
+ with relationship <- get(user, announcement),
+ {:exists, true} <- {:exists, not is_nil(relationship)},
+ {:ok, _} <- Repo.delete(relationship) do
+ :ok
+ else
+ {:exists, false} ->
+ :ok
+
+ _ ->
+ :error
+ end
+ end
+
+ def get(user, announcement) do
+ Repo.get_by(__MODULE__, user_id: user.id, announcement_id: announcement.id)
+ end
+
+ def exists?(user, announcement) do
+ not is_nil(get(user, announcement))
+ end
+end
diff --git a/lib/pleroma/application_requirements.ex b/lib/pleroma/application_requirements.ex
index 06d388694..44b1c1705 100644
--- a/lib/pleroma/application_requirements.ex
+++ b/lib/pleroma/application_requirements.ex
@@ -164,7 +164,8 @@ defmodule Pleroma.ApplicationRequirements do
defp check_system_commands!(:ok) do
filter_commands_statuses = [
- check_filter(Pleroma.Upload.Filter.Exiftool, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.StripLocation, "exiftool"),
+ check_filter(Pleroma.Upload.Filter.Exiftool.ReadDescription, "exiftool"),
check_filter(Pleroma.Upload.Filter.Mogrify, "mogrify"),
check_filter(Pleroma.Upload.Filter.Mogrifun, "mogrify"),
check_filter(Pleroma.Upload.Filter.AnalyzeMetadata, "mogrify"),
diff --git a/lib/pleroma/config/deprecation_warnings.ex b/lib/pleroma/config/deprecation_warnings.ex
index 118dd3acc..599f1d3cf 100644
--- a/lib/pleroma/config/deprecation_warnings.ex
+++ b/lib/pleroma/config/deprecation_warnings.ex
@@ -20,6 +20,43 @@ defmodule Pleroma.Config.DeprecationWarnings do
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
]
+ def check_exiftool_filter do
+ filters = Config.get([Pleroma.Upload]) |> Keyword.get(:filters, [])
+
+ if Pleroma.Upload.Filter.Exiftool in filters do
+ Logger.warn("""
+ !!!DEPRECATION WARNING!!!
+ Your config is using Exiftool as a filter instead of Exiftool.StripLocation. This should work for now, but you are advised to change to the new configuration to prevent possible issues later:
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool]
+ ```
+
+ Is now
+
+
+ ```
+ config :pleroma, Pleroma.Upload,
+ filters: [Pleroma.Upload.Filter.Exiftool.StripLocation]
+ ```
+ """)
+
+ new_config =
+ filters
+ |> Enum.map(fn
+ Pleroma.Upload.Filter.Exiftool -> Pleroma.Upload.Filter.Exiftool.StripLocation
+ filter -> filter
+ end)
+
+ Config.put([Pleroma.Upload, :filters], new_config)
+
+ :error
+ else
+ :ok
+ end
+ end
+
def check_simple_policy_tuples do
has_strings =
Config.get([:mrf_simple])
@@ -180,7 +217,8 @@ defmodule Pleroma.Config.DeprecationWarnings do
check_old_chat_shoutbox(),
check_quarantined_instances_tuples(),
check_transparency_exclusions_tuples(),
- check_simple_policy_tuples()
+ check_simple_policy_tuples(),
+ check_exiftool_filter()
]
|> Enum.reduce(:ok, fn
:ok, :ok -> :ok
diff --git a/lib/pleroma/constants.ex b/lib/pleroma/constants.ex
index a42c71d23..7b63ab06e 100644
--- a/lib/pleroma/constants.ex
+++ b/lib/pleroma/constants.ex
@@ -27,4 +27,10 @@ defmodule Pleroma.Constants do
do:
~w(index.html robots.txt static static-fe finmoji emoji packs sounds images instance sw.js sw-pleroma.js favicon.png schemas doc embed.js embed.css)
)
+
+ # basic regex, just there to weed out potential mistakes
+ # https://datatracker.ietf.org/doc/html/rfc2045#section-5.1
+ const(mime_regex,
+ do: ~r/^[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+\/[^[:cntrl:] ()<>@,;:\\"\/\[\]?=]+(; .*)?$/
+ )
end
diff --git a/lib/pleroma/docs/translator.ex b/lib/pleroma/docs/translator.ex
new file mode 100644
index 000000000..13e33c87e
--- /dev/null
+++ b/lib/pleroma/docs/translator.ex
@@ -0,0 +1,10 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Translator do
+ require Pleroma.Docs.Translator.Compiler
+ require Pleroma.Web.Gettext
+
+ @before_compile Pleroma.Docs.Translator.Compiler
+end
diff --git a/lib/pleroma/docs/translator/compiler.ex b/lib/pleroma/docs/translator/compiler.ex
new file mode 100644
index 000000000..5d27d9fa2
--- /dev/null
+++ b/lib/pleroma/docs/translator/compiler.ex
@@ -0,0 +1,119 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Docs.Translator.Compiler do
+ @external_resource "config/description.exs"
+ @raw_config Pleroma.Config.Loader.read("config/description.exs")
+ @raw_descriptions @raw_config[:pleroma][:config_description]
+
+ defmacro __before_compile__(_env) do
+ strings =
+ __MODULE__.descriptions()
+ |> __MODULE__.extract_strings()
+
+ quote do
+ def placeholder do
+ unquote do
+ Enum.map(
+ strings,
+ fn {path, type, string} ->
+ ctxt = msgctxt_for(path, type)
+
+ quote do
+ Pleroma.Web.Gettext.dpgettext_noop(
+ "config_descriptions",
+ unquote(ctxt),
+ unquote(string)
+ )
+ end
+ end
+ )
+ end
+ end
+ end
+ end
+
+ def descriptions do
+ Pleroma.Web.ActivityPub.MRF.config_descriptions()
+ |> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
+ |> Pleroma.Docs.Generator.convert_to_strings()
+ end
+
+ def extract_strings(descriptions) do
+ descriptions
+ |> Enum.reduce(%{strings: [], path: []}, &process_item/2)
+ |> Map.get(:strings)
+ end
+
+ defp process_item(entity, acc) do
+ current_level =
+ acc
+ |> process_desc(entity)
+ |> process_label(entity)
+
+ process_children(entity, current_level)
+ end
+
+ defp process_desc(acc, %{description: desc} = item) do
+ %{
+ strings: [{acc.path ++ [key_for(item)], "description", desc} | acc.strings],
+ path: acc.path
+ }
+ end
+
+ defp process_desc(acc, _) do
+ acc
+ end
+
+ defp process_label(acc, %{label: label} = item) do
+ %{
+ strings: [{acc.path ++ [key_for(item)], "label", label} | acc.strings],
+ path: acc.path
+ }
+ end
+
+ defp process_label(acc, _) do
+ acc
+ end
+
+ defp process_children(%{children: children} = item, acc) do
+ current_level = Map.put(acc, :path, acc.path ++ [key_for(item)])
+
+ children
+ |> Enum.reduce(current_level, &process_item/2)
+ |> Map.put(:path, acc.path)
+ end
+
+ defp process_children(_, acc) do
+ acc
+ end
+
+ def msgctxt_for(path, type) do
+ "config #{type} at #{Enum.join(path, " > ")}"
+ end
+
+ defp convert_group({_, group}) do
+ group
+ end
+
+ defp convert_group(group) do
+ group
+ end
+
+ def key_for(%{group: group, key: key}) do
+ "#{convert_group(group)}-#{key}"
+ end
+
+ def key_for(%{group: group}) do
+ convert_group(group)
+ end
+
+ def key_for(%{key: key}) do
+ key
+ end
+
+ def key_for(_) do
+ nil
+ end
+end
diff --git a/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex b/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex
new file mode 100644
index 000000000..31d51577d
--- /dev/null
+++ b/lib/pleroma/ecto_type/activity_pub/object_validators/mime.ex
@@ -0,0 +1,25 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.EctoType.ActivityPub.ObjectValidators.MIME do
+ use Ecto.Type
+
+ require Pleroma.Constants
+
+ def type, do: :string
+
+ def cast(mime) when is_binary(mime) do
+ if mime =~ Pleroma.Constants.mime_regex() do
+ {:ok, mime}
+ else
+ {:ok, "application/octet-stream"}
+ end
+ end
+
+ def cast(_), do: :error
+
+ def dump(data), do: {:ok, data}
+
+ def load(data), do: {:ok, data}
+end
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 242813dcd..db2909276 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -60,12 +60,23 @@ defmodule Pleroma.Upload do
width: integer(),
height: integer(),
blurhash: String.t(),
+ description: String.t(),
path: String.t()
}
- defstruct [:id, :name, :tempfile, :content_type, :width, :height, :blurhash, :path]
-
- defp get_description(opts, upload) do
- case {opts[:description], Pleroma.Config.get([Pleroma.Upload, :default_description])} do
+ defstruct [
+ :id,
+ :name,
+ :tempfile,
+ :content_type,
+ :width,
+ :height,
+ :blurhash,
+ :description,
+ :path
+ ]
+
+ defp get_description(upload) do
+ case {upload.description, Pleroma.Config.get([Pleroma.Upload, :default_description])} do
{description, _} when is_binary(description) -> description
{_, :filename} -> upload.name
{_, str} when is_binary(str) -> str
@@ -81,7 +92,7 @@ defmodule Pleroma.Upload do
with {:ok, upload} <- prepare_upload(upload, opts),
upload = %__MODULE__{upload | path: upload.path || "#{upload.id}/#{upload.name}"},
{:ok, upload} <- Pleroma.Upload.Filter.filter(opts.filters, upload),
- description = get_description(opts, upload),
+ description = get_description(upload),
{_, true} <-
{:description_limit,
String.length(description) <= Pleroma.Config.get([:instance, :description_limit])},
@@ -152,7 +163,8 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: file.filename,
tempfile: file.path,
- content_type: file.content_type
+ content_type: file.content_type,
+ description: opts.description
}}
end
end
@@ -172,7 +184,8 @@ defmodule Pleroma.Upload do
id: UUID.generate(),
name: hash <> "." <> ext,
tempfile: tmp_path,
- content_type: content_type
+ content_type: content_type,
+ description: opts.description
}}
end
end
diff --git a/lib/pleroma/upload/filter/exiftool/read_description.ex b/lib/pleroma/upload/filter/exiftool/read_description.ex
new file mode 100644
index 000000000..03d698a81
--- /dev/null
+++ b/lib/pleroma/upload/filter/exiftool/read_description.ex
@@ -0,0 +1,49 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Upload.Filter.Exiftool.ReadDescription do
+ @moduledoc """
+ Gets a valid description from the related EXIF tags and provides them in the response if no description is provided yet.
+ It will first check ImageDescription, when that doesn't probide a valid description, it will check iptc:Caption-Abstract.
+ A valid description means the fields are filled in and not too long (see `:instance, :description_limit`).
+ """
+ @behaviour Pleroma.Upload.Filter
+
+ @spec filter(Pleroma.Upload.t()) :: {:ok, any()} | {:error, String.t()}
+
+ def filter(%Pleroma.Upload{description: description})
+ when is_binary(description),
+ do: {:ok, :noop}
+
+ def filter(%Pleroma.Upload{tempfile: file} = upload),
+ do: {:ok, :filtered, upload |> Map.put(:description, read_description_from_exif_data(file))}
+
+ def filter(_, _), do: {:ok, :noop}
+
+ defp read_description_from_exif_data(file) do
+ nil
+ |> read_when_empty(file, "-ImageDescription")
+ |> read_when_empty(file, "-iptc:Caption-Abstract")
+ end
+
+ defp read_when_empty(current_description, _, _) when is_binary(current_description),
+ do: current_description
+
+ defp read_when_empty(_, file, tag) do
+ try do
+ {tag_content, 0} =
+ System.cmd("exiftool", ["-b", "-s3", tag, file], stderr_to_stdout: true, parallelism: true)
+
+ tag_content = String.trim(tag_content)
+
+ if tag_content != "" and
+ String.length(tag_content) <=
+ Pleroma.Config.get([:instance, :description_limit]),
+ do: tag_content,
+ else: nil
+ rescue
+ _ in ErlangError -> nil
+ end
+ end
+end
diff --git a/lib/pleroma/upload/filter/exiftool.ex b/lib/pleroma/upload/filter/exiftool/strip_location.ex
index 36cc045c2..6100527d3 100644
--- a/lib/pleroma/upload/filter/exiftool.ex
+++ b/lib/pleroma/upload/filter/exiftool/strip_location.ex
@@ -2,7 +2,7 @@
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
-defmodule Pleroma.Upload.Filter.Exiftool do
+defmodule Pleroma.Upload.Filter.Exiftool.StripLocation do
@moduledoc """
Strips GPS related EXIF tags and overwrites the file in place.
Also strips or replaces filesystem metadata e.g., timestamps.
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index 747a83e8d..712d3b1d9 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -706,7 +706,7 @@ defmodule Pleroma.User do
])
|> validate_required([:name, :nickname])
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_not_restricted_nickname(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> put_ap_id()
|> unique_constraint(:ap_id)
@@ -754,17 +754,9 @@ defmodule Pleroma.User do
|> validate_confirmation(:password)
|> unique_constraint(:email)
|> validate_format(:email, @email_regex)
- |> validate_change(:email, fn :email, email ->
- valid? =
- Config.get([User, :email_blacklist])
- |> Enum.all?(fn blacklisted_domain ->
- !String.ends_with?(email, ["@" <> blacklisted_domain, "." <> blacklisted_domain])
- end)
-
- if valid?, do: [], else: [email: "Invalid email"]
- end)
+ |> validate_email_not_in_blacklisted_domain(:email)
|> unique_constraint(:nickname)
- |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
+ |> validate_not_restricted_nickname(:nickname)
|> validate_format(:nickname, local_nickname_regex())
|> validate_length(:bio, max: bio_limit)
|> validate_length(:name, min: 1, max: name_limit)
@@ -778,6 +770,35 @@ defmodule Pleroma.User do
|> put_following_and_follower_and_featured_address()
end
+ def validate_not_restricted_nickname(changeset, field) do
+ validate_change(changeset, field, fn _, value ->
+ valid? =
+ Config.get([User, :restricted_nicknames])
+ |> Enum.all?(fn restricted_nickname ->
+ String.downcase(value) != String.downcase(restricted_nickname)
+ end)
+
+ if valid?, do: [], else: [nickname: "Invalid nickname"]
+ end)
+ end
+
+ def validate_email_not_in_blacklisted_domain(changeset, field) do
+ validate_change(changeset, field, fn _, value ->
+ valid? =
+ Config.get([User, :email_blacklist])
+ |> Enum.all?(fn blacklisted_domain ->
+ blacklisted_domain_downcase = String.downcase(blacklisted_domain)
+
+ !String.ends_with?(String.downcase(value), [
+ "@" <> blacklisted_domain_downcase,
+ "." <> blacklisted_domain_downcase
+ ])
+ end)
+
+ if valid?, do: [], else: [email: "Invalid email"]
+ end)
+ end
+
def maybe_validate_required_email(changeset, true), do: changeset
def maybe_validate_required_email(changeset, _) do
diff --git a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
index d1c61ac82..8b641d88d 100644
--- a/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
+++ b/lib/pleroma/web/activity_pub/object_validators/attachment_validator.ex
@@ -12,14 +12,14 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
@primary_key false
embedded_schema do
field(:type, :string)
- field(:mediaType, :string, default: "application/octet-stream")
+ field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:name, :string)
field(:blurhash, :string)
embeds_many :url, UrlObjectValidator, primary_key: false do
field(:type, :string)
field(:href, ObjectValidators.Uri)
- field(:mediaType, :string, default: "application/octet-stream")
+ field(:mediaType, ObjectValidators.MIME, default: "application/octet-stream")
field(:width, :integer)
field(:height, :integer)
end
@@ -59,13 +59,7 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.AttachmentValidator do
end
def fix_media_type(data) do
- data = Map.put_new(data, "mediaType", data["mimeType"])
-
- if is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] do
- data
- else
- Map.put(data, "mediaType", "application/octet-stream")
- end
+ Map.put_new(data, "mediaType", data["mimeType"])
end
defp handle_href(href, mediaType, data) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index a70330f0e..d6622df86 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -203,13 +203,13 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
media_type =
cond do
- is_map(url) && MIME.extensions(url["mediaType"]) != [] ->
+ is_map(url) && url =~ Pleroma.Constants.mime_regex() ->
url["mediaType"]
- is_bitstring(data["mediaType"]) && MIME.extensions(data["mediaType"]) != [] ->
+ is_bitstring(data["mediaType"]) && data["mediaType"] =~ Pleroma.Constants.mime_regex() ->
data["mediaType"]
- is_bitstring(data["mimeType"]) && MIME.extensions(data["mimeType"]) != [] ->
+ is_bitstring(data["mimeType"]) && data["mimeType"] =~ Pleroma.Constants.mime_regex() ->
data["mimeType"]
true ->
diff --git a/lib/pleroma/web/admin_api/controllers/announcement_controller.ex b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
new file mode 100644
index 000000000..6ad5fc12c
--- /dev/null
+++ b/lib/pleroma/web/admin_api/controllers/announcement_controller.ex
@@ -0,0 +1,83 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.ControllerHelper
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+ plug(OAuthScopesPlug, %{scopes: ["admin:write"]} when action in [:create, :delete, :change])
+ plug(OAuthScopesPlug, %{scopes: ["admin:read"]} when action in [:index, :show])
+ action_fallback(Pleroma.Web.AdminAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.AnnouncementOperation
+
+ defp default_limit, do: 20
+
+ def index(conn, params) do
+ limit = Map.get(params, :limit, default_limit())
+ offset = Map.get(params, :offset, 0)
+
+ announcements = Announcement.list_paginated(%{limit: limit, offset: offset})
+
+ render(conn, "index.json", announcements: announcements)
+ end
+
+ def show(conn, %{id: id} = _params) do
+ announcement = Announcement.get_by_id(id)
+
+ if is_nil(announcement) do
+ {:error, :not_found}
+ else
+ render(conn, "show.json", announcement: announcement)
+ end
+ end
+
+ def create(%{body_params: params} = conn, _params) do
+ with {:ok, announcement} <- Announcement.add(change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def change_params(orig_params) do
+ data =
+ %{}
+ |> Pleroma.Maps.put_if_present("content", orig_params, &Map.fetch(&1, :content))
+ |> Pleroma.Maps.put_if_present("all_day", orig_params, &Map.fetch(&1, :all_day))
+
+ orig_params
+ |> Map.merge(%{data: data})
+ end
+
+ def change(%{body_params: params} = conn, %{id: id} = _params) do
+ with announcement <- Announcement.get_by_id(id),
+ {:exists, true} <- {:exists, not is_nil(announcement)},
+ {:ok, announcement} <- Announcement.update(announcement, change_params(params)) do
+ render(conn, "show.json", announcement: announcement)
+ else
+ {:exists, false} ->
+ {:error, :not_found}
+
+ _ ->
+ {:error, 400}
+ end
+ end
+
+ def delete(conn, %{id: id} = _params) do
+ case Announcement.delete_by_id(id) do
+ :ok ->
+ conn
+ |> ControllerHelper.json_response(:ok, %{})
+
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/admin_api/controllers/config_controller.ex b/lib/pleroma/web/admin_api/controllers/config_controller.ex
index 55ab6d063..a03318c0e 100644
--- a/lib/pleroma/web/admin_api/controllers/config_controller.ex
+++ b/lib/pleroma/web/admin_api/controllers/config_controller.ex
@@ -22,10 +22,58 @@ defmodule Pleroma.Web.AdminAPI.ConfigController do
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.Admin.ConfigOperation
+ defp translate_descriptions(descriptions, path \\ []) do
+ Enum.map(descriptions, fn desc -> translate_item(desc, path) end)
+ end
+
+ defp translate_string(str, path, type) do
+ Gettext.dpgettext(
+ Pleroma.Web.Gettext,
+ "config_descriptions",
+ Pleroma.Docs.Translator.Compiler.msgctxt_for(path, type),
+ str
+ )
+ end
+
+ defp maybe_put_translated(item, key, path) do
+ if item[key] do
+ Map.put(
+ item,
+ key,
+ translate_string(
+ item[key],
+ path ++ [Pleroma.Docs.Translator.Compiler.key_for(item)],
+ to_string(key)
+ )
+ )
+ else
+ item
+ end
+ end
+
+ defp translate_item(item, path) do
+ item
+ |> maybe_put_translated(:label, path)
+ |> maybe_put_translated(:description, path)
+ |> translate_children(path)
+ end
+
+ defp translate_children(%{children: children} = item, path) when is_list(children) do
+ item
+ |> Map.put(
+ :children,
+ translate_descriptions(children, path ++ [Pleroma.Docs.Translator.Compiler.key_for(item)])
+ )
+ end
+
+ defp translate_children(item, _path) do
+ item
+ end
+
def descriptions(conn, _params) do
descriptions = Enum.filter(Pleroma.Docs.JSON.compiled_descriptions(), &whitelisted_config?/1)
- json(conn, descriptions)
+ json(conn, translate_descriptions(descriptions))
end
def show(conn, %{only_db: true}) do
diff --git a/lib/pleroma/web/admin_api/views/announcement_view.ex b/lib/pleroma/web/admin_api/views/announcement_view.ex
new file mode 100644
index 000000000..a35bd60cf
--- /dev/null
+++ b/lib/pleroma/web/admin_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.AdminAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements}) do
+ render_many(announcements, __MODULE__, "show.json")
+ end
+
+ def render("show.json", %{announcement: announcement}) do
+ Pleroma.Announcement.render_json(announcement, admin: true)
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
new file mode 100644
index 000000000..58a039e72
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/admin/announcement_operation.ex
@@ -0,0 +1,165 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Admin.AnnouncementOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Announcement
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ import Pleroma.Web.ApiSpec.Helpers
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Retrieve a list of announcements",
+ operationId: "AdminAPI.AnnouncementController.index",
+ security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :limit,
+ :query,
+ %Schema{type: :integer, minimum: 1},
+ "the maximum number of announcements to return"
+ ),
+ Operation.parameter(
+ :offset,
+ :query,
+ %Schema{type: :integer, minimum: 0},
+ "the offset of the first announcement to return"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 400 => Operation.response("Forbidden", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def show_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Display one announcement",
+ operationId: "AdminAPI.AnnouncementController.show",
+ security: [%{"oAuth" => ["admin:read"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def delete_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Delete one announcement",
+ operationId: "AdminAPI.AnnouncementController.delete",
+ security: [%{"oAuth" => ["admin:write"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :object}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def create_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Create one announcement",
+ operationId: "AdminAPI.AnnouncementController.create",
+ security: [%{"oAuth" => ["admin:write"]}],
+ requestBody: request_body("Parameters", create_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def change_operation do
+ %Operation{
+ tags: ["Announcement managment"],
+ summary: "Change one announcement",
+ operationId: "AdminAPI.AnnouncementController.change",
+ security: [%{"oAuth" => ["admin:write"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ | admin_api_params()
+ ],
+ requestBody: request_body("Parameters", change_request(), required: true),
+ responses: %{
+ 200 => Operation.response("Response", "application/json", Announcement),
+ 400 => Operation.response("Bad Request", "application/json", ApiError),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ defp create_or_change_props do
+ %{
+ content: %Schema{type: :string},
+ starts_at: %Schema{type: :string, format: "date-time", nullable: true},
+ ends_at: %Schema{type: :string, format: "date-time", nullable: true},
+ all_day: %Schema{type: :boolean}
+ }
+ end
+
+ def create_request do
+ %Schema{
+ title: "AnnouncementCreateRequest",
+ type: :object,
+ required: [:content],
+ properties: create_or_change_props()
+ }
+ end
+
+ def change_request do
+ %Schema{
+ title: "AnnouncementChangeRequest",
+ type: :object,
+ properties: create_or_change_props()
+ }
+ end
+
+ def list_of_announcements do
+ %Schema{
+ type: :array,
+ items: Announcement
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/announcement_operation.ex b/lib/pleroma/web/api_spec/operations/announcement_operation.ex
new file mode 100644
index 000000000..71be0002a
--- /dev/null
+++ b/lib/pleroma/web/api_spec/operations/announcement_operation.ex
@@ -0,0 +1,57 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.AnnouncementOperation do
+ alias OpenApiSpex.Operation
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.Announcement
+ alias Pleroma.Web.ApiSpec.Schemas.ApiError
+
+ def open_api_operation(action) do
+ operation = String.to_existing_atom("#{action}_operation")
+ apply(__MODULE__, operation, [])
+ end
+
+ def index_operation do
+ %Operation{
+ tags: ["Announcement"],
+ summary: "Retrieve a list of announcements",
+ operationId: "MastodonAPI.AnnouncementController.index",
+ security: [%{"oAuth" => []}],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", list_of_announcements()),
+ 403 => Operation.response("Forbidden", "application/json", ApiError)
+ }
+ }
+ end
+
+ def mark_read_operation do
+ %Operation{
+ tags: ["Announcement"],
+ summary: "Mark one announcement as read",
+ operationId: "MastodonAPI.AnnouncementController.mark_read",
+ security: [%{"oAuth" => ["write:accounts"]}],
+ parameters: [
+ Operation.parameter(
+ :id,
+ :path,
+ :string,
+ "announcement id"
+ )
+ ],
+ responses: %{
+ 200 => Operation.response("Response", "application/json", %Schema{type: :object}),
+ 403 => Operation.response("Forbidden", "application/json", ApiError),
+ 404 => Operation.response("Not Found", "application/json", ApiError)
+ }
+ }
+ end
+
+ def list_of_announcements do
+ %Schema{
+ type: :array,
+ items: Announcement
+ }
+ end
+end
diff --git a/lib/pleroma/web/api_spec/operations/notification_operation.ex b/lib/pleroma/web/api_spec/operations/notification_operation.ex
index 7f2336ff6..56aa129d2 100644
--- a/lib/pleroma/web/api_spec/operations/notification_operation.ex
+++ b/lib/pleroma/web/api_spec/operations/notification_operation.ex
@@ -51,6 +51,12 @@ defmodule Pleroma.Web.ApiSpec.NotificationOperation do
:include_types,
:query,
%Schema{type: :array, items: notification_type()},
+ "Deprecated, use `types` instead"
+ ),
+ Operation.parameter(
+ :types,
+ :query,
+ %Schema{type: :array, items: notification_type()},
"Include the notifications for activities with the given types"
),
Operation.parameter(
diff --git a/lib/pleroma/web/api_spec/schemas/announcement.ex b/lib/pleroma/web/api_spec/schemas/announcement.ex
new file mode 100644
index 000000000..67d129ef6
--- /dev/null
+++ b/lib/pleroma/web/api_spec/schemas/announcement.ex
@@ -0,0 +1,45 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.ApiSpec.Schemas.Announcement do
+ alias OpenApiSpex.Schema
+ alias Pleroma.Web.ApiSpec.Schemas.FlakeID
+
+ require OpenApiSpex
+
+ OpenApiSpex.schema(%{
+ title: "Announcement",
+ description: "Response schema for an announcement",
+ type: :object,
+ properties: %{
+ id: FlakeID,
+ content: %Schema{type: :string},
+ starts_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true
+ },
+ ends_at: %Schema{
+ type: :string,
+ format: "date-time",
+ nullable: true
+ },
+ all_day: %Schema{type: :boolean},
+ published_at: %Schema{type: :string, format: "date-time"},
+ updated_at: %Schema{type: :string, format: "date-time"},
+ read: %Schema{type: :boolean},
+ mentions: %Schema{type: :array},
+ statuses: %Schema{type: :array},
+ tags: %Schema{type: :array},
+ emojis: %Schema{type: :array},
+ reactions: %Schema{type: :array},
+ pleroma: %Schema{
+ type: :object,
+ properties: %{
+ raw_content: %Schema{type: :string}
+ }
+ }
+ }
+ })
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex
new file mode 100644
index 000000000..080af96d5
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/controllers/announcement_controller.ex
@@ -0,0 +1,60 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementController do
+ use Pleroma.Web, :controller
+
+ import Pleroma.Web.ControllerHelper,
+ only: [
+ json_response: 3
+ ]
+
+ alias Pleroma.Announcement
+ alias Pleroma.Web.Plugs.OAuthScopesPlug
+
+ plug(Pleroma.Web.ApiSpec.CastAndValidate)
+
+ # Mastodon docs say this only requires a user token, no scopes needed
+ # As the op `|` requires at least one scope to be present, we use `&` here.
+ plug(
+ OAuthScopesPlug,
+ %{scopes: [], op: :&}
+ when action in [:index]
+ )
+
+ # Same as in MastodonAPI specs
+ plug(
+ OAuthScopesPlug,
+ %{scopes: ["write:accounts"]}
+ when action in [:mark_read]
+ )
+
+ action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
+
+ defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AnnouncementOperation
+
+ @doc "GET /api/v1/announcements"
+ def index(%{assigns: %{user: user}} = conn, _params) do
+ render(conn, "index.json", announcements: all_visible(), user: user)
+ end
+
+ def index(conn, _params) do
+ render(conn, "index.json", announcements: all_visible(), user: nil)
+ end
+
+ defp all_visible do
+ Announcement.list_all_visible()
+ end
+
+ @doc "POST /api/v1/announcements/:id/dismiss"
+ def mark_read(%{assigns: %{user: user}} = conn, %{id: id} = _params) do
+ with announcement when not is_nil(announcement) <- Announcement.get_by_id(id),
+ {:ok, _} <- Announcement.mark_read_by(announcement, user) do
+ json_response(conn, :ok, %{})
+ else
+ _ ->
+ {:error, :not_found}
+ end
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
index 932bc6423..740cf58e7 100644
--- a/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
+++ b/lib/pleroma/web/mastodon_api/controllers/notification_controller.ex
@@ -55,7 +55,7 @@ defmodule Pleroma.Web.MastodonAPI.NotificationController do
def index(%{assigns: %{user: user}} = conn, params) do
params =
Map.new(params, fn {k, v} -> {to_string(k), v} end)
- |> Map.put_new("include_types", @default_notification_types)
+ |> Map.put_new("types", Map.get(params, :include_types, @default_notification_types))
notifications = MastodonAPI.get_notifications(user, params)
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api.ex b/lib/pleroma/web/mastodon_api/mastodon_api.ex
index 5e32b9611..b4d092eed 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api.ex
@@ -65,7 +65,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
user
|> Notification.for_user_query(options)
- |> restrict(:include_types, options)
+ |> restrict(:types, options)
|> restrict(:exclude_types, options)
|> restrict(:account_ap_id, options)
|> Pagination.fetch_paginated(params)
@@ -80,7 +80,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
defp cast_params(params) do
param_types = %{
exclude_types: {:array, :string},
- include_types: {:array, :string},
+ types: {:array, :string},
exclude_visibilities: {:array, :string},
reblogs: :boolean,
with_muted: :boolean,
@@ -92,7 +92,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPI do
changeset.changes
end
- defp restrict(query, :include_types, %{include_types: mastodon_types = [_ | _]}) do
+ defp restrict(query, :types, %{types: mastodon_types = [_ | _]}) do
where(query, [n], n.type in ^mastodon_types)
end
diff --git a/lib/pleroma/web/mastodon_api/views/announcement_view.ex b/lib/pleroma/web/mastodon_api/views/announcement_view.ex
new file mode 100644
index 000000000..93fdfb1f1
--- /dev/null
+++ b/lib/pleroma/web/mastodon_api/views/announcement_view.ex
@@ -0,0 +1,15 @@
+# Pleroma: A lightweight social networking server
+# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
+# SPDX-License-Identifier: AGPL-3.0-only
+
+defmodule Pleroma.Web.MastodonAPI.AnnouncementView do
+ use Pleroma.Web, :view
+
+ def render("index.json", %{announcements: announcements, user: user}) do
+ render_many(announcements, __MODULE__, "show.json", user: user)
+ end
+
+ def render("show.json", %{announcement: announcement, user: user}) do
+ Pleroma.Announcement.render_json(announcement, for: user)
+ end
+end
diff --git a/lib/pleroma/web/mastodon_api/views/instance_view.ex b/lib/pleroma/web/mastodon_api/views/instance_view.ex
index ee52475d5..62931bd41 100644
--- a/lib/pleroma/web/mastodon_api/views/instance_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/instance_view.ex
@@ -17,6 +17,7 @@ defmodule Pleroma.Web.MastodonAPI.InstanceView do
uri: Pleroma.Web.Endpoint.url(),
title: Keyword.get(instance, :name),
description: Keyword.get(instance, :description),
+ short_description: Keyword.get(instance, :short_description),
version: "#{@mastodon_api_level} (compatible; #{Pleroma.Application.named_version()})",
email: Keyword.get(instance, :email),
urls: %{
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index ceb6c3cfd..ac3de8af3 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -229,6 +229,12 @@ defmodule Pleroma.Web.Router do
post("/frontends/install", FrontendController, :install)
post("/backups", AdminAPIController, :create_backup)
+
+ get("/announcements", AnnouncementController, :index)
+ post("/announcements", AnnouncementController, :create)
+ get("/announcements/:id", AnnouncementController, :show)
+ patch("/announcements/:id", AnnouncementController, :change)
+ delete("/announcements/:id", AnnouncementController, :delete)
end
# AdminAPI: admins and mods (staff) can perform these actions (if enabled by config)
@@ -575,6 +581,9 @@ defmodule Pleroma.Web.Router do
get("/timelines/home", TimelineController, :home)
get("/timelines/direct", TimelineController, :direct)
get("/timelines/list/:list_id", TimelineController, :list)
+
+ get("/announcements", AnnouncementController, :index)
+ post("/announcements/:id/dismiss", AnnouncementController, :mark_read)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
@@ -669,11 +678,6 @@ defmodule Pleroma.Web.Router do
get("/activities/:uuid", OStatus.OStatusController, :activity)
get("/notice/:id", OStatus.OStatusController, :notice)
- # Notice compatibility routes for other frontends
- get("/@:nickname/:id", OStatus.OStatusController, :notice)
- get("/@:nickname/posts/:id", OStatus.OStatusController, :notice)
- get("/:nickname/status/:id", OStatus.OStatusController, :notice)
-
# Mastodon compatibility routes
get("/users/:nickname/statuses/:id", OStatus.OStatusController, :object)
get("/users/:nickname/statuses/:id/activity", OStatus.OStatusController, :activity)
diff --git a/lib/pleroma/web/static_fe/static_fe_controller.ex b/lib/pleroma/web/static_fe/static_fe_controller.ex
index b20a3689c..97c41c6f9 100644
--- a/lib/pleroma/web/static_fe/static_fe_controller.ex
+++ b/lib/pleroma/web/static_fe/static_fe_controller.ex
@@ -167,15 +167,6 @@ defmodule Pleroma.Web.StaticFE.StaticFEController do
defp assign_id(%{path_info: ["notice", notice_id]} = conn, _opts),
do: assign(conn, :notice_id, notice_id)
- defp assign_id(%{path_info: ["@" <> _nickname, notice_id]} = conn, _opts),
- do: assign(conn, :notice_id, notice_id)
-
- defp assign_id(%{path_info: ["@" <> _nickname, "posts", notice_id]} = conn, _opts),
- do: assign(conn, :notice_id, notice_id)
-
- defp assign_id(%{path_info: [_nickname, "status", notice_id]} = conn, _opts),
- do: assign(conn, :notice_id, notice_id)
-
defp assign_id(%{path_info: ["users", user_id]} = conn, _opts),
do: assign(conn, :username_or_id, user_id)
diff --git a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
index bd33d4c0a..8902261b0 100644
--- a/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
+++ b/lib/pleroma/web/twitter_api/views/remote_follow_view.ex
@@ -7,5 +7,9 @@ defmodule Pleroma.Web.TwitterAPI.RemoteFollowView do
import Phoenix.HTML.Form
alias Pleroma.Web.Gettext
- defdelegate avatar_url(user), to: Pleroma.User
+ def avatar_url(user) do
+ user
+ |> Pleroma.User.avatar_url()
+ |> Pleroma.Web.MediaProxy.url()
+ end
end