Browse Source

Merge branch 'so-long-twitterapi' into 'develop'

Removing TwitterAPI

See merge request pleroma/pleroma!1605
tags/v1.1.4
rinpatch 4 years ago
parent
commit
896ffabe37
20 changed files with 79 additions and 5148 deletions
  1. +1
    -0
      CHANGELOG.md
  2. +1
    -1
      README.md
  3. +0
    -100
      lib/pleroma/web/router.ex
  4. +0
    -38
      lib/pleroma/web/twitter_api/representers/base_representer.ex
  5. +0
    -39
      lib/pleroma/web/twitter_api/representers/object_representer.ex
  6. +0
    -195
      lib/pleroma/web/twitter_api/twitter_api.ex
  7. +20
    -746
      lib/pleroma/web/twitter_api/twitter_api_controller.ex
  8. +0
    -366
      lib/pleroma/web/twitter_api/views/activity_view.ex
  9. +0
    -71
      lib/pleroma/web/twitter_api/views/notification_view.ex
  10. +0
    -191
      lib/pleroma/web/twitter_api/views/user_view.ex
  11. +26
    -61
      test/notification_test.exs
  12. +10
    -12
      test/user_test.exs
  13. +2
    -6
      test/web/mastodon_api/mastodon_api_controller_test.exs
  14. +4
    -3
      test/web/mastodon_api/mastodon_api_test.exs
  15. +0
    -60
      test/web/twitter_api/representers/object_representer_test.exs
  16. +0
    -2150
      test/web/twitter_api/twitter_api_controller_test.exs
  17. +15
    -290
      test/web/twitter_api/twitter_api_test.exs
  18. +0
    -384
      test/web/twitter_api/views/activity_view_test.exs
  19. +0
    -112
      test/web/twitter_api/views/notification_view_test.exs
  20. +0
    -323
      test/web/twitter_api/views/user_view_test.exs

+ 1
- 0
CHANGELOG.md View File

@@ -110,6 +110,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
- RichMedia: add the rich media ttl based on image expiration time.

### Removed
- GNU Social API with Qvitter extensions support
- Emoji: Remove longfox emojis.
- Remove `Reply-To` header from report emails for admins.
- ActivityPub: The `accept_blocks` configuration setting.


+ 1
- 1
README.md View File

