Merge remote-tracking branch 'remotes/origin/develop' into output-of-relationships-in-statuses
This commit is contained in:
commit
a21baf89d8
52
CHANGELOG.md
52
CHANGELOG.md
|
@ -4,21 +4,67 @@ All notable changes to this project will be documented in this file.
|
|||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||
|
||||
## [unreleased]
|
||||
### Changed
|
||||
- **Breaking:** BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
|
||||
|
||||
### Removed
|
||||
- **Breaking:** removed `with_move` parameter from notifications timeline.
|
||||
|
||||
### Added
|
||||
- NodeInfo: `pleroma:api/v1/notifications:include_types_filter` to the `features` list.
|
||||
- NodeInfo: `pleroma_emoji_reactions` to the `features` list.
|
||||
- Configuration: `:restrict_unauthenticated` setting, restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
||||
- New HTTP adapter [gun](https://github.com/ninenines/gun). Gun adapter requires minimum OTP version of 22.2 otherwise Pleroma won’t start. For hackney OTP update is not required.
|
||||
<details>
|
||||
<summary>API Changes</summary>
|
||||
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||
</details>
|
||||
|
||||
### Fixed
|
||||
- Support pagination in conversations API
|
||||
|
||||
## [unreleased-patch]
|
||||
|
||||
## [2.0.2] - 2020-04-08
|
||||
### Added
|
||||
- Support for Funkwhale's `Audio` activity
|
||||
- Admin API: `PATCH /api/pleroma/admin/users/:nickname/update_credentials`
|
||||
|
||||
### Fixed
|
||||
- Blocked/muted users still generating push notifications
|
||||
- Input textbox for bio ignoring newlines
|
||||
- OTP: Inability to use PostgreSQL databases with SSL
|
||||
- `user delete_activities` breaking when trying to delete already deleted posts
|
||||
- Incorrect URL for Funkwhale channels
|
||||
|
||||
### Upgrade notes
|
||||
1. Restart Pleroma
|
||||
|
||||
## [2.0.1] - 2020-03-15
|
||||
### Security
|
||||
- Static-FE: Fix remote posts not being sanitized
|
||||
|
||||
### Fixed
|
||||
- 500 errors when no `Accept` header is present if Static-FE is enabled
|
||||
- Instance panel not being updated immediately due to wrong `Cache-Control` headers
|
||||
- Statuses posted with BBCode/Markdown having unncessary newlines in Pleroma-FE
|
||||
- OTP: Fix some settings not being migrated to in-database config properly
|
||||
- No `Cache-Control` headers on attachment/media proxy requests
|
||||
- Character limit enforcement being off by 1
|
||||
- Mastodon Streaming API: hashtag timelines not working
|
||||
|
||||
### Changed
|
||||
- BBCode and Markdown formatters will no longer return any `\n` and only use `<br/>` for newlines
|
||||
- Mastodon API: Allow registration without email if email verification is not enabled
|
||||
|
||||
### Upgrade notes
|
||||
#### Nginx only
|
||||
1. Remove `proxy_ignore_headers Cache-Control;` and `proxy_hide_header Cache-Control;` from your config.
|
||||
|
||||
#### Everyone
|
||||
1. Run database migrations (inside Pleroma directory):
|
||||
- OTP: `./bin/pleroma_ctl migrate`
|
||||
- From Source: `mix ecto.migrate`
|
||||
2. Restart Pleroma
|
||||
|
||||
## [2.0.0] - 2019-03-08
|
||||
### Security
|
||||
- Mastodon API: Fix being able to request enormous amount of statuses in timelines leading to DoS. Now limited to 40 per request.
|
||||
|
|
4
COPYING
4
COPYING
|
@ -1,4 +1,4 @@
|
|||
Unless otherwise stated this repository is copyright © 2017-2019
|
||||
Unless otherwise stated this repository is copyright © 2017-2020
|
||||
Pleroma Authors <https://pleroma.social/>, and is distributed under
|
||||
The GNU Affero General Public License Version 3, you should have received a
|
||||
copy of the license file as AGPL-3.
|
||||
|
@ -23,7 +23,7 @@ priv/static/images/pleroma-fox-tan-shy.png
|
|||
|
||||
---
|
||||
|
||||
The following files are copyright © 2017-2019 Pleroma Authors
|
||||
The following files are copyright © 2017-2020 Pleroma Authors
|
||||
<https://pleroma.social/>, and are distributed under the Creative Commons
|
||||
Attribution-ShareAlike 4.0 International license, you should have received
|
||||
a copy of the license file as CC-BY-SA-4.0.
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"skip_files": [
|
||||
"test/support",
|
||||
"lib/mix/tasks/pleroma/benchmark.ex"
|
||||
]
|
||||
}
|
|
@ -392,6 +392,19 @@ Note: Available `:permission_group` is currently moderator and admin. 404 is ret
|
|||
- `email`
|
||||
- `name`, optional
|
||||
|
||||
- Response:
|
||||
- On success: `204`, empty response
|
||||
- On failure:
|
||||
- 400 Bad Request, JSON:
|
||||
|
||||
```json
|
||||
[
|
||||
{
|
||||
"error": "Appropriate error message here"
|
||||
}
|
||||
]
|
||||
```
|
||||
|
||||
## `GET /api/pleroma/admin/users/:nickname/password_reset`
|
||||
|
||||
### Get a password reset token for a given nickname
|
||||
|
|
|
@ -164,6 +164,7 @@ Additional parameters can be added to the JSON body/Form data:
|
|||
- `actor_type` - the type of this account.
|
||||
|
||||
### Pleroma Settings Store
|
||||
|
||||
Pleroma has mechanism that allows frontends to save blobs of json for each user on the backend. This can be used to save frontend-specific settings for a user that the backend does not need to know about.
|
||||
|
||||
The parameter should have a form of `{frontend_name: {...}}`, with `frontend_name` identifying your type of client, e.g. `pleroma_fe`. It will overwrite everything under this property, but will not overwrite other frontend's settings.
|
||||
|
@ -172,17 +173,20 @@ This information is returned in the `verify_credentials` endpoint.
|
|||
|
||||
## Authentication
|
||||
|
||||
*Pleroma supports refreshing tokens.
|
||||
*Pleroma supports refreshing tokens.*
|
||||
|
||||
`POST /oauth/token`
|
||||
Post here request with grant_type=refresh_token to obtain new access token. Returns an access token.
|
||||
|
||||
Post here request with `grant_type=refresh_token` to obtain new access token. Returns an access token.
|
||||
|
||||
## Account Registration
|
||||
|
||||
`POST /api/v1/accounts`
|
||||
|
||||
Has theses additional parameters (which are the same as in Pleroma-API):
|
||||
* `fullname`: optional
|
||||
* `bio`: optional
|
||||
* `captcha_solution`: optional, contains provider-specific captcha solution,
|
||||
* `captcha_token`: optional, contains provider-specific captcha token
|
||||
* `token`: invite token required when the registerations aren't public.
|
||||
|
||||
- `fullname`: optional
|
||||
- `bio`: optional
|
||||
- `captcha_solution`: optional, contains provider-specific captcha solution,
|
||||
- `captcha_token`: optional, contains provider-specific captcha token
|
||||
- `token`: invite token required when the registrations aren't public.
|
||||
|
|
|
@ -431,7 +431,7 @@ The status posting endpoint takes an additional parameter, `in_reply_to_conversa
|
|||
|
||||
# Emoji Reactions
|
||||
|
||||
Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character.
|
||||
Emoji reactions work a lot like favourites do. They make it possible to react to a post with a single emoji character. To detect the presence of this feature, you can check `pleroma_emoji_reactions` entry in the features list of nodeinfo.
|
||||
|
||||
## `PUT /api/v1/pleroma/statuses/:id/reactions/:emoji`
|
||||
### React to a post with a unicode emoji
|
||||
|
|
|
@ -41,6 +41,6 @@ mix pleroma.emoji gen-pack PACK-URL
|
|||
|
||||
Currently, only .zip archives are recognized as remote pack files and packs are therefore assumed to be zip archives. This command is intended to run interactively and will first ask you some basic questions about the pack, then download the remote file and generate an SHA256 checksum for it, then generate an emoji file list for you.
|
||||
|
||||
The manifest entry will either be written to a newly created `index.json` file or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
|
||||
The manifest entry will either be written to a newly created `pack_name.json` file (pack name is asked in questions) or appended to the existing one, *replacing* the old pack with the same name if it was in the file previously.
|
||||
|
||||
The file list will be written to the file specified previously, *replacing* that file. You _should_ check that the file list doesn't contain anything you don't need in the pack, that is, anything that is not an emoji (the whole pack is downloaded, but only emoji files are extracted).
|
||||
|
|
|
@ -5,7 +5,6 @@
|
|||
defmodule Mix.Pleroma do
|
||||
@doc "Common functions to be reused in mix tasks"
|
||||
def start_pleroma do
|
||||
Mix.Task.run("app.start")
|
||||
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
|
||||
|
||||
if Pleroma.Config.get(:env) != :test do
|
||||
|
|
|
@ -14,8 +14,8 @@ def run(["ls-packs" | args]) do
|
|||
|
||||
{options, [], []} = parse_global_opts(args)
|
||||
|
||||
manifest =
|
||||
fetch_manifest(if options[:manifest], do: options[:manifest], else: default_manifest())
|
||||
url_or_path = options[:manifest] || default_manifest()
|
||||
manifest = fetch_manifest(url_or_path)
|
||||
|
||||
Enum.each(manifest, fn {name, info} ->
|
||||
to_print = [
|
||||
|
@ -40,9 +40,9 @@ def run(["get-packs" | args]) do
|
|||
|
||||
{options, pack_names, []} = parse_global_opts(args)
|
||||
|
||||
manifest_url = if options[:manifest], do: options[:manifest], else: default_manifest()
|
||||
url_or_path = options[:manifest] || default_manifest()
|
||||
|
||||
manifest = fetch_manifest(manifest_url)
|
||||
manifest = fetch_manifest(url_or_path)
|
||||
|
||||
for pack_name <- pack_names do
|
||||
if Map.has_key?(manifest, pack_name) do
|
||||
|
@ -75,7 +75,10 @@ def run(["get-packs" | args]) do
|
|||
end
|
||||
|
||||
# The url specified in files should be in the same directory
|
||||
files_url = Path.join(Path.dirname(manifest_url), pack["files"])
|
||||
files_url =
|
||||
url_or_path
|
||||
|> Path.dirname()
|
||||
|> Path.join(pack["files"])
|
||||
|
||||
IO.puts(
|
||||
IO.ANSI.format([
|
||||
|
@ -133,38 +136,51 @@ def run(["get-packs" | args]) do
|
|||
end
|
||||
end
|
||||
|
||||
def run(["gen-pack", src]) do
|
||||
def run(["gen-pack" | args]) do
|
||||
start_pleroma()
|
||||
|
||||
proposed_name = Path.basename(src) |> Path.rootname()
|
||||
name = String.trim(IO.gets("Pack name [#{proposed_name}]: "))
|
||||
# If there's no name, use the default one
|
||||
name = if String.length(name) > 0, do: name, else: proposed_name
|
||||
|
||||
license = String.trim(IO.gets("License: "))
|
||||
homepage = String.trim(IO.gets("Homepage: "))
|
||||
description = String.trim(IO.gets("Description: "))
|
||||
|
||||
proposed_files_name = "#{name}.json"
|
||||
files_name = String.trim(IO.gets("Save file list to [#{proposed_files_name}]: "))
|
||||
files_name = if String.length(files_name) > 0, do: files_name, else: proposed_files_name
|
||||
|
||||
default_exts = [".png", ".gif"]
|
||||
default_exts_str = Enum.join(default_exts, " ")
|
||||
|
||||
exts =
|
||||
String.trim(
|
||||
IO.gets("Emoji file extensions (separated with spaces) [#{default_exts_str}]: ")
|
||||
{opts, [src], []} =
|
||||
OptionParser.parse(
|
||||
args,
|
||||
strict: [
|
||||
name: :string,
|
||||
license: :string,
|
||||
homepage: :string,
|
||||
description: :string,
|
||||
files: :string,
|
||||
extensions: :string
|
||||
]
|
||||
)
|
||||
|
||||
proposed_name = Path.basename(src) |> Path.rootname()
|
||||
name = get_option(opts, :name, "Pack name:", proposed_name)
|
||||
license = get_option(opts, :license, "License:")
|
||||
homepage = get_option(opts, :homepage, "Homepage:")
|
||||
description = get_option(opts, :description, "Description:")
|
||||
|
||||
proposed_files_name = "#{name}_files.json"
|
||||
files_name = get_option(opts, :files, "Save file list to:", proposed_files_name)
|
||||
|
||||
default_exts = [".png", ".gif"]
|
||||
|
||||
custom_exts =
|
||||
get_option(
|
||||
opts,
|
||||
:extensions,
|
||||
"Emoji file extensions (separated with spaces):",
|
||||
Enum.join(default_exts, " ")
|
||||
)
|
||||
|> String.split(" ", trim: true)
|
||||
|
||||
exts =
|
||||
if String.length(exts) > 0 do
|
||||
String.split(exts, " ")
|
||||
|> Enum.filter(fn e -> e |> String.trim() |> String.length() > 0 end)
|
||||
else
|
||||
if MapSet.equal?(MapSet.new(default_exts), MapSet.new(custom_exts)) do
|
||||
default_exts
|
||||
else
|
||||
custom_exts
|
||||
end
|
||||
|
||||
IO.puts("Using #{Enum.join(exts, " ")} extensions")
|
||||
|
||||
IO.puts("Downloading the pack and generating SHA256")
|
||||
|
||||
binary_archive = Tesla.get!(client(), src).body
|
||||
|
@ -194,14 +210,16 @@ def run(["gen-pack", src]) do
|
|||
IO.puts("""
|
||||
|
||||
#{files_name} has been created and contains the list of all found emojis in the pack.
|
||||
Please review the files in the remove those not needed.
|
||||
Please review the files in the pack and remove those not needed.
|
||||
""")
|
||||
|
||||
if File.exists?("index.json") do
|
||||
existing_data = File.read!("index.json") |> Jason.decode!()
|
||||
pack_file = "#{name}.json"
|
||||
|
||||
if File.exists?(pack_file) do
|
||||
existing_data = File.read!(pack_file) |> Jason.decode!()
|
||||
|
||||
File.write!(
|
||||
"index.json",
|
||||
pack_file,
|
||||
Jason.encode!(
|
||||
Map.merge(
|
||||
existing_data,
|
||||
|
@ -211,11 +229,11 @@ def run(["gen-pack", src]) do
|
|||
)
|
||||
)
|
||||
|
||||
IO.puts("index.json file has been update with the #{name} pack")
|
||||
IO.puts("#{pack_file} has been updated with the #{name} pack")
|
||||
else
|
||||
File.write!("index.json", Jason.encode!(pack_json, pretty: true))
|
||||
File.write!(pack_file, Jason.encode!(pack_json, pretty: true))
|
||||
|
||||
IO.puts("index.json has been created with the #{name} pack")
|
||||
IO.puts("#{pack_file} has been created with the #{name} pack")
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -4,10 +4,16 @@
|
|||
|
||||
import EctoEnum
|
||||
|
||||
defenum(UserRelationshipTypeEnum,
|
||||
defenum(Pleroma.UserRelationship.Type,
|
||||
block: 1,
|
||||
mute: 2,
|
||||
reblog_mute: 3,
|
||||
notification_mute: 4,
|
||||
inverse_subscription: 5
|
||||
)
|
||||
|
||||
defenum(Pleroma.FollowingRelationship.State,
|
||||
follow_pending: 1,
|
||||
follow_accept: 2,
|
||||
follow_reject: 3
|
||||
)
|
||||
|
|
|
@ -8,12 +8,13 @@ defmodule Pleroma.FollowingRelationship do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias FlakeId.Ecto.CompatType
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
||||
schema "following_relationships" do
|
||||
field(:state, :string, default: "accept")
|
||||
field(:state, Pleroma.FollowingRelationship.State, default: :follow_pending)
|
||||
|
||||
belongs_to(:follower, User, type: CompatType)
|
||||
belongs_to(:following, User, type: CompatType)
|
||||
|
@ -27,6 +28,18 @@ def changeset(%__MODULE__{} = following_relationship, attrs) do
|
|||
|> put_assoc(:follower, attrs.follower)
|
||||
|> put_assoc(:following, attrs.following)
|
||||
|> validate_required([:state, :follower, :following])
|
||||
|> unique_constraint(:follower_id,
|
||||
name: :following_relationships_follower_id_following_id_index
|
||||
)
|
||||
|> validate_not_self_relationship()
|
||||
end
|
||||
|
||||
def state_to_enum(state) when state in ["pending", "accept", "reject"] do
|
||||
String.to_existing_atom("follow_#{state}")
|
||||
end
|
||||
|
||||
def state_to_enum(state) do
|
||||
raise "State is not convertible to Pleroma.FollowingRelationship.State: #{state}"
|
||||
end
|
||||
|
||||
def get(%User{} = follower, %User{} = following) do
|
||||
|
@ -35,7 +48,7 @@ def get(%User{} = follower, %User{} = following) do
|
|||
|> Repo.one()
|
||||
end
|
||||
|
||||
def update(follower, following, "reject"), do: unfollow(follower, following)
|
||||
def update(follower, following, :follow_reject), do: unfollow(follower, following)
|
||||
|
||||
def update(%User{} = follower, %User{} = following, state) do
|
||||
case get(follower, following) do
|
||||
|
@ -50,7 +63,7 @@ def update(%User{} = follower, %User{} = following, state) do
|
|||
end
|
||||
end
|
||||
|
||||
def follow(%User{} = follower, %User{} = following, state \\ "accept") do
|
||||
def follow(%User{} = follower, %User{} = following, state \\ :follow_accept) do
|
||||
%__MODULE__{}
|
||||
|> changeset(%{follower: follower, following: following, state: state})
|
||||
|> Repo.insert(on_conflict: :nothing)
|
||||
|
@ -80,7 +93,7 @@ def following_count(%User{} = user) do
|
|||
def get_follow_requests(%User{id: id}) do
|
||||
__MODULE__
|
||||
|> join(:inner, [r], f in assoc(r, :follower))
|
||||
|> where([r], r.state == "pending")
|
||||
|> where([r], r.state == ^:follow_pending)
|
||||
|> where([r], r.following_id == ^id)
|
||||
|> select([r, f], f)
|
||||
|> Repo.all()
|
||||
|
@ -88,7 +101,7 @@ def get_follow_requests(%User{id: id}) do
|
|||
|
||||
def following?(%User{id: follower_id}, %User{id: followed_id}) do
|
||||
__MODULE__
|
||||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: "accept")
|
||||
|> where(follower_id: ^follower_id, following_id: ^followed_id, state: ^:follow_accept)
|
||||
|> Repo.exists?()
|
||||
end
|
||||
|
||||
|
@ -97,7 +110,7 @@ def following(%User{} = user) do
|
|||
__MODULE__
|
||||
|> join(:inner, [r], u in User, on: r.following_id == u.id)
|
||||
|> where([r], r.follower_id == ^user.id)
|
||||
|> where([r], r.state == "accept")
|
||||
|> where([r], r.state == ^:follow_accept)
|
||||
|> select([r, u], u.follower_address)
|
||||
|> Repo.all()
|
||||
|
||||
|
@ -157,4 +170,30 @@ def find(following_relationships, follower, following) do
|
|||
fr -> fr.follower_id == follower.id and fr.following_id == following.id
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_not_self_relationship(%Changeset{} = changeset) do
|
||||
changeset
|
||||
|> validate_follower_id_following_id_inequality()
|
||||
|> validate_following_id_follower_id_inequality()
|
||||
end
|
||||
|
||||
defp validate_follower_id_following_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :follower_id, fn _, follower_id ->
|
||||
if follower_id == get_field(changeset, :following_id) do
|
||||
[source_id: "can't be equal to following_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_following_id_follower_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :following_id, fn _, following_id ->
|
||||
if following_id == get_field(changeset, :follower_id) do
|
||||
[target_id: "can't be equal to follower_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -35,9 +35,19 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
|
|||
nickname_text = get_nickname_text(nickname, opts)
|
||||
|
||||
link =
|
||||
~s(<span class="h-card"><a data-user="#{id}" class="u-url mention" href="#{ap_id}" rel="ugc">@<span>#{
|
||||
nickname_text
|
||||
}</span></a></span>)
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:span,
|
||||
Phoenix.HTML.Tag.content_tag(
|
||||
:a,
|
||||
["@", Phoenix.HTML.Tag.content_tag(:span, nickname_text)],
|
||||
"data-user": id,
|
||||
class: "u-url mention",
|
||||
href: ap_id,
|
||||
rel: "ugc"
|
||||
),
|
||||
class: "h-card"
|
||||
)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|
||||
{link, %{acc | mentions: MapSet.put(acc.mentions, {"@" <> nickname, user})}}
|
||||
|
||||
|
@ -49,7 +59,15 @@ def mention_handler("@" <> nickname, buffer, opts, acc) do
|
|||
def hashtag_handler("#" <> tag = tag_text, _buffer, _opts, acc) do
|
||||
tag = String.downcase(tag)
|
||||
url = "#{Pleroma.Web.base_url()}/tag/#{tag}"
|
||||
link = ~s(<a class="hashtag" data-tag="#{tag}" href="#{url}" rel="tag ugc">#{tag_text}</a>)
|
||||
|
||||
link =
|
||||
Phoenix.HTML.Tag.content_tag(:a, tag_text,
|
||||
class: "hashtag",
|
||||
"data-tag": tag,
|
||||
href: url,
|
||||
rel: "tag ugc"
|
||||
)
|
||||
|> Phoenix.HTML.safe_to_string()
|
||||
|
||||
{link, %{acc | tags: MapSet.put(acc.tags, {tag_text, tag})}}
|
||||
end
|
||||
|
|
|
@ -49,8 +49,10 @@ def open(%URI{} = uri, name, opts) do
|
|||
|
||||
key = "#{uri.scheme}:#{uri.host}:#{uri.port}"
|
||||
|
||||
max_connections = pool_opts[:max_connections] || 250
|
||||
|
||||
conn_pid =
|
||||
if Connections.count(name) < opts[:max_connection] do
|
||||
if Connections.count(name) < max_connections do
|
||||
do_open(uri, opts)
|
||||
else
|
||||
close_least_used_and_do_open(name, uri, opts)
|
||||
|
|
|
@ -32,6 +32,18 @@ def get_actor(%{"actor" => nil, "attributedTo" => actor}) when not is_nil(actor)
|
|||
get_actor(%{"actor" => actor})
|
||||
end
|
||||
|
||||
def get_object(%{"object" => id}) when is_binary(id) do
|
||||
id
|
||||
end
|
||||
|
||||
def get_object(%{"object" => %{"id" => id}}) when is_binary(id) do
|
||||
id
|
||||
end
|
||||
|
||||
def get_object(_) do
|
||||
nil
|
||||
end
|
||||
|
||||
# TODO: We explicitly allow 'tag' URIs through, due to references to legacy OStatus
|
||||
# objects being present in the test suite environment. Once these objects are
|
||||
# removed, please also remove this.
|
||||
|
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.User do
|
|||
alias Pleroma.Conversation.Participation
|
||||
alias Pleroma.Delivery
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Formatter
|
||||
alias Pleroma.HTML
|
||||
alias Pleroma.Keys
|
||||
alias Pleroma.Notification
|
||||
|
@ -452,7 +453,7 @@ defp put_fields(changeset) do
|
|||
|
||||
fields =
|
||||
raw_fields
|
||||
|> Enum.map(fn f -> Map.update!(f, "value", &AutoLinker.link(&1)) end)
|
||||
|> Enum.map(fn f -> Map.update!(f, "value", &parse_fields(&1)) end)
|
||||
|
||||
changeset
|
||||
|> put_change(:raw_fields, raw_fields)
|
||||
|
@ -462,6 +463,12 @@ defp put_fields(changeset) do
|
|||
end
|
||||
end
|
||||
|
||||
defp parse_fields(value) do
|
||||
value
|
||||
|> Formatter.linkify(mentions_format: :full)
|
||||
|> elem(0)
|
||||
end
|
||||
|
||||
defp put_change_if_present(changeset, map_field, value_function) do
|
||||
if value = get_change(changeset, map_field) do
|
||||
with {:ok, new_value} <- value_function.(value) do
|
||||
|
@ -693,7 +700,7 @@ def needs_update?(_), do: true
|
|||
|
||||
@spec maybe_direct_follow(User.t(), User.t()) :: {:ok, User.t()} | {:error, String.t()}
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true, locked: true} = followed) do
|
||||
follow(follower, followed, "pending")
|
||||
follow(follower, followed, :follow_pending)
|
||||
end
|
||||
|
||||
def maybe_direct_follow(%User{} = follower, %User{local: true} = followed) do
|
||||
|
@ -713,14 +720,14 @@ def maybe_direct_follow(%User{} = follower, %User{} = followed) do
|
|||
def follow_all(follower, followeds) do
|
||||
followeds
|
||||
|> Enum.reject(fn followed -> blocks?(follower, followed) || blocks?(followed, follower) end)
|
||||
|> Enum.each(&follow(follower, &1, "accept"))
|
||||
|> Enum.each(&follow(follower, &1, :follow_accept))
|
||||
|
||||
set_cache(follower)
|
||||
end
|
||||
|
||||
defdelegate following(user), to: FollowingRelationship
|
||||
|
||||
def follow(%User{} = follower, %User{} = followed, state \\ "accept") do
|
||||
def follow(%User{} = follower, %User{} = followed, state \\ :follow_accept) do
|
||||
deny_follow_blocked = Pleroma.Config.get([:user, :deny_follow_blocked])
|
||||
|
||||
cond do
|
||||
|
@ -747,7 +754,7 @@ def unfollow(%User{ap_id: ap_id}, %User{ap_id: ap_id}) do
|
|||
|
||||
def unfollow(%User{} = follower, %User{} = followed) do
|
||||
case get_follow_state(follower, followed) do
|
||||
state when state in ["accept", "pending"] ->
|
||||
state when state in [:follow_pending, :follow_accept] ->
|
||||
FollowingRelationship.unfollow(follower, followed)
|
||||
{:ok, followed} = update_follower_count(followed)
|
||||
|
||||
|
@ -765,6 +772,7 @@ def unfollow(%User{} = follower, %User{} = followed) do
|
|||
|
||||
defdelegate following?(follower, followed), to: FollowingRelationship
|
||||
|
||||
@doc "Returns follow state as Pleroma.FollowingRelationship.State value"
|
||||
def get_follow_state(%User{} = follower, %User{} = following) do
|
||||
following_relationship = FollowingRelationship.get(follower, following)
|
||||
get_follow_state(follower, following, following_relationship)
|
||||
|
@ -778,8 +786,11 @@ def get_follow_state(
|
|||
case {following_relationship, following.local} do
|
||||
{nil, false} ->
|
||||
case Utils.fetch_latest_follow(follower, following) do
|
||||
%{data: %{"state" => state}} when state in ["pending", "accept"] -> state
|
||||
_ -> nil
|
||||
%Activity{data: %{"state" => state}} when state in ["pending", "accept"] ->
|
||||
FollowingRelationship.state_to_enum(state)
|
||||
|
||||
_ ->
|
||||
nil
|
||||
end
|
||||
|
||||
{%{state: state}, _} ->
|
||||
|
@ -1278,7 +1289,7 @@ def blocks?(nil, _), do: false
|
|||
|
||||
def blocks?(%User{} = user, %User{} = target) do
|
||||
blocks_user?(user, target) ||
|
||||
(!User.following?(user, target) && blocks_domain?(user, target))
|
||||
(blocks_domain?(user, target) and not User.following?(user, target))
|
||||
end
|
||||
|
||||
def blocks_user?(%User{} = user, %User{} = target) do
|
||||
|
@ -1979,17 +1990,6 @@ def fields(%{fields: nil}), do: []
|
|||
|
||||
def fields(%{fields: fields}), do: fields
|
||||
|
||||
def sanitized_fields(%User{} = user) do
|
||||
user
|
||||
|> User.fields()
|
||||
|> Enum.map(fn %{"name" => name, "value" => value} ->
|
||||
%{
|
||||
"name" => name,
|
||||
"value" => Pleroma.HTML.filter_tags(value, Pleroma.HTML.Scrubber.LinksOnly)
|
||||
}
|
||||
end)
|
||||
end
|
||||
|
||||
def validate_fields(changeset, remote? \\ false) do
|
||||
limit_name = if remote?, do: :max_remote_account_fields, else: :max_account_fields
|
||||
limit = Pleroma.Config.get([:instance, limit_name], 0)
|
||||
|
|
|
@ -148,7 +148,7 @@ defp compose_query({:followers, %User{id: id}}, query) do
|
|||
as: :relationships,
|
||||
on: r.following_id == ^id and r.follower_id == u.id
|
||||
)
|
||||
|> where([relationships: r], r.state == "accept")
|
||||
|> where([relationships: r], r.state == ^:follow_accept)
|
||||
end
|
||||
|
||||
defp compose_query({:friends, %User{id: id}}, query) do
|
||||
|
@ -158,7 +158,7 @@ defp compose_query({:friends, %User{id: id}}, query) do
|
|||
as: :relationships,
|
||||
on: r.following_id == u.id and r.follower_id == ^id
|
||||
)
|
||||
|> where([relationships: r], r.state == "accept")
|
||||
|> where([relationships: r], r.state == ^:follow_accept)
|
||||
end
|
||||
|
||||
defp compose_query({:recipients_from_activity, to}, query) do
|
||||
|
@ -173,7 +173,7 @@ defp compose_query({:recipients_from_activity, to}, query) do
|
|||
)
|
||||
|> where(
|
||||
[u, following: f, relationships: r],
|
||||
u.ap_id in ^to or (f.follower_address in ^to and r.state == "accept")
|
||||
u.ap_id in ^to or (f.follower_address in ^to and r.state == ^:follow_accept)
|
||||
)
|
||||
|> distinct(true)
|
||||
end
|
||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.UserRelationship do
|
|||
import Ecto.Changeset
|
||||
import Ecto.Query
|
||||
|
||||
alias Ecto.Changeset
|
||||
alias Pleroma.FollowingRelationship
|
||||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
|
@ -16,12 +17,12 @@ defmodule Pleroma.UserRelationship do
|
|||
schema "user_relationships" do
|
||||
belongs_to(:source, User, type: FlakeId.Ecto.CompatType)
|
||||
belongs_to(:target, User, type: FlakeId.Ecto.CompatType)
|
||||
field(:relationship_type, UserRelationshipTypeEnum)
|
||||
field(:relationship_type, Pleroma.UserRelationship.Type)
|
||||
|
||||
timestamps(updated_at: false)
|
||||
end
|
||||
|
||||
for relationship_type <- Keyword.keys(UserRelationshipTypeEnum.__enum_map__()) do
|
||||
for relationship_type <- Keyword.keys(Pleroma.UserRelationship.Type.__enum_map__()) do
|
||||
# `def create_block/2`, `def create_mute/2`, `def create_reblog_mute/2`,
|
||||
# `def create_notification_mute/2`, `def create_inverse_subscription/2`
|
||||
def unquote(:"create_#{relationship_type}")(source, target),
|
||||
|
@ -40,7 +41,7 @@ def unquote(:"#{relationship_type}_exists?")(source, target),
|
|||
|
||||
def user_relationship_types, do: Keyword.keys(user_relationship_mappings())
|
||||
|
||||
def user_relationship_mappings, do: UserRelationshipTypeEnum.__enum_map__()
|
||||
def user_relationship_mappings, do: Pleroma.UserRelationship.Type.__enum_map__()
|
||||
|
||||
def changeset(%UserRelationship{} = user_relationship, params \\ %{}) do
|
||||
user_relationship
|
||||
|
@ -157,16 +158,14 @@ def view_relationships_option(%User{} = reading_user, actors, opts) do
|
|||
%{user_relationships: user_relationships, following_relationships: following_relationships}
|
||||
end
|
||||
|
||||
defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
||||
defp validate_not_self_relationship(%Changeset{} = changeset) do
|
||||
changeset
|
||||
|> validate_change(:target_id, fn _, target_id ->
|
||||
if target_id == get_field(changeset, :source_id) do
|
||||
[target_id: "can't be equal to source_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
|> validate_change(:source_id, fn _, source_id ->
|
||||
|> validate_source_id_target_id_inequality()
|
||||
|> validate_target_id_source_id_inequality()
|
||||
end
|
||||
|
||||
defp validate_source_id_target_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :source_id, fn _, source_id ->
|
||||
if source_id == get_field(changeset, :target_id) do
|
||||
[source_id: "can't be equal to target_id"]
|
||||
else
|
||||
|
@ -174,4 +173,14 @@ defp validate_not_self_relationship(%Ecto.Changeset{} = changeset) do
|
|||
end
|
||||
end)
|
||||
end
|
||||
|
||||
defp validate_target_id_source_id_inequality(%Changeset{} = changeset) do
|
||||
validate_change(changeset, :target_id, fn _, target_id ->
|
||||
if target_id == get_field(changeset, :source_id) do
|
||||
[target_id: "can't be equal to source_id"]
|
||||
else
|
||||
[]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -125,6 +125,21 @@ def increase_poll_votes_if_vote(%{
|
|||
|
||||
def increase_poll_votes_if_vote(_create_data), do: :noop
|
||||
|
||||
@spec persist(map(), keyword()) :: {:ok, Activity.t() | Object.t()}
|
||||
def persist(object, meta) do
|
||||
with local <- Keyword.fetch!(meta, :local),
|
||||
{recipients, _, _} <- get_recipients(object),
|
||||
{:ok, activity} <-
|
||||
Repo.insert(%Activity{
|
||||
data: object,
|
||||
local: local,
|
||||
recipients: recipients,
|
||||
actor: object["actor"]
|
||||
}) do
|
||||
{:ok, activity, meta}
|
||||
end
|
||||
end
|
||||
|
||||
@spec insert(map(), boolean(), boolean(), boolean()) :: {:ok, Activity.t()} | {:error, any()}
|
||||
def insert(map, local \\ true, fake \\ false, bypass_actor_check \\ false) when is_map(map) do
|
||||
with nil <- Activity.normalize(map),
|
||||
|
@ -706,7 +721,7 @@ def move(%User{} = origin, %User{} = target, local \\ true) do
|
|||
end
|
||||
end
|
||||
|
||||
defp fetch_activities_for_context_query(context, opts) do
|
||||
def fetch_activities_for_context_query(context, opts) do
|
||||
public = [Constants.as_public()]
|
||||
|
||||
recipients =
|
||||
|
|
|
@ -0,0 +1,43 @@
|
|||
defmodule Pleroma.Web.ActivityPub.Builder do
|
||||
@moduledoc """
|
||||
This module builds the objects. Meant to be used for creating local objects.
|
||||
|
||||
This module encodes our addressing policies and general shape of our objects.
|
||||
"""
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
@spec like(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||
def like(actor, object) do
|
||||
object_actor = User.get_cached_by_ap_id(object.data["actor"])
|
||||
|
||||
# Address the actor of the object, and our actor's follower collection if the post is public.
|
||||
to =
|
||||
if Visibility.is_public?(object) do
|
||||
[actor.follower_address, object.data["actor"]]
|
||||
else
|
||||
[object.data["actor"]]
|
||||
end
|
||||
|
||||
# CC everyone who's been addressed in the object, except ourself and the object actor's
|
||||
# follower collection
|
||||
cc =
|
||||
(object.data["to"] ++ (object.data["cc"] || []))
|
||||
|> List.delete(actor.ap_id)
|
||||
|> List.delete(object_actor.follower_address)
|
||||
|
||||
{:ok,
|
||||
%{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"actor" => actor.ap_id,
|
||||
"type" => "Like",
|
||||
"object" => object.data["id"],
|
||||
"to" => to,
|
||||
"cc" => cc,
|
||||
"context" => object.data["context"]
|
||||
}, []}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||
@moduledoc """
|
||||
This module is responsible for validating an object (which can be an activity)
|
||||
and checking if it is both well formed and also compatible with our view of
|
||||
the system.
|
||||
"""
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
|
||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||
def validate(object, meta)
|
||||
|
||||
def validate(%{"type" => "Like"} = object, meta) do
|
||||
with {:ok, object} <-
|
||||
object |> LikeValidator.cast_and_validate() |> Ecto.Changeset.apply_action(:insert) do
|
||||
object = stringify_keys(object |> Map.from_struct())
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
||||
|
||||
def stringify_keys(object) do
|
||||
object
|
||||
|> Map.new(fn {key, val} -> {to_string(key), val} end)
|
||||
end
|
||||
|
||||
def fetch_actor_and_object(object) do
|
||||
User.get_or_fetch_by_ap_id(object["actor"])
|
||||
Object.normalize(object["object"])
|
||||
:ok
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations do
|
||||
import Ecto.Changeset
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.User
|
||||
|
||||
def validate_actor_presence(cng, field_name \\ :actor) do
|
||||
cng
|
||||
|> validate_change(field_name, fn field_name, actor ->
|
||||
if User.get_cached_by_ap_id(actor) do
|
||||
[]
|
||||
else
|
||||
[{field_name, "can't find user"}]
|
||||
end
|
||||
end)
|
||||
end
|
||||
|
||||
def validate_object_presence(cng, field_name \\ :object) do
|
||||
cng
|
||||
|> validate_change(field_name, fn field_name, object ->
|
||||
if Object.get_cached_by_ap_id(object) do
|
||||
[]
|
||||
else
|
||||
[{field_name, "can't find object"}]
|
||||
end
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,30 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateNoteValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:type, :string)
|
||||
field(:to, {:array, :string})
|
||||
field(:cc, {:array, :string})
|
||||
field(:bto, {:array, :string}, default: [])
|
||||
field(:bcc, {:array, :string}, default: [])
|
||||
|
||||
embeds_one(:object, NoteValidator)
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
cast(%__MODULE__{}, data, __schema__(:fields))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,57 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
import Ecto.Changeset
|
||||
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:type, :string)
|
||||
field(:object, Types.ObjectID)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:context, :string)
|
||||
field(:to, {:array, :string})
|
||||
field(:cc, {:array, :string})
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data()
|
||||
|> validate_data()
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, [:id, :type, :object, :actor, :context, :to, :cc])
|
||||
end
|
||||
|
||||
def validate_data(data_cng) do
|
||||
data_cng
|
||||
|> validate_inclusion(:type, ["Like"])
|
||||
|> validate_required([:id, :type, :object, :actor, :context, :to, :cc])
|
||||
|> validate_actor_presence()
|
||||
|> validate_object_presence()
|
||||
|> validate_existing_like()
|
||||
end
|
||||
|
||||
def validate_existing_like(%{changes: %{actor: actor, object: object}} = cng) do
|
||||
if Utils.get_existing_like(actor, %{data: %{"id" => object}}) do
|
||||
cng
|
||||
|> add_error(:actor, "already liked this object")
|
||||
|> add_error(:object, "already liked by this actor")
|
||||
else
|
||||
cng
|
||||
end
|
||||
end
|
||||
|
||||
def validate_existing_like(cng), do: cng
|
||||
end
|
|
@ -0,0 +1,63 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator do
|
||||
use Ecto.Schema
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types
|
||||
|
||||
import Ecto.Changeset
|
||||
|
||||
@primary_key false
|
||||
|
||||
embedded_schema do
|
||||
field(:id, Types.ObjectID, primary_key: true)
|
||||
field(:to, {:array, :string}, default: [])
|
||||
field(:cc, {:array, :string}, default: [])
|
||||
field(:bto, {:array, :string}, default: [])
|
||||
field(:bcc, {:array, :string}, default: [])
|
||||
# TODO: Write type
|
||||
field(:tag, {:array, :map}, default: [])
|
||||
field(:type, :string)
|
||||
field(:content, :string)
|
||||
field(:context, :string)
|
||||
field(:actor, Types.ObjectID)
|
||||
field(:attributedTo, Types.ObjectID)
|
||||
field(:summary, :string)
|
||||
field(:published, Types.DateTime)
|
||||
# TODO: Write type
|
||||
field(:emoji, :map, default: %{})
|
||||
field(:sensitive, :boolean, default: false)
|
||||
# TODO: Write type
|
||||
field(:attachment, {:array, :map}, default: [])
|
||||
field(:replies_count, :integer, default: 0)
|
||||
field(:like_count, :integer, default: 0)
|
||||
field(:announcement_count, :integer, default: 0)
|
||||
field(:inRepyTo, :string)
|
||||
|
||||
field(:likes, {:array, :string}, default: [])
|
||||
field(:announcements, {:array, :string}, default: [])
|
||||
|
||||
# see if needed
|
||||
field(:conversation, :string)
|
||||
field(:context_id, :string)
|
||||
end
|
||||
|
||||
def cast_and_validate(data) do
|
||||
data
|
||||
|> cast_data()
|
||||
|> validate_data()
|
||||
end
|
||||
|
||||
def cast_data(data) do
|
||||
%__MODULE__{}
|
||||
|> cast(data, __schema__(:fields))
|
||||
end
|
||||
|
||||
def validate_data(data_cng) do
|
||||
data_cng
|
||||
|> validate_inclusion(:type, ["Note"])
|
||||
|> validate_required([:id, :actor, :to, :cc, :type, :content, :context])
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime do
|
||||
@moduledoc """
|
||||
The AP standard defines the date fields in AP as xsd:DateTime. Elixir's
|
||||
DateTime can't parse this, but it can parse the related iso8601. This
|
||||
module punches the date until it looks like iso8601 and normalizes to
|
||||
it.
|
||||
|
||||
DateTimes without a timezone offset are treated as UTC.
|
||||
|
||||
Reference: https://www.w3.org/TR/activitystreams-vocabulary/#dfn-published
|
||||
"""
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(datetime) when is_binary(datetime) do
|
||||
with {:ok, datetime, _} <- DateTime.from_iso8601(datetime) do
|
||||
{:ok, DateTime.to_iso8601(datetime)}
|
||||
else
|
||||
{:error, :missing_offset} -> cast("#{datetime}Z")
|
||||
_e -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def cast(_), do: :error
|
||||
|
||||
def dump(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def load(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,29 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID do
|
||||
use Ecto.Type
|
||||
|
||||
def type, do: :string
|
||||
|
||||
def cast(object) when is_binary(object) do
|
||||
# Host has to be present and scheme has to be an http scheme (for now)
|
||||
case URI.parse(object) do
|
||||
%URI{host: nil} -> :error
|
||||
%URI{host: ""} -> :error
|
||||
%URI{scheme: scheme} when scheme in ["https", "http"] -> {:ok, object}
|
||||
_ -> :error
|
||||
end
|
||||
end
|
||||
|
||||
def cast(%{"id" => object}), do: cast(object)
|
||||
|
||||
def cast(_) do
|
||||
:error
|
||||
end
|
||||
|
||||
def dump(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
|
||||
def load(data) do
|
||||
{:ok, data}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,42 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.Pipeline do
|
||||
alias Pleroma.Activity
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.MRF
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.SideEffects
|
||||
alias Pleroma.Web.Federator
|
||||
|
||||
@spec common_pipeline(map(), keyword()) :: {:ok, Activity.t(), keyword()} | {:error, any()}
|
||||
def common_pipeline(object, meta) do
|
||||
with {_, {:ok, validated_object, meta}} <-
|
||||
{:validate_object, ObjectValidator.validate(object, meta)},
|
||||
{_, {:ok, mrfd_object}} <- {:mrf_object, MRF.filter(validated_object)},
|
||||
{_, {:ok, %Activity{} = activity, meta}} <-
|
||||
{:persist_object, ActivityPub.persist(mrfd_object, meta)},
|
||||
{_, {:ok, %Activity{} = activity, meta}} <-
|
||||
{:execute_side_effects, SideEffects.handle(activity, meta)},
|
||||
{_, {:ok, _}} <- {:federation, maybe_federate(activity, meta)} do
|
||||
{:ok, activity, meta}
|
||||
else
|
||||
{:mrf_object, {:reject, _}} -> {:ok, nil, meta}
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
defp maybe_federate(activity, meta) do
|
||||
with {:ok, local} <- Keyword.fetch(meta, :local) do
|
||||
if local do
|
||||
Federator.publish(activity)
|
||||
{:ok, :federated}
|
||||
else
|
||||
{:ok, :not_federated}
|
||||
end
|
||||
else
|
||||
_e -> {:error, :badarg}
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||
@moduledoc """
|
||||
This module looks at an inserted object and executes the side effects that it
|
||||
implies. For example, a `Like` activity will increase the like count on the
|
||||
liked object, a `Follow` activity will add the user to the follower
|
||||
collection, and so on.
|
||||
"""
|
||||
alias Pleroma.Notification
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
def handle(object, meta \\ [])
|
||||
|
||||
# Tasks this handles:
|
||||
# - Add like to object
|
||||
# - Set up notification
|
||||
def handle(%{data: %{"type" => "Like"}} = object, meta) do
|
||||
liked_object = Object.get_by_ap_id(object.data["object"])
|
||||
Utils.add_like_to_object(object, liked_object)
|
||||
Notification.create_notifications(object)
|
||||
{:ok, object, meta}
|
||||
end
|
||||
|
||||
# Nothing to do
|
||||
def handle(object, meta) do
|
||||
{:ok, object, meta}
|
||||
end
|
||||
end
|
|
@ -13,6 +13,9 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
|
|||
alias Pleroma.Repo
|
||||
alias Pleroma.User
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
alias Pleroma.Web.Federator
|
||||
|
@ -202,16 +205,46 @@ def fix_context(object) do
|
|||
|> Map.put("conversation", context)
|
||||
end
|
||||
|
||||
defp add_if_present(map, _key, nil), do: map
|
||||
|
||||
defp add_if_present(map, key, value) do
|
||||
Map.put(map, key, value)
|
||||
end
|
||||
|
||||
def fix_attachments(%{"attachment" => attachment} = object) when is_list(attachment) do
|
||||
attachments =
|
||||
Enum.map(attachment, fn data ->
|
||||
media_type = data["mediaType"] || data["mimeType"]
|
||||
href = data["url"] || data["href"]
|
||||
url = [%{"type" => "Link", "mediaType" => media_type, "href" => href}]
|
||||
url =
|
||||
cond do
|
||||
is_list(data["url"]) -> List.first(data["url"])
|
||||
is_map(data["url"]) -> data["url"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
data
|
||||
|> Map.put("mediaType", media_type)
|
||||
|> Map.put("url", url)
|
||||
media_type =
|
||||
cond do
|
||||
is_map(url) && is_binary(url["mediaType"]) -> url["mediaType"]
|
||||
is_binary(data["mediaType"]) -> data["mediaType"]
|
||||
is_binary(data["mimeType"]) -> data["mimeType"]
|
||||
true -> nil
|
||||
end
|
||||
|
||||
href =
|
||||
cond do
|
||||
is_map(url) && is_binary(url["href"]) -> url["href"]
|
||||
is_binary(data["url"]) -> data["url"]
|
||||
is_binary(data["href"]) -> data["href"]
|
||||
end
|
||||
|
||||
attachment_url =
|
||||
%{"href" => href}
|
||||
|> add_if_present("mediaType", media_type)
|
||||
|> add_if_present("type", Map.get(url || %{}, "type"))
|
||||
|
||||
%{"url" => [attachment_url]}
|
||||
|> add_if_present("mediaType", media_type)
|
||||
|> add_if_present("type", data["type"])
|
||||
|> add_if_present("name", data["name"])
|
||||
end)
|
||||
|
||||
Map.put(object, "attachment", attachments)
|
||||
|
@ -491,7 +524,8 @@ def handle_incoming(
|
|||
{_, {:ok, follower}} <- {:follow, User.follow(follower, followed)},
|
||||
{_, {:ok, _}} <-
|
||||
{:follow_state_update, Utils.update_follow_state_for_all(activity, "accept")},
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
|
||||
{:ok, _relationship} <-
|
||||
FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
actor: followed,
|
||||
|
@ -501,7 +535,7 @@ def handle_incoming(
|
|||
else
|
||||
{:user_blocked, true} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
|
@ -512,7 +546,7 @@ def handle_incoming(
|
|||
|
||||
{:follow, {:error, _}} ->
|
||||
{:ok, _} = Utils.update_follow_state_for_all(activity, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "reject")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_reject)
|
||||
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
|
@ -522,7 +556,7 @@ def handle_incoming(
|
|||
})
|
||||
|
||||
{:user_locked, true} ->
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, "pending")
|
||||
{:ok, _relationship} = FollowingRelationship.update(follower, followed, :follow_pending)
|
||||
:noop
|
||||
end
|
||||
|
||||
|
@ -542,7 +576,7 @@ def handle_incoming(
|
|||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept") do
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept) do
|
||||
ActivityPub.accept(%{
|
||||
to: follow_activity.data["to"],
|
||||
type: "Accept",
|
||||
|
@ -565,7 +599,7 @@ def handle_incoming(
|
|||
{:ok, follow_activity} <- get_follow_activity(follow_object, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
||||
%User{local: true} = follower <- User.get_cached_by_ap_id(follow_activity.data["actor"]),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
|
||||
{:ok, activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: follow_activity.data["to"],
|
||||
|
@ -609,17 +643,20 @@ def handle_incoming(
|
|||
|> handle_incoming(options)
|
||||
end
|
||||
|
||||
def handle_incoming(
|
||||
%{"type" => "Like", "object" => object_id, "actor" => _actor, "id" => id} = data,
|
||||
_options
|
||||
) do
|
||||
with actor <- Containment.get_actor(data),
|
||||
{:ok, %User{} = actor} <- User.get_or_fetch_by_ap_id(actor),
|
||||
{:ok, object} <- get_obj_helper(object_id),
|
||||
{:ok, activity, _object} <- ActivityPub.like(actor, object, id, false) do
|
||||
def handle_incoming(%{"type" => "Like"} = data, _options) do
|
||||
with {_, {:ok, cast_data_sym}} <-
|
||||
{:casting_data,
|
||||
data |> LikeValidator.cast_data() |> Ecto.Changeset.apply_action(:insert)},
|
||||
cast_data = ObjectValidator.stringify_keys(Map.from_struct(cast_data_sym)),
|
||||
:ok <- ObjectValidator.fetch_actor_and_object(cast_data),
|
||||
{_, {:ok, cast_data}} <- {:ensure_context_presence, ensure_context_presence(cast_data)},
|
||||
{_, {:ok, cast_data}} <-
|
||||
{:ensure_recipients_presence, ensure_recipients_presence(cast_data)},
|
||||
{_, {:ok, activity, _meta}} <-
|
||||
{:common_pipeline, Pipeline.common_pipeline(cast_data, local: false)} do
|
||||
{:ok, activity}
|
||||
else
|
||||
_e -> :error
|
||||
e -> {:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -1243,4 +1280,45 @@ def maybe_fix_user_url(%{"url" => url} = data) when is_map(url) do
|
|||
def maybe_fix_user_url(data), do: data
|
||||
|
||||
def maybe_fix_user_object(data), do: maybe_fix_user_url(data)
|
||||
|
||||
defp ensure_context_presence(%{"context" => context} = data) when is_binary(context),
|
||||
do: {:ok, data}
|
||||
|
||||
defp ensure_context_presence(%{"object" => object} = data) when is_binary(object) do
|
||||
with %{data: %{"context" => context}} when is_binary(context) <- Object.normalize(object) do
|
||||
{:ok, Map.put(data, "context", context)}
|
||||
else
|
||||
_ ->
|
||||
{:error, :no_context}
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_context_presence(_) do
|
||||
{:error, :no_context}
|
||||
end
|
||||
|
||||
defp ensure_recipients_presence(%{"to" => [_ | _], "cc" => [_ | _]} = data),
|
||||
do: {:ok, data}
|
||||
|
||||
defp ensure_recipients_presence(%{"object" => object} = data) do
|
||||
case Object.normalize(object) do
|
||||
%{data: %{"actor" => actor}} ->
|
||||
data =
|
||||
data
|
||||
|> Map.put("to", [actor])
|
||||
|> Map.put("cc", data["cc"] || [])
|
||||
|
||||
{:ok, data}
|
||||
|
||||
nil ->
|
||||
{:error, :no_object}
|
||||
|
||||
_ ->
|
||||
{:error, :no_actor}
|
||||
end
|
||||
end
|
||||
|
||||
defp ensure_recipients_presence(_) do
|
||||
{:error, :no_object}
|
||||
end
|
||||
end
|
||||
|
|
|
@ -576,9 +576,8 @@ def relay_unfollow(%{assigns: %{user: admin}} = conn, %{"relay_url" => target})
|
|||
|
||||
@doc "Sends registration invite via email"
|
||||
def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params) do
|
||||
with true <-
|
||||
Config.get([:instance, :invites_enabled]) &&
|
||||
!Config.get([:instance, :registrations_open]),
|
||||
with {_, false} <- {:registrations_open, Config.get([:instance, :registrations_open])},
|
||||
{_, true} <- {:invites_enabled, Config.get([:instance, :invites_enabled])},
|
||||
{:ok, invite_token} <- UserInviteToken.create_invite(),
|
||||
email <-
|
||||
Pleroma.Emails.UserEmail.user_invitation_email(
|
||||
|
@ -589,6 +588,18 @@ def email_invite(%{assigns: %{user: user}} = conn, %{"email" => email} = params)
|
|||
),
|
||||
{:ok, _} <- Pleroma.Emails.Mailer.deliver(email) do
|
||||
json_response(conn, :no_content, "")
|
||||
else
|
||||
{:registrations_open, _} ->
|
||||
errors(
|
||||
conn,
|
||||
{:error, "To send invites you need to set the `registrations_open` option to false."}
|
||||
)
|
||||
|
||||
{:invites_enabled, _} ->
|
||||
errors(
|
||||
conn,
|
||||
{:error, "To send invites you need to set the `invites_enabled` option to true."}
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,44 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec do
|
||||
alias OpenApiSpex.OpenApi
|
||||
alias Pleroma.Web.Endpoint
|
||||
alias Pleroma.Web.Router
|
||||
|
||||
@behaviour OpenApi
|
||||
|
||||
@impl OpenApi
|
||||
def spec do
|
||||
%OpenApi{
|
||||
servers: [
|
||||
# Populate the Server info from a phoenix endpoint
|
||||
OpenApiSpex.Server.from_endpoint(Endpoint)
|
||||
],
|
||||
info: %OpenApiSpex.Info{
|
||||
title: "Pleroma",
|
||||
description: Application.spec(:pleroma, :description) |> to_string(),
|
||||
version: Application.spec(:pleroma, :vsn) |> to_string()
|
||||
},
|
||||
# populate the paths from a phoenix router
|
||||
paths: OpenApiSpex.Paths.from_router(Router),
|
||||
components: %OpenApiSpex.Components{
|
||||
securitySchemes: %{
|
||||
"oAuth" => %OpenApiSpex.SecurityScheme{
|
||||
type: "oauth2",
|
||||
flows: %OpenApiSpex.OAuthFlows{
|
||||
password: %OpenApiSpex.OAuthFlow{
|
||||
authorizationUrl: "/oauth/authorize",
|
||||
tokenUrl: "/oauth/token",
|
||||
scopes: %{"read" => "read"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
# discover request/response schemas from path specs
|
||||
|> OpenApiSpex.resolve_schema_modules()
|
||||
end
|
||||
end
|
|
@ -0,0 +1,27 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Helpers do
|
||||
def request_body(description, schema_ref, opts \\ []) do
|
||||
media_types = ["application/json", "multipart/form-data"]
|
||||
|
||||
content =
|
||||
media_types
|
||||
|> Enum.map(fn type ->
|
||||
{type,
|
||||
%OpenApiSpex.MediaType{
|
||||
schema: schema_ref,
|
||||
example: opts[:example],
|
||||
examples: opts[:examples]
|
||||
}}
|
||||
end)
|
||||
|> Enum.into(%{})
|
||||
|
||||
%OpenApiSpex.RequestBody{
|
||||
description: description,
|
||||
content: content,
|
||||
required: opts[:required] || false
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,96 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.AppOperation do
|
||||
alias OpenApiSpex.Operation
|
||||
alias OpenApiSpex.Schema
|
||||
alias Pleroma.Web.ApiSpec.Helpers
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||
|
||||
@spec open_api_operation(atom) :: Operation.t()
|
||||
def open_api_operation(action) do
|
||||
operation = String.to_existing_atom("#{action}_operation")
|
||||
apply(__MODULE__, operation, [])
|
||||
end
|
||||
|
||||
@spec create_operation() :: Operation.t()
|
||||
def create_operation do
|
||||
%Operation{
|
||||
tags: ["apps"],
|
||||
summary: "Create an application",
|
||||
description: "Create a new application to obtain OAuth2 credentials",
|
||||
operationId: "AppController.create",
|
||||
requestBody: Helpers.request_body("Parameters", AppCreateRequest, required: true),
|
||||
responses: %{
|
||||
200 => Operation.response("App", "application/json", AppCreateResponse),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unprocessable Entity",
|
||||
"application/json",
|
||||
%Schema{
|
||||
type: :object,
|
||||
description:
|
||||
"If a required parameter is missing or improperly formatted, the request will fail.",
|
||||
properties: %{
|
||||
error: %Schema{type: :string}
|
||||
},
|
||||
example: %{
|
||||
"error" => "Validation failed: Redirect URI must be an absolute URI."
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
|
||||
def verify_credentials_operation do
|
||||
%Operation{
|
||||
tags: ["apps"],
|
||||
summary: "Verify your app works",
|
||||
description: "Confirm that the app's OAuth2 credentials work.",
|
||||
operationId: "AppController.verify_credentials",
|
||||
security: [
|
||||
%{
|
||||
"oAuth" => ["read"]
|
||||
}
|
||||
],
|
||||
responses: %{
|
||||
200 =>
|
||||
Operation.response("App", "application/json", %Schema{
|
||||
type: :object,
|
||||
description:
|
||||
"If the Authorization header was provided with a valid token, you should see your app returned as an Application entity.",
|
||||
properties: %{
|
||||
name: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"name" => "My App",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
}),
|
||||
422 =>
|
||||
Operation.response(
|
||||
"Unauthorized",
|
||||
"application/json",
|
||||
%Schema{
|
||||
type: :object,
|
||||
description:
|
||||
"If the Authorization header contains an invalid token, is malformed, or is not present, an error will be returned indicating an authorization failure.",
|
||||
properties: %{
|
||||
error: %Schema{type: :string}
|
||||
},
|
||||
example: %{
|
||||
"error" => "The access token is invalid."
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateRequest do
|
||||
alias OpenApiSpex.Schema
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "AppCreateRequest",
|
||||
description: "POST body for creating an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
client_name: %Schema{type: :string, description: "A name for your application."},
|
||||
redirect_uris: %Schema{
|
||||
type: :string,
|
||||
description:
|
||||
"Where the user should be redirected after authorization. To display the authorization code to the user instead of redirecting to a web page, use `urn:ietf:wg:oauth:2.0:oob` in this parameter."
|
||||
},
|
||||
scopes: %Schema{
|
||||
type: :string,
|
||||
description: "Space separated list of scopes. If none is provided, defaults to `read`."
|
||||
},
|
||||
website: %Schema{type: :string, description: "A URL to the homepage of your app"}
|
||||
},
|
||||
required: [:client_name, :redirect_uris],
|
||||
example: %{
|
||||
"client_name" => "My App",
|
||||
"redirect_uris" => "https://myapp.com/auth/callback",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
|
@ -0,0 +1,33 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.Schemas.AppCreateResponse do
|
||||
alias OpenApiSpex.Schema
|
||||
|
||||
require OpenApiSpex
|
||||
|
||||
OpenApiSpex.schema(%{
|
||||
title: "AppCreateResponse",
|
||||
description: "Response schema for an app",
|
||||
type: :object,
|
||||
properties: %{
|
||||
id: %Schema{type: :string},
|
||||
name: %Schema{type: :string},
|
||||
client_id: %Schema{type: :string},
|
||||
client_secret: %Schema{type: :string},
|
||||
redirect_uri: %Schema{type: :string},
|
||||
vapid_key: %Schema{type: :string},
|
||||
website: %Schema{type: :string, nullable: true}
|
||||
},
|
||||
example: %{
|
||||
"id" => "123",
|
||||
"name" => "My App",
|
||||
"client_id" => "TWhM-tNSuncnqN7DBJmoyeLnk6K3iJJ71KKXxgL1hPM",
|
||||
"client_secret" => "ZEaFUFmF0umgBX1qKJDjaU99Q31lDkOU8NutzTOoliw",
|
||||
"vapid_key" =>
|
||||
"BCk-QqERU0q-CfYZjcuB6lnyyOYfJ2AifKqfeGIm7Z-HiTU5T9eTG5GxVA0_OH5mMlI4UkkDTpaZwozy0TzdZ2M=",
|
||||
"website" => "https://myapp.com/"
|
||||
}
|
||||
})
|
||||
end
|
|
@ -12,6 +12,8 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
alias Pleroma.User
|
||||
alias Pleroma.UserRelationship
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.Pipeline
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.ActivityPub.Visibility
|
||||
|
||||
|
@ -19,6 +21,7 @@ defmodule Pleroma.Web.CommonAPI do
|
|||
import Pleroma.Web.CommonAPI.Utils
|
||||
|
||||
require Pleroma.Constants
|
||||
require Logger
|
||||
|
||||
def follow(follower, followed) do
|
||||
timeout = Pleroma.Config.get([:activitypub, :follow_handshake_timeout])
|
||||
|
@ -42,7 +45,7 @@ def accept_follow_request(follower, followed) do
|
|||
with {:ok, follower} <- User.follow(follower, followed),
|
||||
%Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "accept"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "accept"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_accept),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.accept(%{
|
||||
to: [follower.ap_id],
|
||||
|
@ -57,7 +60,7 @@ def accept_follow_request(follower, followed) do
|
|||
def reject_follow_request(follower, followed) do
|
||||
with %Activity{} = follow_activity <- Utils.fetch_latest_follow(follower, followed),
|
||||
{:ok, follow_activity} <- Utils.update_follow_state_for_all(follow_activity, "reject"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, "reject"),
|
||||
{:ok, _relationship} <- FollowingRelationship.update(follower, followed, :follow_reject),
|
||||
{:ok, _activity} <-
|
||||
ActivityPub.reject(%{
|
||||
to: [follower.ap_id],
|
||||
|
@ -109,18 +112,51 @@ def unrepeat(id_or_ap_id, user) do
|
|||
end
|
||||
end
|
||||
|
||||
def favorite(id_or_ap_id, user) do
|
||||
with {_, %Activity{} = activity} <- {:find_activity, get_by_id_or_ap_id(id_or_ap_id)},
|
||||
object <- Object.normalize(activity),
|
||||
like_activity <- Utils.get_existing_like(user.ap_id, object) do
|
||||
if like_activity do
|
||||
{:ok, like_activity, object}
|
||||
else
|
||||
ActivityPub.like(user, object)
|
||||
end
|
||||
@spec favorite(User.t(), binary()) :: {:ok, Activity.t() | :already_liked} | {:error, any()}
|
||||
def favorite(%User{} = user, id) do
|
||||
case favorite_helper(user, id) do
|
||||
{:ok, _} = res ->
|
||||
res
|
||||
|
||||
{:error, :not_found} = res ->
|
||||
res
|
||||
|
||||
{:error, e} ->
|
||||
Logger.error("Could not favorite #{id}. Error: #{inspect(e, pretty: true)}")
|
||||
{:error, dgettext("errors", "Could not favorite")}
|
||||
end
|
||||
end
|
||||
|
||||
def favorite_helper(user, id) do
|
||||
with {_, %Activity{object: object}} <- {:find_object, Activity.get_by_id_with_object(id)},
|
||||
{_, {:ok, like_object, meta}} <- {:build_object, Builder.like(user, object)},
|
||||
{_, {:ok, %Activity{} = activity, _meta}} <-
|
||||
{:common_pipeline,
|
||||
Pipeline.common_pipeline(like_object, Keyword.put(meta, :local, true))} do
|
||||
{:ok, activity}
|
||||
else
|
||||
{:find_activity, _} -> {:error, :not_found}
|
||||
_ -> {:error, dgettext("errors", "Could not favorite")}
|
||||
{:find_object, _} ->
|
||||
{:error, :not_found}
|
||||
|
||||
{:common_pipeline,
|
||||
{
|
||||
:error,
|
||||
{
|
||||
:validate_object,
|
||||
{
|
||||
:error,
|
||||
changeset
|
||||
}
|
||||
}
|
||||
}} = e ->
|
||||
if {:object, {"already liked by this actor", []}} in changeset.errors do
|
||||
{:ok, :already_liked}
|
||||
else
|
||||
{:error, e}
|
||||
end
|
||||
|
||||
e ->
|
||||
{:error, e}
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -14,17 +14,20 @@ defmodule Pleroma.Web.MastodonAPI.AppController do
|
|||
action_fallback(Pleroma.Web.MastodonAPI.FallbackController)
|
||||
|
||||
plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :verify_credentials)
|
||||
plug(OpenApiSpex.Plug.CastAndValidate)
|
||||
|
||||
@local_mastodon_name "Mastodon-Local"
|
||||
|
||||
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.AppOperation
|
||||
|
||||
@doc "POST /api/v1/apps"
|
||||
def create(conn, params) do
|
||||
def create(%{body_params: params} = conn, _params) do
|
||||
scopes = Scopes.fetch_scopes(params, ["read"])
|
||||
|
||||
app_attrs =
|
||||
params
|
||||
|> Map.drop(["scope", "scopes"])
|
||||
|> Map.put("scopes", scopes)
|
||||
|> Map.take([:client_name, :redirect_uris, :website])
|
||||
|> Map.put(:scopes, scopes)
|
||||
|
||||
with cs <- App.register_changeset(%App{}, app_attrs),
|
||||
false <- cs.changes[:client_name] == @local_mastodon_name,
|
||||
|
|
|
@ -70,7 +70,8 @@ def clear(%{assigns: %{user: user}} = conn, _params) do
|
|||
json(conn, %{})
|
||||
end
|
||||
|
||||
# POST /api/v1/notifications/dismiss
|
||||
# POST /api/v1/notifications/:id/dismiss
|
||||
# POST /api/v1/notifications/dismiss (deprecated)
|
||||
def dismiss(%{assigns: %{user: user}} = conn, %{"id" => id} = _params) do
|
||||
with {:ok, _notif} <- Notification.dismiss(user, id) do
|
||||
json(conn, %{})
|
||||
|
|
|
@ -213,9 +213,9 @@ def unreblog(%{assigns: %{user: user}} = conn, %{"id" => ap_id_or_id}) do
|
|||
end
|
||||
|
||||
@doc "POST /api/v1/statuses/:id/favourite"
|
||||
def favourite(%{assigns: %{user: user}} = conn, %{"id" => 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
|
||||
def favourite(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) do
|
||||
with {:ok, _fav} <- CommonAPI.favorite(user, activity_id),
|
||||
%Activity{} = activity <- Activity.get_by_id(activity_id) do
|
||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -74,7 +74,7 @@ def render(
|
|||
followed_by =
|
||||
if following_relationships do
|
||||
case FollowingRelationship.find(following_relationships, target, reading_user) do
|
||||
%{state: "accept"} -> true
|
||||
%{state: :follow_accept} -> true
|
||||
_ -> false
|
||||
end
|
||||
else
|
||||
|
@ -84,7 +84,7 @@ def render(
|
|||
# NOTE: adjust UserRelationship.view_relationships_option/2 on new relation-related flags
|
||||
%{
|
||||
id: to_string(target.id),
|
||||
following: follow_state == "accept",
|
||||
following: follow_state == :follow_accept,
|
||||
followed_by: followed_by,
|
||||
blocking:
|
||||
UserRelationship.exists?(
|
||||
|
@ -126,7 +126,7 @@ def render(
|
|||
reading_user,
|
||||
&User.subscribed_to?(&2, &1)
|
||||
),
|
||||
requested: follow_state == "pending",
|
||||
requested: follow_state == :follow_pending,
|
||||
domain_blocking: User.blocks_domain?(reading_user, target),
|
||||
showing_reblogs:
|
||||
not UserRelationship.exists?(
|
||||
|
|
|
@ -75,7 +75,8 @@ def raw_nodeinfo do
|
|||
end,
|
||||
if Config.get([:instance, :safe_dm_mentions]) do
|
||||
"safe_dm_mentions"
|
||||
end
|
||||
end,
|
||||
"pleroma_emoji_reactions"
|
||||
]
|
||||
|> Enum.filter(& &1)
|
||||
|
||||
|
|
|
@ -15,7 +15,12 @@ defmodule Pleroma.Web.OAuth.Scopes do
|
|||
Note: `scopes` is used by Mastodon — supporting it but sticking to
|
||||
OAuth's standard `scope` wherever we control it
|
||||
"""
|
||||
@spec fetch_scopes(map(), list()) :: list()
|
||||
@spec fetch_scopes(map() | struct(), list()) :: list()
|
||||
|
||||
def fetch_scopes(%Pleroma.Web.ApiSpec.Schemas.AppCreateRequest{scopes: scopes}, default) do
|
||||
parse_scopes(scopes, default)
|
||||
end
|
||||
|
||||
def fetch_scopes(params, default) do
|
||||
parse_scopes(params["scope"] || params["scopes"], default)
|
||||
end
|
||||
|
|
|
@ -110,12 +110,11 @@ def conversation(%{assigns: %{user: user}} = conn, %{"id" => participation_id})
|
|||
end
|
||||
|
||||
def conversation_statuses(
|
||||
%{assigns: %{user: user}} = conn,
|
||||
%{assigns: %{user: %{id: user_id} = user}} = conn,
|
||||
%{"id" => participation_id} = params
|
||||
) do
|
||||
with %Participation{} = participation <-
|
||||
Participation.get(participation_id, preload: [:conversation]),
|
||||
true <- user.id == participation.user_id do
|
||||
with %Participation{user_id: ^user_id} = participation <-
|
||||
Participation.get(participation_id, preload: [:conversation]) do
|
||||
params =
|
||||
params
|
||||
|> Map.put("blocking_user", user)
|
||||
|
@ -124,7 +123,8 @@ def conversation_statuses(
|
|||
|
||||
activities =
|
||||
participation.conversation.ap_id
|
||||
|> ActivityPub.fetch_activities_for_context(params)
|
||||
|> ActivityPub.fetch_activities_for_context_query(params)
|
||||
|> Pleroma.Pagination.fetch_paginated(Map.put(params, "total", false))
|
||||
|> Enum.reverse()
|
||||
|
||||
conn
|
||||
|
|
|
@ -29,6 +29,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureUserKeyPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :authenticated_api do
|
||||
|
@ -44,6 +45,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.SetUserSessionIdPlug)
|
||||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :admin_api do
|
||||
|
@ -61,6 +63,7 @@ defmodule Pleroma.Web.Router do
|
|||
plug(Pleroma.Plugs.EnsureAuthenticatedPlug)
|
||||
plug(Pleroma.Plugs.UserIsAdminPlug)
|
||||
plug(Pleroma.Plugs.IdempotencyPlug)
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :mastodon_html do
|
||||
|
@ -94,10 +97,12 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
pipeline :config do
|
||||
plug(:accepts, ["json", "xml"])
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :pleroma_api do
|
||||
plug(:accepts, ["html", "json"])
|
||||
plug(OpenApiSpex.Plug.PutApiSpec, module: Pleroma.Web.ApiSpec)
|
||||
end
|
||||
|
||||
pipeline :mailbox_preview do
|
||||
|
@ -347,9 +352,11 @@ defmodule Pleroma.Web.Router do
|
|||
|
||||
get("/notifications", NotificationController, :index)
|
||||
get("/notifications/:id", NotificationController, :show)
|
||||
post("/notifications/:id/dismiss", NotificationController, :dismiss)
|
||||
post("/notifications/clear", NotificationController, :clear)
|
||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||
delete("/notifications/destroy_multiple", NotificationController, :destroy_multiple)
|
||||
# Deprecated: was removed in Mastodon v3, use `/notifications/:id/dismiss` instead
|
||||
post("/notifications/dismiss", NotificationController, :dismiss)
|
||||
|
||||
get("/scheduled_statuses", ScheduledActivityController, :index)
|
||||
get("/scheduled_statuses/:id", ScheduledActivityController, :show)
|
||||
|
@ -500,6 +507,12 @@ defmodule Pleroma.Web.Router do
|
|||
)
|
||||
end
|
||||
|
||||
scope "/api" do
|
||||
pipe_through(:api)
|
||||
|
||||
get("/openapi", OpenApiSpex.Plug.RenderSpec, [])
|
||||
end
|
||||
|
||||
scope "/api", Pleroma.Web, as: :authenticated_twitter_api do
|
||||
pipe_through(:authenticated_api)
|
||||
|
||||
|
|
16
mix.exs
16
mix.exs
|
@ -37,12 +37,21 @@ def project do
|
|||
pleroma: [
|
||||
include_executables_for: [:unix],
|
||||
applications: [ex_syslogger: :load, syslog: :load],
|
||||
steps: [:assemble, ©_files/1, ©_nginx_config/1]
|
||||
steps: [:assemble, &put_otp_version/1, ©_files/1, ©_nginx_config/1]
|
||||
]
|
||||
]
|
||||
]
|
||||
end
|
||||
|
||||
def put_otp_version(%{path: target_path} = release) do
|
||||
File.write!(
|
||||
Path.join([target_path, "OTP_VERSION"]),
|
||||
Pleroma.OTPVersion.version()
|
||||
)
|
||||
|
||||
release
|
||||
end
|
||||
|
||||
def copy_files(%{path: target_path} = release) do
|
||||
File.cp_r!("./rel/files", target_path)
|
||||
release
|
||||
|
@ -108,7 +117,7 @@ defp deps do
|
|||
{:ecto_enum, "~> 1.4"},
|
||||
{:ecto_sql, "~> 3.3.2"},
|
||||
{:postgrex, ">= 0.13.5"},
|
||||
{:oban, "~> 0.12.1"},
|
||||
{:oban, "~> 1.2"},
|
||||
{:gettext, "~> 0.15"},
|
||||
{:comeonin, "~> 4.1.1"},
|
||||
{:pbkdf2_elixir, "~> 0.12.3"},
|
||||
|
@ -179,7 +188,8 @@ defp deps do
|
|||
git: "https://git.pleroma.social/pleroma/elixir-libraries/elixir-captcha.git",
|
||||
ref: "e0f16822d578866e186a0974d65ad58cddc1e2ab"},
|
||||
{:mox, "~> 0.5", only: :test},
|
||||
{:restarter, path: "./restarter"}
|
||||
{:restarter, path: "./restarter"},
|
||||
{:open_api_spex, "~> 3.6"}
|
||||
] ++ oauth_deps()
|
||||
end
|
||||
|
||||
|
|
7
mix.lock
7
mix.lock
|
@ -26,7 +26,7 @@
|
|||
"decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm", "3cb154b00225ac687f6cbd4acc4b7960027c757a5152b369923ead9ddbca7aec"},
|
||||
"deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm", "ce708e5f094b9cd4e8f2be4f00d2f4250c4095be93f8cd6d018c753894885430"},
|
||||
"earmark": {:hex, :earmark, "1.4.3", "364ca2e9710f6bff494117dbbd53880d84bebb692dafc3a78eb50aa3183f2bfd", [:mix], [], "hexpm", "8cf8a291ebf1c7b9539e3cddb19e9cef066c2441b1640f13c34c1d3cfc825fec"},
|
||||
"ecto": {:hex, :ecto, "3.3.3", "0830bf3aebcbf3d8c1a1811cd581773b6866886c012f52c0f027031fa96a0b53", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "12e368e3c2a2938d7776defaabdae40e82900fc4d8d66120ec1e01dfd8b93c3a"},
|
||||
"ecto": {:hex, :ecto, "3.4.0", "a7a83ab8359bf816ce729e5e65981ce25b9fc5adfc89c2ea3980f4fed0bfd7c1", [:mix], [{:decimal, "~> 1.6 or ~> 2.0", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm", "5eed18252f5b5bbadec56a24112b531343507dbe046273133176b12190ce19cc"},
|
||||
"ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm", "8fb55c087181c2b15eee406519dc22578fa60dd82c088be376d0010172764ee4"},
|
||||
"ecto_sql": {:hex, :ecto_sql, "3.3.4", "aa18af12eb875fbcda2f75e608b3bd534ebf020fc4f6448e4672fcdcbb081244", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.4 or ~> 3.3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "5eccbdbf92e3c6f213007a82d5dbba4cd9bb659d1a21331f89f408e4c0efd7a8"},
|
||||
"esshd": {:hex, :esshd, "0.1.1", "d4dd4c46698093a40a56afecce8a46e246eb35463c457c246dacba2e056f31b5", [:mix], [], "hexpm", "d73e341e3009d390aa36387dc8862860bf9f874c94d9fd92ade2926376f49981"},
|
||||
|
@ -55,7 +55,7 @@
|
|||
"httpoison": {:hex, :httpoison, "1.6.2", "ace7c8d3a361cebccbed19c283c349b3d26991eff73a1eaaa8abae2e3c8089b6", [:mix], [{:hackney, "~> 1.15 and >= 1.15.2", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "aa2c74bd271af34239a3948779612f87df2422c2fdcfdbcec28d9c105f0773fe"},
|
||||
"idna": {:hex, :idna, "6.0.0", "689c46cbcdf3524c44d5f3dde8001f364cd7608a99556d8fbd8239a5798d4c10", [:rebar3], [{:unicode_util_compat, "0.4.1", [hex: :unicode_util_compat, repo: "hexpm", optional: false]}], "hexpm", "4bdd305eb64e18b0273864920695cb18d7a2021f31a11b9c5fbcd9a253f936e2"},
|
||||
"inet_cidr": {:hex, :inet_cidr, "1.0.4", "a05744ab7c221ca8e395c926c3919a821eb512e8f36547c062f62c4ca0cf3d6e", [:mix], [], "hexpm", "64a2d30189704ae41ca7dbdd587f5291db5d1dda1414e0774c29ffc81088c1bc"},
|
||||
"jason": {:hex, :jason, "1.1.2", "b03dedea67a99223a2eaf9f1264ce37154564de899fd3d8b9a21b1a6fd64afe7", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "fdf843bca858203ae1de16da2ee206f53416bbda5dc8c9e78f43243de4bc3afe"},
|
||||
"jason": {:hex, :jason, "1.2.0", "10043418c42d2493d0ee212d3fddd25d7ffe484380afad769a0a38795938e448", [:mix], [{:decimal, "~> 1.0", [hex: :decimal, repo: "hexpm", optional: true]}], "hexpm", "116747dbe057794c3a3e4e143b7c8390b29f634e16c78a7f59ba75bfa6852e7f"},
|
||||
"joken": {:hex, :joken, "2.2.0", "2daa1b12be05184aff7b5ace1d43ca1f81345962285fff3f88db74927c954d3a", [:mix], [{:jose, "~> 1.9", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "b4f92e30388206f869dd25d1af628a1d99d7586e5cf0672f64d4df84c4d2f5e9"},
|
||||
"jose": {:hex, :jose, "1.10.1", "16d8e460dae7203c6d1efa3f277e25b5af8b659febfc2f2eb4bacf87f128b80a", [:mix, :rebar3], [], "hexpm", "3c7ddc8a9394b92891db7c2771da94bf819834a1a4c92e30857b7d582e2f8257"},
|
||||
"jumper": {:hex, :jumper, "1.0.1", "3c00542ef1a83532b72269fab9f0f0c82bf23a35e27d278bfd9ed0865cecabff", [:mix], [], "hexpm", "318c59078ac220e966d27af3646026db9b5a5e6703cb2aa3e26bcfaba65b7433"},
|
||||
|
@ -73,7 +73,8 @@
|
|||
"myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]},
|
||||
"nimble_parsec": {:hex, :nimble_parsec, "0.5.3", "def21c10a9ed70ce22754fdeea0810dafd53c2db3219a0cd54cf5526377af1c6", [:mix], [], "hexpm", "589b5af56f4afca65217a1f3eb3fee7e79b09c40c742fddc1c312b3ac0b3399f"},
|
||||
"nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]},
|
||||
"oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "c1d58d69b8b5a86e7167abbb8cc92764a66f25f12f6172052595067fc6a30a17"},
|
||||
"oban": {:hex, :oban, "1.2.0", "7cca94d341be43d220571e28f69131c4afc21095b25257397f50973d3fc59b07", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ba5f8b3f7d76967b3e23cf8014f6a13e4ccb33431e4808f036709a7f822362ee"},
|
||||
"open_api_spex": {:hex, :open_api_spex, "3.6.0", "64205aba9f2607f71b08fd43e3351b9c5e9898ec5ef49fc0ae35890da502ade9", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:plug, "~> 1.7", [hex: :plug, repo: "hexpm", optional: false]}, {:poison, "~> 3.1", [hex: :poison, repo: "hexpm", optional: true]}], "hexpm", "126ba3473966277132079cb1d5bf1e3df9e36fe2acd00166e75fd125cecb59c5"},
|
||||
"parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm", "17ef63abde837ad30680ea7f857dd9e7ced9476cdd7b0394432af4bfc241b960"},
|
||||
"pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm", "595d09db74cb093b1903381c9de423276a931a2480a46a1a5dc7f932a2a6375b"},
|
||||
"phoenix": {:hex, :phoenix, "1.4.13", "67271ad69b51f3719354604f4a3f968f83aa61c19199343656c9caee057ff3b8", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "ab765a0feddb81fc62e2116c827b5f068df85159c162bee760745276ad7ddc1b"},
|
||||
|
|
|
@ -0,0 +1,29 @@
|
|||
defmodule Pleroma.Repo.Migrations.ChangeFollowingRelationshipsStateToInteger do
|
||||
use Ecto.Migration
|
||||
|
||||
@alter_following_relationship_state "ALTER TABLE following_relationships ALTER COLUMN state"
|
||||
|
||||
def up do
|
||||
execute("""
|
||||
#{@alter_following_relationship_state} TYPE integer USING
|
||||
CASE
|
||||
WHEN state = 'pending' THEN 1
|
||||
WHEN state = 'accept' THEN 2
|
||||
WHEN state = 'reject' THEN 3
|
||||
ELSE 0
|
||||
END;
|
||||
""")
|
||||
end
|
||||
|
||||
def down do
|
||||
execute("""
|
||||
#{@alter_following_relationship_state} TYPE varchar(255) USING
|
||||
CASE
|
||||
WHEN state = 1 THEN 'pending'
|
||||
WHEN state = 2 THEN 'accept'
|
||||
WHEN state = 3 THEN 'reject'
|
||||
ELSE ''
|
||||
END;
|
||||
""")
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Pleroma.Repo.Migrations.AddFollowingRelationshipsFollowingIdIndex do
|
||||
use Ecto.Migration
|
||||
|
||||
# [:follower_index] index is useless because of [:follower_id, :following_id] index
|
||||
# [:following_id] index makes sense because of user's followers-targeted queries
|
||||
def change do
|
||||
drop_if_exists(index(:following_relationships, [:follower_id]))
|
||||
|
||||
create_if_not_exists(index(:following_relationships, [:following_id]))
|
||||
end
|
||||
end
|
|
@ -0,0 +1,11 @@
|
|||
defmodule Pleroma.Repo.Migrations.UpdateObanJobsTable do
|
||||
use Ecto.Migration
|
||||
|
||||
def up do
|
||||
Oban.Migrations.up(version: 8)
|
||||
end
|
||||
|
||||
def down do
|
||||
Oban.Migrations.down(version: 7)
|
||||
end
|
||||
end
|
Binary file not shown.
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"finmoji": {
|
||||
"license": "CC BY-NC-ND 4.0",
|
||||
"homepage": "https://finland.fi/emoji/",
|
||||
"description": "Finland is the first country in the world to publish its own set of country themed emojis. The Finland emoji collection contains 56 tongue-in-cheek emotions, which were created to explain some hard-to-describe Finnish emotions, Finnish words and customs.",
|
||||
"src": "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip",
|
||||
"src_sha256": "384025A1AC6314473863A11AC7AB38A12C01B851A3F82359B89B4D4211D3291D",
|
||||
"files": "finmoji.json"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
{
|
||||
"blank": "blank.png"
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
{
|
||||
"blobs.gg": {
|
||||
"src_sha256": "3a12f3a181678d5b3584a62095411b0d60a335118135910d879920f8ade5a57f",
|
||||
"src": "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip",
|
||||
"license": "Apache 2.0",
|
||||
"homepage": "https://blobs.gg",
|
||||
"files": "blobs_gg.json",
|
||||
"description": "Blob Emoji from blobs.gg repacked as apng"
|
||||
}
|
||||
}
|
|
@ -15,28 +15,28 @@ defmodule Pleroma.FollowingRelationshipTest do
|
|||
test "returns following addresses without internal.fetch" do
|
||||
user = insert(:user)
|
||||
fetch_actor = InternalFetchActor.get_actor()
|
||||
FollowingRelationship.follow(fetch_actor, user, "accept")
|
||||
FollowingRelationship.follow(fetch_actor, user, :follow_accept)
|
||||
assert FollowingRelationship.following(fetch_actor) == [user.follower_address]
|
||||
end
|
||||
|
||||
test "returns following addresses without relay" do
|
||||
user = insert(:user)
|
||||
relay_actor = Relay.get_actor()
|
||||
FollowingRelationship.follow(relay_actor, user, "accept")
|
||||
FollowingRelationship.follow(relay_actor, user, :follow_accept)
|
||||
assert FollowingRelationship.following(relay_actor) == [user.follower_address]
|
||||
end
|
||||
|
||||
test "returns following addresses without remote user" do
|
||||
user = insert(:user)
|
||||
actor = insert(:user, local: false)
|
||||
FollowingRelationship.follow(actor, user, "accept")
|
||||
FollowingRelationship.follow(actor, user, :follow_accept)
|
||||
assert FollowingRelationship.following(actor) == [user.follower_address]
|
||||
end
|
||||
|
||||
test "returns following addresses with local user" do
|
||||
user = insert(:user)
|
||||
actor = insert(:user, local: true)
|
||||
FollowingRelationship.follow(actor, user, "accept")
|
||||
FollowingRelationship.follow(actor, user, :follow_accept)
|
||||
|
||||
assert FollowingRelationship.following(actor) == [
|
||||
actor.follower_address,
|
||||
|
|
|
@ -150,13 +150,13 @@ test "gives a replacement for user links, using local nicknames in user links te
|
|||
assert length(mentions) == 3
|
||||
|
||||
expected_text =
|
||||
~s(<span class="h-card"><a data-user="#{gsimg.id}" class="u-url mention" href="#{
|
||||
~s(<span class="h-card"><a class="u-url mention" data-user="#{gsimg.id}" href="#{
|
||||
gsimg.ap_id
|
||||
}" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a data-user="#{
|
||||
}" rel="ugc">@<span>gsimg</span></a></span> According to <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
archaeme.id
|
||||
}" class="u-url mention" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a data-user="#{
|
||||
}" href="#{"https://archeme/@archa_eme_"}" rel="ugc">@<span>archa_eme_</span></a></span>, that is @daggsy. Also hello <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
archaeme_remote.id
|
||||
}" class="u-url mention" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
|
||||
}" href="#{archaeme_remote.ap_id}" rel="ugc">@<span>archaeme</span></a></span>)
|
||||
|
||||
assert expected_text == text
|
||||
end
|
||||
|
@ -171,7 +171,7 @@ test "gives a replacement for user links when the user is using Osada" do
|
|||
assert length(mentions) == 1
|
||||
|
||||
expected_text =
|
||||
~s(<span class="h-card"><a data-user="#{mike.id}" class="u-url mention" href="#{
|
||||
~s(<span class="h-card"><a class="u-url mention" data-user="#{mike.id}" href="#{
|
||||
mike.ap_id
|
||||
}" rel="ugc">@<span>mike</span></a></span> test)
|
||||
|
||||
|
@ -187,7 +187,7 @@ test "gives a replacement for single-character local nicknames" do
|
|||
assert length(mentions) == 1
|
||||
|
||||
expected_text =
|
||||
~s(<span class="h-card"><a data-user="#{o.id}" class="u-url mention" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
|
||||
~s(<span class="h-card"><a class="u-url mention" data-user="#{o.id}" href="#{o.ap_id}" rel="ugc">@<span>o</span></a></span> hi)
|
||||
|
||||
assert expected_text == text
|
||||
end
|
||||
|
@ -209,17 +209,13 @@ test "given the 'safe_mention' option, it will only mention people in the beginn
|
|||
assert mentions == [{"@#{user.nickname}", user}, {"@#{other_user.nickname}", other_user}]
|
||||
|
||||
assert expected_text ==
|
||||
~s(<span class="h-card"><a data-user="#{user.id}" class="u-url mention" href="#{
|
||||
~s(<span class="h-card"><a class="u-url mention" data-user="#{user.id}" href="#{
|
||||
user.ap_id
|
||||
}" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a data-user="#{
|
||||
}" rel="ugc">@<span>#{user.nickname}</span></a></span> <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
other_user.id
|
||||
}" class="u-url mention" href="#{other_user.ap_id}" rel="ugc">@<span>#{
|
||||
other_user.nickname
|
||||
}</span></a></span> hey dudes i hate <span class="h-card"><a data-user="#{
|
||||
}" href="#{other_user.ap_id}" rel="ugc">@<span>#{other_user.nickname}</span></a></span> hey dudes i hate <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
third_user.id
|
||||
}" class="u-url mention" href="#{third_user.ap_id}" rel="ugc">@<span>#{
|
||||
third_user.nickname
|
||||
}</span></a></span>)
|
||||
}" href="#{third_user.ap_id}" rel="ugc">@<span>#{third_user.nickname}</span></a></span>)
|
||||
end
|
||||
|
||||
test "given the 'safe_mention' option, it will still work without any mention" do
|
||||
|
|
|
@ -537,7 +537,7 @@ test "it does not send notification to mentioned users in likes" do
|
|||
"status" => "hey @#{other_user.nickname}!"
|
||||
})
|
||||
|
||||
{:ok, activity_two, _} = CommonAPI.favorite(activity_one.id, third_user)
|
||||
{:ok, activity_two} = CommonAPI.favorite(third_user, activity_one.id)
|
||||
|
||||
{enabled_receivers, _disabled_receivers} =
|
||||
Notification.get_notified_from_activity(activity_two)
|
||||
|
@ -620,7 +620,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is de
|
|||
|
||||
assert Enum.empty?(Notification.for_user(user))
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
assert length(Notification.for_user(user)) == 1
|
||||
|
||||
|
@ -637,7 +637,7 @@ test "liking an activity results in 1 notification, then 0 if the activity is un
|
|||
|
||||
assert Enum.empty?(Notification.for_user(user))
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
assert length(Notification.for_user(user)) == 1
|
||||
|
||||
|
@ -692,7 +692,7 @@ test "liking an activity which is already deleted does not generate a notificati
|
|||
|
||||
assert Enum.empty?(Notification.for_user(user))
|
||||
|
||||
{:error, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:error, :not_found} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
assert Enum.empty?(Notification.for_user(user))
|
||||
end
|
||||
|
|
|
@ -380,7 +380,8 @@ test "preserves internal fields on refetch", %{mock_modified: mock_modified} do
|
|||
|
||||
user = insert(:user)
|
||||
activity = Activity.get_create_by_object_ap_id(object.data["id"])
|
||||
{:ok, _activity, object} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, activity} = CommonAPI.favorite(user, activity.id)
|
||||
object = Object.get_by_ap_id(activity.data["object"])
|
||||
|
||||
assert object.data["like_count"] == 1
|
||||
|
||||
|
|
|
@ -60,7 +60,7 @@ test "doesn't count unrelated activities" do
|
|||
other_user = insert(:user)
|
||||
{:ok, activity} = CommonAPI.post(user, %{"visibility" => "public", "status" => "hey"})
|
||||
_ = CommonAPI.follow(user, other_user)
|
||||
CommonAPI.favorite(activity.id, other_user)
|
||||
CommonAPI.favorite(other_user, activity.id)
|
||||
CommonAPI.repeat(activity.id, other_user)
|
||||
|
||||
assert %{direct: 0, private: 0, public: 1, unlisted: 0} =
|
||||
|
|
|
@ -102,7 +102,7 @@ test "it turns OrderedCollection likes into empty arrays" do
|
|||
{:ok, %{id: id, object: object}} = CommonAPI.post(user, %{"status" => "test"})
|
||||
{:ok, %{object: object2}} = CommonAPI.post(user, %{"status" => "test test"})
|
||||
|
||||
CommonAPI.favorite(id, user2)
|
||||
CommonAPI.favorite(user2, id)
|
||||
|
||||
likes = %{
|
||||
"first" =>
|
||||
|
|
|
@ -0,0 +1,226 @@
|
|||
defmodule Mix.Tasks.Pleroma.EmojiTest do
|
||||
use ExUnit.Case, async: true
|
||||
|
||||
import ExUnit.CaptureIO
|
||||
import Tesla.Mock
|
||||
|
||||
alias Mix.Tasks.Pleroma.Emoji
|
||||
|
||||
describe "ls-packs" do
|
||||
test "with default manifest as url" do
|
||||
mock(fn
|
||||
%{
|
||||
method: :get,
|
||||
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
|
||||
}
|
||||
end)
|
||||
|
||||
capture_io(fn -> Emoji.run(["ls-packs"]) end) =~
|
||||
"https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
|
||||
end
|
||||
|
||||
test "with passed manifest as file" do
|
||||
capture_io(fn ->
|
||||
Emoji.run(["ls-packs", "-m", "test/fixtures/emoji/packs/manifest.json"])
|
||||
end) =~ "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip"
|
||||
end
|
||||
end
|
||||
|
||||
describe "get-packs" do
|
||||
test "download pack from default manifest" do
|
||||
mock(fn
|
||||
%{
|
||||
method: :get,
|
||||
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
|
||||
}
|
||||
|
||||
%{
|
||||
method: :get,
|
||||
url: "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/emoji/packs/blank.png.zip")
|
||||
}
|
||||
|
||||
%{
|
||||
method: :get,
|
||||
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/finmoji.json"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/emoji/packs/finmoji.json")
|
||||
}
|
||||
end)
|
||||
|
||||
assert capture_io(fn -> Emoji.run(["get-packs", "finmoji"]) end) =~ "Writing pack.json for"
|
||||
|
||||
emoji_path =
|
||||
Path.join(
|
||||
Pleroma.Config.get!([:instance, :static_dir]),
|
||||
"emoji"
|
||||
)
|
||||
|
||||
assert File.exists?(Path.join([emoji_path, "finmoji", "pack.json"]))
|
||||
on_exit(fn -> File.rm_rf!("test/instance_static/emoji/finmoji") end)
|
||||
end
|
||||
|
||||
test "pack not found" do
|
||||
mock(fn
|
||||
%{
|
||||
method: :get,
|
||||
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/emoji/packs/default-manifest.json")
|
||||
}
|
||||
end)
|
||||
|
||||
assert capture_io(fn -> Emoji.run(["get-packs", "not_found"]) end) =~
|
||||
"No pack named \"not_found\" found"
|
||||
end
|
||||
|
||||
test "raise on bad sha256" do
|
||||
mock(fn
|
||||
%{
|
||||
method: :get,
|
||||
url: "https://git.pleroma.social/pleroma/emoji-index/raw/master/packs/blobs_gg.zip"
|
||||
} ->
|
||||
%Tesla.Env{
|
||||
status: 200,
|
||||
body: File.read!("test/fixtures/emoji/packs/blank.png.zip")
|
||||
}
|
||||
end)
|
||||
|
||||
assert_raise RuntimeError, ~r/^Bad SHA256 for blobs.gg/, fn ->
|
||||
capture_io(fn ->
|
||||
Emoji.run(["get-packs", "blobs.gg", "-m", "test/fixtures/emoji/packs/manifest.json"])
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "gen-pack" do
|
||||
setup do
|
||||
url = "https://finland.fi/wp-content/uploads/2017/06/finland-emojis.zip"
|
||||
|
||||
mock(fn %{
|
||||
method: :get,
|
||||
url: ^url
|
||||
} ->
|
||||
%Tesla.Env{status: 200, body: File.read!("test/fixtures/emoji/packs/blank.png.zip")}
|
||||
end)
|
||||
|
||||
{:ok, url: url}
|
||||
end
|
||||
|
||||
test "with default extensions", %{url: url} do
|
||||
name = "pack1"
|
||||
pack_json = "#{name}.json"
|
||||
files_json = "#{name}_file.json"
|
||||
refute File.exists?(pack_json)
|
||||
refute File.exists?(files_json)
|
||||
|
||||
captured =
|
||||
capture_io(fn ->
|
||||
Emoji.run([
|
||||
"gen-pack",
|
||||
url,
|
||||
"--name",
|
||||
name,
|
||||
"--license",
|
||||
"license",
|
||||
"--homepage",
|
||||
"homepage",
|
||||
"--description",
|
||||
"description",
|
||||
"--files",
|
||||
files_json,
|
||||
"--extensions",
|
||||
".png .gif"
|
||||
])
|
||||
end)
|
||||
|
||||
assert captured =~ "#{pack_json} has been created with the pack1 pack"
|
||||
assert captured =~ "Using .png .gif extensions"
|
||||
|
||||
assert File.exists?(pack_json)
|
||||
assert File.exists?(files_json)
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm!(pack_json)
|
||||
File.rm!(files_json)
|
||||
end)
|
||||
end
|
||||
|
||||
test "with custom extensions and update existing files", %{url: url} do
|
||||
name = "pack2"
|
||||
pack_json = "#{name}.json"
|
||||
files_json = "#{name}_file.json"
|
||||
refute File.exists?(pack_json)
|
||||
refute File.exists?(files_json)
|
||||
|
||||
captured =
|
||||
capture_io(fn ->
|
||||
Emoji.run([
|
||||
"gen-pack",
|
||||
url,
|
||||
"--name",
|
||||
name,
|
||||
"--license",
|
||||
"license",
|
||||
"--homepage",
|
||||
"homepage",
|
||||
"--description",
|
||||
"description",
|
||||
"--files",
|
||||
files_json,
|
||||
"--extensions",
|
||||
" .png .gif .jpeg "
|
||||
])
|
||||
end)
|
||||
|
||||
assert captured =~ "#{pack_json} has been created with the pack2 pack"
|
||||
assert captured =~ "Using .png .gif .jpeg extensions"
|
||||
|
||||
assert File.exists?(pack_json)
|
||||
assert File.exists?(files_json)
|
||||
|
||||
captured =
|
||||
capture_io(fn ->
|
||||
Emoji.run([
|
||||
"gen-pack",
|
||||
url,
|
||||
"--name",
|
||||
name,
|
||||
"--license",
|
||||
"license",
|
||||
"--homepage",
|
||||
"homepage",
|
||||
"--description",
|
||||
"description",
|
||||
"--files",
|
||||
files_json,
|
||||
"--extensions",
|
||||
" .png .gif .jpeg "
|
||||
])
|
||||
end)
|
||||
|
||||
assert captured =~ "#{pack_json} has been updated with the pack2 pack"
|
||||
|
||||
on_exit(fn ->
|
||||
File.rm!(pack_json)
|
||||
File.rm!(files_json)
|
||||
end)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -140,7 +140,7 @@ test "no user to toggle" do
|
|||
test "user is unsubscribed" do
|
||||
followed = insert(:user)
|
||||
user = insert(:user)
|
||||
User.follow(user, followed, "accept")
|
||||
User.follow(user, followed, :follow_accept)
|
||||
|
||||
Mix.Tasks.Pleroma.User.run(["unsubscribe", user.nickname])
|
||||
|
||||
|
|
|
@ -194,7 +194,8 @@ test "doesn't return already accepted or duplicate follow requests" do
|
|||
CommonAPI.follow(pending_follower, locked)
|
||||
CommonAPI.follow(pending_follower, locked)
|
||||
CommonAPI.follow(accepted_follower, locked)
|
||||
Pleroma.FollowingRelationship.update(accepted_follower, locked, "accept")
|
||||
|
||||
Pleroma.FollowingRelationship.update(accepted_follower, locked, :follow_accept)
|
||||
|
||||
assert [^pending_follower] = User.get_follow_requests(locked)
|
||||
end
|
||||
|
@ -319,7 +320,7 @@ test "unfollow with syncronizes external user" do
|
|||
following_address: "http://localhost:4001/users/fuser2/following"
|
||||
})
|
||||
|
||||
{:ok, user} = User.follow(user, followed, "accept")
|
||||
{:ok, user} = User.follow(user, followed, :follow_accept)
|
||||
|
||||
{:ok, user, _activity} = User.unfollow(user, followed)
|
||||
|
||||
|
@ -332,7 +333,7 @@ test "unfollow takes a user and another user" do
|
|||
followed = insert(:user)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, user} = User.follow(user, followed, "accept")
|
||||
{:ok, user} = User.follow(user, followed, :follow_accept)
|
||||
|
||||
assert User.following(user) == [user.follower_address, followed.follower_address]
|
||||
|
||||
|
@ -353,7 +354,7 @@ test "unfollow doesn't unfollow yourself" do
|
|||
test "test if a user is following another user" do
|
||||
followed = insert(:user)
|
||||
user = insert(:user)
|
||||
User.follow(user, followed, "accept")
|
||||
User.follow(user, followed, :follow_accept)
|
||||
|
||||
assert User.following?(user, followed)
|
||||
refute User.following?(followed, user)
|
||||
|
@ -1141,8 +1142,8 @@ test "it deletes a user, all follow relationships and all activities", %{user: u
|
|||
object_two = insert(:note, user: follower)
|
||||
activity_two = insert(:note_activity, user: follower, note: object_two)
|
||||
|
||||
{:ok, like, _} = CommonAPI.favorite(activity_two.id, user)
|
||||
{:ok, like_two, _} = CommonAPI.favorite(activity.id, follower)
|
||||
{:ok, like} = CommonAPI.favorite(user, activity_two.id)
|
||||
{:ok, like_two} = CommonAPI.favorite(follower, activity.id)
|
||||
{:ok, repeat, _} = CommonAPI.repeat(activity_two.id, user)
|
||||
|
||||
{:ok, job} = User.delete(user)
|
||||
|
@ -1404,7 +1405,7 @@ test "preserves hosts in user links text" do
|
|||
bio = "A.k.a. @nick@domain.com"
|
||||
|
||||
expected_text =
|
||||
~s(A.k.a. <span class="h-card"><a data-user="#{remote_user.id}" class="u-url mention" href="#{
|
||||
~s(A.k.a. <span class="h-card"><a class="u-url mention" data-user="#{remote_user.id}" href="#{
|
||||
remote_user.ap_id
|
||||
}" rel="ugc">@<span>nick@domain.com</span></a></span>)
|
||||
|
||||
|
|
|
@ -1239,16 +1239,56 @@ test "POST /api/ap/upload_media", %{conn: conn} do
|
|||
filename: "an_image.jpg"
|
||||
}
|
||||
|
||||
conn =
|
||||
object =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
||||
|> json_response(:created)
|
||||
|
||||
assert object = json_response(conn, :created)
|
||||
assert object["name"] == desc
|
||||
assert object["type"] == "Document"
|
||||
assert object["actor"] == user.ap_id
|
||||
assert [%{"href" => object_href, "mediaType" => object_mediatype}] = object["url"]
|
||||
assert is_binary(object_href)
|
||||
assert object_mediatype == "image/jpeg"
|
||||
|
||||
activity_request = %{
|
||||
"@context" => "https://www.w3.org/ns/activitystreams",
|
||||
"type" => "Create",
|
||||
"object" => %{
|
||||
"type" => "Note",
|
||||
"content" => "AP C2S test, attachment",
|
||||
"attachment" => [object]
|
||||
},
|
||||
"to" => "https://www.w3.org/ns/activitystreams#Public",
|
||||
"cc" => []
|
||||
}
|
||||
|
||||
activity_response =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/users/#{user.nickname}/outbox", activity_request)
|
||||
|> json_response(:created)
|
||||
|
||||
assert activity_response["id"]
|
||||
assert activity_response["object"]
|
||||
assert activity_response["actor"] == user.ap_id
|
||||
|
||||
assert %Object{data: %{"attachment" => [attachment]}} =
|
||||
Object.normalize(activity_response["object"])
|
||||
|
||||
assert attachment["type"] == "Document"
|
||||
assert attachment["name"] == desc
|
||||
|
||||
assert [
|
||||
%{
|
||||
"href" => ^object_href,
|
||||
"type" => "Link",
|
||||
"mediaType" => ^object_mediatype
|
||||
}
|
||||
] = attachment["url"]
|
||||
|
||||
# Fails if unauthenticated
|
||||
conn
|
||||
|> post("/api/ap/upload_media", %{"file" => image, "description" => desc})
|
||||
|> json_response(403)
|
||||
|
|
|
@ -1900,14 +1900,14 @@ test "returns a favourite activities sorted by adds to favorite" do
|
|||
{:ok, a4} = CommonAPI.post(user2, %{"status" => "Agent Smith "})
|
||||
{:ok, a5} = CommonAPI.post(user1, %{"status" => "Red or Blue "})
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(a4.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a3.id, other_user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a3.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a5.id, other_user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a5.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a4.id, other_user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a1.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(a1.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(user, a4.id)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, a3.id)
|
||||
{:ok, _} = CommonAPI.favorite(user, a3.id)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, a5.id)
|
||||
{:ok, _} = CommonAPI.favorite(user, a5.id)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, a4.id)
|
||||
{:ok, _} = CommonAPI.favorite(user, a1.id)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, a1.id)
|
||||
result = ActivityPub.fetch_favourites(user)
|
||||
|
||||
assert Enum.map(result, & &1.id) == [a1.id, a5.id, a3.id, a4.id]
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidatorTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidator
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "likes" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post_activity} = CommonAPI.post(user, %{"status" => "uguu"})
|
||||
|
||||
valid_like = %{
|
||||
"to" => [user.ap_id],
|
||||
"cc" => [],
|
||||
"type" => "Like",
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"object" => post_activity.data["object"],
|
||||
"actor" => user.ap_id,
|
||||
"context" => "a context"
|
||||
}
|
||||
|
||||
%{valid_like: valid_like, user: user, post_activity: post_activity}
|
||||
end
|
||||
|
||||
test "returns ok when called in the ObjectValidator", %{valid_like: valid_like} do
|
||||
{:ok, object, _meta} = ObjectValidator.validate(valid_like, [])
|
||||
|
||||
assert "id" in Map.keys(object)
|
||||
end
|
||||
|
||||
test "is valid for a valid object", %{valid_like: valid_like} do
|
||||
assert LikeValidator.cast_and_validate(valid_like).valid?
|
||||
end
|
||||
|
||||
test "it errors when the actor is missing or not known", %{valid_like: valid_like} do
|
||||
without_actor = Map.delete(valid_like, "actor")
|
||||
|
||||
refute LikeValidator.cast_and_validate(without_actor).valid?
|
||||
|
||||
with_invalid_actor = Map.put(valid_like, "actor", "invalidactor")
|
||||
|
||||
refute LikeValidator.cast_and_validate(with_invalid_actor).valid?
|
||||
end
|
||||
|
||||
test "it errors when the object is missing or not known", %{valid_like: valid_like} do
|
||||
without_object = Map.delete(valid_like, "object")
|
||||
|
||||
refute LikeValidator.cast_and_validate(without_object).valid?
|
||||
|
||||
with_invalid_object = Map.put(valid_like, "object", "invalidobject")
|
||||
|
||||
refute LikeValidator.cast_and_validate(with_invalid_object).valid?
|
||||
end
|
||||
|
||||
test "it errors when the actor has already like the object", %{
|
||||
valid_like: valid_like,
|
||||
user: user,
|
||||
post_activity: post_activity
|
||||
} do
|
||||
_like = CommonAPI.favorite(user, post_activity.id)
|
||||
|
||||
refute LikeValidator.cast_and_validate(valid_like).valid?
|
||||
end
|
||||
|
||||
test "it works when actor or object are wrapped in maps", %{valid_like: valid_like} do
|
||||
wrapped_like =
|
||||
valid_like
|
||||
|> Map.put("actor", %{"id" => valid_like["actor"]})
|
||||
|> Map.put("object", %{"id" => valid_like["object"]})
|
||||
|
||||
validated = LikeValidator.cast_and_validate(wrapped_like)
|
||||
|
||||
assert validated.valid?
|
||||
|
||||
assert {:actor, valid_like["actor"]} in validated.changes
|
||||
assert {:object, valid_like["object"]} in validated.changes
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,35 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.NoteValidatorTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.NoteValidator
|
||||
alias Pleroma.Web.ActivityPub.Utils
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "Notes" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
|
||||
note = %{
|
||||
"id" => Utils.generate_activity_id(),
|
||||
"type" => "Note",
|
||||
"actor" => user.ap_id,
|
||||
"to" => [user.follower_address],
|
||||
"cc" => [],
|
||||
"content" => "Hellow this is content.",
|
||||
"context" => "xxx",
|
||||
"summary" => "a post"
|
||||
}
|
||||
|
||||
%{user: user, note: note}
|
||||
end
|
||||
|
||||
test "a basic note validates", %{note: note} do
|
||||
%{valid?: true} = NoteValidator.cast_and_validate(note)
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,32 @@
|
|||
defmodule Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTimeTest do
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.DateTime
|
||||
use Pleroma.DataCase
|
||||
|
||||
test "it validates an xsd:Datetime" do
|
||||
valid_strings = [
|
||||
"2004-04-12T13:20:00",
|
||||
"2004-04-12T13:20:15.5",
|
||||
"2004-04-12T13:20:00-05:00",
|
||||
"2004-04-12T13:20:00Z"
|
||||
]
|
||||
|
||||
invalid_strings = [
|
||||
"2004-04-12T13:00",
|
||||
"2004-04-1213:20:00",
|
||||
"99-04-12T13:00",
|
||||
"2004-04-12"
|
||||
]
|
||||
|
||||
assert {:ok, "2004-04-01T12:00:00Z"} == DateTime.cast("2004-04-01T12:00:00Z")
|
||||
|
||||
Enum.each(valid_strings, fn date_time ->
|
||||
result = DateTime.cast(date_time)
|
||||
assert {:ok, _} = result
|
||||
end)
|
||||
|
||||
Enum.each(invalid_strings, fn date_time ->
|
||||
result = DateTime.cast(date_time)
|
||||
assert :error == result
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,37 @@
|
|||
defmodule Pleroma.Web.ObjectValidators.Types.ObjectIDTest do
|
||||
alias Pleroma.Web.ActivityPub.ObjectValidators.Types.ObjectID
|
||||
use Pleroma.DataCase
|
||||
|
||||
@uris [
|
||||
"http://lain.com/users/lain",
|
||||
"http://lain.com",
|
||||
"https://lain.com/object/1"
|
||||
]
|
||||
|
||||
@non_uris [
|
||||
"https://",
|
||||
"rin",
|
||||
1,
|
||||
:x,
|
||||
%{"1" => 2}
|
||||
]
|
||||
|
||||
test "it accepts http uris" do
|
||||
Enum.each(@uris, fn uri ->
|
||||
assert {:ok, uri} == ObjectID.cast(uri)
|
||||
end)
|
||||
end
|
||||
|
||||
test "it accepts an object with a nested uri id" do
|
||||
Enum.each(@uris, fn uri ->
|
||||
assert {:ok, uri} == ObjectID.cast(%{"id" => uri})
|
||||
end)
|
||||
end
|
||||
|
||||
test "it rejects non-uri strings" do
|
||||
Enum.each(@non_uris, fn non_uri ->
|
||||
assert :error == ObjectID.cast(non_uri)
|
||||
assert :error == ObjectID.cast(%{"id" => non_uri})
|
||||
end)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,87 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.PipelineTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
import Mock
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "common_pipeline/2" do
|
||||
test "it goes through validation, filtering, persisting, side effects and federation for local activities" do
|
||||
activity = insert(:note_activity)
|
||||
meta = [local: true]
|
||||
|
||||
with_mocks([
|
||||
{Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
|
||||
{
|
||||
Pleroma.Web.ActivityPub.MRF,
|
||||
[],
|
||||
[filter: fn o -> {:ok, o} end]
|
||||
},
|
||||
{
|
||||
Pleroma.Web.ActivityPub.ActivityPub,
|
||||
[],
|
||||
[persist: fn o, m -> {:ok, o, m} end]
|
||||
},
|
||||
{
|
||||
Pleroma.Web.ActivityPub.SideEffects,
|
||||
[],
|
||||
[handle: fn o, m -> {:ok, o, m} end]
|
||||
},
|
||||
{
|
||||
Pleroma.Web.Federator,
|
||||
[],
|
||||
[publish: fn _o -> :ok end]
|
||||
}
|
||||
]) do
|
||||
assert {:ok, ^activity, ^meta} =
|
||||
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
|
||||
|
||||
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
|
||||
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
|
||||
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
|
||||
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
|
||||
assert_called(Pleroma.Web.Federator.publish(activity))
|
||||
end
|
||||
end
|
||||
|
||||
test "it goes through validation, filtering, persisting, side effects without federation for remote activities" do
|
||||
activity = insert(:note_activity)
|
||||
meta = [local: false]
|
||||
|
||||
with_mocks([
|
||||
{Pleroma.Web.ActivityPub.ObjectValidator, [], [validate: fn o, m -> {:ok, o, m} end]},
|
||||
{
|
||||
Pleroma.Web.ActivityPub.MRF,
|
||||
[],
|
||||
[filter: fn o -> {:ok, o} end]
|
||||
},
|
||||
{
|
||||
Pleroma.Web.ActivityPub.ActivityPub,
|
||||
[],
|
||||
[persist: fn o, m -> {:ok, o, m} end]
|
||||
},
|
||||
{
|
||||
Pleroma.Web.ActivityPub.SideEffects,
|
||||
[],
|
||||
[handle: fn o, m -> {:ok, o, m} end]
|
||||
},
|
||||
{
|
||||
Pleroma.Web.Federator,
|
||||
[],
|
||||
[]
|
||||
}
|
||||
]) do
|
||||
assert {:ok, ^activity, ^meta} =
|
||||
Pleroma.Web.ActivityPub.Pipeline.common_pipeline(activity, meta)
|
||||
|
||||
assert_called(Pleroma.Web.ActivityPub.ObjectValidator.validate(activity, meta))
|
||||
assert_called(Pleroma.Web.ActivityPub.MRF.filter(activity))
|
||||
assert_called(Pleroma.Web.ActivityPub.ActivityPub.persist(activity, meta))
|
||||
assert_called(Pleroma.Web.ActivityPub.SideEffects.handle(activity, meta))
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -0,0 +1,34 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ActivityPub.SideEffectsTest do
|
||||
use Pleroma.DataCase
|
||||
|
||||
alias Pleroma.Object
|
||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||
alias Pleroma.Web.ActivityPub.Builder
|
||||
alias Pleroma.Web.ActivityPub.SideEffects
|
||||
alias Pleroma.Web.CommonAPI
|
||||
|
||||
import Pleroma.Factory
|
||||
|
||||
describe "like objects" do
|
||||
setup do
|
||||
user = insert(:user)
|
||||
{:ok, post} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
|
||||
{:ok, like_data, _meta} = Builder.like(user, post.object)
|
||||
{:ok, like, _meta} = ActivityPub.persist(like_data, local: true)
|
||||
|
||||
%{like: like, user: user}
|
||||
end
|
||||
|
||||
test "add the like to the original object", %{like: like, user: user} do
|
||||
{:ok, like, _} = SideEffects.handle(like)
|
||||
object = Object.get_by_ap_id(like.data["object"])
|
||||
assert object.data["like_count"] == 1
|
||||
assert user.ap_id in object.data["likes"]
|
||||
end
|
||||
end
|
||||
end
|
|
@ -334,7 +334,9 @@ test "it works for incoming likes" do
|
|||
|> Poison.decode!()
|
||||
|> Map.put("object", activity.data["object"])
|
||||
|
||||
{:ok, %Activity{data: data, local: false}} = Transmogrifier.handle_incoming(data)
|
||||
{:ok, %Activity{data: data, local: false} = activity} = Transmogrifier.handle_incoming(data)
|
||||
|
||||
refute Enum.empty?(activity.recipients)
|
||||
|
||||
assert data["actor"] == "http://mastodon.example.org/users/admin"
|
||||
assert data["type"] == "Like"
|
||||
|
@ -1228,19 +1230,13 @@ test "it remaps video URLs as attachments if necessary" do
|
|||
attachment = %{
|
||||
"type" => "Link",
|
||||
"mediaType" => "video/mp4",
|
||||
"href" =>
|
||||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||
"mimeType" => "video/mp4",
|
||||
"size" => 5_015_880,
|
||||
"url" => [
|
||||
%{
|
||||
"href" =>
|
||||
"https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
"mediaType" => "video/mp4"
|
||||
}
|
||||
],
|
||||
"width" => 480
|
||||
]
|
||||
}
|
||||
|
||||
assert object.data["url"] ==
|
||||
|
@ -1622,7 +1618,7 @@ test "it upgrades a user to activitypub" do
|
|||
})
|
||||
|
||||
user_two = insert(:user)
|
||||
Pleroma.FollowingRelationship.follow(user_two, user, "accept")
|
||||
Pleroma.FollowingRelationship.follow(user_two, user, :follow_accept)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "test"})
|
||||
{:ok, unrelated_activity} = CommonAPI.post(user_two, %{"status" => "test"})
|
||||
|
@ -2061,11 +2057,7 @@ test "returns modified object when attachment is map" do
|
|||
%{
|
||||
"mediaType" => "video/mp4",
|
||||
"url" => [
|
||||
%{
|
||||
"href" => "https://peertube.moe/stat-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
%{"href" => "https://peertube.moe/stat-480.mp4", "mediaType" => "video/mp4"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
@ -2083,23 +2075,13 @@ test "returns modified object when attachment is list" do
|
|||
%{
|
||||
"mediaType" => "video/mp4",
|
||||
"url" => [
|
||||
%{
|
||||
"href" => "https://pe.er/stat-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
%{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
|
||||
]
|
||||
},
|
||||
%{
|
||||
"href" => "https://pe.er/stat-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"mimeType" => "video/mp4",
|
||||
"url" => [
|
||||
%{
|
||||
"href" => "https://pe.er/stat-480.mp4",
|
||||
"mediaType" => "video/mp4",
|
||||
"type" => "Link"
|
||||
}
|
||||
%{"href" => "https://pe.er/stat-480.mp4", "mediaType" => "video/mp4"}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
|
|
@ -59,7 +59,7 @@ test "renders a like activity" do
|
|||
object = Object.normalize(note)
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note.id, user)
|
||||
{:ok, like_activity} = CommonAPI.favorite(user, note.id)
|
||||
|
||||
result = ObjectView.render("object.json", %{object: like_activity})
|
||||
|
||||
|
|
|
@ -625,6 +625,39 @@ test "it returns 403 if requested by a non-admin" do
|
|||
|
||||
assert json_response(conn, :forbidden)
|
||||
end
|
||||
|
||||
test "email with +", %{conn: conn, admin: admin} do
|
||||
recipient_email = "foo+bar@baz.com"
|
||||
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json;charset=utf-8")
|
||||
|> post("/api/pleroma/admin/users/email_invite", %{email: recipient_email})
|
||||
|> json_response(:no_content)
|
||||
|
||||
token_record =
|
||||
Pleroma.UserInviteToken
|
||||
|> Repo.all()
|
||||
|> List.last()
|
||||
|
||||
assert token_record
|
||||
refute token_record.used
|
||||
|
||||
notify_email = Config.get([:instance, :notify_email])
|
||||
instance_name = Config.get([:instance, :name])
|
||||
|
||||
email =
|
||||
Pleroma.Emails.UserEmail.user_invitation_email(
|
||||
admin,
|
||||
token_record,
|
||||
recipient_email
|
||||
)
|
||||
|
||||
Swoosh.TestAssertions.assert_email_sent(
|
||||
from: {instance_name, notify_email},
|
||||
to: recipient_email,
|
||||
html_body: email.html_body
|
||||
)
|
||||
end
|
||||
end
|
||||
|
||||
describe "POST /api/pleroma/admin/users/email_invite, with invalid config" do
|
||||
|
@ -637,7 +670,8 @@ test "it returns 500 if `invites_enabled` is not enabled", %{conn: conn} do
|
|||
|
||||
conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
|
||||
|
||||
assert json_response(conn, :internal_server_error)
|
||||
assert json_response(conn, :bad_request) ==
|
||||
"To send invites you need to set the `invites_enabled` option to true."
|
||||
end
|
||||
|
||||
test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
|
||||
|
@ -646,7 +680,8 @@ test "it returns 500 if `registrations_open` is enabled", %{conn: conn} do
|
|||
|
||||
conn = post(conn, "/api/pleroma/admin/users/email_invite?email=foo@bar.com&name=JD")
|
||||
|
||||
assert json_response(conn, :internal_server_error)
|
||||
assert json_response(conn, :bad_request) ==
|
||||
"To send invites you need to set the `registrations_open` option to false."
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,45 @@
|
|||
# Pleroma: A lightweight social networking server
|
||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||
# SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
defmodule Pleroma.Web.ApiSpec.AppOperationTest do
|
||||
use Pleroma.Web.ConnCase, async: true
|
||||
|
||||
alias Pleroma.Web.ApiSpec
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateRequest
|
||||
alias Pleroma.Web.ApiSpec.Schemas.AppCreateResponse
|
||||
|
||||
import OpenApiSpex.TestAssertions
|
||||
import Pleroma.Factory
|
||||
|
||||
test "AppCreateRequest example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = AppCreateRequest.schema()
|
||||
assert_schema(schema.example, "AppCreateRequest", api_spec)
|
||||
end
|
||||
|
||||
test "AppCreateResponse example matches schema" do
|
||||
api_spec = ApiSpec.spec()
|
||||
schema = AppCreateResponse.schema()
|
||||
assert_schema(schema.example, "AppCreateResponse", api_spec)
|
||||
end
|
||||
|
||||
test "AppController produces a AppCreateResponse", %{conn: conn} do
|
||||
api_spec = ApiSpec.spec()
|
||||
app_attrs = build(:oauth_app)
|
||||
|
||||
json =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post(
|
||||
"/api/v1/apps",
|
||||
Jason.encode!(%{
|
||||
client_name: app_attrs.client_name,
|
||||
redirect_uris: app_attrs.redirect_uris
|
||||
})
|
||||
)
|
||||
|> json_response(200)
|
||||
|
||||
assert_schema(json, "AppCreateResponse", api_spec)
|
||||
end
|
||||
end
|
|
@ -284,9 +284,12 @@ test "favoriting a status" do
|
|||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
{:ok, post_activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
|
||||
{:ok, %Activity{}, _} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, %Activity{data: data}} = CommonAPI.favorite(user, post_activity.id)
|
||||
assert data["type"] == "Like"
|
||||
assert data["actor"] == user.ap_id
|
||||
assert data["object"] == post_activity.data["object"]
|
||||
end
|
||||
|
||||
test "retweeting a status twice returns the status" do
|
||||
|
@ -298,13 +301,13 @@ test "retweeting a status twice returns the status" do
|
|||
{:ok, ^activity, ^object} = CommonAPI.repeat(activity.id, user)
|
||||
end
|
||||
|
||||
test "favoriting a status twice returns the status" do
|
||||
test "favoriting a status twice returns ok, but without the like activity" do
|
||||
user = insert(:user)
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"})
|
||||
{:ok, %Activity{} = activity, object} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, ^activity, ^object} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, %Activity{}} = CommonAPI.favorite(user, activity.id)
|
||||
assert {:ok, :already_liked} = CommonAPI.favorite(user, activity.id)
|
||||
end
|
||||
end
|
||||
|
||||
|
@ -562,7 +565,7 @@ test "cancels a pending follow for a local user" do
|
|||
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
|
||||
CommonAPI.follow(follower, followed)
|
||||
|
||||
assert User.get_follow_state(follower, followed) == "pending"
|
||||
assert User.get_follow_state(follower, followed) == :follow_pending
|
||||
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
||||
assert User.get_follow_state(follower, followed) == nil
|
||||
|
||||
|
@ -584,7 +587,7 @@ test "cancels a pending follow for a remote user" do
|
|||
assert {:ok, follower, followed, %{id: activity_id, data: %{"state" => "pending"}}} =
|
||||
CommonAPI.follow(follower, followed)
|
||||
|
||||
assert User.get_follow_state(follower, followed) == "pending"
|
||||
assert User.get_follow_state(follower, followed) == :follow_pending
|
||||
assert {:ok, follower} = CommonAPI.unfollow(follower, followed)
|
||||
assert User.get_follow_state(follower, followed) == nil
|
||||
|
||||
|
|
|
@ -159,11 +159,11 @@ test "works for text/markdown with mentions" do
|
|||
{output, _, _} = Utils.format_input(text, "text/markdown")
|
||||
|
||||
assert output ==
|
||||
~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a data-user="#{
|
||||
~s(<p><strong>hello world</strong></p><p><em>another <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
user.id
|
||||
}" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a data-user="#{
|
||||
}" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> and <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
user.id
|
||||
}" class="u-url mention" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
|
||||
}" href="http://foo.com/user__test" rel="ugc">@<span>user__test</span></a></span> <a href="http://google.com" rel="ugc">google.com</a> paragraph</em></p>)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -82,9 +82,9 @@ test "updates the user's bio", %{conn: conn} do
|
|||
assert user_data = json_response(conn, 200)
|
||||
|
||||
assert user_data["note"] ==
|
||||
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a data-user="#{
|
||||
~s(I drink <a class="hashtag" data-tag="cofe" href="http://localhost:4001/tag/cofe">#cofe</a> with <span class="h-card"><a class="u-url mention" data-user="#{
|
||||
user2.id
|
||||
}" class="u-url mention" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
|
||||
}" href="#{user2.ap_id}" rel="ugc">@<span>#{user2.nickname}</span></a></span><br/><br/>suya..)
|
||||
end
|
||||
|
||||
test "updates the user's locking status", %{conn: conn} do
|
||||
|
@ -273,7 +273,7 @@ test "updates profile emojos", %{user: user, conn: conn} do
|
|||
test "update fields", %{conn: conn} do
|
||||
fields = [
|
||||
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "<script>bar</script>"},
|
||||
%{"name" => "link", "value" => "cofe.io"}
|
||||
%{"name" => "link.io", "value" => "cofe.io"}
|
||||
]
|
||||
|
||||
account_data =
|
||||
|
@ -283,7 +283,10 @@ test "update fields", %{conn: conn} do
|
|||
|
||||
assert account_data["fields"] == [
|
||||
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
|
||||
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
|
||||
%{
|
||||
"name" => "link.io",
|
||||
"value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)
|
||||
}
|
||||
]
|
||||
|
||||
assert account_data["source"]["fields"] == [
|
||||
|
@ -291,14 +294,16 @@ test "update fields", %{conn: conn} do
|
|||
"name" => "<a href=\"http://google.com\">foo</a>",
|
||||
"value" => "<script>bar</script>"
|
||||
},
|
||||
%{"name" => "link", "value" => "cofe.io"}
|
||||
%{"name" => "link.io", "value" => "cofe.io"}
|
||||
]
|
||||
end
|
||||
|
||||
test "update fields via x-www-form-urlencoded", %{conn: conn} do
|
||||
fields =
|
||||
[
|
||||
"fields_attributes[1][name]=link",
|
||||
"fields_attributes[1][value]=cofe.io",
|
||||
"fields_attributes[0][name]=<a href=\"http://google.com\">foo</a>",
|
||||
"fields_attributes[1][value]=http://cofe.io",
|
||||
"fields_attributes[0][name]=foo",
|
||||
"fields_attributes[0][value]=bar"
|
||||
]
|
||||
|> Enum.join("&")
|
||||
|
@ -310,51 +315,20 @@ test "update fields", %{conn: conn} do
|
|||
|> json_response(200)
|
||||
|
||||
assert account["fields"] == [
|
||||
%{"name" => "<a href=\"http://google.com\">foo</a>", "value" => "bar"},
|
||||
%{"name" => "link", "value" => ~S(<a href="http://cofe.io" rel="ugc">cofe.io</a>)}
|
||||
%{"name" => "foo", "value" => "bar"},
|
||||
%{
|
||||
"name" => "link",
|
||||
"value" => ~S(<a href="http://cofe.io" rel="ugc">http://cofe.io</a>)
|
||||
}
|
||||
]
|
||||
|
||||
assert account["source"]["fields"] == [
|
||||
%{
|
||||
"name" => "<a href=\"http://google.com\">foo</a>",
|
||||
"value" => "bar"
|
||||
},
|
||||
%{"name" => "link", "value" => "cofe.io"}
|
||||
%{"name" => "foo", "value" => "bar"},
|
||||
%{"name" => "link", "value" => "http://cofe.io"}
|
||||
]
|
||||
end
|
||||
|
||||
name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
|
||||
value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
|
||||
|
||||
long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
|
||||
|
||||
fields = [%{"name" => "<b>foo<b>", "value" => long_value}]
|
||||
|
||||
assert %{"error" => "Invalid request"} ==
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|
||||
|> json_response(403)
|
||||
|
||||
long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
|
||||
|
||||
fields = [%{"name" => long_name, "value" => "bar"}]
|
||||
|
||||
assert %{"error" => "Invalid request"} ==
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|
||||
|> json_response(403)
|
||||
|
||||
Pleroma.Config.put([:instance, :max_account_fields], 1)
|
||||
|
||||
fields = [
|
||||
%{"name" => "<b>foo<b>", "value" => "<i>bar</i>"},
|
||||
%{"name" => "link", "value" => "cofe.io"}
|
||||
]
|
||||
|
||||
assert %{"error" => "Invalid request"} ==
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|
||||
|> json_response(403)
|
||||
|
||||
test "update fields with empty name", %{conn: conn} do
|
||||
fields = [
|
||||
%{"name" => "foo", "value" => ""},
|
||||
%{"name" => "", "value" => "bar"}
|
||||
|
@ -369,5 +343,39 @@ test "update fields", %{conn: conn} do
|
|||
%{"name" => "foo", "value" => ""}
|
||||
]
|
||||
end
|
||||
|
||||
test "update fields when invalid request", %{conn: conn} do
|
||||
name_limit = Pleroma.Config.get([:instance, :account_field_name_length])
|
||||
value_limit = Pleroma.Config.get([:instance, :account_field_value_length])
|
||||
|
||||
long_name = Enum.map(0..name_limit, fn _ -> "x" end) |> Enum.join()
|
||||
long_value = Enum.map(0..value_limit, fn _ -> "x" end) |> Enum.join()
|
||||
|
||||
fields = [%{"name" => "foo", "value" => long_value}]
|
||||
|
||||
assert %{"error" => "Invalid request"} ==
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|
||||
|> json_response(403)
|
||||
|
||||
fields = [%{"name" => long_name, "value" => "bar"}]
|
||||
|
||||
assert %{"error" => "Invalid request"} ==
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|
||||
|> json_response(403)
|
||||
|
||||
Pleroma.Config.put([:instance, :max_account_fields], 1)
|
||||
|
||||
fields = [
|
||||
%{"name" => "foo", "value" => "bar"},
|
||||
%{"name" => "link", "value" => "cofe.io"}
|
||||
]
|
||||
|
||||
assert %{"error" => "Invalid request"} ==
|
||||
conn
|
||||
|> patch("/api/v1/accounts/update_credentials", %{"fields_attributes" => fields})
|
||||
|> json_response(403)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
|
|
@ -794,7 +794,9 @@ test "blocking / unblocking a user" do
|
|||
|
||||
test "Account registration via Application", %{conn: conn} do
|
||||
conn =
|
||||
post(conn, "/api/v1/apps", %{
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: "client_name",
|
||||
redirect_uris: "urn:ietf:wg:oauth:2.0:oob",
|
||||
scopes: "read, write, follow"
|
||||
|
|
|
@ -16,8 +16,7 @@ test "apps/verify_credentials", %{conn: conn} do
|
|||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, token.user)
|
||||
|> assign(:token, token)
|
||||
|> put_req_header("authorization", "Bearer #{token.token}")
|
||||
|> get("/api/v1/apps/verify_credentials")
|
||||
|
||||
app = Repo.preload(token, :app).app
|
||||
|
@ -37,6 +36,7 @@ test "creates an oauth app", %{conn: conn} do
|
|||
|
||||
conn =
|
||||
conn
|
||||
|> put_req_header("content-type", "application/json")
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/apps", %{
|
||||
client_name: app_attrs.client_name,
|
||||
|
|
|
@ -21,7 +21,7 @@ test "/api/v1/follow_requests works", %{user: user, conn: conn} do
|
|||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
{:ok, other_user} = User.follow(other_user, user, "pending")
|
||||
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
|
||||
|
||||
assert User.following?(other_user, user) == false
|
||||
|
||||
|
@ -35,7 +35,7 @@ test "/api/v1/follow_requests/:id/authorize works", %{user: user, conn: conn} do
|
|||
other_user = insert(:user)
|
||||
|
||||
{:ok, _activity} = ActivityPub.follow(other_user, user)
|
||||
{:ok, other_user} = User.follow(other_user, user, "pending")
|
||||
{:ok, other_user} = User.follow(other_user, user, :follow_pending)
|
||||
|
||||
user = User.get_cached_by_id(user.id)
|
||||
other_user = User.get_cached_by_id(other_user.id)
|
||||
|
|
|
@ -46,7 +46,7 @@ test "list of notifications" do
|
|||
|> get("/api/v1/notifications")
|
||||
|
||||
expected_response =
|
||||
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
|
||||
"hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
|
||||
user.ap_id
|
||||
}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
|
||||
|
||||
|
@ -65,7 +65,7 @@ test "getting a single notification" do
|
|||
conn = get(conn, "/api/v1/notifications/#{notification.id}")
|
||||
|
||||
expected_response =
|
||||
"hi <span class=\"h-card\"><a data-user=\"#{user.id}\" class=\"u-url mention\" href=\"#{
|
||||
"hi <span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{user.id}\" href=\"#{
|
||||
user.ap_id
|
||||
}\" rel=\"ugc\">@<span>#{user.nickname}</span></a></span>"
|
||||
|
||||
|
@ -73,6 +73,22 @@ test "getting a single notification" do
|
|||
assert response == expected_response
|
||||
end
|
||||
|
||||
test "dismissing a single notification (deprecated endpoint)" do
|
||||
%{user: user, conn: conn} = oauth_access(["write:notifications"])
|
||||
other_user = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "hi @#{user.nickname}"})
|
||||
|
||||
{:ok, [notification]} = Notification.create_notifications(activity)
|
||||
|
||||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
end
|
||||
|
||||
test "dismissing a single notification" do
|
||||
%{user: user, conn: conn} = oauth_access(["write:notifications"])
|
||||
other_user = insert(:user)
|
||||
|
@ -84,7 +100,7 @@ test "dismissing a single notification" do
|
|||
conn =
|
||||
conn
|
||||
|> assign(:user, user)
|
||||
|> post("/api/v1/notifications/dismiss", %{"id" => notification.id})
|
||||
|> post("/api/v1/notifications/#{notification.id}/dismiss")
|
||||
|
||||
assert %{} = json_response(conn, 200)
|
||||
end
|
||||
|
@ -214,10 +230,10 @@ test "filters notifications for Like activities" do
|
|||
{:ok, private_activity} =
|
||||
CommonAPI.post(other_user, %{"status" => ".", "visibility" => "private"})
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(public_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(direct_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(unlisted_activity.id, user)
|
||||
{:ok, _, _} = CommonAPI.favorite(private_activity.id, user)
|
||||
{:ok, _} = CommonAPI.favorite(user, public_activity.id)
|
||||
{:ok, _} = CommonAPI.favorite(user, direct_activity.id)
|
||||
{:ok, _} = CommonAPI.favorite(user, unlisted_activity.id)
|
||||
{:ok, _} = CommonAPI.favorite(user, private_activity.id)
|
||||
|
||||
activity_ids =
|
||||
conn
|
||||
|
@ -294,7 +310,7 @@ test "filters notifications using exclude_types" do
|
|||
|
||||
{:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
|
||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
{:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
|
||||
{:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
|
||||
{:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
|
||||
|
||||
|
@ -330,7 +346,7 @@ test "filters notifications using include_types" do
|
|||
|
||||
{:ok, mention_activity} = CommonAPI.post(other_user, %{"status" => "hey @#{user.nickname}"})
|
||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
{:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, other_user)
|
||||
{:ok, favorite_activity} = CommonAPI.favorite(other_user, create_activity.id)
|
||||
{:ok, reblog_activity, _} = CommonAPI.repeat(create_activity.id, other_user)
|
||||
{:ok, _, _, follow_activity} = CommonAPI.follow(other_user, user)
|
||||
|
||||
|
|
|
@ -775,7 +775,7 @@ test "reblogged status for another user" do
|
|||
user1 = insert(:user)
|
||||
user2 = insert(:user)
|
||||
user3 = insert(:user)
|
||||
CommonAPI.favorite(activity.id, user2)
|
||||
{:ok, _} = CommonAPI.favorite(user2, activity.id)
|
||||
{:ok, _bookmark} = Pleroma.Bookmark.create(user2.id, activity.id)
|
||||
{:ok, reblog_activity1, _object} = CommonAPI.repeat(activity.id, user1)
|
||||
{:ok, _, _object} = CommonAPI.repeat(activity.id, user2)
|
||||
|
@ -850,11 +850,15 @@ test "favoriting twice will just return 200", %{conn: conn} do
|
|||
activity = insert(:note_activity)
|
||||
|
||||
post(conn, "/api/v1/statuses/#{activity.id}/favourite")
|
||||
assert post(conn, "/api/v1/statuses/#{activity.id}/favourite") |> json_response(200)
|
||||
|
||||
assert post(conn, "/api/v1/statuses/#{activity.id}/favourite")
|
||||
|> json_response(200)
|
||||
end
|
||||
|
||||
test "returns 404 error for a wrong id", %{conn: conn} do
|
||||
conn = post(conn, "/api/v1/statuses/1/favourite")
|
||||
conn =
|
||||
conn
|
||||
|> post("/api/v1/statuses/1/favourite")
|
||||
|
||||
assert json_response(conn, 404) == %{"error" => "Record not found"}
|
||||
end
|
||||
|
@ -866,7 +870,7 @@ test "returns 404 error for a wrong id", %{conn: conn} do
|
|||
test "unfavorites a status and returns it", %{user: user, conn: conn} do
|
||||
activity = insert(:note_activity)
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, _} = CommonAPI.favorite(user, activity.id)
|
||||
|
||||
conn = post(conn, "/api/v1/statuses/#{activity.id}/unfavourite")
|
||||
|
||||
|
@ -1178,7 +1182,7 @@ test "Repeated posts that are replies incorrectly have in_reply_to_id null", %{c
|
|||
|
||||
test "returns users who have favorited the status", %{conn: conn, activity: activity} do
|
||||
other_user = insert(:user)
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -1209,7 +1213,7 @@ test "does not return users who have favorited the status but are blocked", %{
|
|||
other_user = insert(:user)
|
||||
{:ok, _user_relationship} = User.block(user, other_user)
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -1221,7 +1225,7 @@ test "does not return users who have favorited the status but are blocked", %{
|
|||
|
||||
test "does not fail on an unauthenticated request", %{activity: activity} do
|
||||
other_user = insert(:user)
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
response =
|
||||
build_conn()
|
||||
|
@ -1241,7 +1245,7 @@ test "requires authentication for private posts", %{user: user} do
|
|||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, other_user)
|
||||
{:ok, _} = CommonAPI.favorite(other_user, activity.id)
|
||||
|
||||
favourited_by_url = "/api/v1/statuses/#{activity.id}/favourited_by"
|
||||
|
||||
|
@ -1401,7 +1405,7 @@ test "returns the favorites of a user" do
|
|||
{:ok, _} = CommonAPI.post(other_user, %{"status" => "bla"})
|
||||
{:ok, activity} = CommonAPI.post(other_user, %{"status" => "traps are happy"})
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, _} = CommonAPI.favorite(user, activity.id)
|
||||
|
||||
first_conn = get(conn, "/api/v1/favourites")
|
||||
|
||||
|
@ -1418,7 +1422,7 @@ test "returns the favorites of a user" do
|
|||
"Trees Are Never Sad Look At Them Every Once In Awhile They're Quite Beautiful."
|
||||
})
|
||||
|
||||
{:ok, _, _} = CommonAPI.favorite(second_activity.id, user)
|
||||
{:ok, _} = CommonAPI.favorite(user, second_activity.id)
|
||||
|
||||
last_like = status["id"]
|
||||
|
||||
|
|
|
@ -54,7 +54,7 @@ test "Favourite notification" do
|
|||
user = insert(:user)
|
||||
another_user = insert(:user)
|
||||
{:ok, create_activity} = CommonAPI.post(user, %{"status" => "hey"})
|
||||
{:ok, favorite_activity, _object} = CommonAPI.favorite(create_activity.id, another_user)
|
||||
{:ok, favorite_activity} = CommonAPI.favorite(another_user, create_activity.id)
|
||||
{:ok, [notification]} = Notification.create_notifications(favorite_activity)
|
||||
create_activity = Activity.get_by_id(create_activity.id)
|
||||
|
||||
|
|
|
@ -7,6 +7,8 @@ defmodule Pleroma.Web.NodeInfoTest do
|
|||
|
||||
import Pleroma.Factory
|
||||
|
||||
alias Pleroma.Config
|
||||
|
||||
setup do: clear_config([:mrf_simple])
|
||||
setup do: clear_config(:instance)
|
||||
|
||||
|
@ -47,7 +49,7 @@ test "nodeinfo shows restricted nicknames", %{conn: conn} do
|
|||
|
||||
assert result = json_response(conn, 200)
|
||||
|
||||
assert Pleroma.Config.get([Pleroma.User, :restricted_nicknames]) ==
|
||||
assert Config.get([Pleroma.User, :restricted_nicknames]) ==
|
||||
result["metadata"]["restrictedNicknames"]
|
||||
end
|
||||
|
||||
|
@ -65,10 +67,10 @@ test "returns software.repository field in nodeinfo 2.1", %{conn: conn} do
|
|||
end
|
||||
|
||||
test "returns fieldsLimits field", %{conn: conn} do
|
||||
Pleroma.Config.put([:instance, :max_account_fields], 10)
|
||||
Pleroma.Config.put([:instance, :max_remote_account_fields], 15)
|
||||
Pleroma.Config.put([:instance, :account_field_name_length], 255)
|
||||
Pleroma.Config.put([:instance, :account_field_value_length], 2048)
|
||||
Config.put([:instance, :max_account_fields], 10)
|
||||
Config.put([:instance, :max_remote_account_fields], 15)
|
||||
Config.put([:instance, :account_field_name_length], 255)
|
||||
Config.put([:instance, :account_field_value_length], 2048)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -82,8 +84,8 @@ test "returns fieldsLimits field", %{conn: conn} do
|
|||
end
|
||||
|
||||
test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
|
||||
option = Pleroma.Config.get([:instance, :safe_dm_mentions])
|
||||
Pleroma.Config.put([:instance, :safe_dm_mentions], true)
|
||||
option = Config.get([:instance, :safe_dm_mentions])
|
||||
Config.put([:instance, :safe_dm_mentions], true)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -92,7 +94,7 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
|
|||
|
||||
assert "safe_dm_mentions" in response["metadata"]["features"]
|
||||
|
||||
Pleroma.Config.put([:instance, :safe_dm_mentions], false)
|
||||
Config.put([:instance, :safe_dm_mentions], false)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -101,14 +103,14 @@ test "it returns the safe_dm_mentions feature if enabled", %{conn: conn} do
|
|||
|
||||
refute "safe_dm_mentions" in response["metadata"]["features"]
|
||||
|
||||
Pleroma.Config.put([:instance, :safe_dm_mentions], option)
|
||||
Config.put([:instance, :safe_dm_mentions], option)
|
||||
end
|
||||
|
||||
describe "`metadata/federation/enabled`" do
|
||||
setup do: clear_config([:instance, :federating])
|
||||
|
||||
test "it shows if federation is enabled/disabled", %{conn: conn} do
|
||||
Pleroma.Config.put([:instance, :federating], true)
|
||||
Config.put([:instance, :federating], true)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -117,7 +119,7 @@ test "it shows if federation is enabled/disabled", %{conn: conn} do
|
|||
|
||||
assert response["metadata"]["federation"]["enabled"] == true
|
||||
|
||||
Pleroma.Config.put([:instance, :federating], false)
|
||||
Config.put([:instance, :federating], false)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -128,15 +130,39 @@ test "it shows if federation is enabled/disabled", %{conn: conn} do
|
|||
end
|
||||
end
|
||||
|
||||
test "it shows MRF transparency data if enabled", %{conn: conn} do
|
||||
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
|
||||
test "it shows default features flags", %{conn: conn} do
|
||||
response =
|
||||
conn
|
||||
|> get("/nodeinfo/2.1.json")
|
||||
|> json_response(:ok)
|
||||
|
||||
option = Pleroma.Config.get([:instance, :mrf_transparency])
|
||||
Pleroma.Config.put([:instance, :mrf_transparency], true)
|
||||
default_features = [
|
||||
"pleroma_api",
|
||||
"mastodon_api",
|
||||
"mastodon_api_streaming",
|
||||
"polls",
|
||||
"pleroma_explicit_addressing",
|
||||
"shareable_emoji_packs",
|
||||
"multifetch",
|
||||
"pleroma_emoji_reactions",
|
||||
"pleroma:api/v1/notifications:include_types_filter"
|
||||
]
|
||||
|
||||
assert MapSet.subset?(
|
||||
MapSet.new(default_features),
|
||||
MapSet.new(response["metadata"]["features"])
|
||||
)
|
||||
end
|
||||
|
||||
test "it shows MRF transparency data if enabled", %{conn: conn} do
|
||||
config = Config.get([:instance, :rewrite_policy])
|
||||
Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
|
||||
|
||||
option = Config.get([:instance, :mrf_transparency])
|
||||
Config.put([:instance, :mrf_transparency], true)
|
||||
|
||||
simple_config = %{"reject" => ["example.com"]}
|
||||
Pleroma.Config.put(:mrf_simple, simple_config)
|
||||
Config.put(:mrf_simple, simple_config)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -145,25 +171,25 @@ test "it shows MRF transparency data if enabled", %{conn: conn} do
|
|||
|
||||
assert response["metadata"]["federation"]["mrf_simple"] == simple_config
|
||||
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], config)
|
||||
Pleroma.Config.put([:instance, :mrf_transparency], option)
|
||||
Pleroma.Config.put(:mrf_simple, %{})
|
||||
Config.put([:instance, :rewrite_policy], config)
|
||||
Config.put([:instance, :mrf_transparency], option)
|
||||
Config.put(:mrf_simple, %{})
|
||||
end
|
||||
|
||||
test "it performs exclusions from MRF transparency data if configured", %{conn: conn} do
|
||||
config = Pleroma.Config.get([:instance, :rewrite_policy])
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
|
||||
config = Config.get([:instance, :rewrite_policy])
|
||||
Config.put([:instance, :rewrite_policy], [Pleroma.Web.ActivityPub.MRF.SimplePolicy])
|
||||
|
||||
option = Pleroma.Config.get([:instance, :mrf_transparency])
|
||||
Pleroma.Config.put([:instance, :mrf_transparency], true)
|
||||
option = Config.get([:instance, :mrf_transparency])
|
||||
Config.put([:instance, :mrf_transparency], true)
|
||||
|
||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
||||
Pleroma.Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
|
||||
exclusions = Config.get([:instance, :mrf_transparency_exclusions])
|
||||
Config.put([:instance, :mrf_transparency_exclusions], ["other.site"])
|
||||
|
||||
simple_config = %{"reject" => ["example.com", "other.site"]}
|
||||
expected_config = %{"reject" => ["example.com"]}
|
||||
|
||||
Pleroma.Config.put(:mrf_simple, simple_config)
|
||||
Config.put(:mrf_simple, simple_config)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -173,9 +199,9 @@ test "it performs exclusions from MRF transparency data if configured", %{conn:
|
|||
assert response["metadata"]["federation"]["mrf_simple"] == expected_config
|
||||
assert response["metadata"]["federation"]["exclusions"] == true
|
||||
|
||||
Pleroma.Config.put([:instance, :rewrite_policy], config)
|
||||
Pleroma.Config.put([:instance, :mrf_transparency], option)
|
||||
Pleroma.Config.put([:instance, :mrf_transparency_exclusions], exclusions)
|
||||
Pleroma.Config.put(:mrf_simple, %{})
|
||||
Config.put([:instance, :rewrite_policy], config)
|
||||
Config.put([:instance, :mrf_transparency], option)
|
||||
Config.put([:instance, :mrf_transparency_exclusions], exclusions)
|
||||
Config.put(:mrf_simple, %{})
|
||||
end
|
||||
end
|
||||
|
|
|
@ -136,7 +136,7 @@ test "render html for redirect for html format", %{conn: conn} do
|
|||
|
||||
user = insert(:user)
|
||||
|
||||
{:ok, like_activity, _} = CommonAPI.favorite(note_activity.id, user)
|
||||
{:ok, like_activity} = CommonAPI.favorite(user, note_activity.id)
|
||||
|
||||
assert like_activity.data["type"] == "Like"
|
||||
|
||||
|
|
|
@ -138,7 +138,7 @@ test "returns list of statuses favorited by specified user", %{
|
|||
user: user
|
||||
} do
|
||||
[activity | _] = insert_pair(:note_activity)
|
||||
CommonAPI.favorite(activity.id, user)
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -155,7 +155,7 @@ test "does not return favorites for specified user_id when user is not logged in
|
|||
user: user
|
||||
} do
|
||||
activity = insert(:note_activity)
|
||||
CommonAPI.favorite(activity.id, user)
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
|
||||
build_conn()
|
||||
|> get("/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|
@ -172,7 +172,7 @@ test "returns favorited DM only when user is logged in and he is one of recipien
|
|||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
CommonAPI.favorite(direct.id, user)
|
||||
CommonAPI.favorite(user, direct.id)
|
||||
|
||||
for u <- [user, current_user] do
|
||||
response =
|
||||
|
@ -202,7 +202,7 @@ test "does not return others' favorited DM when user is not one of recipients",
|
|||
"visibility" => "direct"
|
||||
})
|
||||
|
||||
CommonAPI.favorite(direct.id, user)
|
||||
CommonAPI.favorite(user, direct.id)
|
||||
|
||||
response =
|
||||
conn
|
||||
|
@ -219,7 +219,7 @@ test "paginates favorites using since_id and max_id", %{
|
|||
activities = insert_list(10, :note_activity)
|
||||
|
||||
Enum.each(activities, fn activity ->
|
||||
CommonAPI.favorite(activity.id, user)
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
end)
|
||||
|
||||
third_activity = Enum.at(activities, 2)
|
||||
|
@ -245,7 +245,7 @@ test "limits favorites using limit parameter", %{
|
|||
7
|
||||
|> insert_list(:note_activity)
|
||||
|> Enum.each(fn activity ->
|
||||
CommonAPI.favorite(activity.id, user)
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
end)
|
||||
|
||||
response =
|
||||
|
@ -277,7 +277,7 @@ test "returns 404 error when specified user is not exist", %{conn: conn} do
|
|||
test "returns 403 error when user has hidden own favorites", %{conn: conn} do
|
||||
user = insert(:user, hide_favorites: true)
|
||||
activity = insert(:note_activity)
|
||||
CommonAPI.favorite(activity.id, user)
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
|
||||
conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|
||||
|
@ -287,7 +287,7 @@ test "returns 403 error when user has hidden own favorites", %{conn: conn} do
|
|||
test "hides favorites for new users by default", %{conn: conn} do
|
||||
user = insert(:user)
|
||||
activity = insert(:note_activity)
|
||||
CommonAPI.favorite(activity.id, user)
|
||||
CommonAPI.favorite(user, activity.id)
|
||||
|
||||
assert user.hide_favorites
|
||||
conn = get(conn, "/api/v1/pleroma/accounts/#{user.id}/favourites")
|
||||
|
|
|
@ -169,6 +169,23 @@ test "/api/v1/pleroma/conversations/:id/statuses" do
|
|||
id_one = activity.id
|
||||
id_two = activity_two.id
|
||||
assert [%{"id" => ^id_one}, %{"id" => ^id_two}] = result
|
||||
|
||||
{:ok, %{id: id_three}} =
|
||||
CommonAPI.post(other_user, %{
|
||||
"status" => "Bye!",
|
||||
"in_reply_to_status_id" => activity.id,
|
||||
"in_reply_to_conversation_id" => participation.id
|
||||
})
|
||||
|
||||
assert [%{"id" => ^id_two}, %{"id" => ^id_three}] =
|
||||
conn
|
||||
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?limit=2")
|
||||
|> json_response(:ok)
|
||||
|
||||
assert [%{"id" => ^id_three}] =
|
||||
conn
|
||||
|> get("/api/v1/pleroma/conversations/#{participation.id}/statuses?min_id=#{id_two}")
|
||||
|> json_response(:ok)
|
||||
end
|
||||
|
||||
test "PATCH /api/v1/pleroma/conversations/:id" do
|
||||
|
|
|
@ -170,7 +170,7 @@ test "renders title and body for like activity" do
|
|||
"<span>Lorem ipsum dolor sit amet</span>, consectetur :firefox: adipiscing elit. Fusce sagittis finibus turpis."
|
||||
})
|
||||
|
||||
{:ok, activity, _} = CommonAPI.favorite(activity.id, user)
|
||||
{:ok, activity} = CommonAPI.favorite(user, activity.id)
|
||||
object = Object.normalize(activity)
|
||||
|
||||
assert Impl.format_body(%{activity: activity}, user, object) == "@Bob has favorited your post"
|
||||
|
|
|
@ -64,9 +64,6 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl
|
|||
blocked = insert(:user)
|
||||
{:ok, _user_relationship} = User.block(user, blocked)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
|
||||
{:ok, notif, _} = CommonAPI.favorite(activity.id, blocked)
|
||||
|
||||
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
||||
|
||||
Streamer.add_socket(
|
||||
|
@ -74,6 +71,9 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl
|
|||
%{transport_pid: task.pid, assigns: %{user: user}}
|
||||
)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => ":("})
|
||||
{:ok, notif} = CommonAPI.favorite(blocked, activity.id)
|
||||
|
||||
Streamer.stream("user:notification", notif)
|
||||
Task.await(task)
|
||||
end
|
||||
|
@ -83,10 +83,6 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is
|
|||
} do
|
||||
user2 = insert(:user)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
||||
{:ok, activity} = CommonAPI.add_mute(user, activity)
|
||||
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
|
||||
|
||||
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
||||
|
||||
Streamer.add_socket(
|
||||
|
@ -94,6 +90,10 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is
|
|||
%{transport_pid: task.pid, assigns: %{user: user}}
|
||||
)
|
||||
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
||||
{:ok, activity} = CommonAPI.add_mute(user, activity)
|
||||
{:ok, notif} = CommonAPI.favorite(user2, activity.id)
|
||||
|
||||
Streamer.stream("user:notification", notif)
|
||||
Task.await(task)
|
||||
end
|
||||
|
@ -103,10 +103,6 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is
|
|||
} do
|
||||
user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"})
|
||||
|
||||
{:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
||||
{:ok, notif, _} = CommonAPI.favorite(activity.id, user2)
|
||||
|
||||
task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end)
|
||||
|
||||
Streamer.add_socket(
|
||||
|
@ -114,6 +110,10 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is
|
|||
%{transport_pid: task.pid, assigns: %{user: user}}
|
||||
)
|
||||
|
||||
{:ok, user} = User.block_domain(user, "hecking-lewd-place.com")
|
||||
{:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"})
|
||||
{:ok, notif} = CommonAPI.favorite(user2, activity.id)
|
||||
|
||||
Streamer.stream("user:notification", notif)
|
||||
Task.await(task)
|
||||
end
|
||||
|
@ -209,7 +209,7 @@ test "it doesn't send to user if recipients invalid and thread containment is en
|
|||
Pleroma.Config.put([:instance, :skip_thread_containment], false)
|
||||
author = insert(:user)
|
||||
user = insert(:user)
|
||||
User.follow(user, author, "accept")
|
||||
User.follow(user, author, :follow_accept)
|
||||
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
|
@ -232,7 +232,7 @@ test "it sends message if recipients invalid and thread containment is disabled"
|
|||
Pleroma.Config.put([:instance, :skip_thread_containment], true)
|
||||
author = insert(:user)
|
||||
user = insert(:user)
|
||||
User.follow(user, author, "accept")
|
||||
User.follow(user, author, :follow_accept)
|
||||
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
|
@ -255,7 +255,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
|
|||
Pleroma.Config.put([:instance, :skip_thread_containment], false)
|
||||
author = insert(:user)
|
||||
user = insert(:user, skip_thread_containment: true)
|
||||
User.follow(user, author, "accept")
|
||||
User.follow(user, author, :follow_accept)
|
||||
|
||||
activity =
|
||||
insert(:note_activity,
|
||||
|
@ -476,7 +476,7 @@ test "it does send non-reblog notification for reblog-muted actors" do
|
|||
CommonAPI.hide_reblogs(user1, user2)
|
||||
|
||||
{:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"})
|
||||
{:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, user2)
|
||||
{:ok, favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
|
||||
|
||||
task =
|
||||
Task.async(fn ->
|
||||
|
|
|
@ -109,7 +109,7 @@ test "it registers a new user and parses mentions in the bio" do
|
|||
{:ok, user2} = TwitterAPI.register_user(data2)
|
||||
|
||||
expected_text =
|
||||
~s(<span class="h-card"><a data-user="#{user1.id}" class="u-url mention" href="#{
|
||||
~s(<span class="h-card"><a class="u-url mention" data-user="#{user1.id}" href="#{
|
||||
user1.ap_id
|
||||
}" rel="ugc">@<span>john</span></a></span> test)
|
||||
|
||||
|
|
Loading…
Reference in New Issue