Merge remote-tracking branch 'MAIN/develop' into feature/jobs

This commit is contained in:
Egor Kislitsyn 2019-01-31 15:07:49 +07:00
commit d3677d2b4d
81 changed files with 910 additions and 337 deletions

View file

@ -64,7 +64,7 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
end
end
defp check_remote_limit(%{"object" => %{"content" => content}}) do
defp check_remote_limit(%{"object" => %{"content" => content}}) when not is_nil(content) do
limit = Pleroma.Config.get([:instance, :remote_limit])
String.length(content) <= limit
end
@ -88,6 +88,10 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
recipients: recipients
})
Task.start(fn ->
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
end)
Notification.create_notifications(activity)
stream_out(activity)
{:ok, activity}
@ -426,7 +430,34 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
defp restrict_since(query, _), do: query
defp restrict_tag(query, %{"tag" => tag}) do
defp restrict_tag_reject(query, %{"tag_reject" => tag_reject})
when is_list(tag_reject) and tag_reject != [] do
from(
activity in query,
where: fragment("(not (? #> '{\"object\",\"tag\"}') \\?| ?)", activity.data, ^tag_reject)
)
end
defp restrict_tag_reject(query, _), do: query
defp restrict_tag_all(query, %{"tag_all" => tag_all})
when is_list(tag_all) and tag_all != [] do
from(
activity in query,
where: fragment("(? #> '{\"object\",\"tag\"}') \\?& ?", activity.data, ^tag_all)
)
end
defp restrict_tag_all(query, _), do: query
defp restrict_tag(query, %{"tag" => tag}) when is_list(tag) do
from(
activity in query,
where: fragment("(? #> '{\"object\",\"tag\"}') \\?| ?", activity.data, ^tag)
)
end
defp restrict_tag(query, %{"tag" => tag}) when is_binary(tag) do
from(
activity in query,
where: fragment("? <@ (? #> '{\"object\",\"tag\"}')", ^tag, activity.data)
@ -575,6 +606,8 @@ defmodule Pleroma.Web.ActivityPub.ActivityPub do
base_query
|> restrict_recipients(recipients, opts["user"])
|> restrict_tag(opts)
|> restrict_tag_reject(opts)
|> restrict_tag_all(opts)
|> restrict_since(opts)
|> restrict_local(opts)
|> restrict_limit(opts)

View file

@ -141,11 +141,11 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|> 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
# Check for standardisation
# This is what Peertube does
# curl -H 'Accept: application/activity+json' $likes | jq .totalItems
# Prismo returns only an integer (count) as "likes"
def fix_likes(%{"likes" => likes} = object) when not is_map(likes) do
object
|> Map.put("likes", [])
|> Map.put("like_count", 0)
@ -453,9 +453,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
{:ok, activity} <-
ActivityPub.accept(%{
ActivityPub.reject(%{
to: follow_activity.data["to"],
type: "Accept",
type: "Reject",
actor: followed.ap_id,
object: follow_activity.data["id"],
local: false

View file

@ -316,6 +316,25 @@ defmodule Pleroma.Web.ActivityPub.Utils do
@doc """
Updates a follow activity's state (for locked accounts).
"""
def update_follow_state(
%Activity{data: %{"actor" => actor, "object" => object, "state" => "pending"}} = activity,
state
) do
try do
Ecto.Adapters.SQL.query!(
Repo,
"UPDATE activities SET data = jsonb_set(data, '{state}', $1) WHERE data->>'type' = 'Follow' AND data->>'actor' = $2 AND data->>'object' = $3 AND data->>'state' = 'pending'",
[state, actor, object]
)
activity = Repo.get(Activity, activity.id)
{:ok, activity}
rescue
e ->
{:error, e}
end
end
def update_follow_state(%Activity{} = activity, state) do
with new_data <-
activity.data

View file

@ -499,7 +499,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def upload(%{assigns: %{user: user}} = conn, %{"file" => file} = data) do
with {:ok, object} <-
ActivityPub.upload(file,
ActivityPub.upload(
file,
actor: User.ap_id(user),
description: Map.get(data, "description")
) do
@ -540,15 +541,34 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def hashtag_timeline(%{assigns: %{user: user}} = conn, params) do
local_only = params["local"] in [true, "True", "true", "1"]
params =
tags =
[params["tag"], params["any"]]
|> List.flatten()
|> Enum.uniq()
|> Enum.filter(& &1)
|> Enum.map(&String.downcase(&1))
tag_all =
params["all"] ||
[]
|> Enum.map(&String.downcase(&1))
tag_reject =
params["none"] ||
[]
|> Enum.map(&String.downcase(&1))
query_params =
params
|> Map.put("type", "Create")
|> Map.put("local_only", local_only)
|> Map.put("blocking_user", user)
|> Map.put("tag", String.downcase(params["tag"]))
|> Map.put("tag", tags)
|> Map.put("tag_all", tag_all)
|> Map.put("tag_reject", tag_reject)
activities =
ActivityPub.fetch_public_activities(params)
ActivityPub.fetch_public_activities(query_params)
|> Enum.reverse()
conn
@ -1081,7 +1101,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
def login(conn, _) do
with {:ok, app} <- get_or_make_app() do
path =
o_auth_path(conn, :authorize,
o_auth_path(
conn,
:authorize,
response_type: "code",
client_id: app.client_id,
redirect_uri: ".",
@ -1289,7 +1311,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
[],
adapter: [
timeout: timeout,
recv_timeout: timeout
recv_timeout: timeout,
pool: :default
]
),
{:ok, data} <- Jason.decode(body) do
@ -1322,6 +1345,22 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
end
end
def status_card(conn, %{"id" => status_id}) do
with %Activity{} = activity <- Repo.get(Activity, status_id),
true <- ActivityPub.is_public?(activity) do
data =
StatusView.render(
"card.json",
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
)
json(conn, data)
else
_e ->
%{}
end
end
def try_render(conn, target, params)
when is_binary(target) do
res = render(conn, target, params)

View file

@ -112,7 +112,9 @@ defmodule Pleroma.Web.MastodonAPI.AccountView do
# Pleroma extension
pleroma: %{
confirmation_pending: user_info.confirmation_pending,
tags: user.tags
tags: user.tags,
is_moderator: user.info.is_moderator,
is_admin: user.info.is_admin
}
}
end

View file

@ -49,12 +49,11 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
replied_to_activities = get_replied_to_activities(opts.activities)
opts.activities
|> render_many(
|> safe_render_many(
StatusView,
"status.json",
Map.put(opts, :replied_to_activities, replied_to_activities)
)
|> Enum.filter(fn x -> not is_nil(x) end)
end
def render(
@ -140,6 +139,8 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
__MODULE__
)
card = render("card.json", Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity))
%{
id: to_string(activity.id),
uri: object["id"],
@ -148,6 +149,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,
card: card,
content: content,
created_at: created_at,
reblogs_count: announcement_count,
@ -176,6 +178,29 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
nil
end
def render("card.json", %{rich_media: rich_media, page_url: page_url}) do
page_url = rich_media[:url] || page_url
page_url_data = URI.parse(page_url)
site_name = rich_media[:site_name] || page_url_data.host
%{
type: "link",
provider_name: site_name,
provider_url: page_url_data.scheme <> "://" <> page_url_data.host,
url: page_url,
image: rich_media[:image] |> MediaProxy.url(),
title: rich_media[:title],
description: rich_media[:description],
pleroma: %{
opengraph: rich_media
}
}
end
def render("card.json", _) do
nil
end
def render("attachment.json", %{attachment: attachment}) do
[attachment_url | _] = attachment["url"]
media_type = attachment_url["mediaType"] || attachment_url["mimeType"] || "image"

View file

@ -9,7 +9,8 @@ defmodule Pleroma.Web.OAuth.FallbackController do
# No user/password
def call(conn, _) do
conn
|> put_status(:unauthorized)
|> put_flash(:error, "Invalid Username/Password")
|> OAuthController.authorize(conn.params)
|> OAuthController.authorize(conn.params["authorization"])
end
end

View file

@ -166,10 +166,13 @@ defmodule Pleroma.Web.OStatus.OStatusController do
end
else
{:public?, false} ->
{:error, :not_found}
conn
|> put_status(404)
|> Fallback.RedirectController.redirector(nil, 404)
{:activity, nil} ->
{:error, :not_found}
conn
|> Fallback.RedirectController.redirector(nil, 404)
e ->
e

View file

@ -1,17 +0,0 @@
defmodule Pleroma.Web.RichMedia.RichMediaController do
use Pleroma.Web, :controller
import Pleroma.Web.ControllerHelper, only: [json_response: 3]
def parse(conn, %{"url" => url}) do
case Pleroma.Web.RichMedia.Parser.parse(url) do
{:ok, data} ->
conn
|> json_response(200, data)
{:error, msg} ->
conn
|> json_response(404, msg)
end
end
end

View file

@ -0,0 +1,18 @@
# Pleroma: A lightweight social networking server
# Copyright _ 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Helpers do
alias Pleroma.{Activity, Object, HTML}
alias Pleroma.Web.RichMedia.Parser
def fetch_data_for_activity(%Activity{} = activity) do
with %Object{} = object <- Object.normalize(activity.data["object"]),
{:ok, page_url} <- HTML.extract_first_external_url(object, object.data["content"]),
{:ok, rich_media} <- Parser.parse(page_url) do
%{page_url: page_url, rich_media: rich_media}
else
_ -> %{}
end
end
end

View file

@ -1,3 +1,7 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.RichMedia.Parser do
@parsers [
Pleroma.Web.RichMedia.Parsers.OGP,
@ -5,17 +9,32 @@ defmodule Pleroma.Web.RichMedia.Parser do
Pleroma.Web.RichMedia.Parsers.OEmbed
]
def parse(nil), do: {:error, "No URL provided"}
if Mix.env() == :test do
def parse(url), do: parse_url(url)
else
def parse(url),
do: Cachex.fetch!(:rich_media_cache, url, fn _ -> parse_url(url) end)
def parse(url) do
try do
Cachex.fetch!(:rich_media_cache, url, fn _ ->
{:commit, parse_url(url)}
end)
rescue
e ->
{:error, "Cachex error: #{inspect(e)}"}
end
end
end
defp parse_url(url) do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url)
try do
{:ok, %Tesla.Env{body: html}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
html |> maybe_parse() |> get_parsed_data()
html |> maybe_parse() |> get_parsed_data()
rescue
e ->
{:error, "Parsing error: #{inspect(e)}"}
end
end
defp maybe_parse(html) do
@ -27,11 +46,11 @@ defmodule Pleroma.Web.RichMedia.Parser do
end)
end
defp get_parsed_data(data) when data == %{} do
{:error, "No metadata found"}
defp get_parsed_data(%{title: title} = data) when is_binary(title) and byte_size(title) > 0 do
{:ok, data}
end
defp get_parsed_data(data) do
{:ok, data}
{:error, "Found metadata was invalid or incomplete: #{inspect(data)}"}
end
end

View file

@ -20,8 +20,12 @@ defmodule Pleroma.Web.RichMedia.Parsers.OEmbed do
end
defp get_oembed_data(url) do
{:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url)
{:ok, %Tesla.Env{body: json}} = Pleroma.HTTP.get(url, [], adapter: [pool: :media])
{:ok, Poison.decode!(json)}
{:ok, data} = Jason.decode(json)
data = data |> Map.new(fn {k, v} -> {String.to_atom(k), v} end)
{:ok, data}
end
end

View file

@ -239,12 +239,6 @@ defmodule Pleroma.Web.Router do
put("/settings", MastodonAPIController, :put_settings)
end
scope "/api", Pleroma.Web.RichMedia do
pipe_through(:authenticated_api)
get("/rich_media/parse", RichMediaController, :parse)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:api)
get("/instance", MastodonAPIController, :masto_instance)
@ -258,7 +252,7 @@ defmodule Pleroma.Web.Router do
get("/statuses/:id", MastodonAPIController, :get_status)
get("/statuses/:id/context", MastodonAPIController, :get_context)
get("/statuses/:id/card", MastodonAPIController, :empty_object)
get("/statuses/:id/card", MastodonAPIController, :status_card)
get("/statuses/:id/favourited_by", MastodonAPIController, :favourited_by)
get("/statuses/:id/reblogged_by", MastodonAPIController, :reblogged_by)
@ -284,6 +278,7 @@ defmodule Pleroma.Web.Router do
post("/help/test", TwitterAPI.UtilController, :help_test)
get("/statusnet/config", TwitterAPI.UtilController, :config)
get("/statusnet/version", TwitterAPI.UtilController, :version)
get("/pleroma/frontend_configurations", TwitterAPI.UtilController, :frontend_configurations)
end
scope "/api", Pleroma.Web do
@ -523,10 +518,10 @@ defmodule Fallback.RedirectController do
alias Pleroma.Web.Metadata
alias Pleroma.User
def redirector(conn, _params) do
def redirector(conn, _params, code \\ 200) do
conn
|> put_resp_content_type("text/html")
|> send_file(200, index_file_path())
|> send_file(code, index_file_path())
end
def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id} = params) do

View file

@ -183,25 +183,31 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
invitesEnabled: if(Keyword.get(instance, :invites_enabled, false), do: "1", else: "0")
}
pleroma_fe = %{
theme: Keyword.get(instance_fe, :theme),
background: Keyword.get(instance_fe, :background),
logo: Keyword.get(instance_fe, :logo),
logoMask: Keyword.get(instance_fe, :logo_mask),
logoMargin: Keyword.get(instance_fe, :logo_margin),
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),
formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
collapseMessageWithSubject: Keyword.get(instance_fe, :collapse_message_with_subject),
hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
hideUserStats: Keyword.get(instance_fe, :hide_user_stats),
scopeCopy: Keyword.get(instance_fe, :scope_copy),
subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior),
alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input)
}
pleroma_fe =
if instance_fe do
%{
theme: Keyword.get(instance_fe, :theme),
background: Keyword.get(instance_fe, :background),
logo: Keyword.get(instance_fe, :logo),
logoMask: Keyword.get(instance_fe, :logo_mask),
logoMargin: Keyword.get(instance_fe, :logo_margin),
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),
formattingOptionsEnabled: Keyword.get(instance_fe, :formatting_options_enabled),
collapseMessageWithSubject:
Keyword.get(instance_fe, :collapse_message_with_subject),
hidePostStats: Keyword.get(instance_fe, :hide_post_stats),
hideUserStats: Keyword.get(instance_fe, :hide_user_stats),
scopeCopy: Keyword.get(instance_fe, :scope_copy),
subjectLineBehavior: Keyword.get(instance_fe, :subject_line_behavior),
alwaysShowSubjectInput: Keyword.get(instance_fe, :always_show_subject_input)
}
else
Pleroma.Config.get([:frontend_configurations, :pleroma_fe])
end
managed_config = Keyword.get(instance, :managed_config)
@ -216,6 +222,14 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
end
end
def frontend_configurations(conn, _params) do
config =
Pleroma.Config.get(:frontend_configurations, %{})
|> Enum.into(%{})
json(conn, config)
end
def version(conn, _params) do
version = Pleroma.Application.named_version()