@@ -8,7 +8,7 @@ Pleroma is a microblogging server software that can federate (= exchange message

Pleroma is written in Elixir, high-performance and can run on small devices like a Raspberry Pi.

For clients it supports both the [GNU Social API with Qvitter extensions](https://twitter-api.readthedocs.io/en/latest/index.html) and the [Mastodon client API](https://docs.joinmastodon.org/api/guidelines/).
For clients it supports the [Mastodon client API](https://docs.joinmastodon.org/api/guidelines/) with Pleroma extensions (see "Pleroma's APIs and Mastodon API extensions" section on <https://docs-develop.pleroma.social>).

- [Client Applications for Pleroma](https://docs-develop.pleroma.social/clients.html)



+ 0
- 100
lib/pleroma/web/router.ex View File

@@ -477,53 +477,12 @@ defmodule Pleroma.Web.Router do
scope "/api", Pleroma.Web do
pipe_through(:api)

post("/account/register", TwitterAPI.Controller, :register)
post("/account/password_reset", TwitterAPI.Controller, :password_reset)

post("/account/resend_confirmation_email", TwitterAPI.Controller, :resend_confirmation_email)

get(
"/account/confirm_email/:user_id/:token",
TwitterAPI.Controller,
:confirm_email,
as: :confirm_email
)

scope [] do
pipe_through(:oauth_read_or_public)

get("/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/qvitter/statuses/user_timeline", TwitterAPI.Controller, :user_timeline)
get("/users/show", TwitterAPI.Controller, :show_user)

get("/statuses/followers", TwitterAPI.Controller, :followers)
get("/statuses/friends", TwitterAPI.Controller, :friends)
get("/statuses/blocks", TwitterAPI.Controller, :blocks)
get("/statuses/show/:id", TwitterAPI.Controller, :fetch_status)
get("/statusnet/conversation/:id", TwitterAPI.Controller, :fetch_conversation)

get("/search", TwitterAPI.Controller, :search)
get("/statusnet/tags/timeline/:tag", TwitterAPI.Controller, :public_and_external_timeline)
end
end

scope "/api", Pleroma.Web do
pipe_through([:api, :oauth_read_or_public])

get("/statuses/public_timeline", TwitterAPI.Controller, :public_timeline)

get(
"/statuses/public_and_external_timeline",
TwitterAPI.Controller,
:public_and_external_timeline
)

get("/statuses/networkpublic_timeline", TwitterAPI.Controller, :public_and_external_timeline)
end

scope "/api", Pleroma.Web, as: :twitter_api_search do
pipe_through([:api, :oauth_read_or_public])
get("/pleroma/search_user", TwitterAPI.Controller, :search_user)
end

scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
@@ -535,67 +494,8 @@ defmodule Pleroma.Web.Router do
scope [] do
pipe_through(:oauth_read)

get("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)
post("/account/verify_credentials", TwitterAPI.Controller, :verify_credentials)

get("/statuses/home_timeline", TwitterAPI.Controller, :friends_timeline)
get("/statuses/friends_timeline", TwitterAPI.Controller, :friends_timeline)
get("/statuses/mentions", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/mentions_timeline", TwitterAPI.Controller, :mentions_timeline)
get("/statuses/dm_timeline", TwitterAPI.Controller, :dm_timeline)
get("/qvitter/statuses/notifications", TwitterAPI.Controller, :notifications)

get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)

get("/friends/ids", TwitterAPI.Controller, :friends_ids)
get("/friendships/no_retweets/ids", TwitterAPI.Controller, :empty_array)

get("/mutes/users/ids", TwitterAPI.Controller, :empty_array)
get("/qvitter/mutes", TwitterAPI.Controller, :raw_empty_array)

get("/externalprofile/show", TwitterAPI.Controller, :external_profile)

post("/qvitter/statuses/notifications/read", TwitterAPI.Controller, :notifications_read)
end

scope [] do
pipe_through(:oauth_write)

post("/account/update_profile", TwitterAPI.Controller, :update_profile)
post("/account/update_profile_banner", TwitterAPI.Controller, :update_banner)
post("/qvitter/update_background_image", TwitterAPI.Controller, :update_background)

post("/statuses/update", TwitterAPI.Controller, :status_update)
post("/statuses/retweet/:id", TwitterAPI.Controller, :retweet)
post("/statuses/unretweet/:id", TwitterAPI.Controller, :unretweet)
post("/statuses/destroy/:id", TwitterAPI.Controller, :delete_post)

post("/statuses/pin/:id", TwitterAPI.Controller, :pin)
post("/statuses/unpin/:id", TwitterAPI.Controller, :unpin)

post("/statusnet/media/upload", TwitterAPI.Controller, :upload)
post("/media/upload", TwitterAPI.Controller, :upload_json)
post("/media/metadata/create", TwitterAPI.Controller, :update_media)

post("/favorites/create/:id", TwitterAPI.Controller, :favorite)
post("/favorites/create", TwitterAPI.Controller, :favorite)
post("/favorites/destroy/:id", TwitterAPI.Controller, :unfavorite)

post("/qvitter/update_avatar", TwitterAPI.Controller, :update_avatar)
end

scope [] do
pipe_through(:oauth_follow)

post("/pleroma/friendships/approve", TwitterAPI.Controller, :approve_friend_request)
post("/pleroma/friendships/deny", TwitterAPI.Controller, :deny_friend_request)

post("/friendships/create", TwitterAPI.Controller, :follow)
post("/friendships/destroy", TwitterAPI.Controller, :unfollow)

post("/blocks/create", TwitterAPI.Controller, :block)
post("/blocks/destroy", TwitterAPI.Controller, :unblock)
end
end

pipeline :ap_service_actor do


+ 0
- 38
lib/pleroma/web/twitter_api/representers/base_representer.ex View File

@@ -1,38 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.Representers.BaseRepresenter do
defmacro __using__(_opts) do
quote do
def to_json(object) do
to_json(object, %{})
end

def to_json(object, options) do
object
|> to_map(options)
|> Jason.encode!()
end

def enum_to_list(enum, options) do
mapping = fn el -> to_map(el, options) end
Enum.map(enum, mapping)
end

def to_map(object) do
to_map(object, %{})
end

def enum_to_json(enum) do
enum_to_json(enum, %{})
end

def enum_to_json(enum, options) do
enum
|> enum_to_list(options)
|> Jason.encode!()
end
end
end
end

+ 0
- 39
lib/pleroma/web/twitter_api/representers/object_representer.ex View File

@@ -1,39 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter do
use Pleroma.Web.TwitterAPI.Representers.BaseRepresenter
alias Pleroma.Object

def to_map(%Object{data: %{"url" => [url | _]}} = object, _opts) do
data = object.data

%{
url: url["href"] |> Pleroma.Web.MediaProxy.url(),
mimetype: url["mediaType"] || url["mimeType"],
id: data["uuid"],
oembed: false,
description: data["name"]
}
end

def to_map(%Object{data: %{"url" => url} = data}, _opts) when is_binary(url) do
%{
url: url |> Pleroma.Web.MediaProxy.url(),
mimetype: data["mediaType"] || data["mimeType"],
id: data["uuid"],
oembed: false,
description: data["name"]
}
end

def to_map(%Object{}, _opts) do
%{}
end

# If we only get the naked data, wrap in an object
def to_map(%{} = data, opts) do
to_map(%Object{data: data}, opts)
end
end

+ 0
- 195
lib/pleroma/web/twitter_api/twitter_api.ex View File

@@ -3,133 +3,14 @@
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
alias Pleroma.Activity
alias Pleroma.Emails.Mailer
alias Pleroma.Emails.UserEmail
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.UserView

import Ecto.Query

require Pleroma.Constants

def create_status(%User{} = user, %{"status" => _} = data) do
CommonAPI.post(user, data)
end

def delete(%User{} = user, id) do
with %Activity{data: %{"type" => _type}} <- Activity.get_by_id(id),
{:ok, activity} <- CommonAPI.delete(id, user) do
{:ok, activity}
end
end

def follow(%User{} = follower, params) do
with {:ok, %User{} = followed} <- get_user(params) do
CommonAPI.follow(follower, followed)
end
end

def unfollow(%User{} = follower, params) do
with {:ok, %User{} = unfollowed} <- get_user(params),
{:ok, follower} <- CommonAPI.unfollow(follower, unfollowed) do
{:ok, follower, unfollowed}
end
end

def block(%User{} = blocker, params) do
with {:ok, %User{} = blocked} <- get_user(params),
{:ok, blocker} <- User.block(blocker, blocked),
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
{:ok, blocker, blocked}
else
err -> err
end
end

def unblock(%User{} = blocker, params) do
with {:ok, %User{} = blocked} <- get_user(params),
{:ok, blocker} <- User.unblock(blocker, blocked),
{:ok, _activity} <- ActivityPub.unblock(blocker, blocked) do
{:ok, blocker, blocked}
else
err -> err
end
end

def repeat(%User{} = user, ap_id_or_id) do
with {:ok, _announce, %{data: %{"id" => id}}} <- CommonAPI.repeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
{:ok, activity}
end
end

def unrepeat(%User{} = user, ap_id_or_id) do
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
{:ok, activity}
end
end

def pin(%User{} = user, ap_id_or_id) do
CommonAPI.pin(ap_id_or_id, user)
end

def unpin(%User{} = user, ap_id_or_id) do
CommonAPI.unpin(ap_id_or_id, user)
end

def fav(%User{} = user, ap_id_or_id) do
with {:ok, _fav, %{data: %{"id" => id}}} <- CommonAPI.favorite(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
{:ok, activity}
end
end

def unfav(%User{} = user, ap_id_or_id) do
with {:ok, _unfav, _fav, %{data: %{"id" => id}}} <- CommonAPI.unfavorite(ap_id_or_id, user),
%Activity{} = activity <- Activity.get_create_by_object_ap_id(id) do
{:ok, activity}
end
end

def upload(%Plug.Upload{} = file, %User{} = user, format \\ "xml") do
{:ok, object} = ActivityPub.upload(file, actor: User.ap_id(user))

url = List.first(object.data["url"])
href = url["href"]
type = url["mediaType"]

case format do
"xml" ->
# Fake this as good as possible...
"""
<?xml version="1.0" encoding="UTF-8"?>
<rsp stat="ok" xmlns:atom="http://www.w3.org/2005/Atom">
<mediaid>#{object.id}</mediaid>
<media_id>#{object.id}</media_id>
<media_id_string>#{object.id}</media_id_string>
<media_url>#{href}</media_url>
<mediaurl>#{href}</mediaurl>
<atom:link rel="enclosure" href="#{href}" type="#{type}"></atom:link>
</rsp>
"""

"json" ->
%{
media_id: object.id,
media_id_string: "#{object.id}}",
media_url: href,
size: 0
}
|> Jason.encode!()
end
end

def register_user(params, opts \\ []) do
token = params["token"]

@@ -236,80 +117,4 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPI do
{:error, "unknown user"}
end
end

def get_user(user \\ nil, params) do
case params do
%{"user_id" => user_id} ->
case User.get_cached_by_nickname_or_id(user_id) do
nil ->
{:error, "No user with such user_id"}

%User{info: %{deactivated: true}} ->
{:error, "User has been disabled"}

user ->
{:ok, user}
end

%{"screen_name" => nickname} ->
case User.get_cached_by_nickname(nickname) do
nil -> {:error, "No user with such screen_name"}
target -> {:ok, target}
end

_ ->
if user do
{:ok, user}
else
{:error, "You need to specify screen_name or user_id"}
end
end
end

defp parse_int(string, default)

defp parse_int(string, default) when is_binary(string) do
with {n, _} <- Integer.parse(string) do
n
else
_e -> default
end
end

defp parse_int(_, default), do: default

# TODO: unify the search query with MastoAPI one and do only pagination here
def search(_user, %{"q" => query} = params) do
limit = parse_int(params["rpp"], 20)
page = parse_int(params["page"], 1)
offset = (page - 1) * limit

q =
from(
[a, o] in Activity.with_preloaded_object(Activity),
where: fragment("?->>'type' = 'Create'", a.data),
where: ^Pleroma.Constants.as_public() in a.recipients,
where:
fragment(
"to_tsvector('english', ?->>'content') @@ plainto_tsquery('english', ?)",
o.data,
^query
),
limit: ^limit,
offset: ^offset,
# this one isn't indexed so psql won't take the wrong index.
order_by: [desc: :inserted_at]
)

_activities = Repo.all(q)
end

def get_external_profile(for_user, uri) do
with {:ok, %User{} = user} <- User.get_or_fetch(uri) do
{:ok, UserView.render("show.json", %{user: user, for: for_user})}
else
_e ->
{:error, "Couldn't find user"}
end
end
end

+ 20
- 746
lib/pleroma/web/twitter_api/twitter_api_controller.ex View File

@@ -5,448 +5,16 @@
defmodule Pleroma.Web.TwitterAPI.Controller do
use Pleroma.Web, :controller

import Pleroma.Web.ControllerHelper, only: [json_response: 3]

alias Ecto.Changeset
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TokenView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView

require Logger

plug(Pleroma.Plugs.RateLimiter, :password_reset when action == :password_reset)
plug(:only_if_public_instance when action in [:public_timeline, :public_and_external_timeline])
action_fallback(:errors)

def verify_credentials(%{assigns: %{user: user}} = conn, _params) do
token = Phoenix.Token.sign(conn, "user socket", user.id)

conn
|> put_view(UserView)
|> render("show.json", %{user: user, token: token, for: user})
end

def status_update(%{assigns: %{user: user}} = conn, %{"status" => _} = status_data) do
with media_ids <- extract_media_ids(status_data),
{:ok, activity} <-
TwitterAPI.create_status(user, Map.put(status_data, "media_ids", media_ids)) do
conn
|> json(ActivityView.render("activity.json", activity: activity, for: user))
else
_ -> empty_status_reply(conn)
end
end

def status_update(conn, _status_data) do
empty_status_reply(conn)
end

defp empty_status_reply(conn) do
bad_request_reply(conn, "Client must provide a 'status' parameter with a value.")
end

defp extract_media_ids(status_data) do
with media_ids when not is_nil(media_ids) <- status_data["media_ids"],
split_ids <- String.split(media_ids, ","),
clean_ids <- Enum.reject(split_ids, fn id -> String.length(id) == 0 end) do
clean_ids
else
_e -> []
end
end

def public_and_external_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("blocking_user", user)

activities = ActivityPub.fetch_public_activities(params)

conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end

def public_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce"])
|> Map.put("local_only", true)
|> Map.put("blocking_user", user)

activities = ActivityPub.fetch_public_activities(params)

conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end

def friends_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|> Map.put("blocking_user", user)
|> Map.put("user", user)

activities = ActivityPub.fetch_activities([user.ap_id | user.following], params)

conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end

def show_user(conn, params) do
for_user = conn.assigns.user

with {:ok, shown} <- TwitterAPI.get_user(params),
true <-
User.auth_active?(shown) ||
(for_user && (for_user.id == shown.id || User.superuser?(for_user))) do
params =
if for_user do
%{user: shown, for: for_user}
else
%{user: shown}
end

conn
|> put_view(UserView)
|> render("show.json", params)
else
{:error, msg} ->
bad_request_reply(conn, msg)

false ->
conn
|> put_status(404)
|> json(%{error: "Unconfirmed user"})
end
end

def user_timeline(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.get_user(user, params) do
{:ok, target_user} ->
# Twitter and ActivityPub use a different name and sense for this parameter.
{include_rts, params} = Map.pop(params, "include_rts")

params =
case include_rts do
x when x == "false" or x == "0" -> Map.put(params, "exclude_reblogs", "true")
_ -> params
end

activities = ActivityPub.fetch_user_activities(target_user, user, params)

conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})

{:error, msg} ->
bad_request_reply(conn, msg)
end
end

def mentions_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", ["Create", "Announce", "Follow", "Like"])
|> Map.put("blocking_user", user)
|> Map.put(:visibility, ~w[unlisted public private])

activities = ActivityPub.fetch_activities([user.ap_id], params)

conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end

def dm_timeline(%{assigns: %{user: user}} = conn, params) do
params =
params
|> Map.put("type", "Create")
|> Map.put("blocking_user", user)
|> Map.put("user", user)
|> Map.put(:visibility, "direct")
|> Map.put(:order, :desc)

activities =
ActivityPub.fetch_activities_query([user.ap_id], params)
|> Repo.all()

conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end

def notifications(%{assigns: %{user: user}} = conn, params) do
params =
if Map.has_key?(params, "with_muted") do
Map.put(params, :with_muted, params["with_muted"] in [true, "True", "true", "1"])
else
params
end

notifications = Notification.for_user(user, params)

conn
|> put_view(NotificationView)
|> render("notification.json", %{notifications: notifications, for: user})
end

def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
Notification.set_read_up_to(user, latest_id)

notifications = Notification.for_user(user, params)

conn
|> put_view(NotificationView)
|> render("notification.json", %{notifications: notifications, for: user})
end

def notifications_read(%{assigns: %{user: _user}} = conn, _) do
bad_request_reply(conn, "You need to specify latest_id")
end

def follow(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.follow(user, params) do
{:ok, user, followed, _activity} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: followed, for: user})

{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end

def block(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.block(user, params) do
{:ok, user, blocked} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: blocked, for: user})

{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end

def unblock(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.unblock(user, params) do
{:ok, user, blocked} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: blocked, for: user})

{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end

def delete_post(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.delete(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
end
end

def unfollow(%{assigns: %{user: user}} = conn, params) do
case TwitterAPI.unfollow(user, params) do
{:ok, user, unfollowed} ->
conn
|> put_view(UserView)
|> render("show.json", %{user: unfollowed, for: user})

{:error, msg} ->
forbidden_json_reply(conn, msg)
end
end

def fetch_status(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with %Activity{} = activity <- Activity.get_by_id(id),
true <- Visibility.visible_for_user?(activity, user) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
end
end

def fetch_conversation(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with context when is_binary(context) <- Utils.conversation_id_to_context(id),
activities <-
ActivityPub.fetch_activities_for_context(context, %{
"blocking_user" => user,
"user" => user
}) do
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
end
end

@doc """
Updates metadata of uploaded media object.
Derived from [Twitter API endpoint](https://developer.twitter.com/en/docs/media/upload-media/api-reference/post-media-metadata-create).
"""
def update_media(%{assigns: %{user: user}} = conn, %{"media_id" => id} = data) do
object = Repo.get(Object, id)
description = get_in(data, ["alt_text", "text"]) || data["name"] || data["description"]

{conn, status, response_body} =
cond do
!object ->
{halt(conn), :not_found, ""}

!Object.authorize_mutation(object, user) ->
{halt(conn), :forbidden, "You can only update your own uploads."}

!is_binary(description) ->
{conn, :not_modified, ""}

true ->
new_data = Map.put(object.data, "name", description)

{:ok, _} =
object
|> Object.change(%{data: new_data})
|> Repo.update()

{conn, :no_content, ""}
end

conn
|> put_status(status)
|> json(response_body)
end

def upload(%{assigns: %{user: user}} = conn, %{"media" => media}) do
response = TwitterAPI.upload(media, user)

conn
|> put_resp_content_type("application/atom+xml")
|> send_resp(200, response)
end

def upload_json(%{assigns: %{user: user}} = conn, %{"media" => media}) do
response = TwitterAPI.upload(media, user, "json")

conn
|> json_reply(200, response)
end

def get_by_id_or_ap_id(id) do
activity = Activity.get_by_id(id) || Activity.get_create_by_object_ap_id(id)

if activity.data["type"] == "Create" do
activity
else
Activity.get_create_by_object_ap_id(activity.data["object"])
end
end

def favorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.fav(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end

def unfavorite(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.unfav(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end

def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.repeat(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end

def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.unrepeat(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
_ -> json_reply(conn, 400, Jason.encode!(%{}))
end
end

def pin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.pin(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
{:error, message} -> bad_request_reply(conn, message)
err -> err
end
end

def unpin(%{assigns: %{user: user}} = conn, %{"id" => id}) do
with {:ok, activity} <- TwitterAPI.unpin(user, id) do
conn
|> put_view(ActivityView)
|> render("activity.json", %{activity: activity, for: user})
else
{:error, message} -> bad_request_reply(conn, message)
err -> err
end
end

def register(conn, params) do
with {:ok, user} <- TwitterAPI.register_user(params) do
conn
|> put_view(UserView)
|> render("show.json", %{user: user})
else
{:error, errors} ->
conn
|> json_reply(400, Jason.encode!(errors))
end
end

def password_reset(conn, params) do
nickname_or_email = params["email"] || params["nickname"]

with {:ok, _} <- TwitterAPI.password_reset(nickname_or_email) do
json_response(conn, :no_content, "")
else
{:error, "unknown user"} ->
send_resp(conn, :not_found, "")

{:error, _} ->
send_resp(conn, :bad_request, "")
end
end

def confirm_email(conn, %{"user_id" => uid, "token" => token}) do
with %User{} = user <- User.get_cached_by_id(uid),
true <- user.local,
@@ -460,147 +28,6 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
end
end

def resend_confirmation_email(conn, params) do
nickname_or_email = params["email"] || params["nickname"]

with %User{} = user <- User.get_by_nickname_or_email(nickname_or_email),
{:ok, _} <- User.try_send_confirmation_email(user) do
conn
|> json_response(:no_content, "")
end
end

def update_avatar(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
change = Changeset.change(user, %{avatar: nil})
{:ok, user} = User.update_and_set_cache(change)
CommonAPI.update(user)

conn
|> put_view(UserView)
|> render("show.json", %{user: user, for: user})
end

def update_avatar(%{assigns: %{user: user}} = conn, params) do
{:ok, object} = ActivityPub.upload(params, type: :avatar)
change = Changeset.change(user, %{avatar: object.data})
{:ok, user} = User.update_and_set_cache(change)
CommonAPI.update(user)

conn
|> put_view(UserView)
|> render("show.json", %{user: user, for: user})
end

def update_banner(%{assigns: %{user: user}} = conn, %{"banner" => ""}) do
with new_info <- %{"banner" => %{}},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
response = %{url: nil} |> Jason.encode!()

conn
|> json_reply(200, response)
end
end

def update_banner(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(%{"img" => params["banner"]}, type: :banner),
new_info <- %{"banner" => object.data},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)
%{"url" => [%{"href" => href} | _]} = object.data
response = %{url: href} |> Jason.encode!()

conn
|> json_reply(200, response)
end
end

def update_background(%{assigns: %{user: user}} = conn, %{"img" => ""}) do
with new_info <- %{"background" => %{}},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, _user} <- User.update_and_set_cache(changeset) do
response = %{url: nil} |> Jason.encode!()

conn
|> json_reply(200, response)
end
end

def update_background(%{assigns: %{user: user}} = conn, params) do
with {:ok, object} <- ActivityPub.upload(params, type: :background),
new_info <- %{"background" => object.data},
info_cng <- User.Info.profile_update(user.info, new_info),
changeset <- Ecto.Changeset.change(user) |> Ecto.Changeset.put_embed(:info, info_cng),
{:ok, _user} <- User.update_and_set_cache(changeset) do
%{"url" => [%{"href" => href} | _]} = object.data
response = %{url: href} |> Jason.encode!()

conn
|> json_reply(200, response)
end
end

def external_profile(%{assigns: %{user: current_user}} = conn, %{"profileurl" => uri}) do
with {:ok, user_map} <- TwitterAPI.get_external_profile(current_user, uri),
response <- Jason.encode!(user_map) do
conn
|> json_reply(200, response)
else
_e ->
conn
|> put_status(404)
|> json(%{error: "Can't find user"})
end
end

def followers(%{assigns: %{user: for_user}} = conn, params) do
{:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)

with {:ok, user} <- TwitterAPI.get_user(for_user, params),
{:ok, followers} <- User.get_followers(user, page) do
followers =
cond do
for_user && user.id == for_user.id -> followers
user.info.hide_followers -> []
true -> followers
end

conn
|> put_view(UserView)
|> render("index.json", %{users: followers, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get followers")
end
end

def friends(%{assigns: %{user: for_user}} = conn, params) do
{:ok, page} = Ecto.Type.cast(:integer, params["page"] || 1)
{:ok, export} = Ecto.Type.cast(:boolean, params["all"] || false)

page = if export, do: nil, else: page

with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
{:ok, friends} <- User.get_friends(user, page) do
friends =
cond do
for_user && user.id == for_user.id -> friends
user.info.hide_follows -> []
true -> friends
end

conn
|> put_view(UserView)
|> render("index.json", %{users: friends, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get friends")
end
end

def oauth_tokens(%{assigns: %{user: user}} = conn, _params) do
with oauth_tokens <- Token.get_user_tokens(user) do
conn
@@ -615,160 +42,16 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
json_reply(conn, 201, "")
end

def blocks(%{assigns: %{user: user}} = conn, _params) do
with blocked_users <- User.blocked_users(user) do
conn
|> put_view(UserView)
|> render("index.json", %{users: blocked_users, for: user})
end
end

def friend_requests(conn, params) do
with {:ok, user} <- TwitterAPI.get_user(conn.assigns[:user], params),
{:ok, friend_requests} <- User.get_follow_requests(user) do
conn
|> put_view(UserView)
|> render("index.json", %{users: friend_requests, for: conn.assigns[:user]})
else
_e -> bad_request_reply(conn, "Can't get friend requests")
end
end

def approve_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user],
%User{} = follower <- User.get_cached_by_id(uid),
{:ok, follower} <- CommonAPI.accept_follow_request(follower, followed) do
conn
|> put_view(UserView)
|> render("show.json", %{user: follower, for: followed})
else
e -> bad_request_reply(conn, "Can't approve user: #{inspect(e)}")
end
end

def deny_friend_request(conn, %{"user_id" => uid} = _params) do
with followed <- conn.assigns[:user],
%User{} = follower <- User.get_cached_by_id(uid),
{:ok, follower} <- CommonAPI.reject_follow_request(follower, followed) do
conn
|> put_view(UserView)
|> render("show.json", %{user: follower, for: followed})
else
e -> bad_request_reply(conn, "Can't deny user: #{inspect(e)}")
end
end

def friends_ids(%{assigns: %{user: user}} = conn, _params) do
with {:ok, friends} <- User.get_friends(user) do
ids =
friends
|> Enum.map(fn x -> x.id end)
|> Jason.encode!()

json(conn, ids)
else
_e -> bad_request_reply(conn, "Can't get friends")
end
end

def empty_array(conn, _params) do
json(conn, Jason.encode!([]))
end

def raw_empty_array(conn, _params) do
json(conn, [])
end

defp build_info_cng(user, params) do
info_params =
[
"no_rich_text",
"locked",
"hide_followers",
"hide_follows",
"hide_favorites",
"show_role",
"skip_thread_containment"
]
|> Enum.reduce(%{}, fn key, res ->
if value = params[key] do
Map.put(res, key, value == "true")
else
res
end
end)

info_params =
if value = params["default_scope"] do
Map.put(info_params, "default_scope", value)
else
info_params
end

User.Info.profile_update(user.info, info_params)
end

defp parse_profile_bio(user, params) do
if bio = params["description"] do
emojis_text = (params["description"] || "") <> " " <> (params["name"] || "")

emojis =
((user.info.emoji || []) ++ Formatter.get_emoji_map(emojis_text))
|> Enum.dedup()

user_info =
user.info
|> Map.put(
"emoji",
emojis
)

params
|> Map.put("bio", User.parse_bio(bio, user))
|> Map.put("info", user_info)
else
params
end
end

def update_profile(%{assigns: %{user: user}} = conn, params) do
params = parse_profile_bio(user, params)
info_cng = build_info_cng(user, params)

with changeset <- User.update_changeset(user, params),
changeset <- Ecto.Changeset.put_embed(changeset, :info, info_cng),
{:ok, user} <- User.update_and_set_cache(changeset) do
CommonAPI.update(user)

conn
|> put_view(UserView)
|> render("user.json", %{user: user, for: user})
else
error ->
Logger.debug("Can't update user: #{inspect(error)}")
bad_request_reply(conn, "Can't update user")
end
end

def search(%{assigns: %{user: user}} = conn, %{"q" => _query} = params) do
activities = TwitterAPI.search(user, params)

def errors(conn, {:param_cast, _}) do
conn
|> put_view(ActivityView)
|> render("index.json", %{activities: activities, for: user})
|> put_status(400)
|> json("Invalid parameters")
end

def search_user(%{assigns: %{user: user}} = conn, %{"query" => query}) do
users = User.search(query, resolve: true, for_user: user)

def errors(conn, _) do
conn
|> put_view(UserView)
|> render("index.json", %{users: users, for: user})
end

defp bad_request_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 400, json)
|> put_status(500)
|> json("Something went wrong")
end

defp json_reply(conn, status, json) do
@@ -777,36 +60,27 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|> send_resp(status, json)
end

defp forbidden_json_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 403, json)
end
def notifications_read(%{assigns: %{user: user}} = conn, %{"latest_id" => latest_id} = params) do
Notification.set_read_up_to(user, latest_id)

def only_if_public_instance(%{assigns: %{user: %User{}}} = conn, _), do: conn
notifications = Notification.for_user(user, params)

def only_if_public_instance(conn, _) do
if Pleroma.Config.get([:instance, :public]) do
conn
else
conn
|> forbidden_json_reply("Invalid credentials.")
|> halt()
end
conn
# XXX: This is a hack because pleroma-fe still uses that API.
|> put_view(Pleroma.Web.MastodonAPI.NotificationView)
|> render("index.json", %{notifications: notifications, for: user})
end

defp error_json(conn, error_message) do
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
def notifications_read(%{assigns: %{user: _user}} = conn, _) do
bad_request_reply(conn, "You need to specify latest_id")
end

def errors(conn, {:param_cast, _}) do
conn
|> put_status(400)
|> json("Invalid parameters")
defp bad_request_reply(conn, error_message) do
json = error_json(conn, error_message)
json_reply(conn, 400, json)
end

def errors(conn, _) do
conn
|> put_status(500)
|> json("Something went wrong")
defp error_json(conn, error_message) do
%{"error" => error_message, "request" => conn.request_path} |> Jason.encode!()
end
end

+ 0
- 366
lib/pleroma/web/twitter_api/views/activity_view.ex View File

@@ -1,366 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.ActivityView do
use Pleroma.Web, :view
alias Pleroma.Activity
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MastodonAPI.StatusView
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter
alias Pleroma.Web.TwitterAPI.UserView

import Ecto.Query
require Logger
require Pleroma.Constants

defp query_context_ids([]), do: []

defp query_context_ids(contexts) do
query = from(o in Object, where: fragment("(?)->>'id' = ANY(?)", o.data, ^contexts))

Repo.all(query)
end

defp query_users([]), do: []

defp query_users(user_ids) do
query = from(user in User, where: user.ap_id in ^user_ids)

Repo.all(query)
end

defp collect_context_ids(activities) do
_contexts =
activities
|> Enum.reject(& &1.data["context_id"])
|> Enum.map(fn %{data: data} ->
data["context"]
end)
|> Enum.filter(& &1)
|> query_context_ids()
|> Enum.reduce(%{}, fn %{data: %{"id" => ap_id}, id: id}, acc ->
Map.put(acc, ap_id, id)
end)
end

defp collect_users(activities) do
activities
|> Enum.map(fn activity ->
case activity.data do
data = %{"type" => "Follow"} ->
[data["actor"], data["object"]]

data ->
[data["actor"]]
end ++ activity.recipients
end)
|> List.flatten()
|> Enum.uniq()
|> query_users()
|> Enum.reduce(%{}, fn user, acc ->
Map.put(acc, user.ap_id, user)
end)
end

defp get_context_id(%{data: %{"context_id" => context_id}}, _) when not is_nil(context_id),
do: context_id

defp get_context_id(%{data: %{"context" => nil}}, _), do: nil

defp get_context_id(%{data: %{"context" => context}}, options) do
cond do
id = options[:context_ids][context] -> id
true -> Utils.context_to_conversation_id(context)
end
end

defp get_context_id(_, _), do: nil

defp get_user(ap_id, opts) do
cond do
user = opts[:users][ap_id] ->
user

String.ends_with?(ap_id, "/followers") ->
nil

ap_id == Pleroma.Constants.as_public() ->
nil

user = User.get_cached_by_ap_id(ap_id) ->
user

user = User.get_by_guessed_nickname(ap_id) ->
user

true ->
User.error_user(ap_id)
end
end

def render("index.json", opts) do
context_ids = collect_context_ids(opts.activities)
users = collect_users(opts.activities)

opts =
opts
|> Map.put(:context_ids, context_ids)
|> Map.put(:users, users)

safe_render_many(
opts.activities,
ActivityView,
"activity.json",
opts
)
end

def render("activity.json", %{activity: %{data: %{"type" => "Delete"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
created_at = activity.data["published"] |> Utils.date_to_asctime()

%{
"id" => activity.id,
"uri" => activity.data["object"],
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => "deleted notice {{tag",
"text" => "deleted notice {{tag",
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "delete"
}
end

def render("activity.json", %{activity: %{data: %{"type" => "Follow"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
created_at = activity.data["published"] || DateTime.to_iso8601(activity.inserted_at)
created_at = created_at |> Utils.date_to_asctime()

followed = get_user(activity.data["object"], opts)
text = "#{user.nickname} started following #{followed.nickname}"

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"attentions" => [],
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"created_at" => created_at,
"in_reply_to_status_id" => nil,
"external_url" => activity.data["id"],
"activity_type" => "follow"
}
end

def render("activity.json", %{activity: %{data: %{"type" => "Announce"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
created_at = activity.data["published"] |> Utils.date_to_asctime()
announced_activity = Activity.get_create_by_object_ap_id(activity.data["object"])

text = "#{user.nickname} repeated a status."

retweeted_status = render("activity.json", Map.merge(opts, %{activity: announced_activity}))

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=note",
"created_at" => created_at,
"retweeted_status" => retweeted_status,
"statusnet_conversation_id" => get_context_id(announced_activity, opts),
"external_url" => activity.data["id"],
"activity_type" => "repeat"
}
end

def render("activity.json", %{activity: %{data: %{"type" => "Like"}} = activity} = opts) do
user = get_user(activity.data["actor"], opts)
liked_activity = Activity.get_create_by_object_ap_id(activity.data["object"])
liked_activity_id = if liked_activity, do: liked_activity.id, else: nil

created_at =
activity.data["published"]
|> Utils.date_to_asctime()

text = "#{user.nickname} favorited a status."

favorited_status =
if liked_activity,
do: render("activity.json", Map.merge(opts, %{activity: liked_activity})),
else: nil

%{
"id" => activity.id,
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => text,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => false,
"uri" => "tag:#{activity.data["id"]}:objectType=Favourite",
"created_at" => created_at,
"favorited_status" => favorited_status,
"in_reply_to_status_id" => liked_activity_id,
"external_url" => activity.data["id"],
"activity_type" => "like"
}
end

def render(
"activity.json",
%{activity: %{data: %{"type" => "Create", "object" => object_id}} = activity} = opts
) do
user = get_user(activity.data["actor"], opts)

object = Object.normalize(object_id)

created_at = object.data["published"] |> Utils.date_to_asctime()
like_count = object.data["like_count"] || 0
announcement_count = object.data["announcement_count"] || 0
favorited = opts[:for] && opts[:for].ap_id in (object.data["likes"] || [])
repeated = opts[:for] && opts[:for].ap_id in (object.data["announcements"] || [])
pinned = activity.id in user.info.pinned_activities

attentions =
[]
|> Utils.maybe_notify_to_recipients(activity)
|> Utils.maybe_notify_mentioned_recipients(activity)
|> Enum.map(fn ap_id -> get_user(ap_id, opts) end)
|> Enum.filter(& &1)
|> Enum.map(fn user -> UserView.render("show.json", %{user: user, for: opts[:for]}) end)

conversation_id = get_context_id(activity, opts)

tags = object.data["tag"] || []
possibly_sensitive = object.data["sensitive"] || Enum.member?(tags, "nsfw")

tags = if possibly_sensitive, do: Enum.uniq(["nsfw" | tags]), else: tags

{summary, content} = render_content(object.data)

html =
content
|> HTML.get_cached_scrubbed_html_for_activity(
User.html_filter_policy(opts[:for]),
activity,
"twitterapi:content"
)
|> Formatter.emojify(object.data["emoji"])

text =
if content do
content
|> String.replace(~r/<br\s?\/?>/, "\n")
|> HTML.get_cached_stripped_html_for_activity(activity, "twitterapi:content")
else
""
end

reply_parent = Activity.get_in_reply_to_activity(activity)

reply_user = reply_parent && User.get_cached_by_ap_id(reply_parent.actor)

summary = HTML.strip_tags(summary)

card =
StatusView.render(
"card.json",
Pleroma.Web.RichMedia.Helpers.fetch_data_for_activity(activity)
)

thread_muted? =
case activity.thread_muted? do
thread_muted? when is_boolean(thread_muted?) -> thread_muted?
nil -> CommonAPI.thread_muted?(user, activity)
end

%{
"id" => activity.id,
"uri" => object.data["id"],
"user" => UserView.render("show.json", %{user: user, for: opts[:for]}),
"statusnet_html" => html,
"text" => text,
"is_local" => activity.local,
"is_post_verb" => true,
"created_at" => created_at,
"in_reply_to_status_id" => reply_parent && reply_parent.id,
"in_reply_to_screen_name" => reply_user && reply_user.nickname,
"in_reply_to_profileurl" => User.profile_url(reply_user),
"in_reply_to_ostatus_uri" => reply_user && reply_user.ap_id,
"in_reply_to_user_id" => reply_user && reply_user.id,
"statusnet_conversation_id" => conversation_id,
"attachments" => (object.data["attachment"] || []) |> ObjectRepresenter.enum_to_list(opts),
"attentions" => attentions,
"fave_num" => like_count,
"repeat_num" => announcement_count,
"favorited" => !!favorited,
"repeated" => !!repeated,
"pinned" => pinned,
"external_url" => object.data["external_url"] || object.data["id"],
"tags" => tags,
"activity_type" => "post",
"possibly_sensitive" => possibly_sensitive,
"visibility" => Pleroma.Web.ActivityPub.Visibility.get_visibility(object),
"summary" => summary,
"summary_html" => summary |> Formatter.emojify(object.data["emoji"]),
"card" => card,
"muted" => thread_muted? || User.mutes?(opts[:for], user)
}
end

def render("activity.json", %{activity: unhandled_activity}) do
Logger.warn("#{__MODULE__} unhandled activity: #{inspect(unhandled_activity)}")
nil
end

def render_content(%{"type" => "Note"} = object) do
summary = object["summary"]

content =
if !!summary and summary != "" do
"<p>#{summary}</p>#{object["content"]}"
else
object["content"]
end

{summary, content}
end

def render_content(%{"type" => object_type} = object)
when object_type in ["Article", "Page", "Video"] do
summary = object["name"] || object["summary"]

content =
if !!summary and summary != "" and is_bitstring(object["url"]) do
"<p><a href=\"#{object["url"]}\">#{summary}</a></p>#{object["content"]}"
else
object["content"]
end

{summary, content}
end

def render_content(object) do
summary = object["summary"] || "Unhandled activity type: #{object["type"]}"
content = "<p>#{summary}</p>#{object["content"]}"

{summary, content}
end
end

+ 0
- 71
lib/pleroma/web/twitter_api/views/notification_view.ex View File

@@ -1,71 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.NotificationView do
use Pleroma.Web, :view
alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.UserView

require Pleroma.Constants

defp get_user(ap_id, opts) do
cond do
user = opts[:users][ap_id] ->
user

String.ends_with?(ap_id, "/followers") ->
nil

ap_id == Pleroma.Constants.as_public() ->
nil

true ->
User.get_cached_by_ap_id(ap_id)
end
end

def render("notification.json", %{notifications: notifications, for: user}) do
render_many(
notifications,
Pleroma.Web.TwitterAPI.NotificationView,
"notification.json",
for: user
)
end

def render(
"notification.json",
%{
notification: %Notification{
id: id,
seen: seen,
activity: activity,
inserted_at: created_at
},
for: user
} = opts
) do
ntype =
case activity.data["type"] do
"Create" -> "mention"
"Like" -> "like"
"Announce" -> "repeat"
"Follow" -> "follow"
end

from = get_user(activity.data["actor"], opts)

%{
"id" => id,
"ntype" => ntype,
"notice" => ActivityView.render("activity.json", %{activity: activity, for: user}),
"from_profile" => UserView.render("show.json", %{user: from, for: user}),
"is_seen" => if(seen, do: 1, else: 0),
"created_at" => created_at |> Utils.format_naive_asctime()
}
end
end

+ 0
- 191
lib/pleroma/web/twitter_api/views/user_view.ex View File

@@ -1,191 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.UserView do
use Pleroma.Web, :view
alias Pleroma.Formatter
alias Pleroma.HTML
alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.MediaProxy

def render("show.json", %{user: user = %User{}} = assigns) do
render_one(user, Pleroma.Web.TwitterAPI.UserView, "user.json", assigns)
end

def render("index.json", %{users: users, for: user}) do
users
|> render_many(Pleroma.Web.TwitterAPI.UserView, "user.json", for: user)
|> Enum.filter(&Enum.any?/1)
end

def render("user.json", %{user: user = %User{}} = assigns) do
if User.visible_for?(user, assigns[:for]),
do: do_render("user.json", assigns),
else: %{}
end

def render("short.json", %{
user: %User{
nickname: nickname,
id: id,
ap_id: ap_id,
name: name
}
}) do
%{
"fullname" => name,
"id" => id,
"ostatus_uri" => ap_id,
"profile_url" => ap_id,
"screen_name" => nickname
}
end

defp do_render("user.json", %{user: user = %User{}} = assigns) do
for_user = assigns[:for]
image = User.avatar_url(user) |> MediaProxy.url()

{following, follows_you, statusnet_blocking} =
if for_user do
{
User.following?(for_user, user),
User.following?(user, for_user),
User.blocks?(for_user, user)
}
else
{false, false, false}
end

user_info = User.get_cached_user_info(user)

emoji =
(user.info.source_data["tag"] || [])
|> Enum.filter(fn %{"type" => t} -> t == "Emoji" end)
|> Enum.map(fn %{"icon" => %{"url" => url}, "name" => name} ->
{String.trim(name, ":"), url}
end)

emoji = Enum.dedup(emoji ++ user.info.emoji)

description_html =
(user.bio || "")
|> HTML.filter_tags(User.html_filter_policy(for_user))
|> Formatter.emojify(emoji)

fields =
user.info
|> User.Info.fields()
|> Enum.map(fn %{"name" => name, "value" => value} ->
%{
"name" => Pleroma.HTML.strip_tags(name),
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
}
end)

data =
%{
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"description" => HTML.strip_tags((user.bio || "") |> String.replace("<br>", "\n")),
"description_html" => description_html,
"favourites_count" => 0,
"followers_count" => user_info[:follower_count],
"following" => following,
"follows_you" => follows_you,
"statusnet_blocking" => statusnet_blocking,
"friends_count" => user_info[:following_count],
"id" => user.id,
"name" => user.name || user.nickname,
"name_html" =>
if(user.name,
do: HTML.strip_tags(user.name) |> Formatter.emojify(emoji),
else: user.nickname
),
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"screen_name" => user.nickname,
"statuses_count" => user_info[:note_count],
"statusnet_profile_url" => user.ap_id,
"cover_photo" => User.banner_url(user) |> MediaProxy.url(),
"background_image" => image_url(user.info.background) |> MediaProxy.url(),
"is_local" => user.local,
"locked" => user.info.locked,
"hide_followers" => user.info.hide_followers,
"hide_follows" => user.info.hide_follows,
"fields" => fields,

# Pleroma extension
"pleroma" =>
%{
"confirmation_pending" => user_info.confirmation_pending,
"tags" => user.tags,
"skip_thread_containment" => user.info.skip_thread_containment
}
|> maybe_with_activation_status(user, for_user)
|> with_notification_settings(user, for_user)
}
|> maybe_with_user_settings(user, for_user)
|> maybe_with_role(user, for_user)

if assigns[:token] do
Map.put(data, "token", token_string(assigns[:token]))
else
data
end
end

defp with_notification_settings(data, %User{id: user_id} = user, %User{id: user_id}) do
Map.put(data, "notification_settings", user.info.notification_settings)
end

defp with_notification_settings(data, _, _), do: data

defp maybe_with_activation_status(data, user, %User{info: %{is_admin: true}}) do
Map.put(data, "deactivated", user.info.deactivated)
end

defp maybe_with_activation_status(data, _, _), do: data

defp maybe_with_role(data, %User{id: id} = user, %User{id: id}) do
Map.merge(data, %{
"role" => role(user),
"show_role" => user.info.show_role,
"rights" => %{
"delete_others_notice" => !!user.info.is_moderator,
"admin" => !!user.info.is_admin
}
})
end

defp maybe_with_role(data, %User{info: %{show_role: true}} = user, _user) do
Map.merge(data, %{
"role" => role(user),
"rights" => %{
"delete_others_notice" => !!user.info.is_moderator,
"admin" => !!user.info.is_admin
}
})
end

defp maybe_with_role(data, _, _), do: data

defp maybe_with_user_settings(data, %User{info: info, id: id} = _user, %User{id: id}) do
data
|> Kernel.put_in(["default_scope"], info.default_scope)
|> Kernel.put_in(["no_rich_text"], info.no_rich_text)
end

defp maybe_with_user_settings(data, _, _), do: data
defp role(%User{info: %{:is_admin => true}}), do: "admin"
defp role(%User{info: %{:is_moderator => true}}), do: "moderator"
defp role(_), do: "member"

defp image_url(%{"url" => [%{"href" => href} | _]}), do: href
defp image_url(_), do: nil

defp token_string(%Pleroma.Web.OAuth.Token{token: token_str}), do: token_str
defp token_string(token), do: token
end

+ 26
- 61
test/notification_test.exs View File

@@ -12,7 +12,6 @@ defmodule Pleroma.NotificationTest do
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.Streamer
alias Pleroma.Web.TwitterAPI.TwitterAPI

describe "create_notifications" do
test "notifies someone when they are directly addressed" do
@@ -21,7 +20,7 @@ defmodule Pleroma.NotificationTest do
third_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname}"
})

@@ -39,7 +38,7 @@ defmodule Pleroma.NotificationTest do

User.subscribe(subscriber, user)

{:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
{:ok, [notification]} = Notification.create_notifications(status)

assert notification.user_id == subscriber.id
@@ -184,47 +183,20 @@ defmodule Pleroma.NotificationTest do
test "it doesn't create a notification for follow-unfollow-follow chains" do
user = insert(:user)
followed_user = insert(:user)
{:ok, _, _, activity} = TwitterAPI.follow(user, %{"user_id" => followed_user.id})
{:ok, _, _, activity} = CommonAPI.follow(user, followed_user)
Notification.create_notification(activity, followed_user)
TwitterAPI.unfollow(user, %{"user_id" => followed_user.id})
{:ok, _, _, activity_dupe} = TwitterAPI.follow(user, %{"user_id" => followed_user.id})
CommonAPI.unfollow(user, followed_user)
{:ok, _, _, activity_dupe} = CommonAPI.follow(user, followed_user)
refute Notification.create_notification(activity_dupe, followed_user)
end

test "it doesn't create a notification for like-unlike-like chains" do
user = insert(:user)
liked_user = insert(:user)
{:ok, status} = TwitterAPI.create_status(liked_user, %{"status" => "Yui is best yuru"})
{:ok, fav_status} = TwitterAPI.fav(user, status.id)
Notification.create_notification(fav_status, liked_user)
TwitterAPI.unfav(user, status.id)
{:ok, dupe} = TwitterAPI.fav(user, status.id)
refute Notification.create_notification(dupe, liked_user)
end

test "it doesn't create a notification for repeat-unrepeat-repeat chains" do
user = insert(:user)
retweeted_user = insert(:user)

{:ok, status} =
TwitterAPI.create_status(retweeted_user, %{
"status" => "Send dupe notifications to the shadow realm"
})

{:ok, retweeted_activity} = TwitterAPI.repeat(user, status.id)
Notification.create_notification(retweeted_activity, retweeted_user)
TwitterAPI.unrepeat(user, status.id)
{:ok, dupe} = TwitterAPI.repeat(user, status.id)
refute Notification.create_notification(dupe, retweeted_user)
end

test "it doesn't create duplicate notifications for follow+subscribed users" do
user = insert(:user)
subscriber = insert(:user)

{:ok, _, _, _} = TwitterAPI.follow(subscriber, %{"user_id" => user.id})
{:ok, _, _, _} = CommonAPI.follow(subscriber, user)
User.subscribe(subscriber, user)
{:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})
{:ok, [_notif]} = Notification.create_notifications(status)
end

@@ -234,8 +206,7 @@ defmodule Pleroma.NotificationTest do

User.subscribe(subscriber, user)

{:ok, status} =
TwitterAPI.create_status(user, %{"status" => "inwisible", "visibility" => "direct"})
{:ok, status} = CommonAPI.post(user, %{"status" => "inwisible", "visibility" => "direct"})

assert {:ok, []} == Notification.create_notifications(status)
end
@@ -246,8 +217,7 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
other_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"})

{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.get(other_user, notification.id)
@@ -259,8 +229,7 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
other_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"})

{:ok, [notification]} = Notification.create_notifications(activity)
{:error, _notification} = Notification.get(user, notification.id)
@@ -272,8 +241,7 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
other_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"})

{:ok, [notification]} = Notification.create_notifications(activity)
{:ok, notification} = Notification.dismiss(other_user, notification.id)
@@ -285,8 +253,7 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
other_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(user, %{"status" => "hey @#{other_user.nickname}"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "hey @#{other_user.nickname}"})

{:ok, [notification]} = Notification.create_notifications(activity)
{:error, _notification} = Notification.dismiss(user, notification.id)
@@ -300,14 +267,14 @@ defmodule Pleroma.NotificationTest do
third_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname} and @#{third_user.nickname} !"
})

{:ok, _notifs} = Notification.create_notifications(activity)

{:ok, activity} =
TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey again @#{other_user.nickname} and @#{third_user.nickname} !"
})

@@ -325,12 +292,12 @@ defmodule Pleroma.NotificationTest do
other_user = insert(:user)

{:ok, _activity} =
TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey @#{other_user.nickname}!"
})

{:ok, _activity} =
TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey again @#{other_user.nickname}!"
})

@@ -340,7 +307,7 @@ defmodule Pleroma.NotificationTest do
assert n2.id > n1.id

{:ok, _activity} =
TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey yet again @#{other_user.nickname}!"
})

@@ -677,7 +644,7 @@ defmodule Pleroma.NotificationTest do
muted = insert(:user)
{:ok, user} = User.mute(user, muted, false)

{:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})

assert length(Notification.for_user(user)) == 1
end
@@ -687,7 +654,7 @@ defmodule Pleroma.NotificationTest do
muted = insert(:user)
{:ok, user} = User.mute(user, muted)

{:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})

assert Notification.for_user(user) == []
end
@@ -697,7 +664,7 @@ defmodule Pleroma.NotificationTest do
blocked = insert(:user)
{:ok, user} = User.block(user, blocked)

{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})

assert Notification.for_user(user) == []
end
@@ -707,7 +674,7 @@ defmodule Pleroma.NotificationTest do
blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com")

{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})

assert Notification.for_user(user) == []
end
@@ -716,8 +683,7 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
another_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "hey @#{user.nickname}"})

{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
assert Notification.for_user(user) == []
@@ -728,7 +694,7 @@ defmodule Pleroma.NotificationTest do
muted = insert(:user)
{:ok, user} = User.mute(user, muted)

{:ok, _activity} = TwitterAPI.create_status(muted, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(muted, %{"status" => "hey @#{user.nickname}"})

assert length(Notification.for_user(user, %{with_muted: true})) == 1
end
@@ -738,7 +704,7 @@ defmodule Pleroma.NotificationTest do
blocked = insert(:user)
{:ok, user} = User.block(user, blocked)

{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})

assert length(Notification.for_user(user, %{with_muted: true})) == 1
end
@@ -748,7 +714,7 @@ defmodule Pleroma.NotificationTest do
blocked = insert(:user, ap_id: "http://some-domain.com")
{:ok, user} = User.block_domain(user, "some-domain.com")

{:ok, _activity} = TwitterAPI.create_status(blocked, %{"status" => "hey @#{user.nickname}"})
{:ok, _activity} = CommonAPI.post(blocked, %{"status" => "hey @#{user.nickname}"})

assert length(Notification.for_user(user, %{with_muted: true})) == 1
end
@@ -757,8 +723,7 @@ defmodule Pleroma.NotificationTest do
user = insert(:user)
another_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(another_user, %{"status" => "hey @#{user.nickname}"})
{:ok, activity} = CommonAPI.post(another_user, %{"status" => "hey @#{user.nickname}"})

{:ok, _} = Pleroma.ThreadMute.add_mute(user.id, activity.data["context"])
assert length(Notification.for_user(user, %{with_muted: true})) == 1


+ 10
- 12
test/user_test.exs View File

@@ -69,8 +69,8 @@ defmodule Pleroma.UserTest do
locked = insert(:user, %{info: %{locked: true}})
follower = insert(:user)

Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => unlocked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(follower, %{"user_id" => locked.id})
CommonAPI.follow(follower, unlocked)
CommonAPI.follow(follower, locked)

assert {:ok, []} = User.get_follow_requests(unlocked)
assert {:ok, [activity]} = User.get_follow_requests(locked)
@@ -83,9 +83,9 @@ defmodule Pleroma.UserTest do
pending_follower = insert(:user)
accepted_follower = insert(:user)

Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(pending_follower, %{"user_id" => locked.id})
Pleroma.Web.TwitterAPI.TwitterAPI.follow(accepted_follower, %{"user_id" => locked.id})
CommonAPI.follow(pending_follower, locked)
CommonAPI.follow(pending_follower, locked)
CommonAPI.follow(accepted_follower, locked)
User.follow(accepted_follower, locked)

assert {:ok, [activity]} = User.get_follow_requests(locked)
@@ -1279,11 +1279,9 @@ defmodule Pleroma.UserTest do
{:ok, _follower2} = User.follow(follower2, user)
{:ok, _follower3} = User.follow(follower3, user)

{:ok, _} = User.block(user, follower)
{:ok, user} = User.block(user, follower)

user_show = Pleroma.Web.TwitterAPI.UserView.render("show.json", %{user: user})

assert Map.get(user_show, "followers_count") == 2
assert User.user_info(user).follower_count == 2
end

describe "list_inactive_users_query/1" do
@@ -1327,7 +1325,7 @@ defmodule Pleroma.UserTest do
to = Enum.random(users -- [user])

{:ok, _} =
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(user, %{
CommonAPI.post(user, %{
"status" => "hey @#{to.nickname}"
})
end)
@@ -1359,12 +1357,12 @@ defmodule Pleroma.UserTest do

Enum.each(recipients, fn to ->
{:ok, _} =
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
CommonAPI.post(sender, %{
"status" => "hey @#{to.nickname}"
})

{:ok, _} =
Pleroma.Web.TwitterAPI.TwitterAPI.create_status(sender, %{
CommonAPI.post(sender, %{
"status" => "hey again @#{to.nickname}"
})
end)


+ 2
- 6
test/web/mastodon_api/mastodon_api_controller_test.exs View File

@@ -21,7 +21,6 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
alias Pleroma.Web.OAuth.Token
alias Pleroma.Web.OStatus
alias Pleroma.Web.Push
alias Pleroma.Web.TwitterAPI.TwitterAPI
import Pleroma.Factory
import ExUnit.CaptureLog
import Tesla.Mock
@@ -1484,12 +1483,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
filename: "an_image.jpg"
}

media =
TwitterAPI.upload(file, user, "json")
|> Jason.decode!()
{:ok, %{id: media_id}} = ActivityPub.upload(file, actor: user.ap_id)

{:ok, image_post} =
CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media["media_id"]]})
{:ok, image_post} = CommonAPI.post(user, %{"status" => "cofe", "media_ids" => [media_id]})

conn =
conn


+ 4
- 3
test/web/mastodon_api/mastodon_api_test.exs View File

@@ -8,8 +8,8 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do
alias Pleroma.Notification
alias Pleroma.ScheduledActivity
alias Pleroma.User
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.MastodonAPI
alias Pleroma.Web.TwitterAPI.TwitterAPI

import Pleroma.Factory

@@ -75,8 +75,9 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPITest do

User.subscribe(subscriber, user)

{:ok, status} = TwitterAPI.create_status(user, %{"status" => "Akariiiin"})
{:ok, status1} = TwitterAPI.create_status(user, %{"status" => "Magi"})
{:ok, status} = CommonAPI.post(user, %{"status" => "Akariiiin"})

{:ok, status1} = CommonAPI.post(user, %{"status" => "Magi"})
{:ok, [notification]} = Notification.create_notifications(status)
{:ok, [notification1]} = Notification.create_notifications(status1)
res = MastodonAPI.get_notifications(subscriber)


+ 0
- 60
test/web/twitter_api/representers/object_representer_test.exs View File

@@ -1,60 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.Representers.ObjectReprenterTest do
use Pleroma.DataCase

alias Pleroma.Object
alias Pleroma.Web.TwitterAPI.Representers.ObjectRepresenter

test "represent an image attachment" do
object = %Object{
id: 5,
data: %{
"type" => "Image",
"url" => [
%{
"mediaType" => "sometype",
"href" => "someurl"
}
],
"uuid" => 6
}
}

expected_object = %{
id: 6,
url: "someurl",
mimetype: "sometype",
oembed: false,
description: nil
}

assert expected_object == ObjectRepresenter.to_map(object)
end

test "represents mastodon-style attachments" do
object = %Object{
id: nil,
data: %{
"mediaType" => "image/png",
"name" => "blabla",
"type" => "Document",
"url" =>
"http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png"
}
}

expected_object = %{
url:
"http://mastodon.example.org/system/media_attachments/files/000/000/001/original/8619f31c6edec470.png",
mimetype: "image/png",
oembed: false,
id: nil,
description: "blabla"
}

assert expected_object == ObjectRepresenter.to_map(object)
end
end

+ 0
- 2150
test/web/twitter_api/twitter_api_controller_test.exs
File diff suppressed because it is too large
View File


+ 15
- 290
test/web/twitter_api/twitter_api_test.exs View File

@@ -4,270 +4,17 @@

defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
use Pleroma.DataCase
alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.UserInviteToken
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.MastodonAPI.AccountView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView

import Pleroma.Factory

setup_all do
Tesla.Mock.mock_global(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end

test "create a status" do
user = insert(:user)
mentioned_user = insert(:user, %{nickname: "shp", ap_id: "shp"})

object_data = %{
"type" => "Image",
"url" => [
%{
"type" => "Link",
"mediaType" => "image/jpg",
"href" => "http://example.org/image.jpg"
}
],
"uuid" => 1
}

object = Repo.insert!(%Object{data: object_data})

input = %{
"status" =>
"Hello again, @shp.<script></script>\nThis is on another :firefox: line. #2hu #epic #phantasmagoric",
"media_ids" => [object.id]
}

{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
object = Object.normalize(activity)

expected_text =
"Hello again, <span class='h-card'><a data-user='#{mentioned_user.id}' class='u-url mention' href='shp'>@<span>shp</span></a></span>.&lt;script&gt;&lt;/script&gt;<br>This is on another :firefox: line. <a class='hashtag' data-tag='2hu' href='http://localhost:4001/tag/2hu' rel='tag'>#2hu</a> <a class='hashtag' data-tag='epic' href='http://localhost:4001/tag/epic' rel='tag'>#epic</a> <a class='hashtag' data-tag='phantasmagoric' href='http://localhost:4001/tag/phantasmagoric' rel='tag'>#phantasmagoric</a><br><a href=\"http://example.org/image.jpg\" class='attachment'>image.jpg</a>"

assert get_in(object.data, ["content"]) == expected_text
assert get_in(object.data, ["type"]) == "Note"
assert get_in(object.data, ["actor"]) == user.ap_id
assert get_in(activity.data, ["actor"]) == user.ap_id
assert Enum.member?(get_in(activity.data, ["cc"]), User.ap_followers(user))

assert Enum.member?(
get_in(activity.data, ["to"]),
"https://www.w3.org/ns/activitystreams#Public"
)

assert Enum.member?(get_in(activity.data, ["to"]), "shp")
assert activity.local == true

assert %{"firefox" => "http://localhost:4001/emoji/Firefox.gif"} = object.data["emoji"]

# hashtags
assert object.data["tag"] == ["2hu", "epic", "phantasmagoric"]

# Add a context
assert is_binary(get_in(activity.data, ["context"]))
assert is_binary(get_in(object.data, ["context"]))

assert is_list(object.data["attachment"])

assert activity.data["object"] == object.data["id"]

user = User.get_cached_by_ap_id(user.ap_id)

assert user.info.note_count == 1
end

test "create a status that is a reply" do
user = insert(:user)

input = %{
"status" => "Hello again."
}

{:ok, activity = %Activity{}} = TwitterAPI.create_status(user, input)
object = Object.normalize(activity)

input = %{
"status" => "Here's your (you).",
"in_reply_to_status_id" => activity.id
}

{:ok, reply = %Activity{}} = TwitterAPI.create_status(user, input)
reply_object = Object.normalize(reply)

assert get_in(reply.data, ["context"]) == get_in(activity.data, ["context"])

assert get_in(reply_object.data, ["context"]) == get_in(object.data, ["context"])

assert get_in(reply_object.data, ["inReplyTo"]) == get_in(activity.data, ["object"])
assert Activity.get_in_reply_to_activity(reply).id == activity.id
end

test "Follow another user using user_id" do
user = insert(:user)
followed = insert(:user)

{:ok, user, followed, _activity} = TwitterAPI.follow(user, %{"user_id" => followed.id})
assert User.ap_followers(followed) in user.following

{:ok, _, _, _} = TwitterAPI.follow(user, %{"user_id" => followed.id})
end

test "Follow another user using screen_name" do
user = insert(:user)
followed = insert(:user)

{:ok, user, followed, _activity} =
TwitterAPI.follow(user, %{"screen_name" => followed.nickname})

assert User.ap_followers(followed) in user.following

followed = User.get_cached_by_ap_id(followed.ap_id)
assert followed.info.follower_count == 1

{:ok, _, _, _} = TwitterAPI.follow(user, %{"screen_name" => followed.nickname})
end

test "Unfollow another user using user_id" do
unfollowed = insert(:user)
user = insert(:user, %{following: [User.ap_followers(unfollowed)]})
ActivityPub.follow(user, unfollowed)

{:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id})
assert user.following == []

{:error, msg} = TwitterAPI.unfollow(user, %{"user_id" => unfollowed.id})
assert msg == "Not subscribed!"
end

test "Unfollow another user using screen_name" do
unfollowed = insert(:user)
user = insert(:user, %{following: [User.ap_followers(unfollowed)]})

ActivityPub.follow(user, unfollowed)

{:ok, user, unfollowed} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname})
assert user.following == []

{:error, msg} = TwitterAPI.unfollow(user, %{"screen_name" => unfollowed.nickname})
assert msg == "Not subscribed!"
end

test "Block another user using user_id" do
user = insert(:user)
blocked = insert(:user)

{:ok, user, blocked} = TwitterAPI.block(user, %{"user_id" => blocked.id})
assert User.blocks?(user, blocked)
end

test "Block another user using screen_name" do
user = insert(:user)
blocked = insert(:user)

{:ok, user, blocked} = TwitterAPI.block(user, %{"screen_name" => blocked.nickname})
assert User.blocks?(user, blocked)
end

test "Unblock another user using user_id" do
unblocked = insert(:user)
user = insert(:user)
{:ok, user, _unblocked} = TwitterAPI.block(user, %{"user_id" => unblocked.id})

{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"user_id" => unblocked.id})
assert user.info.blocks == []
end

test "Unblock another user using screen_name" do
unblocked = insert(:user)
user = insert(:user)
{:ok, user, _unblocked} = TwitterAPI.block(user, %{"screen_name" => unblocked.nickname})

{:ok, user, _unblocked} = TwitterAPI.unblock(user, %{"screen_name" => unblocked.nickname})
assert user.info.blocks == []
end

test "upload a file" do
user = insert(:user)

file = %Plug.Upload{
content_type: "image/jpg",
path: Path.absname("test/fixtures/image.jpg"),
filename: "an_image.jpg"
}

response = TwitterAPI.upload(file, user)

assert is_binary(response)
end

test "it favorites a status, returns the updated activity" do
user = insert(:user)
other_user = insert(:user)
note_activity = insert(:note_activity)

{:ok, status} = TwitterAPI.fav(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 1

object = Object.normalize(note_activity)

assert object.data["like_count"] == 1

assert status == updated_activity

{:ok, _status} = TwitterAPI.fav(other_user, note_activity.id)

object = Object.normalize(note_activity)

assert object.data["like_count"] == 2

updated_activity = Activity.get_by_ap_id(note_activity.data["id"])
assert ActivityView.render("activity.json", %{activity: updated_activity})["fave_num"] == 2
end

test "it unfavorites a status, returns the updated activity" do
user = insert(:user)
note_activity = insert(:note_activity)
object = Object.normalize(note_activity)

{:ok, _like_activity, _object} = ActivityPub.like(user, object)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])

assert ActivityView.render("activity.json", activity: updated_activity)["fave_num"] == 1

{:ok, activity} = TwitterAPI.unfav(user, note_activity.id)

assert ActivityView.render("activity.json", activity: activity)["fave_num"] == 0
end

test "it retweets a status and returns the retweet" do
user = insert(:user)
note_activity = insert(:note_activity)

{:ok, status} = TwitterAPI.repeat(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])

assert status == updated_activity
end

test "it unretweets an already retweeted status" do
user = insert(:user)
note_activity = insert(:note_activity)

{:ok, _status} = TwitterAPI.repeat(user, note_activity.id)
{:ok, status} = TwitterAPI.unrepeat(user, note_activity.id)
updated_activity = Activity.get_by_ap_id(note_activity.data["id"])

assert status == updated_activity
end

test "it registers a new user and returns the user." do
data = %{
"nickname" => "lain",
@@ -281,8 +28,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do

fetched_user = User.get_cached_by_nickname("lain")

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})
end

test "it registers a new user with empty string in bio and returns the user." do
@@ -299,8 +46,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do

fetched_user = User.get_cached_by_nickname("lain")

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})
end

