Merge branch 'develop' into feature/configurable-blocks
This commit is contained in:
commit
2e294ee44a
72
CONFIGURATION.md
Normal file
72
CONFIGURATION.md
Normal file
|
@ -0,0 +1,72 @@
|
|||
# Configuring Pleroma
|
||||
|
||||
In the `config/` directory, you will find the following relevant files:
|
||||
|
||||
* `config.exs`: default base configuration
|
||||
* `dev.exs`: default additional configuration for `MIX_ENV=dev`
|
||||
* `prod.exs`: default additional configuration for `MIX_ENV=prod`
|
||||
|
||||
|
||||
Do not modify files in the list above.
|
||||
Instead, overload the settings by editing the following files:
|
||||
|
||||
* `dev.secret.exs`: custom additional configuration for `MIX_ENV=dev`
|
||||
* `prod.secret.exs`: custom additional configuration for `MIX_ENV=prod`
|
||||
|
||||
## Message Rewrite Filters (MRFs)
|
||||
|
||||
Modify incoming and outgoing posts.
|
||||
|
||||
config :pleroma, :instance,
|
||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy
|
||||
|
||||
`rewrite_policy` specifies which MRF policies to apply.
|
||||
It can either be a single policy or a list of policies.
|
||||
Currently, MRFs availible by default are:
|
||||
|
||||
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`
|
||||
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`
|
||||
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`
|
||||
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`
|
||||
|
||||
Some policies, such as SimplePolicy and RejectNonPublic,
|
||||
can be additionally configured in their respective sections.
|
||||
|
||||
### NoOpPolicy
|
||||
|
||||
Does not modify posts (this is the default `rewrite_policy`)
|
||||
|
||||
### DropPolicy
|
||||
|
||||
Drops all posts.
|
||||
It generally does not make sense to use this in production.
|
||||
|
||||
### SimplePolicy
|
||||
|
||||
Restricts the visibility of posts from certain instances.
|
||||
|
||||
config :pleroma, :mrf_simple,
|
||||
media_removal: [],
|
||||
media_nsfw: [],
|
||||
federated_timeline_removal: [],
|
||||
reject: []
|
||||
|
||||
* `media_removal`: posts from these instances will have attachments
|
||||
removed
|
||||
* `media_nsfw`: posts from these instances will have attachments marked
|
||||
as nsfw
|
||||
* `federated_timeline_removal`: posts from these instances will be
|
||||
marked as unlisted
|
||||
* `reject`: posts from these instances will be dropped
|
||||
|
||||
### RejectNonPublic
|
||||
|
||||
Drops posts with non-public visibility settings.
|
||||
|
||||
config :pleroma :mrf_rejectnonpublic
|
||||
allow_followersonly: false,
|
||||
allow_direct: false,
|
||||
|
||||
* `allow_followersonly`: whether to allow follower-only posts through
|
||||
the filter
|
||||
* `allow_direct`: whether to allow direct messages through the filter
|
|
@ -64,6 +64,10 @@
|
|||
|
||||
config :pleroma, :user, deny_follow_blocked: true
|
||||
|
||||
config :pleroma, :mrf_rejectnonpublic,
|
||||
allow_followersonly: false,
|
||||
allow_direct: false
|
||||
|
||||
config :pleroma, :mrf_simple,
|
||||
media_removal: [],
|
||||
media_nsfw: [],
|
||||
|
|
|
@ -24,18 +24,27 @@ server {
|
|||
# }
|
||||
}
|
||||
|
||||
# Enable SSL session caching for improved performance
|
||||
ssl_session_cache shared:ssl_session_cache:10m;
|
||||
|
||||
server {
|
||||
listen 443 ssl http2;
|
||||
ssl on;
|
||||
ssl_session_timeout 5m;
|
||||
|
||||
ssl_trusted_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||
ssl_certificate /etc/letsencrypt/live/example.tld/fullchain.pem;
|
||||
ssl_certificate_key /etc/letsencrypt/live/example.tld/privkey.pem;
|
||||
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
|
||||
# Add TLSv1.0 to support older devices
|
||||
ssl_protocols TLSv1.2;
|
||||
# Uncomment line below if you want to support older devices (Before Android 4.4.2, IE 8, etc.)
|
||||
# ssl_ciphers "HIGH:!aNULL:!MD5 or HIGH:!aNULL:!MD5:!3DES";
|
||||
ssl_ciphers "ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-SHA384:ECDHE-RSA-AES256-SHA384:!aNULL:!eNULL:!EXPORT:!DES:!MD5:!PSK:!RC4";
|
||||
ssl_prefer_server_ciphers on;
|
||||
|
||||
ssl_ecdh_curve X25519:prime256v1:secp384r1:secp521r1;
|
||||
ssl_stapling on;
|
||||
ssl_stapling_verify on;
|
||||
|
||||
server_name example.tld;
|
||||
|
||||
gzip_vary on;
|
||||
|
|
|
@ -5,7 +5,7 @@ defmodule Mix.Tasks.SetModerator do
|
|||
|
||||
@shortdoc "Set moderator status"
|
||||
def run([nickname | rest]) do
|
||||
ensure_started(Repo, [])
|
||||
Application.ensure_all_started(:pleroma)
|
||||
|
||||
moderator =
|
||||
case rest do
|
||||
|
@ -19,7 +19,7 @@ def run([nickname | rest]) do
|
|||
|> Map.put("is_moderator", !!moderator)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
user = Repo.update!(cng)
|
||||
{:ok, user} = User.update_and_set_cache(cng)
|
||||
|
||||
IO.puts("Moderator status of #{nickname}: #{user.info["is_moderator"]}")
|
||||
else
|
||||
|
|
|
@ -8,7 +8,8 @@ config :pleroma, :instance,
|
|||
name: "<%= name %>",
|
||||
email: "<%= email %>",
|
||||
limit: 5000,
|
||||
registrations_open: true
|
||||
registrations_open: true,
|
||||
dedupe_media: false
|
||||
|
||||
config :pleroma, :media_proxy,
|
||||
enabled: false,
|
||||
|
|
30
lib/mix/tasks/set_locked.ex
Normal file
30
lib/mix/tasks/set_locked.ex
Normal file
|
@ -0,0 +1,30 @@
|
|||
defmodule Mix.Tasks.SetLocked do
|
||||
use Mix.Task
|
||||
import Mix.Ecto
|
||||
alias Pleroma.{Repo, User}
|
||||
|
||||
@shortdoc "Set locked status"
|
||||
def run([nickname | rest]) do
|
||||
ensure_started(Repo, [])
|
||||
|
||||
locked =
|
||||
case rest do
|
||||
[locked] -> locked == "true"
|
||||
_ -> true
|
||||
end
|
||||
|
||||
with %User{local: true} = user <- User.get_by_nickname(nickname) do
|
||||
info =
|
||||
user.info
|
||||
|> Map.put("locked", !!locked)
|
||||
|
||||
cng = User.info_changeset(user, %{info: info})
|
||||
user = Repo.update!(cng)
|
||||
|
||||
IO.puts("locked status of #{nickname}: #{user.info["locked"]}")
|
||||
else
|
||||
_ ->
|
||||
IO.puts("No local user #{nickname}")
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Pleroma.List do
|
||||
use Ecto.Schema
|
||||
import Ecto.{Changeset, Query}
|
||||
alias Pleroma.{User, Repo}
|
||||
alias Pleroma.{User, Repo, Activity}
|
||||
|
||||
schema "lists" do
|
||||
belongs_to(:user, Pleroma.User)
|
||||
|
@ -56,6 +56,19 @@ def get_following(%Pleroma.List{following: following} = list) do
|
|||
{:ok, Repo.all(q)}
|
||||
end
|
||||
|
||||
# Get lists the activity should be streamed to.
|
||||
def get_lists_from_activity(%Activity{actor: ap_id}) do
|
||||
actor = User.get_cached_by_ap_id(ap_id)
|
||||
|
||||
query =
|
||||
from(
|
||||
l in Pleroma.List,
|
||||
where: fragment("? && ?", l.following, ^[actor.follower_address])
|
||||
)
|
||||
|
||||
Repo.all(query)
|
||||
end
|
||||
|
||||
def rename(%Pleroma.List{} = list, title) do
|
||||
list
|
||||
|> title_changeset(%{title: title})
|
||||
|
|
|
@ -2,20 +2,21 @@ defmodule Pleroma.Upload do
|
|||
alias Ecto.UUID
|
||||
alias Pleroma.Web
|
||||
|
||||
def store(%Plug.Upload{} = file) do
|
||||
uuid = UUID.generate()
|
||||
upload_folder = Path.join(upload_path(), uuid)
|
||||
File.mkdir_p!(upload_folder)
|
||||
result_file = Path.join(upload_folder, file.filename)
|
||||
File.cp!(file.path, result_file)
|
||||
def store(%Plug.Upload{} = file, should_dedupe) do
|
||||
content_type = get_content_type(file.path)
|
||||
uuid = get_uuid(file, should_dedupe)
|
||||
name = get_name(file, uuid, content_type, should_dedupe)
|
||||
upload_folder = get_upload_path(uuid, should_dedupe)
|
||||
url_path = get_url(name, uuid, should_dedupe)
|
||||
|
||||
# fix content type on some image uploads
|
||||
content_type =
|
||||
if file.content_type in [nil, "application/octet-stream"] do
|
||||
get_content_type(file.path)
|
||||
else
|
||||
file.content_type
|
||||
end
|
||||
File.mkdir_p!(upload_folder)
|
||||
result_file = Path.join(upload_folder, name)
|
||||
|
||||
if File.exists?(result_file) do
|
||||
File.rm!(file.path)
|
||||
else
|
||||
File.cp!(file.path, result_file)
|
||||
end
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
|
@ -23,26 +24,48 @@ def store(%Plug.Upload{} = file) do
|
|||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(file.filename)))
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => file.filename,
|
||||
"uuid" => uuid
|
||||
"name" => name
|
||||
}
|
||||
end
|
||||
|
||||
def store(%{"img" => "data:image/" <> image_data}) do
|
||||
def store(%{"img" => "data:image/" <> image_data}, should_dedupe) do
|
||||
parsed = Regex.named_captures(~r/(?<filetype>jpeg|png|gif);base64,(?<data>.*)/, image_data)
|
||||
data = Base.decode64!(parsed["data"])
|
||||
data = Base.decode64!(parsed["data"], ignore: :whitespace)
|
||||
uuid = UUID.generate()
|
||||
upload_folder = Path.join(upload_path(), uuid)
|
||||
uuidpath = Path.join(upload_path(), uuid)
|
||||
uuid = UUID.generate()
|
||||
|
||||
File.mkdir_p!(upload_path())
|
||||
|
||||
File.write!(uuidpath, data)
|
||||
|
||||
content_type = get_content_type(uuidpath)
|
||||
|
||||
name =
|
||||
create_name(
|
||||
String.downcase(Base.encode16(:crypto.hash(:sha256, data))),
|
||||
parsed["filetype"],
|
||||
content_type
|
||||
)
|
||||
|
||||
upload_folder = get_upload_path(uuid, should_dedupe)
|
||||
url_path = get_url(name, uuid, should_dedupe)
|
||||
|
||||
File.mkdir_p!(upload_folder)
|
||||
filename = Base.encode16(:crypto.hash(:sha256, data)) <> ".#{parsed["filetype"]}"
|
||||
result_file = Path.join(upload_folder, filename)
|
||||
result_file = Path.join(upload_folder, name)
|
||||
|
||||
File.write!(result_file, data)
|
||||
|
||||
content_type = "image/#{parsed["filetype"]}"
|
||||
if should_dedupe do
|
||||
if !File.exists?(result_file) do
|
||||
File.rename(uuidpath, result_file)
|
||||
else
|
||||
File.rm!(uuidpath)
|
||||
end
|
||||
else
|
||||
File.rename(uuidpath, result_file)
|
||||
end
|
||||
|
||||
%{
|
||||
"type" => "Image",
|
||||
|
@ -50,11 +73,10 @@ def store(%{"img" => "data:image/" <> image_data}) do
|
|||
%{
|
||||
"type" => "Link",
|
||||
"mediaType" => content_type,
|
||||
"href" => url_for(Path.join(uuid, :cow_uri.urlencode(filename)))
|
||||
"href" => url_path
|
||||
}
|
||||
],
|
||||
"name" => filename,
|
||||
"uuid" => uuid
|
||||
"name" => name
|
||||
}
|
||||
end
|
||||
|
||||
|
@ -63,6 +85,65 @@ def upload_path do
|
|||
Keyword.fetch!(settings, :uploads)
|
||||
end
|
||||
|
||||
defp create_name(uuid, ext, type) do
|
||||
case type do
|
||||
"application/octet-stream" ->
|
||||
String.downcase(Enum.join([uuid, ext], "."))
|
||||
|
||||
"audio/mpeg" ->
|
||||
String.downcase(Enum.join([uuid, "mp3"], "."))
|
||||
|
||||
_ ->
|
||||
String.downcase(Enum.join([uuid, List.last(String.split(type, "/"))], "."))
|
||||
end
|
||||
end
|
||||
|
||||
defp get_uuid(file, should_dedupe) do
|
||||
if should_dedupe do
|
||||
Base.encode16(:crypto.hash(:sha256, File.read!(file.path)))
|
||||
else
|
||||
UUID.generate()
|
||||
end
|
||||
end
|
||||
|
||||
defp get_name(file, uuid, type, should_dedupe) do
|
||||
if should_dedupe do
|
||||
create_name(uuid, List.last(String.split(file.filename, ".")), type)
|
||||
else
|
||||
unless String.contains?(file.filename, ".") do
|
||||
case type do
|
||||
"image/png" -> file.filename <> ".png"
|
||||
"image/jpeg" -> file.filename <> ".jpg"
|
||||
"image/gif" -> file.filename <> ".gif"
|
||||
"video/webm" -> file.filename <> ".webm"
|
||||
"video/mp4" -> file.filename <> ".mp4"
|
||||
"audio/mpeg" -> file.filename <> ".mp3"
|
||||
"audio/ogg" -> file.filename <> ".ogg"
|
||||
"audio/wav" -> file.filename <> ".wav"
|
||||
_ -> file.filename
|
||||
end
|
||||
else
|
||||
file.filename
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
defp get_upload_path(uuid, should_dedupe) do
|
||||
if should_dedupe do
|
||||
upload_path()
|
||||
else
|
||||
Path.join(upload_path(), uuid)
|
||||
end
|
||||
end
|
||||
|
||||
defp get_url(name, uuid, should_dedupe) do
|
||||
if should_dedupe do
|
||||
url_for(:cow_uri.urlencode(name))
|
||||
else
|
||||
url_for(Path.join(uuid, :cow_uri.urlencode(name)))
|
||||
end
|
||||
end
|
||||
|
||||
defp url_for(file) do
|
||||
"#{Web.base_url()}/media/#{file}"
|
||||
end
|
||||
|
@ -89,6 +170,9 @@ def get_content_type(file) do
|
|||
<<0x49, 0x44, 0x33, _, _, _, _, _>> ->
|
||||
"audio/mpeg"
|
||||
|
||||
<<255, 251, _, 68, 0, 0, 0, 0>> ->
|
||||
"audio/mpeg"
|
||||
|
||||
<<0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00, 0x00>> ->
|
||||
"audio/ogg"
|
||||
|
||||
|
|
|
@ -201,6 +201,14 @@ def maybe_direct_follow(%User{} = follower, %User{info: info} = followed) do
|
|||
end
|
||||
end
|
||||
|
||||
def maybe_follow(%User{} = follower, %User{info: info} = followed) do
|
||||
if not following?(follower, followed) do
|
||||
follow(follower, followed)
|
||||
else
|
||||
{:ok, follower}
|
||||
end
|
||||
end
|
||||
|
||||
@user_config Application.get_env(:pleroma, :user)
|
||||
@deny_follow_blocked Keyword.get(@user_config, :deny_follow_blocked)
|
||||
|
||||
|
@ -259,6 +267,10 @@ def following?(%User{} = follower, %User{} = followed) do
|
|||
Enum.member?(follower.following, followed.follower_address)
|
||||
end
|
||||
|
||||
def locked?(%User{} = user) do
|
||||
user.info["locked"] || false
|
||||
end
|
||||
|
||||
def get_by_ap_id(ap_id) do
|
||||
Repo.get_by(User, ap_id: ap_id)
|
||||
end
|
||||
|
@ -356,6 +368,40 @@ def get_friends(user) do
|
|||
{:ok, Repo.all(q)}
|
||||
end
|
||||
|
||||
def get_follow_requests_query(%User{} = user) do
|
||||
from(
|
||||
a in Activity,
|
||||
where:
|
||||
fragment(
|
||||
"? ->> 'type' = 'Follow'",
|
||||
a.data
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"? ->> 'state' = 'pending'",
|
||||
a.data
|
||||
),
|
||||
where:
|
||||
fragment(
|
||||
"? @> ?",
|
||||
a.data,
|
||||
^%{"object" => user.ap_id}
|
||||
)
|
||||
)
|
||||
end
|
||||
|
||||
def get_follow_requests(%User{} = user) do
|
||||
q = get_follow_requests_query(user)
|
||||
reqs = Repo.all(q)
|
||||
|
||||
users =
|
||||
Enum.map(reqs, fn req -> req.actor end)
|
||||
|> Enum.uniq()
|
||||
|> Enum.map(fn ap_id -> get_by_ap_id(ap_id) end)
|
||||
|
||||
{:ok, users}
|
||||
end
|
||||
|
||||
def increase_note_count(%User{} = user) do
|
||||
note_count = (user.info["note_count"] || 0) + 1
|
||||
new_info = Map.put(user.info, "note_count", note_count)
|
||||
|
@ -486,7 +532,31 @@ def unblock(user, %{ap_id: ap_id}) do
|
|||
|
||||
def blocks?(user, %{ap_id: ap_id}) do
|
||||
blocks = user.info["blocks"] || []
|
||||
Enum.member?(blocks, ap_id)
|
||||
domain_blocks = user.info["domain_blocks"] || []
|
||||
%{host: host} = URI.parse(ap_id)
|
||||
|
||||
Enum.member?(blocks, ap_id) ||
|
||||
Enum.any?(domain_blocks, fn domain ->
|
||||
host == domain
|
||||
end)
|
||||
end
|
||||
|
||||
def block_domain(user, domain) do
|
||||
domain_blocks = user.info["domain_blocks"] || []
|
||||
new_blocks = Enum.uniq([domain | domain_blocks])
|
||||
new_info = Map.put(user.info, "domain_blocks", new_blocks)
|
||||
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def unblock_domain(user, domain) do
|
||||
blocks = user.info["domain_blocks"] || []
|
||||
new_blocks = List.delete(blocks, domain)
|
||||
new_info = Map.put(user.info, "domain_blocks", new_blocks)
|
||||
|
||||
cs = User.info_changeset(user, %{info: new_info})
|
||||
update_and_set_cache(cs)
|
||||
end
|
||||
|
||||
def local_user_query() do
|
||||
|
|
|
@ -57,6 +57,7 @@ def stream_out(activity) do
|
|||
|
||||
if activity.data["type"] in ["Create", "Announce"] do
|
||||
Pleroma.Web.Streamer.stream("user", activity)
|
||||
Pleroma.Web.Streamer.stream("list", activity)
|
||||
|
||||
if Enum.member?(activity.data["to"], public) do
|
||||
Pleroma.Web.Streamer.stream("public", activity)
|
||||
|
@ -198,7 +199,7 @@ def unannounce(
|
|||
:ok <- maybe_federate(unannounce_activity),
|
||||
{:ok, _activity} <- Repo.delete(announce_activity),
|
||||
{:ok, object} <- remove_announce_from_object(announce_activity, object) do
|
||||
{:ok, unannounce_activity, announce_activity, object}
|
||||
{:ok, unannounce_activity, object}
|
||||
else
|
||||
_e -> {:ok, object}
|
||||
end
|
||||
|
@ -214,6 +215,7 @@ def follow(follower, followed, activity_id \\ nil, local \\ true) do
|
|||
|
||||
def unfollow(follower, followed, activity_id \\ nil, local \\ true) do
|
||||
with %Activity{} = follow_activity <- fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- update_follow_state(follow_activity, "cancelled"),
|
||||
unfollow_data <- make_unfollow_data(follower, followed, follow_activity, activity_id),
|
||||
{:ok, activity} <- insert(unfollow_data, local),
|
||||
:ok <- maybe_federate(activity) do
|
||||
|
@ -449,11 +451,13 @@ defp restrict_recent(query, _) do
|
|||
|
||||
defp restrict_blocked(query, %{"blocking_user" => %User{info: info}}) do
|
||||
blocks = info["blocks"] || []
|
||||
domain_blocks = info["domain_blocks"] || []
|
||||
|
||||
from(
|
||||
activity in query,
|
||||
where: fragment("not (? = ANY(?))", activity.actor, ^blocks),
|
||||
where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks)
|
||||
where: fragment("not (?->'to' \\?| ?)", activity.data, ^blocks),
|
||||
where: fragment("not (split_part(?, '/', 3) = ANY(?))", activity.actor, ^domain_blocks)
|
||||
)
|
||||
end
|
||||
|
||||
|
@ -502,7 +506,7 @@ def fetch_activities(recipients, opts \\ %{}) do
|
|||
end
|
||||
|
||||
def upload(file) do
|
||||
data = Upload.store(file)
|
||||
data = Upload.store(file, Application.get_env(:pleroma, :instance)[:dedupe_media])
|
||||
Repo.insert(%Object{data: data})
|
||||
end
|
||||
|
||||
|
|
|
@ -2,6 +2,10 @@ defmodule Pleroma.Web.ActivityPub.MRF.RejectNonPublic do
|
|||
alias Pleroma.User
|
||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||
|
||||
@mrf_rejectnonpublic Application.get_env(:pleroma, :mrf_rejectnonpublic)
|
||||
@allow_followersonly Keyword.get(@mrf_rejectnonpublic, :allow_followersonly)
|
||||
@allow_direct Keyword.get(@mrf_rejectnonpublic, :allow_direct)
|
||||
|
||||
@impl true
|
||||
def filter(object) do
|
||||
if object["type"] == "Create" do
|
||||
|
@ -18,9 +22,25 @@ def filter(object) do
|
|||
end
|
||||
|
||||
case visibility do
|
||||
"public" -> {:ok, object}
|
||||
"unlisted" -> {:ok, object}
|
||||
_ -> {:reject, nil}
|
||||
"public" ->
|
||||
{:ok, object}
|
||||
|
||||
"unlisted" ->
|
||||
{:ok, object}
|
||||
|
||||
"followers" ->
|
||||
with true <- @allow_followersonly do
|
||||
{:ok, object}
|
||||
else
|
||||
_e -> {:reject, nil}
|
||||
end
|
||||
|
||||
"direct" ->
|
||||
with true <- @allow_direct do
|
||||
{:ok, object}
|
||||
else
|
||||
_e -> {:reject, nil}
|
||||
end
|
||||
end
|
||||
else
|
||||
{:ok, object}
|
||||
|
|
|
@ -30,14 +30,19 @@ def fix_in_reply_to(%{"inReplyTo" => in_reply_to_id} = object)
|
|||
when not is_nil(in_reply_to_id) do
|
||||
case ActivityPub.fetch_object_from_id(in_reply_to_id) do
|
||||
{:ok, replied_object} ->
|
||||
activity = Activity.get_create_activity_by_object_ap_id(replied_object.data["id"])
|
||||
|
||||
object
|
||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||
|> Map.put("inReplyToStatusId", activity.id)
|
||||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
with %Activity{} = activity <-
|
||||
Activity.get_create_activity_by_object_ap_id(replied_object.data["id"]) do
|
||||
object
|
||||
|> Map.put("inReplyTo", replied_object.data["id"])
|
||||
|> Map.put("inReplyToAtomUri", object["inReplyToAtomUri"] || in_reply_to_id)
|
||||
|> Map.put("inReplyToStatusId", activity.id)
|
||||
|> Map.put("conversation", replied_object.data["context"] || object["conversation"])
|
||||
|> Map.put("context", replied_object.data["context"] || object["conversation"])
|
||||
else
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
object
|
||||
end
|
||||
|
||||
e ->
|
||||
Logger.error("Couldn't fetch #{object["inReplyTo"]} #{inspect(e)}")
|
||||
|
@ -137,9 +142,17 @@ def handle_incoming(
|
|||
with %User{local: true} = followed <- User.get_cached_by_ap_id(followed),
|
||||
%User{} = follower <- User.get_or_fetch_by_ap_id(follower),
|
||||
{:ok, activity} <- ActivityPub.follow(follower, followed, id, false) do
|
||||
ActivityPub.accept(%{to: [follower.ap_id], actor: followed.ap_id, object: data, local: true})
|
||||
if not User.locked?(followed) do
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed.ap_id,
|
||||
object: data,
|
||||
local: true
|
||||
})
|
||||
|
||||
User.follow(follower, followed)
|
||||
end
|
||||
|
||||
User.follow(follower, followed)
|
||||
{:ok, activity}
|
||||
else
|
||||
_e -> :error
|
||||
|
@ -252,7 +265,7 @@ def handle_incoming(
|
|||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
||||
|
||||
banner = new_user_data[:info]["banner"]
|
||||
locked = new_user_data[:info]["locked"]
|
||||
locked = new_user_data[:info]["locked"] || false
|
||||
|
||||
update_data =
|
||||
new_user_data
|
||||
|
@ -304,7 +317,7 @@ def handle_incoming(
|
|||
with %User{} = actor <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <-
|
||||
get_obj_helper(object_id) || ActivityPub.fetch_object_from_id(object_id),
|
||||
{:ok, activity, _, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||
{:ok, activity, _} <- ActivityPub.unannounce(actor, object, id, false) do
|
||||
{:ok, activity}
|
||||
else
|
||||
_e -> :error
|
||||
|
@ -432,6 +445,58 @@ def prepare_outgoing(%{"type" => "Create", "object" => %{"type" => "Note"} = obj
|
|||
{:ok, data}
|
||||
end
|
||||
|
||||
# Mastodon Accept/Reject requires a non-normalized object containing the actor URIs,
|
||||
# because of course it does.
|
||||
def prepare_outgoing(%{"type" => "Accept"} = data) do
|
||||
follow_activity_id =
|
||||
if is_binary(data["object"]) do
|
||||
data["object"]
|
||||
else
|
||||
data["object"]["id"]
|
||||
end
|
||||
|
||||
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
|
||||
object = %{
|
||||
"actor" => follow_activity.actor,
|
||||
"object" => follow_activity.data["object"],
|
||||
"id" => follow_activity.data["id"],
|
||||
"type" => "Follow"
|
||||
}
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_outgoing(%{"type" => "Reject"} = data) do
|
||||
follow_activity_id =
|
||||
if is_binary(data["object"]) do
|
||||
data["object"]
|
||||
else
|
||||
data["object"]["id"]
|
||||
end
|
||||
|
||||
with follow_activity <- Activity.get_by_ap_id(follow_activity_id) do
|
||||
object = %{
|
||||
"actor" => follow_activity.actor,
|
||||
"object" => follow_activity.data["object"],
|
||||
"id" => follow_activity.data["id"],
|
||||
"type" => "Follow"
|
||||
}
|
||||
|
||||
data =
|
||||
data
|
||||
|> Map.put("object", object)
|
||||
|> Map.put("@context", "https://www.w3.org/ns/activitystreams")
|
||||
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
||||
|
||||
def prepare_outgoing(%{"type" => _type} = data) do
|
||||
data =
|
||||
data
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.ActivityPub.Utils do
|
|||
alias Pleroma.Web.Endpoint
|
||||
alias Ecto.{Changeset, UUID}
|
||||
import Ecto.Query
|
||||
require Logger
|
||||
|
||||
# Some implementations send the actor URI as the actor field, others send the entire actor object,
|
||||
# so figure out what the actor's URI is based on what we have.
|
||||
|
@ -216,10 +217,27 @@ def remove_like_from_object(%Activity{data: %{"actor" => actor}}, object) do
|
|||
|
||||
#### Follow-related helpers
|
||||
|
||||
@doc """
|
||||
Updates a follow activity's state (for locked accounts).
|
||||
"""
|
||||
def update_follow_state(%Activity{} = activity, state) do
|
||||
with new_data <-
|
||||
activity.data
|
||||
|> Map.put("state", state),
|
||||
changeset <- Changeset.change(activity, data: new_data),
|
||||
{:ok, activity} <- Repo.update(changeset) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
@doc """
|
||||
Makes a follow activity data for the given follower and followed
|
||||
"""
|
||||
def make_follow_data(%User{ap_id: follower_id}, %User{ap_id: followed_id}, activity_id) do
|
||||
def make_follow_data(
|
||||
%User{ap_id: follower_id},
|
||||
%User{ap_id: followed_id} = followed,
|
||||
activity_id
|
||||
) do
|
||||
data = %{
|
||||
"type" => "Follow",
|
||||
"actor" => follower_id,
|
||||
|
@ -228,7 +246,10 @@ def make_follow_data(%User{ap_id: follower_id}, %User{ap_id: followed_id}, activ
|
|||
"object" => followed_id
|
||||
}
|
||||
|
||||
if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||
data = if activity_id, do: Map.put(data, "id", activity_id), else: data
|
||||
data = if User.locked?(followed), do: Map.put(data, "state", "pending"), else: data
|
||||
|
||||
data
|
||||
end
|
||||
|
||||
def fetch_latest_follow(%User{ap_id: follower_id}, %User{ap_id: followed_id}) do
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIController do
|
|||
alias Pleroma.Web
|
||||
alias Pleroma.Web.MastodonAPI.{StatusView, AccountView, MastodonView, ListView}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.{CommonAPI, OStatus}
|
||||
alias Pleroma.Web.OAuth.{Authorization, Token, App}
|
||||
alias Comeonin.Pbkdf2
|
||||
|
@ -71,6 +72,20 @@ def update_credentials(%{assigns: %{user: user}} = conn, params) do
|
|||
user
|
||||
end
|
||||
|
||||
user =
|
||||
if locked = params["locked"] do
|
||||
with locked <- locked == "true",
|
||||
new_info <- Map.put(user.info, "locked", locked),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
end
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
if original_user != user do
|
||||
|
@ -345,7 +360,7 @@ def reblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|||
end
|
||||
|
||||
def unreblog_status(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
||||
with {:ok, _, _, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
||||
with {:ok, _unannounce, %{data: %{"id" => id}}} <- CommonAPI.unrepeat(ap_id_or_id, user),
|
||||
%Activity{} = activity <- Activity.get_create_activity_by_object_ap_id(id) do
|
||||
render(conn, StatusView, "status.json", %{activity: activity, for: user, as: :activity})
|
||||
end
|
||||
|
@ -476,6 +491,53 @@ def following(conn, %{"id" => id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def follow_requests(%{assigns: %{user: followed}} = conn, _params) do
|
||||
with {:ok, follow_requests} <- User.get_follow_requests(followed) do
|
||||
render(conn, AccountView, "accounts.json", %{users: follow_requests, as: :user})
|
||||
end
|
||||
end
|
||||
|
||||
def authorize_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||
with %User{} = follower <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.maybe_follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed.ap_id,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Accept"
|
||||
}) do
|
||||
render(conn, AccountView, "relationship.json", %{user: followed, target: follower})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def reject_follow_request(%{assigns: %{user: followed}} = conn, %{"id" => id}) do
|
||||
with %User{} = follower <- Repo.get(User, id),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed.ap_id,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Reject"
|
||||
}) do
|
||||
render(conn, AccountView, "relationship.json", %{user: followed, target: follower})
|
||||
else
|
||||
{:error, message} ->
|
||||
conn
|
||||
|> put_resp_content_type("application/json")
|
||||
|> send_resp(403, Jason.encode!(%{"error" => message}))
|
||||
end
|
||||
end
|
||||
|
||||
def follow(%{assigns: %{user: follower}} = conn, %{"id" => id}) do
|
||||
with %User{} = followed <- Repo.get(User, id),
|
||||
{:ok, follower} <- User.maybe_direct_follow(follower, followed),
|
||||
|
@ -545,6 +607,20 @@ def blocks(%{assigns: %{user: user}} = conn, _) do
|
|||
end
|
||||
end
|
||||
|
||||
def domain_blocks(%{assigns: %{user: %{info: info}}} = conn, _) do
|
||||
json(conn, info["domain_blocks"] || [])
|
||||
end
|
||||
|
||||
def block_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||
User.block_domain(blocker, domain)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
def unblock_domain(%{assigns: %{user: blocker}} = conn, %{"domain" => domain}) do
|
||||
User.unblock_domain(blocker, domain)
|
||||
json(conn, %{})
|
||||
end
|
||||
|
||||
def search(%{assigns: %{user: user}} = conn, %{"q" => query} = params) do
|
||||
accounts = User.search(query, params["resolve"] == "true")
|
||||
|
||||
|
|
|
@ -15,10 +15,13 @@ def connect(params, socket) do
|
|||
with token when not is_nil(token) <- params["access_token"],
|
||||
%Token{user_id: user_id} <- Repo.get_by(Token, token: token),
|
||||
%User{} = user <- Repo.get(User, user_id),
|
||||
stream when stream in ["public", "public:local", "user", "direct"] <- params["stream"] do
|
||||
stream when stream in ["public", "public:local", "user", "direct", "list"] <-
|
||||
params["stream"] do
|
||||
topic = if stream == "list", do: "list:#{params["list"]}", else: stream
|
||||
|
||||
socket =
|
||||
socket
|
||||
|> assign(:topic, params["stream"])
|
||||
|> assign(:topic, topic)
|
||||
|> assign(:user, user)
|
||||
|
||||
Pleroma.Web.Streamer.add_socket(params["stream"], socket)
|
||||
|
|
|
@ -125,8 +125,8 @@ def render("status.json", %{activity: %{data: %{"object" => object}} = activity}
|
|||
uri: object["id"],
|
||||
url: object["external_url"] || object["id"],
|
||||
account: AccountView.render("account.json", %{user: user}),
|
||||
in_reply_to_id: reply_to && reply_to.id,
|
||||
in_reply_to_account_id: reply_to_user && reply_to_user.id,
|
||||
in_reply_to_id: reply_to && to_string(reply_to.id),
|
||||
in_reply_to_account_id: reply_to_user && to_string(reply_to_user.id),
|
||||
reblog: nil,
|
||||
content: HtmlSanitizeEx.basic_html(object["content"]),
|
||||
created_at: created_at,
|
||||
|
|
|
@ -81,10 +81,10 @@ def token_exchange(conn, %{"grant_type" => "authorization_code"} = params) do
|
|||
# - investigate a way to verify the user wants to grant read/write/follow once scope handling is done
|
||||
def token_exchange(
|
||||
conn,
|
||||
%{"grant_type" => "password", "name" => name, "password" => password} = params
|
||||
%{"grant_type" => "password", "username" => name, "password" => password} = params
|
||||
) do
|
||||
with %App{} = app <- get_app_from_request(conn, params),
|
||||
%User{} = user <- User.get_cached_by_nickname(name),
|
||||
%User{} = user <- User.get_by_nickname_or_email(name),
|
||||
true <- Pbkdf2.checkpw(password, user.password_hash),
|
||||
{:ok, auth} <- Authorization.create_authorization(app, user),
|
||||
{:ok, token} <- Token.exchange_token(app, auth) do
|
||||
|
@ -104,6 +104,18 @@ def token_exchange(
|
|||
end
|
||||
end
|
||||
|
||||
def token_exchange(
|
||||
conn,
|
||||
%{"grant_type" => "password", "name" => name, "password" => password} = params
|
||||
) do
|
||||
params =
|
||||
params
|
||||
|> Map.delete("name")
|
||||
|> Map.put("username", name)
|
||||
|
||||
token_exchange(conn, params)
|
||||
end
|
||||
|
||||
defp fix_padding(token) do
|
||||
token
|
||||
|> Base.url_decode64!(padding: false)
|
||||
|
|
|
@ -41,7 +41,7 @@ def user_fetcher(username) do
|
|||
end
|
||||
|
||||
pipeline :well_known do
|
||||
plug(:accepts, ["xml", "xrd+xml", "json", "jrd+json"])
|
||||
plug(:accepts, ["json", "jrd+json", "xml", "xrd+xml"])
|
||||
end
|
||||
|
||||
pipeline :config do
|
||||
|
@ -97,12 +97,14 @@ def user_fetcher(username) do
|
|||
post("/accounts/:id/mute", MastodonAPIController, :relationship_noop)
|
||||
post("/accounts/:id/unmute", MastodonAPIController, :relationship_noop)
|
||||
|
||||
get("/follow_requests", MastodonAPIController, :follow_requests)
|
||||
post("/follow_requests/:id/authorize", MastodonAPIController, :authorize_follow_request)
|
||||
post("/follow_requests/:id/reject", MastodonAPIController, :reject_follow_request)
|
||||
|
||||
post("/follows", MastodonAPIController, :follow)
|
||||
|
||||
get("/blocks", MastodonAPIController, :blocks)
|
||||
|
||||
get("/domain_blocks", MastodonAPIController, :empty_array)
|
||||
get("/follow_requests", MastodonAPIController, :empty_array)
|
||||
get("/mutes", MastodonAPIController, :empty_array)
|
||||
|
||||
get("/timelines/home", MastodonAPIController, :home_timeline)
|
||||
|
@ -134,6 +136,10 @@ def user_fetcher(username) do
|
|||
get("/lists/:id/accounts", MastodonAPIController, :list_accounts)
|
||||
post("/lists/:id/accounts", MastodonAPIController, :add_to_list)
|
||||
delete("/lists/:id/accounts", MastodonAPIController, :remove_from_list)
|
||||
|
||||
get("/domain_blocks", MastodonAPIController, :domain_blocks)
|
||||
post("/domain_blocks", MastodonAPIController, :block_domain)
|
||||
delete("/domain_blocks", MastodonAPIController, :unblock_domain)
|
||||
end
|
||||
|
||||
scope "/api/web", Pleroma.Web.MastodonAPI do
|
||||
|
@ -238,8 +244,13 @@ def user_fetcher(username) do
|
|||
|
||||
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)
|
||||
|
||||
get("/pleroma/friend_requests", TwitterAPI.Controller, :friend_requests)
|
||||
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)
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
defmodule Pleroma.Web.Streamer do
|
||||
use GenServer
|
||||
require Logger
|
||||
alias Pleroma.{User, Notification}
|
||||
alias Pleroma.{User, Notification, Activity, Object}
|
||||
|
||||
def init(args) do
|
||||
{:ok, args}
|
||||
|
@ -59,6 +59,19 @@ def handle_cast(%{action: :stream, topic: "direct", item: item}, topics) do
|
|||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "list", item: item}, topics) do
|
||||
recipient_topics =
|
||||
Pleroma.List.get_lists_from_activity(item)
|
||||
|> Enum.map(fn %{id: id} -> "list:#{id}" end)
|
||||
|
||||
Enum.each(recipient_topics || [], fn list_topic ->
|
||||
Logger.debug("Trying to push message to #{list_topic}\n\n")
|
||||
push_to_socket(topics, list_topic, item)
|
||||
end)
|
||||
|
||||
{:noreply, topics}
|
||||
end
|
||||
|
||||
def handle_cast(%{action: :stream, topic: "user", item: %Notification{} = item}, topics) do
|
||||
topic = "user:#{item.user_id}"
|
||||
|
||||
|
@ -125,6 +138,34 @@ def handle_cast(m, state) do
|
|||
{:noreply, state}
|
||||
end
|
||||
|
||||
defp represent_update(%Activity{} = activity, %User{} = user) do
|
||||
%{
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
"status.json",
|
||||
activity: activity,
|
||||
for: user
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, %Activity{data: %{"type" => "Announce"}} = item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
user = User.get_cached_by_ap_id(socket.assigns[:user].ap_id)
|
||||
blocks = user.info["blocks"] || []
|
||||
|
||||
parent = Object.get_by_ap_id(item.data["object"])
|
||||
|
||||
unless is_nil(parent) or item.actor in blocks or parent.data["actor"] in blocks do
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def push_to_socket(topics, topic, item) do
|
||||
Enum.each(topics[topic] || [], fn socket ->
|
||||
# Get the current user so we have up-to-date blocks etc.
|
||||
|
@ -132,20 +173,7 @@ def push_to_socket(topics, topic, item) do
|
|||
blocks = user.info["blocks"] || []
|
||||
|
||||
unless item.actor in blocks do
|
||||
json =
|
||||
%{
|
||||
event: "update",
|
||||
payload:
|
||||
Pleroma.Web.MastodonAPI.StatusView.render(
|
||||
"status.json",
|
||||
activity: item,
|
||||
for: user
|
||||
)
|
||||
|> Jason.encode!()
|
||||
}
|
||||
|> Jason.encode!()
|
||||
|
||||
send(socket.transport_pid, {:text, json})
|
||||
send(socket.transport_pid, {:text, represent_update(item, user)})
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
|
|
@ -12,14 +12,9 @@ def create_status(%User{} = user, %{"status" => _} = data) do
|
|||
end
|
||||
|
||||
def delete(%User{} = user, id) do
|
||||
# TwitterAPI does not have an "unretweet" endpoint; instead this is done
|
||||
# via the "destroy" endpoint. Therefore, we need to handle
|
||||
# when the status to "delete" is actually an Announce (repeat) object.
|
||||
with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id) do
|
||||
case type do
|
||||
"Announce" -> unrepeat(user, id)
|
||||
_ -> CommonAPI.delete(id, user)
|
||||
end
|
||||
with %Activity{data: %{"type" => type}} <- Repo.get(Activity, id),
|
||||
{:ok, activity} <- CommonAPI.delete(id, user) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -70,8 +65,9 @@ def repeat(%User{} = user, ap_id_or_id) do
|
|||
end
|
||||
end
|
||||
|
||||
defp unrepeat(%User{} = user, ap_id_or_id) do
|
||||
with {:ok, _unannounce, activity, _object} <- CommonAPI.unrepeat(ap_id_or_id, user) do
|
||||
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_activity_by_object_ap_id(id) do
|
||||
{:ok, activity}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.TwitterAPI.Controller do
|
|||
alias Pleroma.Web.CommonAPI
|
||||
alias Pleroma.{Repo, Activity, User, Notification}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Ecto.Changeset
|
||||
|
||||
require Logger
|
||||
|
@ -240,6 +241,13 @@ def retweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
|||
end
|
||||
end
|
||||
|
||||
def unretweet(%{assigns: %{user: user}} = conn, %{"id" => id}) do
|
||||
with {_, {:ok, id}} <- {:param_cast, Ecto.Type.cast(:integer, id)},
|
||||
{:ok, activity} <- TwitterAPI.unrepeat(user, id) do
|
||||
render(conn, ActivityView, "activity.json", %{activity: activity, for: user})
|
||||
end
|
||||
end
|
||||
|
||||
def register(conn, params) do
|
||||
with {:ok, user} <- TwitterAPI.register_user(params) do
|
||||
render(conn, UserView, "show.json", %{user: user})
|
||||
|
@ -331,6 +339,54 @@ def friends(conn, params) do
|
|||
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
|
||||
render(conn, UserView, "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],
|
||||
uid when is_number(uid) <- String.to_integer(uid),
|
||||
%User{} = follower <- Repo.get(User, uid),
|
||||
{:ok, follower} <- User.maybe_follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "accept"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed.ap_id,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Accept"
|
||||
}) do
|
||||
render(conn, UserView, "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],
|
||||
uid when is_number(uid) <- String.to_integer(uid),
|
||||
%User{} = follower <- Repo.get(User, uid),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state(follow_activity, "reject"),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed.ap_id,
|
||||
object: follow_activity.data["id"],
|
||||
type: "Reject"
|
||||
}) do
|
||||
render(conn, UserView, "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 =
|
||||
|
@ -357,6 +413,20 @@ def update_profile(%{assigns: %{user: user}} = conn, params) do
|
|||
params
|
||||
end
|
||||
|
||||
user =
|
||||
if locked = params["locked"] do
|
||||
with locked <- locked == "true",
|
||||
new_info <- Map.put(user.info, "locked", locked),
|
||||
change <- User.info_changeset(user, %{info: new_info}),
|
||||
{:ok, user} <- User.update_and_set_cache(change) do
|
||||
user
|
||||
else
|
||||
_e -> user
|
||||
end
|
||||
else
|
||||
user
|
||||
end
|
||||
|
||||
with changeset <- User.update_changeset(user, params),
|
||||
{:ok, user} <- User.update_and_set_cache(changeset) do
|
||||
CommonAPI.update(user)
|
||||
|
|
|
@ -51,7 +51,8 @@ def render("user.json", %{user: user = %User{}} = assigns) do
|
|||
"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
|
||||
"is_local" => user.local,
|
||||
"locked" => !!user.info["locked"]
|
||||
}
|
||||
|
||||
if assigns[:token] do
|
||||
|
|
|
@ -25,35 +25,17 @@ def host_meta do
|
|||
|> XmlBuilder.to_doc()
|
||||
end
|
||||
|
||||
def webfinger(resource, "JSON") do
|
||||
def webfinger(resource, fmt) when fmt in ["XML", "JSON"] do
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
||||
user = User.get_by_nickname(username)
|
||||
{:ok, represent_user(user, "JSON")}
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource),
|
||||
%User{} = user <- User.get_by_nickname(username) do
|
||||
{:ok, represent_user(user, fmt)}
|
||||
else
|
||||
_e ->
|
||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, "JSON")}
|
||||
else
|
||||
_e ->
|
||||
{:error, "Couldn't find user"}
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def webfinger(resource, "XML") do
|
||||
host = Pleroma.Web.Endpoint.host()
|
||||
regex = ~r/(acct:)?(?<username>\w+)@#{host}/
|
||||
|
||||
with %{"username" => username} <- Regex.named_captures(regex, resource) do
|
||||
user = User.get_by_nickname(username)
|
||||
{:ok, represent_user(user, "XML")}
|
||||
else
|
||||
_e ->
|
||||
with user when not is_nil(user) <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, "XML")}
|
||||
with %User{} = user <- User.get_cached_by_ap_id(resource) do
|
||||
{:ok, represent_user(user, fmt)}
|
||||
else
|
||||
_e ->
|
||||
{:error, "Couldn't find user"}
|
||||
|
|
|
@ -0,0 +1,7 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddListFollowIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
def change do
|
||||
create index(:lists, [:following])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,8 @@
|
|||
defmodule Pleroma.Repo.Migrations.CreateApidHostExtractionIndex do
|
||||
use Ecto.Migration
|
||||
@disable_ddl_transaction true
|
||||
|
||||
def change do
|
||||
create index(:activities, ["(split_part(actor, '/', 3))"], concurrently: true, name: :activities_hosts)
|
||||
end
|
||||
end
|
Binary file not shown.
Before Width: | Height: | Size: 3.5 KiB After Width: | Height: | Size: 997 B |
|
@ -1 +1 @@
|
|||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.c0e1e1e1fcff94fd1e14fc44bfee9a1e.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.16ab7851cdbf730f9cbc.js></script><script type=text/javascript src=/static/js/vendor.56aa9f8c34786f6af6b7.js></script><script type=text/javascript src=/static/js/app.13c0bda10eb515cdf8ed.js></script></body></html>
|
||||
<!DOCTYPE html><html lang=en><head><meta charset=utf-8><meta name=viewport content="width=device-width,initial-scale=1"><title>Pleroma</title><link rel=icon type=image/png href=/favicon.png><link rel=stylesheet href=/static/font/css/fontello.css><link rel=stylesheet href=/static/font/css/animation.css><link href=/static/css/app.5d0189b6f119febde070b703869bbd06.css rel=stylesheet></head><body style="display: none"><div id=app></div><script type=text/javascript src=/static/js/manifest.f2341edd686e54ee9b4a.js></script><script type=text/javascript src=/static/js/vendor.a93310d51acbd9480094.js></script><script type=text/javascript src=/static/js/app.de965bb2a0a8bffbeafa.js></script></body></html>
|
|
@ -10,5 +10,6 @@
|
|||
"whoToFollowProviderDummy2": "https://followlink.osa-p.net/api/get_recommend.json?acct=@{{user}}@{{host}}",
|
||||
"whoToFollowLink": "https://vinayaka.distsn.org/?{{host}}+{{user}}",
|
||||
"whoToFollowLinkDummy2": "https://followlink.osa-p.net/recommend.html",
|
||||
"showInstanceSpecificPanel": false
|
||||
"showInstanceSpecificPanel": false,
|
||||
"scopeOptionsEnabled": false
|
||||
}
|
||||
|
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
8
priv/static/static/js/app.de965bb2a0a8bffbeafa.js
Normal file
8
priv/static/static/js/app.de965bb2a0a8bffbeafa.js
Normal file
File diff suppressed because one or more lines are too long
1
priv/static/static/js/app.de965bb2a0a8bffbeafa.js.map
Normal file
1
priv/static/static/js/app.de965bb2a0a8bffbeafa.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -1,2 +0,0 @@
|
|||
!function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a=window.webpackJsonp;window.webpackJsonp=function(c,o){for(var p,l,s=0,i=[];s<c.length;s++)l=c[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(p in o)Object.prototype.hasOwnProperty.call(o,p)&&(e[p]=o[p]);for(a&&a(c,o);i.length;)i.shift().call(null,t);if(o[0])return r[0]=0,t(0)};var r={},n={0:0};t.e=function(e,a){if(0===n[e])return a.call(null,t);if(void 0!==n[e])n[e].push(a);else{n[e]=[a];var r=document.getElementsByTagName("head")[0],c=document.createElement("script");c.type="text/javascript",c.charset="utf-8",c.async=!0,c.src=t.p+"static/js/"+e+"."+{1:"56aa9f8c34786f6af6b7",2:"13c0bda10eb515cdf8ed"}[e]+".js",r.appendChild(c)}},t.m=e,t.c=r,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.16ab7851cdbf730f9cbc.js.map
|
2
priv/static/static/js/manifest.f2341edd686e54ee9b4a.js
Normal file
2
priv/static/static/js/manifest.f2341edd686e54ee9b4a.js
Normal file
|
@ -0,0 +1,2 @@
|
|||
!function(e){function t(a){if(r[a])return r[a].exports;var n=r[a]={exports:{},id:a,loaded:!1};return e[a].call(n.exports,n,n.exports,t),n.loaded=!0,n.exports}var a=window.webpackJsonp;window.webpackJsonp=function(o,p){for(var c,l,s=0,i=[];s<o.length;s++)l=o[s],n[l]&&i.push.apply(i,n[l]),n[l]=0;for(c in p)Object.prototype.hasOwnProperty.call(p,c)&&(e[c]=p[c]);for(a&&a(o,p);i.length;)i.shift().call(null,t);if(p[0])return r[0]=0,t(0)};var r={},n={0:0};t.e=function(e,a){if(0===n[e])return a.call(null,t);if(void 0!==n[e])n[e].push(a);else{n[e]=[a];var r=document.getElementsByTagName("head")[0],o=document.createElement("script");o.type="text/javascript",o.charset="utf-8",o.async=!0,o.src=t.p+"static/js/"+e+"."+{1:"a93310d51acbd9480094",2:"de965bb2a0a8bffbeafa"}[e]+".js",r.appendChild(o)}},t.m=e,t.c=r,t.p="/"}([]);
|
||||
//# sourceMappingURL=manifest.f2341edd686e54ee9b4a.js.map
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
priv/static/static/js/vendor.a93310d51acbd9480094.js.map
Normal file
1
priv/static/static/js/vendor.a93310d51acbd9480094.js.map
Normal file
File diff suppressed because one or more lines are too long
|
@ -74,4 +74,20 @@ test "getting all lists by an user" do
|
|||
assert list_two in lists
|
||||
refute list_three in lists
|
||||
end
|
||||
|
||||
test "getting all lists the user is a member of" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
{:ok, list_one} = Pleroma.List.create("title", user)
|
||||
{:ok, list_two} = Pleroma.List.create("other title", user)
|
||||
{:ok, list_three} = Pleroma.List.create("third title", other_user)
|
||||
{:ok, list_one} = Pleroma.List.follow(list_one, other_user)
|
||||
{:ok, list_two} = Pleroma.List.follow(list_two, other_user)
|
||||
{:ok, list_three} = Pleroma.List.follow(list_three, user)
|
||||
|
||||
lists = Pleroma.List.get_lists_from_activity(%Pleroma.Activity{actor: other_user.ap_id})
|
||||
assert list_one in lists
|
||||
assert list_two in lists
|
||||
refute list_three in lists
|
||||
end
|
||||
end
|
||||
|
|
|
@ -3,40 +3,58 @@ defmodule Pleroma.UploadTest do
|
|||
use Pleroma.DataCase
|
||||
|
||||
describe "Storing a file" do
|
||||
test "copies the file to the configured folder" do
|
||||
test "copies the file to the configured folder with deduping" do
|
||||
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
path: Path.absname("test/fixtures/image_tmp.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
}
|
||||
|
||||
data = Upload.store(file)
|
||||
assert data["name"] == "an [image.jpg"
|
||||
data = Upload.store(file, true)
|
||||
|
||||
assert List.first(data["url"])["href"] ==
|
||||
"http://localhost:4001/media/#{data["uuid"]}/an%20%5Bimage.jpg"
|
||||
assert data["name"] ==
|
||||
"e7a6d0cf595bff76f14c9a98b6c199539559e8b844e02e51e5efcfd1f614a2df.jpeg"
|
||||
end
|
||||
|
||||
test "fixes an incorrect content type" do
|
||||
test "copies the file to the configured folder without deduping" do
|
||||
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "application/octet-stream",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image_tmp.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
}
|
||||
|
||||
data = Upload.store(file)
|
||||
data = Upload.store(file, false)
|
||||
assert data["name"] == "an [image.jpg"
|
||||
end
|
||||
|
||||
test "fixes incorrect content type" do
|
||||
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "application/octet-stream",
|
||||
path: Path.absname("test/fixtures/image_tmp.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
}
|
||||
|
||||
data = Upload.store(file, true)
|
||||
assert hd(data["url"])["mediaType"] == "image/jpeg"
|
||||
end
|
||||
|
||||
test "does not modify a valid content type" do
|
||||
test "adds missing extension" do
|
||||
File.cp!("test/fixtures/image.jpg", "test/fixtures/image_tmp.jpg")
|
||||
|
||||
file = %Plug.Upload{
|
||||
content_type: "image/png",
|
||||
path: Path.absname("test/fixtures/image.jpg"),
|
||||
filename: "an [image.jpg"
|
||||
content_type: "image/jpg",
|
||||
path: Path.absname("test/fixtures/image_tmp.jpg"),
|
||||
filename: "an [image"
|
||||
}
|
||||
|
||||
data = Upload.store(file)
|
||||
assert hd(data["url"])["mediaType"] == "image/png"
|
||||
data = Upload.store(file, false)
|
||||
assert data["name"] == "an [image.jpg"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -361,6 +361,27 @@ test "it unblocks users" do
|
|||
end
|
||||
end
|
||||
|
||||
describe "domain blocking" do
|
||||
test "blocks domains" do
|
||||
user = insert(:user)
|
||||
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
|
||||
|
||||
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
|
||||
|
||||
assert User.blocks?(user, collateral_user)
|
||||
end
|
||||
|
||||
test "unblocks domains" do
|
||||
user = insert(:user)
|
||||
collateral_user = insert(:user, %{ap_id: "https://awful-and-rude-instance.com/user/bully"})
|
||||
|
||||
{:ok, user} = User.block_domain(user, "awful-and-rude-instance.com")
|
||||
{:ok, user} = User.unblock_domain(user, "awful-and-rude-instance.com")
|
||||
|
||||
refute User.blocks?(user, collateral_user)
|
||||
end
|
||||
end
|
||||
|
||||
test "get recipients from activity" do
|
||||
actor = insert(:user)
|
||||
user = insert(:user, local: true)
|
||||
|
|
|
@ -318,11 +318,9 @@ test "unannouncing a previously announced object" do
|
|||
{:ok, announce_activity, object} = ActivityPub.announce(user, object)
|
||||
assert object.data["announcement_count"] == 1
|
||||
|
||||
{:ok, unannounce_activity, activity, object} = ActivityPub.unannounce(user, object)
|
||||
{:ok, unannounce_activity, object} = ActivityPub.unannounce(user, object)
|
||||
assert object.data["announcement_count"] == 0
|
||||
|
||||
assert activity == announce_activity
|
||||
|
||||
assert unannounce_activity.data["to"] == [
|
||||
User.ap_followers(user),
|
||||
announce_activity.data["actor"]
|
||||
|
|
|
@ -4,6 +4,7 @@ defmodule Pleroma.Web.MastodonAPI.MastodonAPIControllerTest do
|
|||
alias Pleroma.Web.TwitterAPI.TwitterAPI
|
||||
alias Pleroma.{Repo, User, Activity, Notification}
|
||||
alias Pleroma.Web.{OStatus, CommonAPI}
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
|
||||
import Pleroma.Factory
|
||||
import ExUnit.CaptureLog
|
||||
|
@ -644,6 +645,73 @@ test "returns the relationships for the current user", %{conn: conn} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "locked accounts" do
|
||||
test "/api/v1/follow_requests works" do
|
||||
user = insert(:user, %{info: %{"locked" => true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(other_user, user)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/follow_requests")
|
||||
|
||||
assert [relationship] = json_response(conn, 200)
|
||||
assert to_string(other_user.id) == relationship["id"]
|
||||
end
|
||||
|
||||
test "/api/v1/follow_requests/:id/authorize works" do
|
||||
user = insert(:user, %{info: %{"locked" => true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(other_user, user)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/follow_requests/#{other_user.id}/authorize")
|
||||
|
||||
assert relationship = json_response(conn, 200)
|
||||
assert to_string(other_user.id) == relationship["id"]
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == true
|
||||
end
|
||||
|
||||
test "/api/v1/follow_requests/:id/reject works" do
|
||||
user = insert(:user, %{info: %{"locked" => true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(other_user, user)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/follow_requests/#{other_user.id}/reject")
|
||||
|
||||
assert relationship = json_response(conn, 200)
|
||||
assert to_string(other_user.id) == relationship["id"]
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
end
|
||||
end
|
||||
|
||||
test "account fetching", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
|
@ -792,6 +860,46 @@ test "getting a list of blocks", %{conn: conn} do
|
|||
assert [%{"id" => ^other_user_id}] = json_response(conn, 200)
|
||||
end
|
||||
|
||||
test "blocking / unblocking a domain", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user, %{ap_id: "https://dogwhistle.zone/@pundit"})
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
user = User.get_cached_by_ap_id(user.ap_id)
|
||||
assert User.blocks?(user, other_user)
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> delete("/api/v1/domain_blocks", %{"domain" => "dogwhistle.zone"})
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
user = User.get_cached_by_ap_id(user.ap_id)
|
||||
refute User.blocks?(user, other_user)
|
||||
end
|
||||
|
||||
test "getting a list of domain blocks" do
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, user} = User.block_domain(user, "bad.site")
|
||||
{:ok, user} = User.block_domain(user, "even.worse.site")
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> get("/api/v1/domain_blocks")
|
||||
|
||||
domain_blocks = json_response(conn, 200)
|
||||
|
||||
assert "bad.site" in domain_blocks
|
||||
assert "even.worse.site" in domain_blocks
|
||||
end
|
||||
|
||||
test "unimplemented mute endpoints" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
|
|
@ -64,11 +64,11 @@ test "a reply" do
|
|||
|
||||
status = StatusView.render("status.json", %{activity: activity})
|
||||
|
||||
assert status.in_reply_to_id == note.id
|
||||
assert status.in_reply_to_id == to_string(note.id)
|
||||
|
||||
[status] = StatusView.render("index.json", %{activities: [activity], as: :activity})
|
||||
|
||||
assert status.in_reply_to_id == note.id
|
||||
assert status.in_reply_to_id == to_string(note.id)
|
||||
end
|
||||
|
||||
test "contains mentions" do
|
||||
|
|
|
@ -580,6 +580,40 @@ test "with credentials", %{conn: conn, user: current_user} do
|
|||
end
|
||||
end
|
||||
|
||||
describe "POST /api/statuses/unretweet/:id" do
|
||||
setup [:valid_user]
|
||||
|
||||
test "without valid credentials", %{conn: conn} do
|
||||
note_activity = insert(:note_activity)
|
||||
conn = post(conn, "/api/statuses/unretweet/#{note_activity.id}.json")
|
||||
assert json_response(conn, 403) == %{"error" => "Invalid credentials."}
|
||||
end
|
||||
|
||||
test "with credentials", %{conn: conn, user: current_user} do
|
||||
note_activity = insert(:note_activity)
|
||||
|
||||
request_path = "/api/statuses/retweet/#{note_activity.id}.json"
|
||||
|
||||
_response =
|
||||
conn
|
||||
|> with_credentials(current_user.nickname, "test")
|
||||
|> post(request_path)
|
||||
|
||||
request_path = String.replace(request_path, "retweet", "unretweet")
|
||||
|
||||
response =
|
||||
conn
|
||||
|> with_credentials(current_user.nickname, "test")
|
||||
|> post(request_path)
|
||||
|
||||
activity = Repo.get(Activity, note_activity.id)
|
||||
activity_user = Repo.get_by(User, ap_id: note_activity.data["actor"])
|
||||
|
||||
assert json_response(response, 200) ==
|
||||
ActivityRepresenter.to_map(activity, %{user: activity_user, for: current_user})
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/account/register" do
|
||||
test "it creates a new user", %{conn: conn} do
|
||||
data = %{
|
||||
|
@ -762,6 +796,38 @@ test "it updates a user's profile", %{conn: conn} do
|
|||
|
||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
|
||||
end
|
||||
|
||||
test "it locks an account", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/account/update_profile.json", %{
|
||||
"locked" => "true"
|
||||
})
|
||||
|
||||
user = Repo.get!(User, user.id)
|
||||
assert user.info["locked"] == true
|
||||
|
||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
|
||||
end
|
||||
|
||||
test "it unlocks an account", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/account/update_profile.json", %{
|
||||
"locked" => "false"
|
||||
})
|
||||
|
||||
user = Repo.get!(User, user.id)
|
||||
assert user.info["locked"] == false
|
||||
|
||||
assert json_response(conn, 200) == UserView.render("user.json", %{user: user, for: user})
|
||||
end
|
||||
end
|
||||
|
||||
defp valid_user(_context) do
|
||||
|
@ -926,4 +992,72 @@ test "with credentials and valid password", %{conn: conn, user: current_user} do
|
|||
:timer.sleep(1000)
|
||||
end
|
||||
end
|
||||
|
||||
describe "GET /api/pleroma/friend_requests" do
|
||||
test "it lists friend requests" do
|
||||
user = insert(:user, %{info: %{"locked" => true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(other_user, user)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> get("/api/pleroma/friend_requests")
|
||||
|
||||
assert [relationship] = json_response(conn, 200)
|
||||
assert other_user.id == relationship["id"]
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/friendships/approve" do
|
||||
test "it approves a friend request" do
|
||||
user = insert(:user, %{info: %{"locked" => true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(other_user, user)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/pleroma/friendships/approve", %{"user_id" => to_string(other_user.id)})
|
||||
|
||||
assert relationship = json_response(conn, 200)
|
||||
assert other_user.id == relationship["id"]
|
||||
assert relationship["follows_you"] == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/friendships/deny" do
|
||||
test "it denies a friend request" do
|
||||
user = insert(:user, %{info: %{"locked" => true}})
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = ActivityPub.follow(other_user, user)
|
||||
|
||||
user = Repo.get(User, user.id)
|
||||
other_user = Repo.get(User, other_user.id)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
||||
conn =
|
||||
build_conn()
|
||||
|> assign(:user, user)
|
||||
|> post("/api/pleroma/friendships/deny", %{"user_id" => to_string(other_user.id)})
|
||||
|
||||
assert relationship = json_response(conn, 200)
|
||||
assert other_user.id == relationship["id"]
|
||||
assert relationship["follows_you"] == false
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -228,6 +228,17 @@ test "it retweets a status and returns the retweet" do
|
|||
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",
|
||||
|
|
|
@ -59,7 +59,8 @@ test "A user" do
|
|||
"statusnet_profile_url" => user.ap_id,
|
||||
"cover_photo" => banner,
|
||||
"background_image" => nil,
|
||||
"is_local" => true
|
||||
"is_local" => true,
|
||||
"locked" => false
|
||||
}
|
||||
|
||||
assert represented == UserView.render("show.json", %{user: user})
|
||||
|
@ -94,7 +95,8 @@ test "A user for a given other follower", %{user: user} do
|
|||
"statusnet_profile_url" => user.ap_id,
|
||||
"cover_photo" => banner,
|
||||
"background_image" => nil,
|
||||
"is_local" => true
|
||||
"is_local" => true,
|
||||
"locked" => false
|
||||
}
|
||||
|
||||
assert represented == UserView.render("show.json", %{user: user, for: follower})
|
||||
|
@ -130,7 +132,8 @@ test "A user that follows you", %{user: user} do
|
|||
"statusnet_profile_url" => follower.ap_id,
|
||||
"cover_photo" => banner,
|
||||
"background_image" => nil,
|
||||
"is_local" => true
|
||||
"is_local" => true,
|
||||
"locked" => false
|
||||
}
|
||||
|
||||
assert represented == UserView.render("show.json", %{user: follower, for: user})
|
||||
|
@ -173,7 +176,8 @@ test "A blocked user for the blocker" do
|
|||
"statusnet_profile_url" => user.ap_id,
|
||||
"cover_photo" => banner,
|
||||
"background_image" => nil,
|
||||
"is_local" => true
|
||||
"is_local" => true,
|
||||
"locked" => false
|
||||
}
|
||||
|
||||
blocker = Repo.get(User, blocker.id)
|
||||
|
|
Loading…
Reference in a new issue