View file

@ -12,6 +12,7 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Web.MastodonAPI.StatusView
defp user_by_ap_id(user_list, ap_id) do
Enum.find(user_list, fn %{ap_id: user_id} -> ap_id == user_id end)
@ -186,6 +187,12 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
summary = HTML.strip_tags(object["summary"])
card =
StatusView.render(
"card.json",
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
)
%{
"id" => activity.id,
"uri" => activity.data["object"]["id"],
@ -214,7 +221,8 @@ defmodule Pleroma.Web.TwitterAPI.Representers.ActivityRepresenter do
"possibly_sensitive" => possibly_sensitive,
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
"summary" => summary,
"summary_html" => summary |> Formatter.emojify(object["emoji"])
"summary_html" => summary |> Formatter.emojify(object["emoji"]),
"card" => card
}
end

View file

@ -10,6 +10,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Activity
alias Pleroma.HTML
alias Pleroma.Object
@ -114,7 +115,7 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
|> Map.put(:context_ids, context_ids)
|> Map.put(:users, users)
render_many(
safe_render_many(
opts.activities,
ActivityView,
"activity.json",
@ -274,6 +275,12 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
summary = HTML.strip_tags(summary)
card =
StatusView.render(
"card.json",
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
)
%{
"id" => activity.id,
"uri" => activity.data["object"]["id"],
@ -300,9 +307,10 @@ defmodule Pleroma.Web.TwitterAPI.ActivityView do
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
"visibility" => Pleroma.Web.MastodonAPI.StatusView.get_visibility(object),
"visibility" => StatusView.get_visibility(object),
"summary" => summary,
"summary_html" => summary |> Formatter.emojify(object["emoji"])
"summary_html" => summary |> Formatter.emojify(object["emoji"]),
"card" => card
}
end

View file

@ -38,6 +38,33 @@ defmodule Pleroma.Web do
import Phoenix.Controller, only: [get_csrf_token: 0, get_flash: 2, view_module: 1]
import Pleroma.Web.{ErrorHelpers, Gettext, Router.Helpers}
require Logger
@doc "Same as `render/3` but wrapped in a rescue block"
def safe_render(view, template, assigns \\ %{}) do
Phoenix.View.render(view, template, assigns)
rescue
error ->
Logger.error(
"#{__MODULE__} failed to render #{inspect({view, template})}: #{inspect(error)}"
)
Logger.error(inspect(__STACKTRACE__))
nil
end
@doc """
Same as `render_many/4` but wrapped in rescue block.
"""
def safe_render_many(collection, view, template, assigns \\ %{}) do
Enum.map(collection, fn resource ->
as = Map.get(assigns, :as) || view.__resource__
assigns = Map.put(assigns, as, resource)
safe_render(view, template, assigns)
end)
|> Enum.filter(& &1)
end
end
end