test "it sends confirmation email if :account_activation_required is specified in instance config" do
@@ -397,8 +144,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do

assert invite.used == true

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})
end

test "returns error on invalid token" do
@@ -462,8 +209,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
{:ok, user} = TwitterAPI.register_user(data)
fetched_user = User.get_cached_by_nickname("vinny")

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})
end

{:ok, data: data, check_fn: check_fn}
@@ -537,8 +284,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do

assert invite.used == true

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})

data = %{
"nickname" => "GrimReaper",
@@ -588,8 +335,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do

refute invite.used

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})
end

test "error after max uses" do
@@ -612,8 +359,8 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
invite = Repo.get_by(UserInviteToken, token: invite.token)
assert invite.used == true

assert UserView.render("show.json", %{user: user}) ==
UserView.render("show.json", %{user: fetched_user})
assert AccountView.render("account.json", %{user: user}) ==
AccountView.render("account.json", %{user: fetched_user})

data = %{
"nickname" => "GrimReaper",
@@ -689,31 +436,9 @@ defmodule Pleroma.Web.TwitterAPI.TwitterAPITest do
refute User.get_cached_by_nickname("lain")
end

test "it assigns an integer conversation_id" do
note_activity = insert(:note_activity)
status = ActivityView.render("activity.json", activity: note_activity)

assert is_number(status["statusnet_conversation_id"])
end

setup do
Supervisor.terminate_child(Pleroma.Supervisor, Cachex)
Supervisor.restart_child(Pleroma.Supervisor, Cachex)
:ok
end

describe "fetching a user by uri" do
test "fetches a user by uri" do
id = "https://mastodon.social/users/lambadalambda"
user = insert(:user)
{:ok, represented} = TwitterAPI.get_external_profile(user, id)
remote = User.get_cached_by_ap_id(id)

assert represented["id"] == UserView.render("show.json", %{user: remote, for: user})["id"]

# Also fetches the feed.
# assert Activity.get_create_by_object_ap_id("tag:mastodon.social,2017-04-05:objectId=1641750:objectType=Status")
# credo:disable-for-previous-line Credo.Check.Readability.MaxLineLength
end
end
end

+ 0
- 384
test/web/twitter_api/views/activity_view_test.exs View File

@@ -1,384 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2019 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.ActivityViewTest do
use Pleroma.DataCase

alias Pleroma.Activity
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.UserView

import Pleroma.Factory
import Tesla.Mock

setup do
mock(fn env -> apply(HttpRequestMock, :request, [env]) end)
:ok
end

import Mock

test "returns a temporary ap_id based user for activities missing db users" do
user = insert(:user)

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})

