aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/mix/tasks/generate_invite_token.ex25
-rw-r--r--lib/pleroma/activity.ex4
-rw-r--r--lib/pleroma/formatter.ex65
-rw-r--r--lib/pleroma/gopher/server.ex6
-rw-r--r--lib/pleroma/http/http.ex25
-rw-r--r--lib/pleroma/plugs/digest.ex10
-rw-r--r--lib/pleroma/plugs/http_signature.ex10
-rw-r--r--lib/pleroma/upload.ex42
-rw-r--r--lib/pleroma/user.ex40
-rw-r--r--lib/pleroma/user_invite_token.ex40
-rw-r--r--lib/pleroma/web/activity_pub/activity_pub.ex59
-rw-r--r--lib/pleroma/web/activity_pub/mrf/simple_policy.ex12
-rw-r--r--lib/pleroma/web/activity_pub/transmogrifier.ex109
-rw-r--r--lib/pleroma/web/activity_pub/utils.ex20
-rw-r--r--lib/pleroma/web/activity_pub/views/user_view.ex14
-rw-r--r--lib/pleroma/web/common_api/common_api.ex33
-rw-r--r--lib/pleroma/web/common_api/utils.ex17
-rw-r--r--lib/pleroma/web/endpoint.ex3
-rw-r--r--lib/pleroma/web/federator/federator.ex2
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_api_controller.ex154
-rw-r--r--lib/pleroma/web/mastodon_api/mastodon_socket.ex12
-rw-r--r--lib/pleroma/web/mastodon_api/views/account_view.ex16
-rw-r--r--lib/pleroma/web/mastodon_api/views/status_view.ex47
-rw-r--r--lib/pleroma/web/nodeinfo/nodeinfo_controller.ex20
-rw-r--r--lib/pleroma/web/ostatus/activity_representer.ex2
-rw-r--r--lib/pleroma/web/ostatus/handlers/delete_handler.ex2
-rw-r--r--lib/pleroma/web/ostatus/ostatus.ex4
-rw-r--r--lib/pleroma/web/ostatus/ostatus_controller.ex21
-rw-r--r--lib/pleroma/web/router.ex35
-rw-r--r--lib/pleroma/web/streamer.ex2
-rw-r--r--lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex2
-rw-r--r--lib/pleroma/web/twitter_api/controllers/util_controller.ex27
-rw-r--r--lib/pleroma/web/twitter_api/representers/activity_representer.ex25
-rw-r--r--lib/pleroma/web/twitter_api/representers/object_representer.ex10
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api.ex40
-rw-r--r--lib/pleroma/web/twitter_api/twitter_api_controller.ex33
-rw-r--r--lib/pleroma/web/twitter_api/views/activity_view.ex46
-rw-r--r--lib/pleroma/web/twitter_api/views/user_view.ex16
38 files changed, 869 insertions, 181 deletions
diff --git a/lib/mix/tasks/generate_invite_token.ex b/lib/mix/tasks/generate_invite_token.ex
new file mode 100644
index 000000000..c4daa9a6c
--- /dev/null
+++ b/lib/mix/tasks/generate_invite_token.ex
@@ -0,0 +1,25 @@
+defmodule Mix.Tasks.GenerateInviteToken do
+ use Mix.Task
+
+ @shortdoc "Generate invite token for user"
+ def run([]) do
+ Mix.Task.run("app.start")
+
+ with {:ok, token} <- Pleroma.UserInviteToken.create_token() do
+ IO.puts("Generated user invite token")
+
+ IO.puts(
+ "Url: #{
+ Pleroma.Web.Router.Helpers.redirect_url(
+ Pleroma.Web.Endpoint,
+ :registration_page,
+ token.token
+ )
+ }"
+ )
+ else
+ _ ->
+ IO.puts("Error creating token")
+ end
+ end
+end
diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex
index dd6805125..bed96861f 100644
--- a/lib/pleroma/activity.ex
+++ b/lib/pleroma/activity.ex
@@ -78,4 +78,8 @@ defmodule Pleroma.Activity do
end
def get_create_activity_by_object_ap_id(_), do: nil
+
+ def normalize(obj) when is_map(obj), do: Activity.get_by_ap_id(obj["id"])
+ def normalize(ap_id) when is_binary(ap_id), do: Activity.get_by_ap_id(ap_id)
+ def normalize(_), do: nil
end
diff --git a/lib/pleroma/formatter.ex b/lib/pleroma/formatter.ex
index df7ffbc41..cf2944c38 100644
--- a/lib/pleroma/formatter.ex
+++ b/lib/pleroma/formatter.ex
@@ -16,7 +16,7 @@ defmodule Pleroma.Formatter do
def parse_mentions(text) do
# Modified from https://www.w3.org/TR/html5/forms.html#valid-e-mail-address
regex =
- ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]+@?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
+ ~r/@[a-zA-Z0-9.!#$%&'*+\/=?^_`{|}~-]*@?[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*/u
Regex.scan(regex, text)
|> List.flatten()
@@ -116,7 +116,28 @@ defmodule Pleroma.Formatter do
_ -> []
end)
- @emoji @finmoji_with_filenames ++ @emoji_from_file
+ @emoji_from_globs (
+ static_path = Path.join(:code.priv_dir(:pleroma), "static")
+
+ globs =
+ Application.get_env(:pleroma, :emoji, [])
+ |> Keyword.get(:shortcode_globs, [])
+
+ paths =
+ Enum.map(globs, fn glob ->
+ Path.join(static_path, glob)
+ |> Path.wildcard()
+ end)
+ |> Enum.concat()
+
+ Enum.map(paths, fn path ->
+ shortcode = Path.basename(path, Path.extname(path))
+ external_path = Path.join("/", Path.relative_to(path, static_path))
+ {shortcode, external_path}
+ end)
+ )
+
+ @emoji @finmoji_with_filenames ++ @emoji_from_globs ++ @emoji_from_file
def emojify(text, emoji \\ @emoji)
def emojify(text, nil), do: text
@@ -144,8 +165,29 @@ defmodule Pleroma.Formatter do
@emoji
end
- @link_regex ~r/https?:\/\/[\w\.\/?=\-#\+%&@~'\(\):]+[\w\/]/u
+ @link_regex ~r/[0-9a-z+\-\.]+:[0-9a-z$-_.+!*'(),]+/ui
+
+ # IANA got a list https://www.iana.org/assignments/uri-schemes/ but
+ # Stuff like ipfs isn’t in it
+ # There is very niche stuff
+ @uri_schemes [
+ "https://",
+ "http://",
+ "dat://",
+ "dweb://",
+ "gopher://",
+ "ipfs://",
+ "ipns://",
+ "irc:",
+ "ircs:",
+ "magnet:",
+ "mailto:",
+ "mumble:",
+ "ssb://",
+ "xmpp:"
+ ]
+ # TODO: make it use something other than @link_regex
def html_escape(text) do
Regex.split(@link_regex, text, include_captures: true)
|> Enum.map_every(2, fn chunk ->
@@ -155,11 +197,18 @@ defmodule Pleroma.Formatter do
|> Enum.join("")
end
- @doc "changes http:... links to html links"
+ @doc "changes scheme:... urls to html links"
def add_links({subs, text}) do
+ additionnal_schemes =
+ Application.get_env(:pleroma, :uri_schemes, [])
+ |> Keyword.get(:additionnal_schemes, [])
+
links =
- Regex.scan(@link_regex, text)
- |> Enum.map(fn [url] -> {Ecto.UUID.generate(), url} end)
+ text
+ |> String.split([" ", "\t", "<br>"])
+ |> Enum.filter(fn word -> String.starts_with?(word, @uri_schemes ++ additionnal_schemes) end)
+ |> Enum.filter(fn word -> Regex.match?(@link_regex, word) end)
+ |> Enum.map(fn url -> {Ecto.UUID.generate(), url} end)
|> Enum.sort_by(fn {_, url} -> -String.length(url) end)
uuid_text =
@@ -223,8 +272,8 @@ defmodule Pleroma.Formatter do
subs =
subs ++
- Enum.map(tags, fn {_, tag, uuid} ->
- url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
+ Enum.map(tags, fn {tag_text, tag, uuid} ->
+ url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag_text}</a>"
{uuid, url}
end)
diff --git a/lib/pleroma/gopher/server.ex b/lib/pleroma/gopher/server.ex
index f6abcd4d0..97a1dea77 100644
--- a/lib/pleroma/gopher/server.ex
+++ b/lib/pleroma/gopher/server.ex
@@ -54,7 +54,7 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
String.split(text, "\r")
|> Enum.map(fn text ->
- "i#{text}\tfake\(NULL)\t0\r\n"
+ "i#{text}\tfake\t(NULL)\t0\r\n"
end)
|> Enum.join("")
end
@@ -77,14 +77,14 @@ defmodule Pleroma.Gopher.Server.ProtocolHandler do
link("Post ##{activity.id} by #{user.nickname}", "/notices/#{activity.id}") <>
info("#{like_count} likes, #{announcement_count} repeats") <>
- "\r\n" <>
+ "i\tfake\t(NULL)\t0\r\n" <>
info(
HtmlSanitizeEx.strip_tags(
String.replace(activity.data["object"]["content"], "<br>", "\r")
)
)
end)
- |> Enum.join("\r\n")
+ |> Enum.join("i\tfake\t(NULL)\t0\r\n")
end
def response("") do
diff --git a/lib/pleroma/http/http.ex b/lib/pleroma/http/http.ex
index 84f34eb4a..c19bccf60 100644
--- a/lib/pleroma/http/http.ex
+++ b/lib/pleroma/http/http.ex
@@ -1,5 +1,23 @@
defmodule Pleroma.HTTP do
- use HTTPoison.Base
+ require HTTPoison
+
+ def request(method, url, body \\ "", headers \\ [], options \\ []) do
+ options =
+ process_request_options(options)
+ |> process_sni_options(url)
+
+ HTTPoison.request(method, url, body, headers, options)
+ end
+
+ defp process_sni_options(options, url) do
+ uri = URI.parse(url)
+ host = uri.host |> to_charlist()
+
+ case uri.scheme do
+ "https" -> options ++ [ssl: [server_name_indication: host]]
+ _ -> options
+ end
+ end
def process_request_options(options) do
config = Application.get_env(:pleroma, :http, [])
@@ -10,4 +28,9 @@ defmodule Pleroma.HTTP do
_ -> options ++ [proxy: proxy]
end
end
+
+ def get(url, headers \\ [], options \\ []), do: request(:get, url, "", headers, options)
+
+ def post(url, body, headers \\ [], options \\ []),
+ do: request(:post, url, body, headers, options)
end
diff --git a/lib/pleroma/plugs/digest.ex b/lib/pleroma/plugs/digest.ex
new file mode 100644
index 000000000..9d6bbb085
--- /dev/null
+++ b/lib/pleroma/plugs/digest.ex
@@ -0,0 +1,10 @@
+defmodule Pleroma.Web.Plugs.DigestPlug do
+ alias Plug.Conn
+ require Logger
+
+ def read_body(conn, opts) do
+ {:ok, body, conn} = Conn.read_body(conn, opts)
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, body) |> Base.encode64())
+ {:ok, body, Conn.assign(conn, :digest, digest)}
+ end
+end
diff --git a/lib/pleroma/plugs/http_signature.ex b/lib/pleroma/plugs/http_signature.ex
index 38bcd3a78..9e53371b7 100644
--- a/lib/pleroma/plugs/http_signature.ex
+++ b/lib/pleroma/plugs/http_signature.ex
@@ -19,6 +19,8 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
cond do
signature && String.contains?(signature, user) ->
+ # set (request-target) header to the appropriate value
+ # we also replace the digest header with the one we computed
conn =
conn
|> put_req_header(
@@ -26,6 +28,14 @@ defmodule Pleroma.Web.Plugs.HTTPSignaturePlug do
String.downcase("#{conn.method}") <> " #{conn.request_path}"
)
+ conn =
+ if conn.assigns[:digest] do
+ conn
+ |> put_req_header("digest", conn.assigns[:digest])
+ else
+ conn
+ end
+
assign(conn, :valid_signature, HTTPSignatures.validate_conn(conn))
signature ->
diff --git a/lib/pleroma/upload.ex b/lib/pleroma/upload.ex
index 43df0d418..e0cb545b0 100644
--- a/lib/pleroma/upload.ex
+++ b/lib/pleroma/upload.ex
@@ -18,8 +18,10 @@ defmodule Pleroma.Upload do
File.cp!(file.path, result_file)
end
+ strip_exif_data(content_type, result_file)
+
%{
- "type" => "Image",
+ "type" => "Document",
"url" => [
%{
"type" => "Link",
@@ -67,6 +69,8 @@ defmodule Pleroma.Upload do
File.rename(uuidpath, result_file)
end
+ strip_exif_data(content_type, result_file)
+
%{
"type" => "Image",
"url" => [
@@ -80,6 +84,16 @@ defmodule Pleroma.Upload do
}
end
+ def strip_exif_data(content_type, file) do
+ settings = Application.get_env(:pleroma, Pleroma.Upload)
+ do_strip = Keyword.fetch!(settings, :strip_exif)
+ [filetype, ext] = String.split(content_type, "/")
+
+ if filetype == "image" and do_strip == true do
+ Mogrify.open(file) |> Mogrify.custom("strip") |> Mogrify.save(in_place: true)
+ end
+ end
+
def upload_path do
settings = Application.get_env(:pleroma, Pleroma.Upload)
Keyword.fetch!(settings, :uploads)
@@ -110,20 +124,20 @@ defmodule Pleroma.Upload do
if should_dedupe do
create_name(uuid, List.last(String.split(file.filename, ".")), type)
else
- unless String.contains?(file.filename, ".") do
- case type do
- "image/png" -> file.filename <> ".png"
- "image/jpeg" -> file.filename <> ".jpg"
- "image/gif" -> file.filename <> ".gif"
- "video/webm" -> file.filename <> ".webm"
- "video/mp4" -> file.filename <> ".mp4"
- "audio/mpeg" -> file.filename <> ".mp3"
- "audio/ogg" -> file.filename <> ".ogg"
- "audio/wav" -> file.filename <> ".wav"
- _ -> file.filename
+ parts = String.split(file.filename, ".")
+
+ new_filename =
+ if length(parts) > 1 do
+ Enum.drop(parts, -1) |> Enum.join(".")
+ else
+ Enum.join(parts)
end
- else
- file.filename
+
+ case type do
+ "application/octet-stream" -> file.filename
+ "audio/mpeg" -> new_filename <> ".mp3"
+ "image/jpeg" -> new_filename <> ".jpg"
+ _ -> Enum.join([new_filename, String.split(type, "/") |> List.last()], ".")
end
end
end
diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex
index d68aef52a..7d7f3b23e 100644
--- a/lib/pleroma/user.ex
+++ b/lib/pleroma/user.ex
@@ -169,6 +169,9 @@ defmodule Pleroma.User do
end
def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
+ user_config = Application.get_env(:pleroma, :user)
+ deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
+
user_info = user_info(followed)
should_direct_follow =
@@ -178,7 +181,8 @@ defmodule Pleroma.User do
false
# if the users are blocking each other, we shouldn't even be here, but check for it anyway
- User.blocks?(follower, followed) == true or User.blocks?(followed, follower) == true ->
+ deny_follow_blocked and
+ (User.blocks?(follower, followed) or User.blocks?(followed, follower)) ->
false
# if OStatus, then there is no three-way handshake to follow
@@ -206,13 +210,16 @@ defmodule Pleroma.User do
end
def follow(%User{} = follower, %User{info: info} = followed) do
+ user_config = Application.get_env(:pleroma, :user)
+ deny_follow_blocked = Keyword.get(user_config, :deny_follow_blocked)
+
ap_followers = followed.follower_address
cond do
following?(follower, followed) or info["deactivated"] ->
{:error, "Could not follow user: #{followed.nickname} is already on your list."}
- blocks?(followed, follower) ->
+ deny_follow_blocked and blocks?(followed, follower) ->
{:error, "Could not follow user: #{followed.nickname} blocked you."}
true ->
@@ -391,6 +398,7 @@ defmodule Pleroma.User do
Enum.map(reqs, fn req -> req.actor end)
|> Enum.uniq()
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
+ |> Enum.filter(fn u -> !following?(u, user) end)
{:ok, users}
end
@@ -521,15 +529,33 @@ defmodule Pleroma.User do
Repo.all(q)
end
- def block(user, %{ap_id: ap_id}) do
- blocks = user.info["blocks"] || []
+ def block(blocker, %User{ap_id: ap_id} = blocked) do
+ # sever any follow relationships to prevent leaks per activitypub (Pleroma issue #213)
+ blocker =
+ if following?(blocker, blocked) do
+ {:ok, blocker, _} = unfollow(blocker, blocked)
+ blocker
+ else
+ blocker
+ end
+
+ if following?(blocked, blocker) do
+ unfollow(blocked, blocker)
+ end
+
+ blocks = blocker.info["blocks"] || []
new_blocks = Enum.uniq([ap_id | blocks])
- new_info = Map.put(user.info, "blocks", new_blocks)
+ new_info = Map.put(blocker.info, "blocks", new_blocks)
- cs = User.info_changeset(user, %{info: new_info})
+ cs = User.info_changeset(blocker, %{info: new_info})
update_and_set_cache(cs)
end
+ # helper to handle the block given only an actor's AP id
+ def block(blocker, %{ap_id: ap_id}) do
+ block(blocker, User.get_by_ap_id(ap_id))
+ end
+
def unblock(user, %{ap_id: ap_id}) do
blocks = user.info["blocks"] || []
new_blocks = List.delete(blocks, ap_id)
@@ -598,7 +624,7 @@ defmodule Pleroma.User do
|> Enum.each(fn activity ->
case activity.data["type"] do
"Create" ->
- ActivityPub.delete(Object.get_by_ap_id(activity.data["object"]["id"]))
+ ActivityPub.delete(Object.normalize(activity.data["object"]))
# TODO: Do something with likes, follows, repeats.
_ ->
diff --git a/lib/pleroma/user_invite_token.ex b/lib/pleroma/user_invite_token.ex
new file mode 100644
index 000000000..48ee1019a
--- /dev/null
+++ b/lib/pleroma/user_invite_token.ex
@@ -0,0 +1,40 @@
+defmodule Pleroma.UserInviteToken do
+ use Ecto.Schema
+
+ import Ecto.Changeset
+
+ alias Pleroma.{User, UserInviteToken, Repo}
+
+ schema "user_invite_tokens" do
+ field(:token, :string)
+ field(:used, :boolean, default: false)
+
+ timestamps()
+ end
+
+ def create_token do
+ token = :crypto.strong_rand_bytes(32) |> Base.url_encode64()
+
+ token = %UserInviteToken{
+ used: false,
+ token: token
+ }
+
+ Repo.insert(token)
+ end
+
+ def used_changeset(struct) do
+ struct
+ |> cast(%{}, [])
+ |> put_change(:used, true)
+ end
+
+ def mark_as_used(token) do
+ with %{used: false} = token <- Repo.get_by(UserInviteToken, %{token: token}),
+ {:ok, token} <- Repo.update(used_changeset(token)) do
+ {:ok, token}
+ else
+ _e -> {:error, token}
+ end
+ end
+end
diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex
index a4b49e73c..298d7817a 100644
--- a/lib/pleroma/web/activity_pub/activity_pub.ex
+++ b/lib/pleroma/web/activity_pub/activity_pub.ex
@@ -48,7 +48,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def insert(map, local \\ true) when is_map(map) do
- with nil <- Activity.get_by_ap_id(map["id"]),
+ with nil <- Activity.normalize(map),
map <- lazy_put_activity_defaults(map),
:ok <- check_actor_is_active(map["actor"]),
{:ok, map} <- MRF.filter(map),
@@ -83,6 +83,14 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
if activity.local do
Pleroma.Web.Streamer.stream("public:local", activity)
end
+
+ if activity.data["object"]["attachment"] != [] do
+ Pleroma.Web.Streamer.stream("public:media", activity)
+
+ if activity.local do
+ Pleroma.Web.Streamer.stream("public:local:media", activity)
+ end
+ end
else
if !Enum.member?(activity.data["cc"] || [], public) &&
!Enum.member?(
@@ -261,16 +269,25 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
- follow_activity = fetch_latest_follow(blocker, blocked)
+ ap_config = Application.get_env(:pleroma, :activitypub)
+ unfollow_blocked = Keyword.get(ap_config, :unfollow_blocked)
+ outgoing_blocks = Keyword.get(ap_config, :outgoing_blocks)
- if follow_activity do
- unfollow(blocker, blocked, nil, local)
+ with true <- unfollow_blocked do
+ follow_activity = fetch_latest_follow(blocker, blocked)
+
+ if follow_activity do
+ unfollow(blocker, blocked, nil, local)
+ end
end
- with block_data <- make_block_data(blocker, blocked, activity_id),
+ with true <- outgoing_blocks,
+ block_data <- make_block_data(blocker, blocked, activity_id),
{:ok, activity} <- insert(block_data, local),
:ok <- maybe_federate(activity) do
{:ok, activity}
+ else
+ _e -> {:ok, nil}
end
end
@@ -448,6 +465,15 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_media(query, _), do: query
+ defp restrict_replies(query, %{"exclude_replies" => val}) when val == "true" or val == "1" do
+ from(
+ activity in query,
+ where: fragment("?->'object'->>'inReplyTo' is null", activity.data)
+ )
+ end
+
+ defp restrict_replies(query, _), do: query
+
# Only search through last 100_000 activities by default
defp restrict_recent(query, %{"whole_db" => true}), do: query
@@ -505,6 +531,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
|> restrict_blocked(opts)
|> restrict_media(opts)
|> restrict_visibility(opts)
+ |> restrict_replies(opts)
end
def fetch_activities(recipients, opts \\ %{}) do
@@ -556,7 +583,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
def fetch_and_prepare_user_from_ap_id(ap_id) do
with {:ok, %{status_code: 200, body: body}} <-
- @httpoison.get(ap_id, Accept: "application/activity+json"),
+ @httpoison.get(ap_id, [Accept: "application/activity+json"], follow_redirect: true),
{:ok, data} <- Jason.decode(body) do
user_data_from_user_object(data)
else
@@ -632,13 +659,23 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.info("Federating #{id} to #{inbox}")
host = URI.parse(inbox).host
+ digest = "SHA-256=" <> (:crypto.hash(:sha256, json) |> Base.encode64())
+
signature =
- Pleroma.Web.HTTPSignatures.sign(actor, %{host: host, "content-length": byte_size(json)})
+ Pleroma.Web.HTTPSignatures.sign(actor, %{
+ host: host,
+ "content-length": byte_size(json),
+ digest: digest
+ })
@httpoison.post(
inbox,
json,
- [{"Content-Type", "application/activity+json"}, {"signature", signature}],
+ [
+ {"Content-Type", "application/activity+json"},
+ {"signature", signature},
+ {"digest", digest}
+ ],
hackney: [pool: :default]
)
end
@@ -661,7 +698,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
recv_timeout: 20000
),
{:ok, data} <- Jason.decode(body),
- nil <- Object.get_by_ap_id(data["id"]),
+ nil <- Object.normalize(data),
params <- %{
"type" => "Create",
"to" => data["to"],
@@ -670,7 +707,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
"object" => data
},
{:ok, activity} <- Transmogrifier.handle_incoming(params) do
- {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
+ {:ok, Object.normalize(activity.data["object"])}
else
object = %Object{} ->
{:ok, object}
@@ -679,7 +716,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
Logger.info("Couldn't get object via AP, trying out OStatus fetching...")
case OStatus.fetch_activity_from_url(id) do
- {:ok, [activity | _]} -> {:ok, Object.get_by_ap_id(activity.data["object"]["id"])}
+ {:ok, [activity | _]} -> {:ok, Object.normalize(activity.data["object"])}
e -> e
end
end
diff --git a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
index 8d770387d..7fecb8a4f 100644
--- a/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
+++ b/lib/pleroma/web/activity_pub/mrf/simple_policy.ex
@@ -4,6 +4,15 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
@mrf_policy Application.get_env(:pleroma, :mrf_simple)
+ @accept Keyword.get(@mrf_policy, :accept)
+ defp check_accept(actor_info, object) do
+ if length(@accept) > 0 and not (actor_info.host in @accept) do
+ {:reject, nil}
+ else
+ {:ok, object}
+ end
+ end
+
@reject Keyword.get(@mrf_policy, :reject)
defp check_reject(actor_info, object) do
if actor_info.host in @reject do
@@ -74,7 +83,8 @@ defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
def filter(object) do
actor_info = URI.parse(object["actor"])
- with {:ok, object} <- check_reject(actor_info, object),
+ with {:ok, object} <- check_accept(actor_info, object),
+ {:ok, object} <- check_reject(actor_info, object),
{:ok, object} <- check_media_removal(actor_info, object),
{:ok, object} <- check_media_nsfw(actor_info, object),
{:ok, object} <- check_ftl_removal(actor_info, object) do
diff --git a/lib/pleroma/web/activity_pub/transmogrifier.ex b/lib/pleroma/web/activity_pub/transmogrifier.ex
index 300e0fcdd..1367bc7e3 100644
--- a/lib/pleroma/web/activity_pub/transmogrifier.ex
+++ b/lib/pleroma/web/activity_pub/transmogrifier.ex
@@ -13,17 +13,73 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
require Logger
+ def get_actor(%{"actor" => actor}) when is_binary(actor) do
+ actor
+ end
+
+ def get_actor(%{"actor" => actor}) when is_list(actor) do
+ if is_binary(Enum.at(actor, 0)) do
+ Enum.at(actor, 0)
+ else
+ Enum.find(actor, fn %{"type" => type} -> type == "Person" end)
+ |> Map.get("id")
+ end
+ end
+
+ def get_actor(%{"actor" => actor}) when is_map(actor) do
+ actor["id"]
+ end
+
@doc """
Modifies an incoming AP object (mastodon format) to our internal format.
"""
def fix_object(object) do
object
- |> Map.put("actor", object["attributedTo"])
+ |> fix_actor
|> fix_attachments
|> fix_context
|> fix_in_reply_to
|> fix_emoji
|> fix_tag
+ |> fix_content_map
+ |> fix_likes
+ |> fix_addressing
+ end
+
+ def fix_addressing_list(map, field) do
+ if is_binary(map[field]) do
+ map
+ |> Map.put(field, [map[field]])
+ else
+ map
+ end
+ end
+
+ def fix_addressing(map) do
+ map
+ |> fix_addressing_list("to")
+ |> fix_addressing_list("cc")
+ |> fix_addressing_list("bto")
+ |> fix_addressing_list("bcc")
+ end
+
+ def fix_actor(%{"attributedTo" => actor} = object) do
+ object
+ |> Map.put("actor", get_actor(%{"actor" => actor}))
+ end
+
+ def fix_likes(%{"likes" => likes} = object)
+ when is_bitstring(likes) do
+ # Check for standardisation
+ # This is what Peertube does
+ # curl -H 'Accept: application/activity+json' $likes | jq .totalItems
+ object
+ |> Map.put("likes", [])
+ |> Map.put("like_count", 0)
+ end
+
+ def fix_likes(object) do
+ object
end
def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
@@ -53,8 +109,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def fix_in_reply_to(object), do: object
def fix_context(object) do
+ context = object["context"] || object["conversation"] || Utils.generate_context_id()
+
object
- |> Map.put("context", object["conversation"])
+ |> Map.put("context", context)
+ |> Map.put("conversation", context)
end
def fix_attachments(object) do
@@ -107,10 +166,34 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> Map.put("tag", combined)
end
+ # content map usually only has one language so this will do for now.
+ def fix_content_map(%{"contentMap" => content_map} = object) do
+ content_groups = Map.to_list(content_map)
+ {_, content} = Enum.at(content_groups, 0)
+
+ object
+ |> Map.put("content", content)
+ end
+
+ def fix_content_map(object), do: object
+
+ # disallow objects with bogus IDs
+ def handle_incoming(%{"id" => nil}), do: :error
+ def handle_incoming(%{"id" => ""}), do: :error
+ # length of https:// = 8, should validate better, but good enough for now.
+ def handle_incoming(%{"id" => id}) when not (is_binary(id) and length(id) > 8), do: :error
+
# TODO: validate those with a Ecto scheme
# - tags
# - emoji
- def handle_incoming(%{"type" => "Create", "object" => %{"type" => "Note"} = object} = data) do
+ def handle_incoming(%{"type" => "Create", "object" => %{"type" => objtype} = object} = data)
+ when objtype in ["Article", "Note", "Video"] do
+ actor = get_actor(data)
+
+ data =
+ Map.put(data, "actor", actor)
+ |> fix_addressing
+
with nil <- Activity.get_create_activity_by_object_ap_id(object["id"]),
%User{} = user <- User.get_or_fetch_by_ap_id(data["actor"]) do
object = fix_object(data["object"])
@@ -400,7 +483,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
def handle_incoming(_), do: :error
def get_obj_helper(id) do
- if object = Object.get_by_ap_id(id), do: {:ok, object}, else: nil
+ if object = Object.normalize(id), do: {:ok, object}, else: nil
end
def set_reply_to_uri(%{"inReplyTo" => inReplyTo} = object) do
@@ -448,14 +531,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
# because of course it does.
def prepare_outgoing(%{"type" => "Accept"} = data) do
- follow_activity_id =
- if is_binary(data["object"]) do
- data["object"]
- else
- data["object"]["id"]
- end
-
- with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
+ with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
@@ -473,14 +549,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
end
def prepare_outgoing(%{"type" => "Reject"} = data) do
- follow_activity_id =
- if is_binary(data["object"]) do
- data["object"]
- else
- data["object"]["id"]
- end
-
- with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
+ with follow_activity <- Activity.normalize(data["object"]) do
object = %{
"actor" => follow_activity.actor,
"object" => follow_activity.data["object"],
diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex
index 64329b710..7cdc1656b 100644
--- a/lib/pleroma/web/activity_pub/utils.ex
+++ b/lib/pleroma/web/activity_pub/utils.ex
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
Inserts a full object if it is contained in an activity.
"""
def insert_full_object(%{"object" => %{"type" => type} = object_data})
- when is_map(object_data) and type in ["Note"] do
+ when is_map(object_data) and type in ["Article", "Note", "Video"] do
with {:ok, _} <- Object.create(object_data) do
:ok
end
@@ -204,13 +204,17 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do
- with likes <- [actor | object.data["likes"] || []] |> Enum.uniq() do
+ likes = if is_list(object.data["likes"]), do: object.data["likes"], else: []
+
+ with likes <- [actor | likes] |> Enum.uniq() do
update_likes_in_object(likes, object)
end
end
def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do
- with likes <- (object.data["likes"] || []) |> List.delete(actor) do
+ likes = if is_list(object.data["likes"]), do: object.data["likes"], else: []
+
+ with likes <- likes |> List.delete(actor) do
update_likes_in_object(likes, object)
end
end
@@ -357,13 +361,19 @@ defmodule Pleroma.Web.ActivityPub.Utils do
end
def add_announce_to_object(%Activity{data: %{"actor" => actor}}, object) do
- with announcements <- [actor | object.data["announcements"] || []] |> Enum.uniq() do
+ announcements =
+ if is_list(object.data["announcements"]), do: object.data["announcements"], else: []
+
+ with announcements <- [actor | announcements] |> Enum.uniq() do
update_element_in_object("announcement", announcements, object)
end
end
def remove_announce_from_object(%Activity{data: %{"actor" => actor}}, object) do
- with announcements <- (object.data["announcements"] || []) |> List.delete(actor) do
+ announcements =
+ if is_list(object.data["announcements"]), do: object.data["announcements"], else: []
+
+ with announcements <- announcements |> List.delete(actor) do
update_element_in_object("announcement", announcements, object)
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 f4b2e0610..fc76f2940 100644
--- a/lib/pleroma/web/activity_pub/views/user_view.ex
+++ b/lib/pleroma/web/activity_pub/views/user_view.ex
@@ -12,7 +12,7 @@ defmodule Pleroma.Web.ActivityPub.UserView do
def render("user.json", %{user: user}) do
{:ok, user} = WebFinger.ensure_keys_present(user)
{:ok, _, public_key} = Salmon.keys_from_pem(user.info["keys"])
- public_key = :public_key.pem_entry_encode(:RSAPublicKey, public_key)
+ public_key = :public_key.pem_entry_encode(:SubjectPublicKeyInfo, public_key)
public_key = :public_key.pem_encode([public_key])
%{
@@ -42,7 +42,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
"image" => %{
"type" => "Image",
"url" => User.banner_url(user)
- }
+ },
+ "tag" => user.info["source_data"]["tag"] || []
}
|> Map.merge(Utils.make_json_ld_header())
end
@@ -98,9 +99,6 @@ defmodule Pleroma.Web.ActivityPub.UserView do
info = User.user_info(user)
params = %{
- "type" => ["Create", "Announce"],
- "actor_id" => user.ap_id,
- "whole_db" => true,
"limit" => "10"
}
@@ -111,10 +109,8 @@ defmodule Pleroma.Web.ActivityPub.UserView do
params
end
- activities = ActivityPub.fetch_public_activities(params)
- min_id = Enum.at(activities, 0).id
-
- activities = Enum.reverse(activities)
+ activities = ActivityPub.fetch_user_activities(user, nil, params)
+ min_id = Enum.at(Enum.reverse(activities), 0).id
max_id = Enum.at(activities, 0).id
collection =
diff --git a/lib/pleroma/web/common_api/common_api.ex b/lib/pleroma/web/common_api/common_api.ex
index 8845419c2..125c57d05 100644
--- a/lib/pleroma/web/common_api/common_api.ex
+++ b/lib/pleroma/web/common_api/common_api.ex
@@ -1,5 +1,5 @@
defmodule Pleroma.Web.CommonAPI do
- alias Pleroma.{Repo, Activity, Object}
+ alias Pleroma.{User, Repo, Activity, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Formatter
@@ -7,7 +7,7 @@ defmodule Pleroma.Web.CommonAPI do
def delete(activity_id, user) do
with %Activity{data: %{"object" => %{"id" => object_id}}} <- Repo.get(Activity, activity_id),
- %Object{} = object <- Object.get_by_ap_id(object_id),
+ %Object{} = object <- Object.normalize(object_id),
true <- user.info["is_moderator"] || user.ap_id == object.data["actor"],
{:ok, delete} <- ActivityPub.delete(object) do
{:ok, delete}
@@ -16,7 +16,7 @@ defmodule Pleroma.Web.CommonAPI do
def repeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.announce(user, object)
else
_ ->
@@ -26,7 +26,7 @@ defmodule Pleroma.Web.CommonAPI do
def unrepeat(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.unannounce(user, object)
else
_ ->
@@ -37,7 +37,7 @@ defmodule Pleroma.Web.CommonAPI do
def favorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
false <- activity.data["actor"] == user.ap_id,
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.like(user, object)
else
_ ->
@@ -48,7 +48,7 @@ defmodule Pleroma.Web.CommonAPI do
def unfavorite(id_or_ap_id, user) do
with %Activity{} = activity <- get_by_id_or_ap_id(id_or_ap_id),
false <- activity.data["actor"] == user.ap_id,
- object <- Object.get_by_ap_id(activity.data["object"]["id"]) do
+ object <- Object.normalize(activity.data["object"]["id"]) do
ActivityPub.unlike(user, object)
else
_ ->
@@ -61,8 +61,13 @@ defmodule Pleroma.Web.CommonAPI do
do: visibility
def get_visibility(%{"in_reply_to_status_id" => status_id}) when not is_nil(status_id) do
- inReplyTo = get_replied_to_activity(status_id)
- Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
+ case get_replied_to_activity(status_id) do
+ nil ->
+ "public"
+
+ inReplyTo ->
+ Pleroma.Web.MastodonAPI.StatusView.get_visibility(inReplyTo.data["object"])
+ end
end
def get_visibility(_), do: "public"
@@ -118,6 +123,18 @@ defmodule Pleroma.Web.CommonAPI do
end
def update(user) do
+ user =
+ with emoji <- emoji_from_profile(user),
+ source_data <- (user.info["source_data"] || %{}) |> Map.put("tag", emoji),
+ new_info <- Map.put(user.info, "source_data", source_data),
+ change <- User.info_changeset(user, %{info: new_info}),
+ {:ok, user} <- User.update_and_set_cache(change) do
+ user
+ else
+ _e ->
+ user
+ end
+
ActivityPub.update(%{
local: true,
to: [user.follower_address],
diff --git a/lib/pleroma/web/common_api/utils.ex b/lib/pleroma/web/common_api/utils.ex
index 30089f553..358ca22ac 100644
--- a/lib/pleroma/web/common_api/utils.ex
+++ b/lib/pleroma/web/common_api/utils.ex
@@ -1,6 +1,7 @@
defmodule Pleroma.Web.CommonAPI.Utils do
alias Pleroma.{Repo, Object, Formatter, Activity}
alias Pleroma.Web.ActivityPub.Utils
+ alias Pleroma.Web.Endpoint
alias Pleroma.User
alias Calendar.Strftime
alias Comeonin.Pbkdf2
@@ -64,7 +65,6 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def make_content_html(status, mentions, attachments, tags, no_attachment_links \\ false) do
status
- |> String.replace("\r", "")
|> format_input(mentions, tags)
|> maybe_add_attachments(attachments, no_attachment_links)
end
@@ -95,7 +95,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
def format_input(text, mentions, tags) do
text
|> Formatter.html_escape()
- |> String.replace("\n", "<br>")
+ |> String.replace(~r/\r?\n/, "<br>")
|> (&{[], &1}).()
|> Formatter.add_links()
|> Formatter.add_user_links(mentions)
@@ -109,7 +109,7 @@ defmodule Pleroma.Web.CommonAPI.Utils do
|> Enum.sort_by(fn {tag, _} -> -String.length(tag) end)
Enum.reduce(tags, text, fn {full, tag}, text ->
- url = "#<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>#{tag}</a>"
+ url = "<a href='#{Pleroma.Web.base_url()}/tag/#{tag}' rel='tag'>##{tag}</a>"
String.replace(text, full, url)
end)
end
@@ -196,4 +196,15 @@ defmodule Pleroma.Web.CommonAPI.Utils do
_ -> {:error, "Invalid password."}
end
end
+
+ def emoji_from_profile(%{info: info} = user) do
+ (Formatter.get_emoji(user.bio) ++ Formatter.get_emoji(user.name))
+ |> Enum.map(fn {shortcode, url} ->
+ %{
+ "type" => "Emoji",
+ "icon" => %{"type" => "Image", "url" => "#{Endpoint.url()}#{url}"},
+ "name" => ":#{shortcode}:"
+ }
+ end)
+ end
end
diff --git a/lib/pleroma/web/endpoint.ex b/lib/pleroma/web/endpoint.ex
index 1a012c1b4..cbedca004 100644
--- a/lib/pleroma/web/endpoint.ex
+++ b/lib/pleroma/web/endpoint.ex
@@ -35,7 +35,8 @@ defmodule Pleroma.Web.Endpoint do
parsers: [:urlencoded, :multipart, :json],
pass: ["*/*"],
json_decoder: Jason,
- length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit)
+ length: Application.get_env(:pleroma, :instance) |> Keyword.get(:upload_limit),
+ body_reader: {Pleroma.Web.Plugs.DigestPlug, :read_body, []}
)
plug(Plug.MethodOverride)
diff --git a/lib/pleroma/web/federator/federator.ex b/lib/pleroma/web/federator/federator.ex
index 8ca530031..ccefb0bdf 100644
--- a/lib/pleroma/web/federator/federator.ex
+++ b/lib/pleroma/web/federator/federator.ex
@@ -95,7 +95,7 @@ defmodule Pleroma.Web.Federator do
params = Utils.normalize_params(params)
with {:ok, _user} <- ap_enabled_actor(params["actor"]),
- nil <- Activity.get_by_ap_id(params["id"]),
+ nil <- Activity.normalize(params["id"]),
{:ok, _activity} <- Transmogrifier.handle_incoming(params) do
else
%Activity{} ->
diff --git a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
index 8a8d1e050..f482de6fd 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_api_controller.ex
@@ -1,25 +1,30 @@
defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
use Pleroma.Web, :controller
- alias Pleroma.{Repo, Activity, User, Notification, Stats}
+ alias Pleroma.{Repo, Object, Activity, User, Notification, Stats}
alias Pleroma.Web
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
- alias Pleroma.Web.{CommonAPI, OStatus}
+ alias Pleroma.Web.CommonAPI
alias Pleroma.Web.OAuth.{Authorization, Token, App}
alias Comeonin.Pbkdf2
import Ecto.Query
require Logger
+ @httpoison Application.get_env(:pleroma, :httpoison)
+
action_fallback(:errors)
def create_app(conn, params) do
with cs <- App.register_changeset(%App{}, params) |> IO.inspect(),
{:ok, app} <- Repo.insert(cs) |> IO.inspect() do
res = %{
- id: app.id,
+ id: app.id |> to_string,
+ name: app.client_name,
client_id: app.client_id,
- client_secret: app.client_secret
+ client_secret: app.client_secret,
+ redirect_uri: app.redirect_uris,
+ website: app.website
}
json(conn, res)
@@ -125,7 +130,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
response = %{
uri: Web.base_url(),
title: Keyword.get(@instance, :name),
- description: "A Pleroma instance, an alternative fediverse server",
+ description: Keyword.get(@instance, :description),
version: "#{@mastodon_api_level} (compatible; #{Keyword.get(@instance, :version)})",
email: Keyword.get(@instance, :email),
urls: %{
@@ -428,16 +433,43 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
render(conn, AccountView, "relationships.json", %{user: user, targets: targets})
end
- def upload(%{assigns: %{user: _}} = conn, %{"file" => file}) do
- with {:ok, object} <- ActivityPub.upload(file) do
+ def update_media(%{assigns: %{user: _}} = conn, data) do
+ with %Object{} = object <- Repo.get(Object, data["id"]),
+ true <- is_binary(data["description"]),
+ description <- data["description"] do
+ new_data = %{object.data | "name" => description}
+
+ change = Object.change(object, %{data: new_data})
+ {:ok, media_obj} = Repo.update(change)
+
data =
- object.data
+ new_data
|> Map.put("id", object.id)
render(conn, StatusView, "attachment.json", %{attachment: data})
end
end
+ def upload(%{assigns: %{user: _}} = conn, %{"file" => file} = data) do
+ with {:ok, object} <- ActivityPub.upload(file) do
+ objdata =
+ if Map.has_key?(data, "description") do
+ Map.put(object.data, "name", data["description"])
+ else
+ object.data
+ end
+
+ change = Object.change(object, %{data: objdata})
+ {:ok, object} = Repo.update(change)
+
+ objdata =
+ objdata
+ |> Map.put("id", object.id)
+
+ render(conn, StatusView, "attachment.json", %{attachment: objdata})
+ end
+ end
+
def favourited_by(conn, %{"id" => id}) do
with %Activity{data: %{"object" => %{"likes" => likes}}} <- Repo.get(Activity, id) do
q = from(u in User, where: u.ap_id in ^likes)
@@ -621,17 +653,61 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
json(conn, %{})
end
+ def search2(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
+ accounts = User.search(query, params["resolve"] == "true")
+
+ fetched =
+ if Regex.match?(~r/https?:/, query) do
+ with {:ok, object} <- ActivityPub.fetch_object_from_id(query) do
+ [Activity.get_create_activity_by_object_ap_id(object.data["id"])]
+ else
+ _e -> []
+ end
+ end || []
+
+ q =
+ from(
+ a in Activity,
+ where: fragment("?->>'type' = 'Create'", a.data),
+ where: "https://www.w3.org/ns/activitystreams#Public" in a.recipients,
+ where:
+ fragment(
+ "to_tsvector('english', ?->'object'->>'content') @@ plainto_tsquery('english', ?)",
+ a.data,
+ ^query
+ ),
+ limit: 20,
+ order_by: [desc: :id]
+ )
+
+ statuses = Repo.all(q) ++ fetched
+
+ tags_path = Web.base_url() <> "/tag/"
+
+ tags =
+ String.split(query)
+ |> Enum.uniq()
+ |> Enum.filter(fn tag -> String.starts_with?(tag, "#") end)
+ |> Enum.map(fn tag -> String.slice(tag, 1..-1) end)
+ |> Enum.map(fn tag -> %{name: tag, url: tags_path <> tag} end)
+
+ res = %{
+ "accounts" => AccountView.render("accounts.json", users: accounts, for: user, as: :user),
+ "statuses" =>
+ StatusView.render("index.json", activities: statuses, for: user, as: :activity),
+ "hashtags" => tags
+ }
+
+ json(conn, res)
+ end
+
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
accounts = User.search(query, params["resolve"] == "true")
fetched =
if Regex.match?(~r/https?:/, query) do
- with {:ok, activities} <- OStatus.fetch_activity_from_url(query) do
- activities
- |> Enum.filter(fn
- %{data: %{"type" => "Create"}} -> true
- _ -> false
- end)
+ with {:ok, object} <- ActivityPub.fetch_object_from_id(query) do
+ [Activity.get_create_activity_by_object_ap_id(object.data["id"])]
else
_e -> []
end
@@ -812,11 +888,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
boost_modal: false,
delete_modal: true,
auto_play_gif: false,
- reduce_motion: false
+ display_sensitive_media: false,
+ reduce_motion: false,
+ max_toot_chars: Keyword.get(@instance, :limit)
+ },
+ rights: %{
+ delete_others_notice: !!user.info["is_moderator"]
},
compose: %{
me: "#{user.id}",
- default_privacy: "public",
+ default_privacy: user.info["default_scope"] || "public",
default_sensitive: false
},
media_attachments: %{
@@ -1013,4 +1094,45 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|> put_status(500)
|> json("Something went wrong")
end
+
+ @suggestions Application.get_env(:pleroma, :suggestions)
+
+ def suggestions(%{assigns: %{user: user}} = conn, _) do
+ if Keyword.get(@suggestions, :enabled, false) do
+ api = Keyword.get(@suggestions, :third_party_engine, "")
+ timeout = Keyword.get(@suggestions, :timeout, 5000)
+
+ host =
+ Application.get_env(:pleroma, Pleroma.Web.Endpoint)
+ |> Keyword.get(:url)
+ |> Keyword.get(:host)
+
+ user = user.nickname
+ url = String.replace(api, "{{host}}", host) |> String.replace("{{user}}", user)
+
+ with {:ok, %{status_code: 200, body: body}} <-
+ @httpoison.get(url, [], timeout: timeout, recv_timeout: timeout),
+ {:ok, data} <- Jason.decode(body) do
+ data2 =
+ Enum.slice(data, 0, 40)
+ |> Enum.map(fn x ->
+ Map.put(
+ x,
+ "id",
+ case User.get_or_fetch(x["acct"]) do
+ %{id: id} -> id
+ _ -> 0
+ end
+ )
+ end)
+
+ conn
+ |> json(data2)
+ else
+ e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}")
+ end
+ else
+ json(conn, [])
+ end
+ end
end
diff --git a/lib/pleroma/web/mastodon_api/mastodon_socket.ex b/lib/pleroma/web/mastodon_api/mastodon_socket.ex
index 46648c366..174293906 100644
--- a/lib/pleroma/web/mastodon_api/mastodon_socket.ex
+++ b/lib/pleroma/web/mastodon_api/mastodon_socket.ex
@@ -15,8 +15,16 @@ defmodule Pleroma.Web.MastodonAPI.MastodonSocket do
with token when not is_nil(token) <- params["access_token"],
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
%User{} = user <- Repo.get(User, user_id),
- stream when stream in ["public", "public:local", "user", "direct", "list"] <-
- params["stream"] do
+ stream
+ when stream in [
+ "public",
+ "public:local",
+ "public:media",
+ "public:local:media",
+ "user",
+ "direct",
+ "list"
+ ] <- params["stream"] do
topic = if stream == "list", do: "list:#{params["list"]}", else: stream
socket =
diff --git a/lib/pleroma/web/mastodon_api/views/account_view.ex b/lib/pleroma/web/mastodon_api/views/account_view.ex
index 9db683f44..d9edcae7f 100644
--- a/lib/pleroma/web/mastodon_api/views/account_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/account_view.ex
@@ -14,6 +14,18 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
header = User.banner_url(user) |> MediaProxy.url()
user_info = User.user_info(user)
+ emojis =
+ (user.info["source_data"]["tag"] || [])
+ |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
+ |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
+ %{
+ "shortcode" => String.trim(name, ":"),
+ "url" => MediaProxy.url(url),
+ "static_url" => MediaProxy.url(url),
+ "visible_in_picker" => false
+ }
+ end)
+
%{
id: to_string(user.id),
username: hd(String.split(user.nickname, "@")),
@@ -24,12 +36,14 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
followers_count: user_info.follower_count,
following_count: user_info.following_count,
statuses_count: user_info.note_count,
- note: user.bio || "",
+ note: HtmlSanitizeEx.basic_html(user.bio) || "",
url: user.ap_id,
avatar: image,
avatar_static: image,
header: header,
header_static: header,
+ emojis: emojis,
+ fields: [],
source: %{
note: "",
privacy: "public",
diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex
index 59898457b..6962aa54f 100644
--- a/lib/pleroma/web/mastodon_api/views/status_view.ex
+++ b/lib/pleroma/web/mastodon_api/views/status_view.ex
@@ -54,8 +54,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
%{
id: to_string(activity.id),
uri: object,
- # TODO: This might be wrong, check with mastodon.
- url: nil,
+ url: object,
account: AccountView.render("account.json", %{user: user}),
in_reply_to_id: nil,
in_reply_to_account_id: nil,
@@ -100,8 +99,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
repeated = opts[:for] && opts[:for].ap_id in (object["announcements"] || [])
favorited = opts[:for] && opts[:for].ap_id in (object["likes"] || [])
- attachments =
- render_many(object["attachment"] || [], StatusView, "attachment.json", as: :attachment)
+ attachment_data = object["attachment"] || []
+ attachment_data = attachment_data ++ if object["type"] == "Video", do: [object], else: []
+ attachments = render_many(attachment_data, StatusView, "attachment.json", as: :attachment)
created_at = Utils.to_masto_date(object["published"])
@@ -128,7 +128,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
in_reply_to_id: reply_to && to_string(reply_to.id),
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
reblog: nil,
- content: HtmlSanitizeEx.basic_html(object["content"]),
+ content: render_content(object),
created_at: created_at,
reblogs_count: announcement_count,
favourites_count: like_count,
@@ -152,7 +152,9 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
end
def render("attachment.json", %{attachment: attachment}) do
- [%{"mediaType" => media_type, "href" => href} | _] = attachment["url"]
+ [attachment_url | _] = attachment["url"]
+ media_type = attachment_url["mediaType"] || attachment_url["mimeType"]
+ href = attachment_url["href"]
type =
cond do
@@ -170,7 +172,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
remote_url: href,
preview_url: MediaProxy.url(href),
text_url: href,
- type: type
+ type: type,
+ description: attachment["name"]
}
end
@@ -207,4 +210,34 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
"direct"
end
end
+
+ def render_content(%{"type" => "Video"} = object) do
+ name = object["name"]
+
+ content =
+ if !!name and name != "" do
+ "<p><a href=\"#{object["id"]}\">#{name}</a></p>#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ HtmlSanitizeEx.basic_html(content)
+ end
+
+ def render_content(%{"type" => "Article"} = object) do
+ summary = object["name"]
+
+ content =
+ if !!summary and summary != "" do
+ "<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ HtmlSanitizeEx.basic_html(content)
+ end
+
+ def render_content(object) do
+ HtmlSanitizeEx.basic_html(object["content"])
+ end
end
diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
index aec77168a..2fab60274 100644
--- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
+++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex
@@ -4,8 +4,6 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
alias Pleroma.Stats
alias Pleroma.Web
- @instance Application.get_env(:pleroma, :instance)
-
def schemas(conn, _params) do
response = %{
links: [
@@ -21,20 +19,23 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
def nodeinfo(conn, %{"version" => "2.0"}) do
+ instance = Application.get_env(:pleroma, :instance)
+ media_proxy = Application.get_env(:pleroma, :media_proxy)
+ suggestions = Application.get_env(:pleroma, :suggestions)
stats = Stats.get_stats()
response = %{
version: "2.0",
software: %{
name: "pleroma",
- version: Keyword.get(@instance, :version)
+ version: Keyword.get(instance, :version)
},
protocols: ["ostatus", "activitypub"],
services: %{
inbound: [],
outbound: []
},
- openRegistrations: Keyword.get(@instance, :registrations_open),
+ openRegistrations: Keyword.get(instance, :registrations_open),
usage: %{
users: %{
total: stats.user_count || 0
@@ -42,7 +43,16 @@ defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
localPosts: stats.status_count || 0
},
metadata: %{
- nodeName: Keyword.get(@instance, :name)
+ nodeName: Keyword.get(instance, :name),
+ nodeDescription: Keyword.get(instance, :description),
+ mediaProxy: Keyword.get(media_proxy, :enabled),
+ private: !Keyword.get(instance, :public, true),
+ suggestions: %{
+ enabled: Keyword.get(suggestions, :enabled, false),
+ thirdPartyEngine: Keyword.get(suggestions, :third_party_engine, ""),
+ timeout: Keyword.get(suggestions, :timeout, 5000),
+ web: Keyword.get(suggestions, :web, "")
+ }
}
}
diff --git a/lib/pleroma/web/ostatus/activity_representer.ex b/lib/pleroma/web/ostatus/activity_representer.ex
index 4c4a0c233..537bd9f77 100644
--- a/lib/pleroma/web/ostatus/activity_representer.ex
+++ b/lib/pleroma/web/ostatus/activity_representer.ex
@@ -249,7 +249,7 @@ defmodule Pleroma.Web.OStatus.ActivityRepresenter do
author = if with_author, do: [{:author, UserRepresenter.to_simple_form(user)}], else: []
mentions = (activity.recipients || []) |> get_mentions
- follow_activity = Activity.get_by_ap_id(follow_activity["id"])
+ follow_activity = Activity.normalize(follow_activity)
[
{:"activity:object-type", ['http://activitystrea.ms/schema/1.0/activity']},
diff --git a/lib/pleroma/web/ostatus/handlers/delete_handler.ex b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
index 4f3016b65..6330d7f64 100644
--- a/lib/pleroma/web/ostatus/handlers/delete_handler.ex
+++ b/lib/pleroma/web/ostatus/handlers/delete_handler.ex
@@ -6,7 +6,7 @@ defmodule Pleroma.Web.OStatus.DeleteHandler do
def handle_delete(entry, _doc \\ nil) do
with id <- XML.string_from_xpath("//id", entry),
- object when not is_nil(object) <- Object.get_by_ap_id(id),
+ %Object{} = object <- Object.normalize(id),
{:ok, delete} <- ActivityPub.delete(object, false) do
delete
end
diff --git a/lib/pleroma/web/ostatus/ostatus.ex b/lib/pleroma/web/ostatus/ostatus.ex
index f0ff0624f..916c894eb 100644
--- a/lib/pleroma/web/ostatus/ostatus.ex
+++ b/lib/pleroma/web/ostatus/ostatus.ex
@@ -89,7 +89,7 @@ defmodule Pleroma.Web.OStatus do
def make_share(entry, doc, retweeted_activity) do
with {:ok, actor} <- find_make_or_update_user(doc),
- %Object{} = object <- Object.get_by_ap_id(retweeted_activity.data["object"]["id"]),
+ %Object{} = object <- Object.normalize(retweeted_activity.data["object"]),
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
{:ok, activity, _object} = ActivityPub.announce(actor, object, id, false) do
{:ok, activity}
@@ -107,7 +107,7 @@ defmodule Pleroma.Web.OStatus do
def make_favorite(entry, doc, favorited_activity) do
with {:ok, actor} <- find_make_or_update_user(doc),
- %Object{} = object <- Object.get_by_ap_id(favorited_activity.data["object"]["id"]),
+ %Object{} = object <- Object.normalize(favorited_activity.data["object"]),
id when not is_nil(id) <- string_from_xpath("/entry/id", entry),
{:ok, activity, _object} = ActivityPub.like(actor, object, id, false) do
{:ok, activity}
diff --git a/lib/pleroma/web/ostatus/ostatus_controller.ex b/lib/pleroma/web/ostatus/ostatus_controller.ex
index 2f72fdb16..09d1b1110 100644
--- a/lib/pleroma/web/ostatus/ostatus_controller.ex
+++ b/lib/pleroma/web/ostatus/ostatus_controller.ex
@@ -6,6 +6,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
alias Pleroma.Repo
alias Pleroma.Web.{OStatus, Federator}
alias Pleroma.Web.XML
+ alias Pleroma.Web.ActivityPub.ObjectView
alias Pleroma.Web.ActivityPub.ActivityPubController
alias Pleroma.Web.ActivityPub.ActivityPub
@@ -90,7 +91,7 @@ defmodule Pleroma.Web.OStatus.OStatusController do
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
case get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
- _ -> represent_activity(conn, activity, user)
+ _ -> represent_activity(conn, nil, activity, user)
end
else
{:public?, false} ->
@@ -107,12 +108,12 @@ defmodule Pleroma.Web.OStatus.OStatusController do
def activity(conn, %{"uuid" => uuid}) do
with id <- o_status_url(conn, :activity, uuid),
- {_, %Activity{} = activity} <- {:activity, Activity.get_by_ap_id(id)},
+ {_, %Activity{} = activity} <- {:activity, Activity.normalize(id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case get_format(conn) do
+ case format = get_format(conn) do
"html" -> redirect(conn, to: "/notice/#{activity.id}")
- _ -> represent_activity(conn, activity, user)
+ _ -> represent_activity(conn, format, activity, user)
end
else
{:public?, false} ->
@@ -130,14 +131,14 @@ defmodule Pleroma.Web.OStatus.OStatusController do
with {_, %Activity{} = activity} <- {:activity, Repo.get(Activity, id)},
{_, true} <- {:public?, ActivityPub.is_public?(activity)},
%User{} = user <- User.get_cached_by_ap_id(activity.data["actor"]) do
- case get_format(conn) do
+ case format = get_format(conn) do
"html" ->
conn
|> put_resp_content_type("text/html")
|> send_file(200, "priv/static/index.html")
_ ->
- represent_activity(conn, activity, user)
+ represent_activity(conn, format, activity, user)
end
else
{:public?, false} ->
@@ -151,7 +152,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end
end
- defp represent_activity(conn, activity, user) do
+ defp represent_activity(conn, "activity+json", activity, user) do
+ conn
+ |> put_resp_header("content-type", "application/activity+json")
+ |> json(ObjectView.render("object.json", %{object: activity}))
+ end
+
+ defp represent_activity(conn, _, activity, user) do
response =
activity
|> ActivityRepresenter.to_simple_form(user, true)
diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex
index 13bd393ab..68e159f6a 100644
--- a/lib/pleroma/web/router.ex
+++ b/lib/pleroma/web/router.ex
@@ -8,8 +8,19 @@ defmodule Pleroma.Web.Router do
@public Keyword.get(@instance, :public)
@registrations_open Keyword.get(@instance, :registrations_open)
- def user_fetcher(username) do
- {:ok, Repo.get_by(User, %{nickname: username})}
+ def user_fetcher(username_or_email) do
+ {
+ :ok,
+ cond do
+ # First, try logging in as if it was a name
+ user = Repo.get_by(User, %{nickname: username_or_email}) ->
+ user
+
+ # If we get nil, we try using it as an email
+ user = Repo.get_by(User, %{email: username_or_email}) ->
+ user
+ end
+ }
end
pipeline :api do
@@ -127,6 +138,7 @@ defmodule Pleroma.Web.Router do
get("/notifications/:id", MastodonAPIController, :get_notification)
post("/media", MastodonAPIController, :upload)
+ put("/media/:id", MastodonAPIController, :update_media)
get("/lists", MastodonAPIController, :get_lists)
get("/lists/:id", MastodonAPIController, :get_list)
@@ -140,6 +152,8 @@ defmodule Pleroma.Web.Router do
get("/domain_blocks", MastodonAPIController, :domain_blocks)
post("/domain_blocks", MastodonAPIController, :block_domain)
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
+
+ get("/suggestions", MastodonAPIController, :suggestions)
end
scope "/api/web", Pleroma.Web.MastodonAPI do
@@ -170,9 +184,16 @@ defmodule Pleroma.Web.Router do
get("/accounts/:id/following", MastodonAPIController, :following)
get("/accounts/:id", MastodonAPIController, :user)
+ get("/trends", MastodonAPIController, :empty_array)
+
get("/search", MastodonAPIController, :search)
end
+ scope "/api/v2", Pleroma.Web.MastodonAPI do
+ pipe_through(:api)
+ get("/search", MastodonAPIController, :search2)
+ end
+
scope "/api", Pleroma.Web do
pipe_through(:config)
@@ -194,9 +215,7 @@ defmodule Pleroma.Web.Router do
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)
- if @registrations_open do
- post("/account/register", TwitterAPI.Controller, :register)
- end
+ post("/account/register", TwitterAPI.Controller, :register)
get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
@@ -269,6 +288,7 @@ defmodule Pleroma.Web.Router do
get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array)
get("/mutes/users/ids", TwitterAPI.Controller, :empty_array)
+ get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array)
get("/externalprofile/show", TwitterAPI.Controller, :external_profile)
end
@@ -347,6 +367,7 @@ defmodule Pleroma.Web.Router do
end
scope "/", Fallback do
+ get("/registration/:token", RedirectController, :registration_page)
get("/*path", RedirectController, :redirector)
end
end
@@ -361,4 +382,8 @@ defmodule Fallback.RedirectController do
|> send_file(200, "priv/static/index.html")
end
end
+
+ def registration_page(conn, params) do
+ redirector(conn, params)
+ end
end
diff --git a/lib/pleroma/web/streamer.ex b/lib/pleroma/web/streamer.ex
index ce38f3cc3..c61bad830 100644
--- a/lib/pleroma/web/streamer.ex
+++ b/lib/pleroma/web/streamer.ex
@@ -158,7 +158,7 @@ defmodule Pleroma.Web.Streamer do
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
blocks = user.info["blocks"] || []
- parent = Object.get_by_ap_id(item.data["object"])
+ parent = Object.normalize(item.data["object"])
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
send(socket.transport_pid, {:text, represent_update(item, user)})
diff --git a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
index 6a00b9e2c..0862412ea 100644
--- a/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
+++ b/lib/pleroma/web/templates/mastodon_api/mastodon/index.html.eex
@@ -19,7 +19,7 @@
<script id='initial-state' type='application/json'><%= raw @initial_state %></script>
<script src="/packs/application.js"></script>
</head>
-<body class='app-body no-reduce-motion'>
+<body class='app-body no-reduce-motion system-font'>
<div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
</div>
</body>
diff --git a/lib/pleroma/web/twitter_api/controllers/util_controller.ex b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
index 7a0c37ce9..d1ecebf61 100644
--- a/lib/pleroma/web/twitter_api/controllers/util_controller.ex
+++ b/lib/pleroma/web/twitter_api/controllers/util_controller.ex
@@ -99,6 +99,10 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
conn
|> render("followed.html", %{error: false})
else
+ # Was already following user
+ {:error, "Could not follow user:" <> _rest} ->
+ render(conn, "followed.html", %{error: false})
+
_e ->
conn
|> render("follow_login.html", %{
@@ -117,6 +121,11 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
conn
|> render("followed.html", %{error: false})
else
+ # Was already following user
+ {:error, "Could not follow user:" <> _rest} ->
+ conn
+ |> render("followed.html", %{error: false})
+
e ->
Logger.debug("Remote follow failed with error #{inspect(e)}")
@@ -126,6 +135,8 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
@instance Application.get_env(:pleroma, :instance)
+ @instance_fe Application.get_env(:pleroma, :fe)
+ @instance_chat Application.get_env(:pleroma, :chat)
def config(conn, _params) do
case get_format(conn) do
"xml" ->
@@ -148,9 +159,23 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
json(conn, %{
site: %{
name: Keyword.get(@instance, :name),
+ description: Keyword.get(@instance, :description),
server: Web.base_url(),
textlimit: to_string(Keyword.get(@instance, :limit)),
- closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1")
+ closed: if(Keyword.get(@instance, :registrations_open), do: "0", else: "1"),
+ private: if(Keyword.get(@instance, :public, true), do: "0", else: "1"),
+ pleromafe: %{
+ theme: Keyword.get(@instance_fe, :theme),
+ background: Keyword.get(@instance_fe, :background),
+ logo: Keyword.get(@instance_fe, :logo),
+ redirectRootNoLogin: Keyword.get(@instance_fe, :redirect_root_no_login),
+ redirectRootLogin: Keyword.get(@instance_fe, :redirect_root_login),
+ chatDisabled: !Keyword.get(@instance_chat, :enabled),
+ showInstanceSpecificPanel: Keyword.get(@instance_fe, :show_instance_panel),
+ scopeOptionsEnabled: Keyword.get(@instance_fe, :scope_options_enabled),
+ collapseMessageWithSubject:
+ Keyword.get(@instance_fe, :collapse_message_with_subject)
+ }
}
})
end
diff --git a/lib/pleroma/web/twitter_api/representers/activity_representer.ex b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
index 57837205e..9abea59a7 100644
--- a/lib/pleroma/web/twitter_api/representers/activity_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/activity_representer.ex
@@ -4,7 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.{Activity, User}
- alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView}
+ alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView}
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Formatter
@@ -164,19 +164,21 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
- summary = activity.data["object"]["summary"]
-
- content =
- if !!summary and summary != "" do
- "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
- else
- content
- end
+ {summary, content} = ActivityView.render_content(object)
html =
HtmlSanitizeEx.basic_html(content)
|> Formatter.emojify(object["emoji"])
+ video =
+ if object["type"] == "Video" do
+ vid = [object]
+ else
+ []
+ end
+
+ attachments = (object["attachment"] || []) ++ video
+
%{
"id" => activity.id,
"uri" => activity.data["object"]["id"],
@@ -188,7 +190,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
"created_at" => created_at,
"in_reply_to_status_id" => object["inReplyToStatusId"],
"statusnet_conversation_id" => conversation_id,
- "attachments" => (object["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
+ "attachments" => attachments |> ObjectRepresenter.enum_to_list(opts),
"attentions" => attentions,
"fave_num" => like_count,
"repeat_num" => announcement_count,
@@ -198,7 +200,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
- "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
+ "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
+ "summary" => object["summary"]
}
end
diff --git a/lib/pleroma/web/twitter_api/representers/object_representer.ex b/lib/pleroma/web/twitter_api/representers/object_representer.ex
index 9af8a1691..6aa794a59 100644
--- a/lib/pleroma/web/twitter_api/representers/object_representer.ex
+++ b/lib/pleroma/web/twitter_api/representers/object_representer.ex
@@ -7,18 +7,20 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do
%{
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
- mimetype: url["mediaType"],
+ mimetype: url["mediaType"] || url["mimeType"],
id: data["uuid"],
- oembed: false
+ oembed: false,
+ description: data["name"]
}
end
def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do
%{
url: url |> Pleroma.Web.MediaProxy.url(),
- mimetype: data["mediaType"],
+ mimetype: data["mediaType"] || url["mimeType"],
id: data["uuid"],
- oembed: false
+ oembed: false,
+ description: data["name"]
}
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api.ex b/lib/pleroma/web/twitter_api/twitter_api.ex
index c23b3c2c4..dbad08e66 100644
--- a/lib/pleroma/web/twitter_api/twitter_api.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api.ex
@@ -1,11 +1,13 @@
defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
- alias Pleroma.{User, Activity, Repo, Object}
+ alias Pleroma.{UserInviteToken, User, Activity, Repo, Object}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.UserView
alias Pleroma.Web.{OStatus, CommonAPI}
import Ecto.Query
+ @instance Application.get_env(:pleroma, :instance)
@httpoison Application.get_env(:pleroma, :httpoison)
+ @registrations_open Keyword.get(@instance, :registrations_open)
def create_status(%User{} = user, %{"status" => _} = data) do
CommonAPI.post(user, data)
@@ -120,6 +122,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
end
def register_user(params) do
+ tokenString = params["token"]
+
params = %{
nickname: params["nickname"],
name: params["fullname"],
@@ -129,17 +133,33 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
password_confirmation: params["confirm"]
}
- changeset = User.register_changeset(%User{}, params)
+ # no need to query DB if registration is open
+ token =
+ unless @registrations_open || is_nil(tokenString) do
+ Repo.get_by(UserInviteToken, %{token: tokenString})
+ end
- with {:ok, user} <- Repo.insert(changeset) do
- {:ok, user}
- else
- {:error, changeset} ->
- errors =
- Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
- |> Jason.encode!()
+ cond do
+ @registrations_open || (!is_nil(token) && !token.used) ->
+ changeset = User.register_changeset(%User{}, params)
+
+ with {:ok, user} <- Repo.insert(changeset) do
+ !@registrations_open && UserInviteToken.mark_as_used(token.token)
+ {:ok, user}
+ else
+ {:error, changeset} ->
+ errors =
+ Ecto.Changeset.traverse_errors(changeset, fn {msg, _opts} -> msg end)
+ |> Jason.encode!()
+
+ {:error, %{error: errors}}
+ end
+
+ !@registrations_open && is_nil(token) ->
+ {:error, "Invalid token"}
- {:error, %{error: errors}}
+ !@registrations_open && token.used ->
+ {:error, "Expired token"}
end
end
diff --git a/lib/pleroma/web/twitter_api/twitter_api_controller.ex b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
index ff5921807..b3a56b27e 100644
--- a/lib/pleroma/web/twitter_api/twitter_api_controller.ex
+++ b/lib/pleroma/web/twitter_api/twitter_api_controller.ex
@@ -1,7 +1,9 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller
+ alias Pleroma.Formatter
alias Pleroma.Web.TwitterAPI.{TwitterAPI, UserView, ActivityView, NotificationView}
alias Pleroma.Web.CommonAPI
+ alias Pleroma.Web.CommonAPI.Utils, as: CommonUtils
alias Pleroma.{Repo, Activity, User, Notification}
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Utils
@@ -404,11 +406,25 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
json(conn, Jason.encode!([]))
end
+ def raw_empty_array(conn, _params) do
+ json(conn, [])
+ end
+
def update_profile(%{assigns: %{user: user}} = conn, params) do
params =
if bio = params["description"] do
- bio_brs = Regex.replace(~r/\r?\n/, bio, "<br>")
- Map.put(params, "bio", bio_brs)
+ mentions = Formatter.parse_mentions(bio)
+ tags = Formatter.parse_tags(bio)
+
+ emoji =
+ (user.info["source_data"]["tag"] || [])
+ |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
+ |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
+ {String.trim(name, ":"), url}
+ end)
+
+ bio_html = CommonUtils.format_input(bio, mentions, tags)
+ Map.put(params, "bio", bio_html |> Formatter.emojify(emoji))
else
params
end
@@ -427,6 +443,19 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
user
end
+ user =
+ if default_scope = params["default_scope"] do
+ with new_info <- Map.put(user.info, "default_scope", default_scope),
+ change <- User.info_changeset(user, %{info: new_info}),
+ {:ok, user} <- User.update_and_set_cache(change) do
+ user
+ else
+ _e -> user
+ end
+ else
+ user
+ end
+
with changeset <- User.update_changeset(user, params),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
diff --git a/lib/pleroma/web/twitter_api/views/activity_view.ex b/lib/pleroma/web/twitter_api/views/activity_view.ex
index 62ce3b7b5..55b5287f5 100644
--- a/lib/pleroma/web/twitter_api/views/activity_view.ex
+++ b/lib/pleroma/web/twitter_api/views/activity_view.ex
@@ -228,15 +228,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags
- summary = activity.data["object"]["summary"]
- content = object["content"]
-
- content =
- if !!summary and summary != "" do
- "<span>#{activity.data["object"]["summary"]}</span><br />#{content}</span>"
- else
- content
- end
+ {summary, content} = render_content(object)
html =
HtmlSanitizeEx.basic_html(content)
@@ -263,7 +255,41 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
- "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object)
+ "visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
+ "summary" => summary
}
end
+
+ def render_content(%{"type" => "Note"} = object) do
+ summary = object["summary"]
+
+ content =
+ if !!summary and summary != "" do
+ "<p>#{summary}</p>#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ {summary, content}
+ end
+
+ def render_content(%{"type" => "Article"} = object) do
+ summary = object["name"] || object["summary"]
+
+ content =
+ if !!summary and summary != "" do
+ "<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
+ else
+ object["content"]
+ end
+
+ {summary, content}
+ end
+
+ def render_content(object) do
+ summary = object["summary"] || "Unhandled activity type: #{object["type"]}"
+ content = "<p>#{summary}</p>#{object["content"]}"
+
+ {summary, content}
+ end
end
diff --git a/lib/pleroma/web/twitter_api/views/user_view.ex b/lib/pleroma/web/twitter_api/views/user_view.ex
index 711008973..32f93153d 100644
--- a/lib/pleroma/web/twitter_api/views/user_view.ex
+++ b/lib/pleroma/web/twitter_api/views/user_view.ex
@@ -1,6 +1,7 @@
defmodule Pleroma.Web.TwitterAPI.UserView do
use Pleroma.Web, :view
alias Pleroma.User
+ alias Pleroma.Formatter
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy
@@ -28,9 +29,18 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
user_info = User.get_cached_user_info(user)
+ emoji =
+ (user.info["source_data"]["tag"] || [])
+ |> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
+ |> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
+ {String.trim(name, ":"), url}
+ end)
+
data = %{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
- "description" => HtmlSanitizeEx.strip_tags(user.bio),
+ "description" =>
+ HtmlSanitizeEx.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
+ "description_html" => HtmlSanitizeEx.basic_html(user.bio),
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,
@@ -39,6 +49,7 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name,
+ "name_html" => HtmlSanitizeEx.strip_tags(user.name) |> Formatter.emojify(emoji),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
@@ -52,7 +63,8 @@ defmodule Pleroma.Web.TwitterAPI.UserView do
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info["background"]) |> MediaProxy.url(),
"is_local" => user.local,
- "locked" => !!user.info["locked"]
+ "locked" => !!user.info["locked"],
+ "default_scope" => user.info["default_scope"] || "public"
}
if assigns[:token] do