Repo.delete(user)
Cachex.clear(:user_cache)

%{"user" => tw_user} = ActivityView.render("activity.json", activity: activity)

assert tw_user["screen_name"] == "erroruser@example.com"
assert tw_user["name"] == user.ap_id
assert tw_user["statusnet_profile_url"] == user.ap_id
end

test "tries to get a user by nickname if fetching by ap_id doesn't work" do
user = insert(:user)

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})

{:ok, user} =
user
|> Ecto.Changeset.change(%{ap_id: "#{user.ap_id}/extension/#{user.nickname}"})
|> Repo.update()

Cachex.clear(:user_cache)

result = ActivityView.render("activity.json", activity: activity)
assert result["user"]["id"] == user.id
end

test "tells if the message is muted for some reason" do
user = insert(:user)
other_user = insert(:user)

{:ok, user} = User.mute(user, other_user)

{:ok, activity} = CommonAPI.post(other_user, %{"status" => "test"})
status = ActivityView.render("activity.json", %{activity: activity})

assert status["muted"] == false

status = ActivityView.render("activity.json", %{activity: activity, for: user})

assert status["muted"] == true
end

test "a create activity with a html status" do
text = """
#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg
"""

{:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text})

result = ActivityView.render("activity.json", activity: activity)

assert result["statusnet_html"] ==
"<a class=\"hashtag\" data-tag=\"bike\" href=\"http://localhost:4001/tag/bike\" rel=\"tag\">#Bike</a> log - Commute Tuesday<br /><a href=\"https://pla.bike/posts/20181211/\">https://pla.bike/posts/20181211/</a><br /><a class=\"hashtag\" data-tag=\"cycling\" href=\"http://localhost:4001/tag/cycling\" rel=\"tag\">#cycling</a> <a class=\"hashtag\" data-tag=\"chscycling\" href=\"http://localhost:4001/tag/chscycling\" rel=\"tag\">#CHScycling</a> <a class=\"hashtag\" data-tag=\"commute\" href=\"http://localhost:4001/tag/commute\" rel=\"tag\">#commute</a><br />MVIMG_20181211_054020.jpg"

assert result["text"] ==
"#Bike log - Commute Tuesday\nhttps://pla.bike/posts/20181211/\n#cycling #CHScycling #commute\nMVIMG_20181211_054020.jpg"
end

test "a create activity with a summary containing emoji" do
{:ok, activity} =
CommonAPI.post(insert(:user), %{
"spoiler_text" => ":firefox: meow",
"status" => "."
})

result = ActivityView.render("activity.json", activity: activity)

expected = ":firefox: meow"

expected_html =
"<img class=\"emoji\" alt=\"firefox\" title=\"firefox\" src=\"http://localhost:4001/emoji/Firefox.gif\" /> meow"

assert result["summary"] == expected
assert result["summary_html"] == expected_html
end

test "a create activity with a summary containing invalid HTML" do
{:ok, activity} =
CommonAPI.post(insert(:user), %{
"spoiler_text" => "<span style=\"color: magenta; font-size: 32px;\">meow</span>",
"status" => "."
})

result = ActivityView.render("activity.json", activity: activity)

expected = "meow"

assert result["summary"] == expected
assert result["summary_html"] == expected
end

test "a create activity with a note" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!", "visibility" => "direct"})
object = Object.normalize(activity)

result = ActivityView.render("activity.json", activity: activity)

convo_id = Utils.context_to_conversation_id(object.data["context"])

expected = %{
"activity_type" => "post",
"attachments" => [],
"attentions" => [
UserView.render("show.json", %{user: other_user})
],
"created_at" => object.data["published"] |> Utils.date_to_asctime(),
"external_url" => object.data["id"],
"fave_num" => 0,
"favorited" => false,
"id" => activity.id,
"in_reply_to_status_id" => nil,
"in_reply_to_screen_name" => nil,
"in_reply_to_user_id" => nil,
"in_reply_to_profileurl" => nil,
"in_reply_to_ostatus_uri" => nil,
"is_local" => true,
"is_post_verb" => true,
"possibly_sensitive" => false,
"repeat_num" => 0,
"repeated" => false,
"pinned" => false,
"statusnet_conversation_id" => convo_id,
"summary" => "",
"summary_html" => "",
"statusnet_html" =>
"Hey <span class=\"h-card\"><a data-user=\"#{other_user.id}\" class=\"u-url mention\" href=\"#{
other_user.ap_id
}\">@<span>shp</span></a></span>!",
"tags" => [],
"text" => "Hey @shp!",
"uri" => object.data["id"],
"user" => UserView.render("show.json", %{user: user}),
"visibility" => "direct",
"card" => nil,
"muted" => false
}

assert result == expected
end

test "a list of activities" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})
{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
object = Object.normalize(activity)

convo_id = Utils.context_to_conversation_id(object.data["context"])

mocks = [
{
Utils,
[:passthrough],
[context_to_conversation_id: fn _ -> false end]
},
{
User,
[:passthrough],
[get_cached_by_ap_id: fn _ -> nil end]
}
]

with_mocks mocks do
[result] = ActivityView.render("index.json", activities: [activity])

assert result["statusnet_conversation_id"] == convo_id
assert result["user"]
refute called(Utils.context_to_conversation_id(:_))
refute called(User.get_cached_by_ap_id(user.ap_id))
refute called(User.get_cached_by_ap_id(other_user.ap_id))
end
end

test "an activity that is a reply" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})

{:ok, answer} =
CommonAPI.post(other_user, %{"status" => "Hi!", "in_reply_to_status_id" => activity.id})

result = ActivityView.render("activity.json", %{activity: answer})

assert result["in_reply_to_status_id"] == activity.id
end

test "a like activity" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
{:ok, like, _object} = CommonAPI.favorite(activity.id, other_user)

result = ActivityView.render("activity.json", activity: like)
activity = Pleroma.Activity.get_by_ap_id(activity.data["id"])

expected = %{
"activity_type" => "like",
"created_at" => like.data["published"] |> Utils.date_to_asctime(),
"external_url" => like.data["id"],
"id" => like.id,
"in_reply_to_status_id" => activity.id,
"is_local" => true,
"is_post_verb" => false,
"favorited_status" => ActivityView.render("activity.json", activity: activity),
"statusnet_html" => "shp favorited a status.",
"text" => "shp favorited a status.",
"uri" => "tag:#{like.data["id"]}:objectType=Favourite",
"user" => UserView.render("show.json", user: other_user)
}

assert result == expected
end

test "a like activity for deleted post" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
{:ok, like, _object} = CommonAPI.favorite(activity.id, other_user)
CommonAPI.delete(activity.id, user)

result = ActivityView.render("activity.json", activity: like)

expected = %{
"activity_type" => "like",
"created_at" => like.data["published"] |> Utils.date_to_asctime(),
"external_url" => like.data["id"],
"id" => like.id,
"in_reply_to_status_id" => nil,
"is_local" => true,
"is_post_verb" => false,
"favorited_status" => nil,
"statusnet_html" => "shp favorited a status.",
"text" => "shp favorited a status.",
"uri" => "tag:#{like.data["id"]}:objectType=Favourite",
"user" => UserView.render("show.json", user: other_user)
}

assert result == expected
end

test "an announce activity" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
{:ok, announce, object} = CommonAPI.repeat(activity.id, other_user)

convo_id = Utils.context_to_conversation_id(object.data["context"])

activity = Activity.get_by_id(activity.id)

result = ActivityView.render("activity.json", activity: announce)

expected = %{
"activity_type" => "repeat",
"created_at" => announce.data["published"] |> Utils.date_to_asctime(),
"external_url" => announce.data["id"],
"id" => announce.id,
"is_local" => true,
"is_post_verb" => false,
"statusnet_html" => "shp repeated a status.",
"text" => "shp repeated a status.",
"uri" => "tag:#{announce.data["id"]}:objectType=note",
"user" => UserView.render("show.json", user: other_user),
"retweeted_status" => ActivityView.render("activity.json", activity: activity),
"statusnet_conversation_id" => convo_id
}

assert result == expected
end

test "A follow activity" do
user = insert(:user)
other_user = insert(:user, %{nickname: "shp"})

{:ok, follower} = User.follow(user, other_user)
{:ok, follow} = ActivityPub.follow(follower, other_user)

result = ActivityView.render("activity.json", activity: follow)

expected = %{
"activity_type" => "follow",
"attentions" => [],
"created_at" => follow.data["published"] |> Utils.date_to_asctime(),
"external_url" => follow.data["id"],
"id" => follow.id,
"in_reply_to_status_id" => nil,
"is_local" => true,
"is_post_verb" => false,
"statusnet_html" => "#{user.nickname} started following shp",
"text" => "#{user.nickname} started following shp",
"user" => UserView.render("show.json", user: user)
}

assert result == expected
end

test "a delete activity" do
user = insert(:user)

{:ok, activity} = CommonAPI.post(user, %{"status" => "Hey @shp!"})
{:ok, delete} = CommonAPI.delete(activity.id, user)

result = ActivityView.render("activity.json", activity: delete)

expected = %{
"activity_type" => "delete",
"attentions" => [],
"created_at" => delete.data["published"] |> Utils.date_to_asctime(),
"external_url" => delete.data["id"],
"id" => delete.id,
"in_reply_to_status_id" => nil,
"is_local" => true,
"is_post_verb" => false,
"statusnet_html" => "deleted notice {{tag",
"text" => "deleted notice {{tag",
"uri" => Object.normalize(delete).data["id"],
"user" => UserView.render("show.json", user: user)
}

assert result == expected
end

test "a peertube video" do
{:ok, object} =
Pleroma.Object.Fetcher.fetch_object_from_id(
"https://peertube.moe/videos/watch/df5f464b-be8d-46fb-ad81-2d4c2d1630e3"
)

%Activity{} = activity = Activity.get_create_by_object_ap_id(object.data["id"])

result = ActivityView.render("activity.json", activity: activity)

assert length(result["attachments"]) == 1
assert result["summary"] == "Friday Night"
end

test "special characters are not escaped in text field for status created" do
text = "<3 is on the way"

{:ok, activity} = CommonAPI.post(insert(:user), %{"status" => text})

result = ActivityView.render("activity.json", activity: activity)

assert result["text"] == text
end
end

+ 0
- 112
test/web/twitter_api/views/notification_view_test.exs View File

@@ -1,112 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.NotificationViewTest do
use Pleroma.DataCase

alias Pleroma.Notification
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.TwitterAPI.ActivityView
alias Pleroma.Web.TwitterAPI.NotificationView
alias Pleroma.Web.TwitterAPI.TwitterAPI
alias Pleroma.Web.TwitterAPI.UserView

import Pleroma.Factory

setup do
user = insert(:user, bio: "<span>Here's some html</span>")
[user: user]
end

test "A follow notification" do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
follower = insert(:user)

{:ok, follower} = User.follow(follower, user)
{:ok, activity} = ActivityPub.follow(follower, user)
Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id)))
[follow_notif] = Notification.for_user(user)

represented = %{
"created_at" => follow_notif.inserted_at |> Utils.format_naive_asctime(),
"from_profile" => UserView.render("show.json", %{user: follower, for: user}),
"id" => follow_notif.id,
"is_seen" => 0,
"notice" => ActivityView.render("activity.json", %{activity: activity, for: user}),
"ntype" => "follow"
}

assert represented ==
NotificationView.render("notification.json", %{notification: follow_notif, for: user})
end

test "A mention notification" do
user = insert(:user)
other_user = insert(:user)

{:ok, activity} =
TwitterAPI.create_status(other_user, %{"status" => "Päivää, @#{user.nickname}"})

[notification] = Notification.for_user(user)

represented = %{
"created_at" => notification.inserted_at |> Utils.format_naive_asctime(),
"from_profile" => UserView.render("show.json", %{user: other_user, for: user}),
"id" => notification.id,
"is_seen" => 0,
"notice" => ActivityView.render("activity.json", %{activity: activity, for: user}),
"ntype" => "mention"
}

assert represented ==
NotificationView.render("notification.json", %{notification: notification, for: user})
end

test "A retweet notification" do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
repeater = insert(:user)

{:ok, _activity} = TwitterAPI.repeat(repeater, note_activity.id)
[notification] = Notification.for_user(user)

represented = %{
"created_at" => notification.inserted_at |> Utils.format_naive_asctime(),
"from_profile" => UserView.render("show.json", %{user: repeater, for: user}),
"id" => notification.id,
"is_seen" => 0,
"notice" =>
ActivityView.render("activity.json", %{activity: notification.activity, for: user}),
"ntype" => "repeat"
}

assert represented ==
NotificationView.render("notification.json", %{notification: notification, for: user})
end

test "A like notification" do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
liker = insert(:user)

{:ok, _activity} = TwitterAPI.fav(liker, note_activity.id)
[notification] = Notification.for_user(user)

represented = %{
"created_at" => notification.inserted_at |> Utils.format_naive_asctime(),
"from_profile" => UserView.render("show.json", %{user: liker, for: user}),
"id" => notification.id,
"is_seen" => 0,
"notice" =>
ActivityView.render("activity.json", %{activity: notification.activity, for: user}),
"ntype" => "like"
}

assert represented ==
NotificationView.render("notification.json", %{notification: notification, for: user})
end
end

+ 0
- 323
test/web/twitter_api/views/user_view_test.exs View File

@@ -1,323 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2018 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only

defmodule Pleroma.Web.TwitterAPI.UserViewTest do
use Pleroma.DataCase

alias Pleroma.User
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.TwitterAPI.UserView

import Pleroma.Factory

setup do
user = insert(:user, bio: "<span>Here's some html</span>")
[user: user]
end

test "A user with only a nickname", %{user: user} do
user = %{user | name: nil, nickname: "scarlett@catgirl.science"}
represented = UserView.render("show.json", %{user: user})
assert represented["name"] == user.nickname
assert represented["name_html"] == user.nickname
end

test "A user with an avatar object", %{user: user} do
image = "image"
user = %{user | avatar: %{"url" => [%{"href" => image}]}}
represented = UserView.render("show.json", %{user: user})
assert represented["profile_image_url"] == image
end

test "A user with emoji in username" do
expected =
"<img class=\"emoji\" alt=\"karjalanpiirakka\" title=\"karjalanpiirakka\" src=\"/file.png\" /> man"

user =
insert(:user, %{
info: %{
source_data: %{
"tag" => [
%{
"type" => "Emoji",
"icon" => %{"url" => "/file.png"},
"name" => ":karjalanpiirakka:"
}
]
}
},
name: ":karjalanpiirakka: man"
})

represented = UserView.render("show.json", %{user: user})
assert represented["name_html"] == expected
end

test "A user" do
note_activity = insert(:note_activity)
user = User.get_cached_by_ap_id(note_activity.data["actor"])
{:ok, user} = User.update_note_count(user)
follower = insert(:user)
second_follower = insert(:user)

User.follow(follower, user)
User.follow(second_follower, user)
User.follow(user, follower)
{:ok, user} = User.update_follower_count(user)
Cachex.put(:user_cache, "user_info:#{user.id}", User.user_info(Repo.get!(User, user.id)))

image = "http://localhost:4001/images/avi.png"
banner = "http://localhost:4001/images/banner.png"

represented = %{
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
"name_html" => user.name,
"description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")),
"description_html" => HtmlSanitizeEx.basic_html(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 1,
"friends_count" => 1,
"followers_count" => 2,
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"following" => false,
"follows_you" => false,
"statusnet_blocking" => false,
"statusnet_profile_url" => user.ap_id,
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
"locked" => false,
"hide_follows" => false,
"hide_followers" => false,
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
}

assert represented == UserView.render("show.json", %{user: user})
end

test "User exposes settings for themselves and only for themselves", %{user: user} do
as_user = UserView.render("show.json", %{user: user, for: user})
assert as_user["default_scope"] == user.info.default_scope
assert as_user["no_rich_text"] == user.info.no_rich_text
assert as_user["pleroma"]["notification_settings"] == user.info.notification_settings
as_stranger = UserView.render("show.json", %{user: user})
refute as_stranger["default_scope"]
refute as_stranger["no_rich_text"]
refute as_stranger["pleroma"]["notification_settings"]
end

test "A user for a given other follower", %{user: user} do
follower = insert(:user, %{following: [User.ap_followers(user)]})
{:ok, user} = User.update_follower_count(user)
image = "http://localhost:4001/images/avi.png"
banner = "http://localhost:4001/images/banner.png"

represented = %{
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
"name_html" => user.name,
"description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")),
"description_html" => HtmlSanitizeEx.basic_html(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 0,
"friends_count" => 0,
"followers_count" => 1,
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"following" => true,
"follows_you" => false,
"statusnet_blocking" => false,
"statusnet_profile_url" => user.ap_id,
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
"locked" => false,
"hide_follows" => false,
"hide_followers" => false,
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
}

assert represented == UserView.render("show.json", %{user: user, for: follower})
end

test "A user that follows you", %{user: user} do
follower = insert(:user)
{:ok, follower} = User.follow(follower, user)
{:ok, user} = User.update_follower_count(user)
image = "http://localhost:4001/images/avi.png"
banner = "http://localhost:4001/images/banner.png"

represented = %{
"id" => follower.id,
"name" => follower.name,
"screen_name" => follower.nickname,
"name_html" => follower.name,
"description" => HtmlSanitizeEx.strip_tags(follower.bio |> String.replace("<br>", "\n")),
"description_html" => HtmlSanitizeEx.basic_html(follower.bio),
"created_at" => follower.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 0,
"friends_count" => 1,
"followers_count" => 0,
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"following" => false,
"follows_you" => true,
"statusnet_blocking" => false,
"statusnet_profile_url" => follower.ap_id,
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
"locked" => false,
"hide_follows" => false,
"hide_followers" => false,
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
}

assert represented == UserView.render("show.json", %{user: follower, for: user})
end

test "a user that is a moderator" do
user = insert(:user, %{info: %{is_moderator: true}})
represented = UserView.render("show.json", %{user: user, for: user})

assert represented["rights"]["delete_others_notice"]
assert represented["role"] == "moderator"
end

test "a user that is a admin" do
user = insert(:user, %{info: %{is_admin: true}})
represented = UserView.render("show.json", %{user: user, for: user})

assert represented["rights"]["admin"]
assert represented["role"] == "admin"
end

test "A moderator with hidden role for another user", %{user: user} do
admin = insert(:user, %{info: %{is_moderator: true, show_role: false}})
represented = UserView.render("show.json", %{user: admin, for: user})

assert represented["role"] == nil
end

test "An admin with hidden role for another user", %{user: user} do
admin = insert(:user, %{info: %{is_admin: true, show_role: false}})
represented = UserView.render("show.json", %{user: admin, for: user})

assert represented["role"] == nil
end

test "A regular user for the admin", %{user: user} do
admin = insert(:user, %{info: %{is_admin: true}})
represented = UserView.render("show.json", %{user: user, for: admin})

assert represented["pleroma"]["deactivated"] == false
end

test "A blocked user for the blocker" do
user = insert(:user)
blocker = insert(:user)
User.block(blocker, user)
image = "http://localhost:4001/images/avi.png"
banner = "http://localhost:4001/images/banner.png"

represented = %{
"id" => user.id,
"name" => user.name,
"screen_name" => user.nickname,
"name_html" => user.name,
"description" => HtmlSanitizeEx.strip_tags(user.bio |> String.replace("<br>", "\n")),
"description_html" => HtmlSanitizeEx.basic_html(user.bio),
"created_at" => user.inserted_at |> Utils.format_naive_asctime(),
"favourites_count" => 0,
"statuses_count" => 0,
"friends_count" => 0,
"followers_count" => 0,
"profile_image_url" => image,
"profile_image_url_https" => image,
"profile_image_url_profile_size" => image,
"profile_image_url_original" => image,
"following" => false,
"follows_you" => false,
"statusnet_blocking" => true,
"statusnet_profile_url" => user.ap_id,
"cover_photo" => banner,
"background_image" => nil,
"is_local" => true,
"locked" => false,
"hide_follows" => false,
"hide_followers" => false,
"fields" => [],
"pleroma" => %{
"confirmation_pending" => false,
"tags" => [],
"skip_thread_containment" => false
},
"rights" => %{"admin" => false, "delete_others_notice" => false},
"role" => "member"
}

blocker = User.get_cached_by_id(blocker.id)
assert represented == UserView.render("show.json", %{user: user, for: blocker})
end

test "a user with mastodon fields" do
fields = [
%{
"name" => "Pronouns",
"value" => "she/her"
},
%{
"name" => "Website",
"value" => "https://example.org/"
}
]

user =
insert(:user, %{
info: %{
source_data: %{
"attachment" =>
Enum.map(fields, fn field -> Map.put(field, "type", "PropertyValue") end)
}
}
})

userview = UserView.render("show.json", %{user: user})
assert userview["fields"] == fields
end
end

Loading…
Cancel
Save