forked from AkkomaGang/akkoma
Merge branch 'develop' into frontend-admin-api
This commit is contained in:
commit
08cbd655d1
72 changed files with 1090 additions and 643 deletions
|
@ -17,6 +17,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- App metrics: ability to restrict access to specified IP whitelist.
|
- App metrics: ability to restrict access to specified IP whitelist.
|
||||||
- Account backup
|
- Account backup
|
||||||
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
- Configuration: Add `:instance, autofollowing_nicknames` setting to provide a way to make accounts automatically follow new users that register on the local Pleroma instance.
|
||||||
|
- Ability to view remote timelines, with ex. `/api/v1/timelines/public?instance=lain.com` and streams `public:remote` and `public:remote:media`
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
|
@ -31,6 +32,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Users with the `discoverable` field set to false will not show up in searches.
|
- Users with the `discoverable` field set to false will not show up in searches.
|
||||||
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
- Minimum lifetime for ephmeral activities changed to 10 minutes and made configurable (`:min_lifetime` option).
|
||||||
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
|
- Introduced optional dependencies on `ffmpeg`, `ImageMagick`, `exiftool` software packages. Please refer to `docs/installation/optional/media_graphics_packages.md`.
|
||||||
|
- Polls now always return a `voters_count`, even if they are single-choice
|
||||||
|
- Admin Emails: The ap id is used as the user link in emails now.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
@ -41,6 +44,8 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
|
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `unconfirmed` status
|
||||||
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
|
- Admin API: (`GET /api/pleroma/admin/users`) added filters user by `actor_type`
|
||||||
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
|
- Pleroma API: Add `idempotency_key` to the chat message entity that can be used for optimistic message sending.
|
||||||
|
- Pleroma API: (`GET /api/v1/pleroma/federation_status`) Add a way to get a list of unreachable instances.
|
||||||
|
- Mastodon API: User and conversation mutes can now auto-expire if `expires_in` parameter was given while adding the mute.
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
|
|
|
@ -563,7 +563,9 @@
|
||||||
remote_fetcher: 2,
|
remote_fetcher: 2,
|
||||||
attachments_cleanup: 5,
|
attachments_cleanup: 5,
|
||||||
new_users_digest: 1,
|
new_users_digest: 1,
|
||||||
frontend_installer: 1
|
frontend_installer: 1,
|
||||||
|
mute_expire: 5
|
||||||
|
|
||||||
],
|
],
|
||||||
plugins: [Oban.Plugins.Pruner],
|
plugins: [Oban.Plugins.Pruner],
|
||||||
crontab: [
|
crontab: [
|
||||||
|
@ -821,7 +823,7 @@
|
||||||
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
||||||
|
|
||||||
config :pleroma, :mrf,
|
config :pleroma, :mrf,
|
||||||
policies: Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy,
|
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
|
||||||
transparency: true,
|
transparency: true,
|
||||||
transparency_exclusions: []
|
transparency_exclusions: []
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
use Mix.Config
|
use Mix.Config
|
||||||
alias Pleroma.Docs.Generator
|
|
||||||
|
|
||||||
websocket_config = [
|
websocket_config = [
|
||||||
path: "/websocket",
|
path: "/websocket",
|
||||||
|
@ -1555,298 +1554,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf,
|
|
||||||
tab: :mrf,
|
|
||||||
label: "MRF",
|
|
||||||
type: :group,
|
|
||||||
description: "General MRF settings",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :policies,
|
|
||||||
type: [:module, {:list, :module}],
|
|
||||||
description:
|
|
||||||
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
|
|
||||||
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :transparency,
|
|
||||||
label: "MRF transparency",
|
|
||||||
type: :boolean,
|
|
||||||
description:
|
|
||||||
"Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :transparency_exclusions,
|
|
||||||
label: "MRF transparency exclusions",
|
|
||||||
type: {:list, :string},
|
|
||||||
description:
|
|
||||||
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
|
|
||||||
suggestions: [
|
|
||||||
"exclusion.com"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_simple,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
|
|
||||||
label: "MRF Simple",
|
|
||||||
type: :group,
|
|
||||||
description: "Simple ingress policies",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :media_removal,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to strip media attachments from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :media_nsfw,
|
|
||||||
label: "Media NSFW",
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to tag all media as NSFW (sensitive) from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :federated_timeline_removal,
|
|
||||||
type: {:list, :string},
|
|
||||||
description:
|
|
||||||
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :reject,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to reject activities from (except deletes)",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :accept,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to only accept activities from (except deletes)",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :followers_only,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "Force posts from the given instances to be visible by followers only",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :report_removal,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to reject reports from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :avatar_removal,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to strip avatars from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :banner_removal,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to strip banners from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :reject_deletes,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "List of instances to reject deletions from",
|
|
||||||
suggestions: ["example.com", "*.example.com"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_activity_expiration,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy",
|
|
||||||
label: "MRF Activity Expiration Policy",
|
|
||||||
type: :group,
|
|
||||||
description: "Adds automatic expiration to all local activities",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :days,
|
|
||||||
type: :integer,
|
|
||||||
description: "Default global expiration time for all local activities (in days)",
|
|
||||||
suggestions: [90, 365]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_subchain,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
|
|
||||||
label: "MRF Subchain",
|
|
||||||
type: :group,
|
|
||||||
description:
|
|
||||||
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
|
|
||||||
" All criteria are configured as a map of regular expressions to lists of policy modules.",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :match_actor,
|
|
||||||
type: {:map, {:list, :string}},
|
|
||||||
description: "Matches a series of regular expressions against the actor field",
|
|
||||||
suggestions: [
|
|
||||||
%{
|
|
||||||
~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_rejectnonpublic,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic",
|
|
||||||
description: "RejectNonPublic drops posts with non-public visibility settings.",
|
|
||||||
label: "MRF Reject Non Public",
|
|
||||||
type: :group,
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :allow_followersonly,
|
|
||||||
label: "Allow followers-only",
|
|
||||||
type: :boolean,
|
|
||||||
description: "Whether to allow followers-only posts"
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :allow_direct,
|
|
||||||
type: :boolean,
|
|
||||||
description: "Whether to allow direct messages"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_hellthread,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
|
|
||||||
label: "MRF Hellthread",
|
|
||||||
type: :group,
|
|
||||||
description: "Block messages with excessive user mentions",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :delist_threshold,
|
|
||||||
type: :integer,
|
|
||||||
description:
|
|
||||||
"Number of mentioned users after which the message gets removed from timelines and" <>
|
|
||||||
"disables notifications. Set to 0 to disable.",
|
|
||||||
suggestions: [10]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :reject_threshold,
|
|
||||||
type: :integer,
|
|
||||||
description:
|
|
||||||
"Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.",
|
|
||||||
suggestions: [20]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_keyword,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
|
|
||||||
label: "MRF Keyword",
|
|
||||||
type: :group,
|
|
||||||
description:
|
|
||||||
"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :reject,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: """
|
|
||||||
A list of patterns which result in message being rejected.
|
|
||||||
|
|
||||||
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
|
||||||
""",
|
|
||||||
suggestions: ["foo", ~r/foo/iu]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :federated_timeline_removal,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: """
|
|
||||||
A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
|
|
||||||
|
|
||||||
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
|
||||||
""",
|
|
||||||
suggestions: ["foo", ~r/foo/iu]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :replace,
|
|
||||||
type: {:list, :tuple},
|
|
||||||
description: """
|
|
||||||
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
|
||||||
|
|
||||||
**Replacement**: a string. Leaving the field empty is permitted.
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_mention,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
|
|
||||||
label: "MRF Mention",
|
|
||||||
type: :group,
|
|
||||||
description: "Block messages which mention a specific user",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :actors,
|
|
||||||
type: {:list, :string},
|
|
||||||
description: "A list of actors for which any post mentioning them will be dropped",
|
|
||||||
suggestions: ["actor1", "actor2"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_vocabulary,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
|
|
||||||
label: "MRF Vocabulary",
|
|
||||||
type: :group,
|
|
||||||
description: "Filter messages which belong to certain activity vocabularies",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :accept,
|
|
||||||
type: {:list, :string},
|
|
||||||
description:
|
|
||||||
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
|
|
||||||
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :reject,
|
|
||||||
type: {:list, :string},
|
|
||||||
description:
|
|
||||||
"A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
|
|
||||||
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
# %{
|
|
||||||
# group: :pleroma,
|
|
||||||
# key: :mrf_user_allowlist,
|
|
||||||
# tab: :mrf,
|
|
||||||
# related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy",
|
|
||||||
# type: :map,
|
|
||||||
# description:
|
|
||||||
# "The keys in this section are the domain names that the policy should apply to." <>
|
|
||||||
# " Each key should be assigned a list of users that should be allowed through by their ActivityPub ID",
|
|
||||||
# suggestions: [
|
|
||||||
# %{"example.org" => ["https://example.org/users/admin"]}
|
|
||||||
# ]
|
|
||||||
# ]
|
|
||||||
# },
|
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :media_proxy,
|
key: :media_proxy,
|
||||||
|
@ -3159,22 +2866,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_normalize_markup,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup",
|
|
||||||
label: "MRF Normalize Markup",
|
|
||||||
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
|
|
||||||
type: :group,
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :scrub_policy,
|
|
||||||
type: :module,
|
|
||||||
suggestions: [Pleroma.HTML.Scrubber.Default]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: Pleroma.User,
|
key: Pleroma.User,
|
||||||
|
@ -3364,33 +3055,6 @@
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
group: :pleroma,
|
|
||||||
key: :mrf_object_age,
|
|
||||||
tab: :mrf,
|
|
||||||
related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy",
|
|
||||||
label: "MRF Object Age",
|
|
||||||
type: :group,
|
|
||||||
description:
|
|
||||||
"Rejects or delists posts based on their timestamp deviance from your server's clock.",
|
|
||||||
children: [
|
|
||||||
%{
|
|
||||||
key: :threshold,
|
|
||||||
type: :integer,
|
|
||||||
description: "Required age (in seconds) of a post before actions are taken.",
|
|
||||||
suggestions: [172_800]
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :actions,
|
|
||||||
type: {:list, :atom},
|
|
||||||
description:
|
|
||||||
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
|
|
||||||
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
|
|
||||||
"`:reject` rejects the message entirely",
|
|
||||||
suggestions: [:delist, :strip_followers, :reject]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :modules,
|
key: :modules,
|
||||||
|
|
|
@ -116,6 +116,10 @@ The modified chat message
|
||||||
This will return a list of chats that you have been involved in, sorted by their
|
This will return a list of chats that you have been involved in, sorted by their
|
||||||
last update (so new chats will be at the top).
|
last update (so new chats will be at the top).
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
|
||||||
|
- with_muted: Include chats from muted users (boolean).
|
||||||
|
|
||||||
Returned data:
|
Returned data:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
|
@ -9,9 +9,13 @@ Pleroma uses 128-bit ids as opposed to Mastodon's 64 bits. However just like Mas
|
||||||
## Timelines
|
## Timelines
|
||||||
|
|
||||||
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
|
Adding the parameter `with_muted=true` to the timeline queries will also return activities by muted (not by blocked!) users.
|
||||||
|
|
||||||
Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
Adding the parameter `exclude_visibilities` to the timeline queries will exclude the statuses with the given visibilities. The parameter accepts an array of visibility types (`public`, `unlisted`, `private`, `direct`), e.g., `exclude_visibilities[]=direct&exclude_visibilities[]=private`.
|
||||||
|
|
||||||
Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
|
Adding the parameter `reply_visibility` to the public and home timelines queries will filter replies. Possible values: without parameter (default) shows all replies, `following` - replies directed to you or users you follow, `self` - replies directed to you.
|
||||||
|
|
||||||
|
Adding the parameter `instance=lain.com` to the public timeline will show only statuses originating from `lain.com` (or any remote instance).
|
||||||
|
|
||||||
## Statuses
|
## Statuses
|
||||||
|
|
||||||
- `visibility`: has an additional possible value `list`
|
- `visibility`: has an additional possible value `list`
|
||||||
|
@ -249,6 +253,12 @@ Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
|
There is an additional `user:pleroma_chat` stream. Incoming chat messages will make the current chat be sent to this `user` stream. The `event` of an incoming chat message is `pleroma:chat_update`. The payload is the updated chat with the incoming chat message in the `last_message` field.
|
||||||
|
|
||||||
|
For viewing remote server timelines, there are `public:remote` and `public:remote:media` streams. Each of these accept a parameter like `?instance=lain.com`.
|
||||||
|
|
||||||
|
## User muting and thread muting
|
||||||
|
|
||||||
|
Both user muting and thread muting can be done for only a certain time by adding an `expires_in` parameter to the API calls and giving the expiration time in seconds.
|
||||||
|
|
||||||
## Not implemented
|
## Not implemented
|
||||||
|
|
||||||
Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer features and non-essential features are omitted. These features usually return an HTTP 200 status code, but with an empty response. While they may be added in the future, they are considered low priority.
|
Pleroma is generally compatible with the Mastodon 2.7.2 API, but some newer features and non-essential features are omitted. These features usually return an HTTP 200 status code, but with an empty response. While they may be added in the future, they are considered low priority.
|
||||||
|
|
|
@ -1,9 +0,0 @@
|
||||||
# Generate release environment file
|
|
||||||
|
|
||||||
```sh tab="OTP"
|
|
||||||
./bin/pleroma_ctl release_env gen
|
|
||||||
```
|
|
||||||
|
|
||||||
```sh tab="From Source"
|
|
||||||
mix pleroma.release_env gen
|
|
||||||
```
|
|
136
docs/configuration/howto_ejabberd.md
Normal file
136
docs/configuration/howto_ejabberd.md
Normal file
|
@ -0,0 +1,136 @@
|
||||||
|
# Configuring Ejabberd (XMPP Server) to use Pleroma for authentication
|
||||||
|
|
||||||
|
If you want to give your Pleroma users an XMPP (chat) account, you can configure [Ejabberd](https://github.com/processone/ejabberd) to use your Pleroma server for user authentication, automatically giving every local user an XMPP account.
|
||||||
|
|
||||||
|
In general, you just have to follow the configuration described at [https://docs.ejabberd.im/admin/configuration/authentication/#external-script](https://docs.ejabberd.im/admin/configuration/authentication/#external-script). Please read this section carefully.
|
||||||
|
|
||||||
|
Copy the script below to suitable path on your system and set owner and permissions. Also do not forget adjusting `PLEROMA_HOST` and `PLEROMA_PORT`, if necessary.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp pleroma_ejabberd_auth.py /etc/ejabberd/pleroma_ejabberd_auth.py
|
||||||
|
chown ejabberd /etc/ejabberd/pleroma_ejabberd_auth.py
|
||||||
|
chmod 700 /etc/ejabberd/pleroma_ejabberd_auth.py
|
||||||
|
```
|
||||||
|
|
||||||
|
Set external auth params in ejabberd.yaml file:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
auth_method: [external]
|
||||||
|
extauth_program: "python3 /etc/ejabberd/pleroma_ejabberd_auth.py"
|
||||||
|
extauth_instances: 3
|
||||||
|
auth_use_cache: false
|
||||||
|
```
|
||||||
|
|
||||||
|
Restart / reload your ejabberd service.
|
||||||
|
|
||||||
|
After restarting your Ejabberd server, your users should now be able to connect with their Pleroma credentials.
|
||||||
|
|
||||||
|
|
||||||
|
```python
|
||||||
|
import sys
|
||||||
|
import struct
|
||||||
|
import http.client
|
||||||
|
from base64 import b64encode
|
||||||
|
import logging
|
||||||
|
|
||||||
|
|
||||||
|
PLEROMA_HOST = "127.0.0.1"
|
||||||
|
PLEROMA_PORT = "4000"
|
||||||
|
AUTH_ENDPOINT = "/api/v1/accounts/verify_credentials"
|
||||||
|
USER_ENDPOINT = "/api/v1/accounts"
|
||||||
|
LOGFILE = "/var/log/ejabberd/pleroma_auth.log"
|
||||||
|
|
||||||
|
logging.basicConfig(filename=LOGFILE, level=logging.INFO)
|
||||||
|
|
||||||
|
|
||||||
|
# Pleroma functions
|
||||||
|
def create_connection():
|
||||||
|
return http.client.HTTPConnection(PLEROMA_HOST, PLEROMA_PORT)
|
||||||
|
|
||||||
|
|
||||||
|
def verify_credentials(user: str, password: str) -> bool:
|
||||||
|
user_pass_b64 = b64encode("{}:{}".format(
|
||||||
|
user, password).encode('utf-8')).decode("ascii")
|
||||||
|
params = {}
|
||||||
|
headers = {
|
||||||
|
"Authorization": "Basic {}".format(user_pass_b64)
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
conn = create_connection()
|
||||||
|
conn.request("GET", AUTH_ENDPOINT, params, headers)
|
||||||
|
|
||||||
|
response = conn.getresponse()
|
||||||
|
if response.status == 200:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
logging.info("Can not connect: %s", str(e))
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def does_user_exist(user: str) -> bool:
|
||||||
|
conn = create_connection()
|
||||||
|
conn.request("GET", "{}/{}".format(USER_ENDPOINT, user))
|
||||||
|
|
||||||
|
response = conn.getresponse()
|
||||||
|
if response.status == 200:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def auth(username: str, server: str, password: str) -> bool:
|
||||||
|
return verify_credentials(username, password)
|
||||||
|
|
||||||
|
|
||||||
|
def isuser(username, server):
|
||||||
|
return does_user_exist(username)
|
||||||
|
|
||||||
|
|
||||||
|
def read():
|
||||||
|
(pkt_size,) = struct.unpack('>H', bytes(sys.stdin.read(2), encoding='utf8'))
|
||||||
|
pkt = sys.stdin.read(pkt_size)
|
||||||
|
cmd = pkt.split(':')[0]
|
||||||
|
if cmd == 'auth':
|
||||||
|
username, server, password = pkt.split(':', 3)[1:]
|
||||||
|
write(auth(username, server, password))
|
||||||
|
elif cmd == 'isuser':
|
||||||
|
username, server = pkt.split(':', 2)[1:]
|
||||||
|
write(isuser(username, server))
|
||||||
|
elif cmd == 'setpass':
|
||||||
|
# u, s, p = pkt.split(':', 3)[1:]
|
||||||
|
write(False)
|
||||||
|
elif cmd == 'tryregister':
|
||||||
|
# u, s, p = pkt.split(':', 3)[1:]
|
||||||
|
write(False)
|
||||||
|
elif cmd == 'removeuser':
|
||||||
|
# u, s = pkt.split(':', 2)[1:]
|
||||||
|
write(False)
|
||||||
|
elif cmd == 'removeuser3':
|
||||||
|
# u, s, p = pkt.split(':', 3)[1:]
|
||||||
|
write(False)
|
||||||
|
else:
|
||||||
|
write(False)
|
||||||
|
|
||||||
|
|
||||||
|
def write(result):
|
||||||
|
if result:
|
||||||
|
sys.stdout.write('\x00\x02\x00\x01')
|
||||||
|
else:
|
||||||
|
sys.stdout.write('\x00\x02\x00\x00')
|
||||||
|
sys.stdout.flush()
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
logging.info("Starting pleroma ejabberd auth daemon...")
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
read()
|
||||||
|
except Exception as e:
|
||||||
|
logging.info(
|
||||||
|
"Error while processing data from ejabberd %s", str(e))
|
||||||
|
pass
|
||||||
|
|
||||||
|
```
|
|
@ -182,7 +182,6 @@ sudo cp /opt/pleroma/installation/pleroma.service /etc/systemd/system/pleroma.se
|
||||||
```
|
```
|
||||||
|
|
||||||
* Edit the service file and make sure that all paths fit your installation
|
* Edit the service file and make sure that all paths fit your installation
|
||||||
* Check that `EnvironmentFile` contains the correct path to the env file. Or generate the env file: `sudo -Hu pleroma mix pleroma.release_env gen`
|
|
||||||
* Enable and start `pleroma.service`:
|
* Enable and start `pleroma.service`:
|
||||||
|
|
||||||
```shell
|
```shell
|
||||||
|
|
|
@ -149,9 +149,6 @@ chown -R pleroma /etc/pleroma
|
||||||
# Run the config generator
|
# Run the config generator
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
|
su pleroma -s $SHELL -lc "./bin/pleroma_ctl instance gen --output /etc/pleroma/config.exs --output-psql /tmp/setup_db.psql"
|
||||||
|
|
||||||
# Run the environment file generator.
|
|
||||||
su pleroma -s $SHELL -lc "./bin/pleroma_ctl release_env gen"
|
|
||||||
|
|
||||||
# Create the postgres database
|
# Create the postgres database
|
||||||
su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
|
su postgres -s $SHELL -lc "psql -f /tmp/setup_db.psql"
|
||||||
|
|
||||||
|
|
|
@ -8,7 +8,6 @@ pidfile="/var/run/pleroma.pid"
|
||||||
directory=/opt/pleroma
|
directory=/opt/pleroma
|
||||||
healthcheck_delay=60
|
healthcheck_delay=60
|
||||||
healthcheck_timer=30
|
healthcheck_timer=30
|
||||||
export $(cat /opt/pleroma/config/pleroma.env)
|
|
||||||
|
|
||||||
: ${pleroma_port:-4000}
|
: ${pleroma_port:-4000}
|
||||||
|
|
||||||
|
|
|
@ -17,8 +17,6 @@ Environment="MIX_ENV=prod"
|
||||||
Environment="HOME=/var/lib/pleroma"
|
Environment="HOME=/var/lib/pleroma"
|
||||||
; Path to the folder containing the Pleroma installation.
|
; Path to the folder containing the Pleroma installation.
|
||||||
WorkingDirectory=/opt/pleroma
|
WorkingDirectory=/opt/pleroma
|
||||||
; Path to the environment file. the file contains RELEASE_COOKIE and etc
|
|
||||||
EnvironmentFile=/opt/pleroma/config/pleroma.env
|
|
||||||
; Path to the Mix binary.
|
; Path to the Mix binary.
|
||||||
ExecStart=/usr/bin/mix phx.server
|
ExecStart=/usr/bin/mix phx.server
|
||||||
|
|
||||||
|
|
|
@ -36,9 +36,7 @@ def run(["gen" | rest]) do
|
||||||
listen_port: :string,
|
listen_port: :string,
|
||||||
strip_uploads: :string,
|
strip_uploads: :string,
|
||||||
anonymize_uploads: :string,
|
anonymize_uploads: :string,
|
||||||
dedupe_uploads: :string,
|
dedupe_uploads: :string
|
||||||
skip_release_env: :boolean,
|
|
||||||
release_env_file: :string
|
|
||||||
],
|
],
|
||||||
aliases: [
|
aliases: [
|
||||||
o: :output,
|
o: :output,
|
||||||
|
@ -243,24 +241,6 @@ def run(["gen" | rest]) do
|
||||||
|
|
||||||
write_robots_txt(static_dir, indexable, template_dir)
|
write_robots_txt(static_dir, indexable, template_dir)
|
||||||
|
|
||||||
if Keyword.get(options, :skip_release_env, false) do
|
|
||||||
shell_info("""
|
|
||||||
Release environment file is skip. Please generate the release env file before start.
|
|
||||||
`MIX_ENV=#{Mix.env()} mix pleroma.release_env gen`
|
|
||||||
""")
|
|
||||||
else
|
|
||||||
shell_info("Generation the environment file:")
|
|
||||||
|
|
||||||
release_env_args =
|
|
||||||
with path when not is_nil(path) <- Keyword.get(options, :release_env_file) do
|
|
||||||
["gen", "--path", path]
|
|
||||||
else
|
|
||||||
_ -> ["gen"]
|
|
||||||
end
|
|
||||||
|
|
||||||
Mix.Tasks.Pleroma.ReleaseEnv.run(release_env_args)
|
|
||||||
end
|
|
||||||
|
|
||||||
shell_info(
|
shell_info(
|
||||||
"\n All files successfully written! Refer to the installation instructions for your platform for next steps."
|
"\n All files successfully written! Refer to the installation instructions for your platform for next steps."
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,76 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.ReleaseEnv do
|
|
||||||
use Mix.Task
|
|
||||||
import Mix.Pleroma
|
|
||||||
|
|
||||||
@shortdoc "Generate Pleroma environment file."
|
|
||||||
@moduledoc File.read!("docs/administration/CLI_tasks/release_environments.md")
|
|
||||||
|
|
||||||
def run(["gen" | rest]) do
|
|
||||||
{options, [], []} =
|
|
||||||
OptionParser.parse(
|
|
||||||
rest,
|
|
||||||
strict: [
|
|
||||||
force: :boolean,
|
|
||||||
path: :string
|
|
||||||
],
|
|
||||||
aliases: [
|
|
||||||
p: :path,
|
|
||||||
f: :force
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
file_path =
|
|
||||||
get_option(
|
|
||||||
options,
|
|
||||||
:path,
|
|
||||||
"Environment file path",
|
|
||||||
"./config/pleroma.env"
|
|
||||||
)
|
|
||||||
|
|
||||||
env_path = Path.expand(file_path)
|
|
||||||
|
|
||||||
proceed? =
|
|
||||||
if File.exists?(env_path) do
|
|
||||||
get_option(
|
|
||||||
options,
|
|
||||||
:force,
|
|
||||||
"Environment file already exists. Do you want to overwrite the #{env_path} file? (y/n)",
|
|
||||||
"n"
|
|
||||||
) === "y"
|
|
||||||
else
|
|
||||||
true
|
|
||||||
end
|
|
||||||
|
|
||||||
if proceed? do
|
|
||||||
case do_generate(env_path) do
|
|
||||||
{:error, reason} ->
|
|
||||||
shell_error(
|
|
||||||
File.Error.message(%{action: "write to file", reason: reason, path: env_path})
|
|
||||||
)
|
|
||||||
|
|
||||||
_ ->
|
|
||||||
shell_info("\nThe file generated: #{env_path}.\n")
|
|
||||||
|
|
||||||
shell_info("""
|
|
||||||
WARNING: before start pleroma app please make sure to make the file read-only and non-modifiable.
|
|
||||||
Example:
|
|
||||||
chmod 0444 #{file_path}
|
|
||||||
chattr +i #{file_path}
|
|
||||||
""")
|
|
||||||
end
|
|
||||||
else
|
|
||||||
shell_info("\nThe file is exist. #{env_path}.\n")
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def do_generate(path) do
|
|
||||||
content = "RELEASE_COOKIE=#{Base.encode32(:crypto.strong_rand_bytes(32))}"
|
|
||||||
|
|
||||||
File.mkdir_p!(Path.dirname(path))
|
|
||||||
File.write(path, content)
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -40,7 +40,8 @@ defp visibility_tags(object, activity) do
|
||||||
end
|
end
|
||||||
|
|
||||||
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
|
defp item_creation_tags(tags, object, %{data: %{"type" => "Create"}} = activity) do
|
||||||
tags ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
|
tags ++
|
||||||
|
remote_topics(activity) ++ hashtags_to_topics(object) ++ attachment_topics(object, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp item_creation_tags(tags, _, _) do
|
defp item_creation_tags(tags, _, _) do
|
||||||
|
@ -55,9 +56,19 @@ defp hashtags_to_topics(%{data: %{"tag" => tags}}) do
|
||||||
|
|
||||||
defp hashtags_to_topics(_), do: []
|
defp hashtags_to_topics(_), do: []
|
||||||
|
|
||||||
|
defp remote_topics(%{local: true}), do: []
|
||||||
|
|
||||||
|
defp remote_topics(%{actor: actor}) when is_binary(actor),
|
||||||
|
do: ["public:remote:" <> URI.parse(actor).host]
|
||||||
|
|
||||||
|
defp remote_topics(_), do: []
|
||||||
|
|
||||||
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
|
defp attachment_topics(%{data: %{"attachment" => []}}, _act), do: []
|
||||||
|
|
||||||
defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
|
defp attachment_topics(_object, %{local: true}), do: ["public:media", "public:local:media"]
|
||||||
|
|
||||||
|
defp attachment_topics(_object, %{actor: actor}) when is_binary(actor),
|
||||||
|
do: ["public:media", "public:remote:media:" <> URI.parse(actor).host]
|
||||||
|
|
||||||
defp attachment_topics(_object, _act), do: ["public:media"]
|
defp attachment_topics(_object, _act), do: ["public:media"]
|
||||||
end
|
end
|
||||||
|
|
|
@ -11,7 +11,11 @@ defmodule Pleroma.Docs.JSON do
|
||||||
|
|
||||||
@spec compile :: :ok
|
@spec compile :: :ok
|
||||||
def compile do
|
def compile do
|
||||||
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(@raw_descriptions))
|
descriptions =
|
||||||
|
Pleroma.Web.ActivityPub.MRF.config_descriptions()
|
||||||
|
|> Enum.reduce(@raw_descriptions, fn description, acc -> [description | acc] end)
|
||||||
|
|
||||||
|
:persistent_term.put(@term, Pleroma.Docs.Generator.convert_to_strings(descriptions))
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec compiled_descriptions :: Map.t()
|
@spec compiled_descriptions :: Map.t()
|
||||||
|
|
|
@ -18,10 +18,6 @@ defp instance_notify_email do
|
||||||
Keyword.get(instance_config(), :notify_email, instance_config()[:email])
|
Keyword.get(instance_config(), :notify_email, instance_config()[:email])
|
||||||
end
|
end
|
||||||
|
|
||||||
defp user_url(user) do
|
|
||||||
Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id)
|
|
||||||
end
|
|
||||||
|
|
||||||
def test_email(mail_to \\ nil) do
|
def test_email(mail_to \\ nil) do
|
||||||
html_body = """
|
html_body = """
|
||||||
<h3>Instance Test Email</h3>
|
<h3>Instance Test Email</h3>
|
||||||
|
@ -69,8 +65,8 @@ def report(to, reporter, account, statuses, comment) do
|
||||||
end
|
end
|
||||||
|
|
||||||
html_body = """
|
html_body = """
|
||||||
<p>Reported by: <a href="#{user_url(reporter)}">#{reporter.nickname}</a></p>
|
<p>Reported by: <a href="#{reporter.ap_id}">#{reporter.nickname}</a></p>
|
||||||
<p>Reported Account: <a href="#{user_url(account)}">#{account.nickname}</a></p>
|
<p>Reported Account: <a href="#{account.ap_id}">#{account.nickname}</a></p>
|
||||||
#{comment_html}
|
#{comment_html}
|
||||||
#{statuses_html}
|
#{statuses_html}
|
||||||
<p>
|
<p>
|
||||||
|
@ -86,7 +82,7 @@ def report(to, reporter, account, statuses, comment) do
|
||||||
|
|
||||||
def new_unapproved_registration(to, account) do
|
def new_unapproved_registration(to, account) do
|
||||||
html_body = """
|
html_body = """
|
||||||
<p>New account for review: <a href="#{user_url(account)}">@#{account.nickname}</a></p>
|
<p>New account for review: <a href="#{account.ap_id}">@#{account.nickname}</a></p>
|
||||||
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
|
<blockquote>#{HTML.strip_tags(account.registration_reason)}</blockquote>
|
||||||
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
|
<a href="#{Pleroma.Web.base_url()}/pleroma/admin/#/users/#{account.id}/">Visit AdminFE</a>
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -11,6 +11,7 @@ defmodule Pleroma.Instances do
|
||||||
defdelegate reachable?(url_or_host), to: @adapter
|
defdelegate reachable?(url_or_host), to: @adapter
|
||||||
defdelegate set_reachable(url_or_host), to: @adapter
|
defdelegate set_reachable(url_or_host), to: @adapter
|
||||||
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter
|
defdelegate set_unreachable(url_or_host, unreachable_since \\ nil), to: @adapter
|
||||||
|
defdelegate get_consistently_unreachable(), to: @adapter
|
||||||
|
|
||||||
def set_consistently_unreachable(url_or_host),
|
def set_consistently_unreachable(url_or_host),
|
||||||
do: set_unreachable(url_or_host, reachability_datetime_threshold())
|
do: set_unreachable(url_or_host, reachability_datetime_threshold())
|
||||||
|
|
|
@ -119,6 +119,17 @@ def set_unreachable(url_or_host, unreachable_since) when is_binary(url_or_host)
|
||||||
|
|
||||||
def set_unreachable(_, _), do: {:error, nil}
|
def set_unreachable(_, _), do: {:error, nil}
|
||||||
|
|
||||||
|
def get_consistently_unreachable do
|
||||||
|
reachability_datetime_threshold = Instances.reachability_datetime_threshold()
|
||||||
|
|
||||||
|
from(i in Instance,
|
||||||
|
where: ^reachability_datetime_threshold > i.unreachable_since,
|
||||||
|
order_by: i.unreachable_since,
|
||||||
|
select: {i.host, i.unreachable_since}
|
||||||
|
)
|
||||||
|
|> Repo.all()
|
||||||
|
end
|
||||||
|
|
||||||
defp parse_datetime(datetime) when is_binary(datetime) do
|
defp parse_datetime(datetime) when is_binary(datetime) do
|
||||||
NaiveDateTime.from_iso8601(datetime)
|
NaiveDateTime.from_iso8601(datetime)
|
||||||
end
|
end
|
||||||
|
|
|
@ -23,7 +23,6 @@ def start_link(_) do
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def init(_args) do
|
def init(_args) do
|
||||||
if Pleroma.Config.get(:env) == :test, do: :ok = Ecto.Adapters.SQL.Sandbox.checkout(Repo)
|
|
||||||
{:ok, nil, {:continue, :calculate_stats}}
|
{:ok, nil, {:continue, :calculate_stats}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -32,11 +31,6 @@ def force_update do
|
||||||
GenServer.call(__MODULE__, :force_update)
|
GenServer.call(__MODULE__, :force_update)
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "Performs collect stats"
|
|
||||||
def do_collect do
|
|
||||||
GenServer.cast(__MODULE__, :run_update)
|
|
||||||
end
|
|
||||||
|
|
||||||
@doc "Returns stats data"
|
@doc "Returns stats data"
|
||||||
@spec get_stats() :: %{
|
@spec get_stats() :: %{
|
||||||
domain_count: non_neg_integer(),
|
domain_count: non_neg_integer(),
|
||||||
|
@ -111,7 +105,11 @@ def get_status_visibility_count(instance \\ nil) do
|
||||||
@impl true
|
@impl true
|
||||||
def handle_continue(:calculate_stats, _) do
|
def handle_continue(:calculate_stats, _) do
|
||||||
stats = calculate_stat_data()
|
stats = calculate_stat_data()
|
||||||
|
|
||||||
|
unless Pleroma.Config.get(:env) == :test do
|
||||||
Process.send_after(self(), :run_update, @interval)
|
Process.send_after(self(), :run_update, @interval)
|
||||||
|
end
|
||||||
|
|
||||||
{:noreply, stats}
|
{:noreply, stats}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -126,13 +124,6 @@ def handle_call(:get_state, _from, state) do
|
||||||
{:reply, state, state}
|
{:reply, state, state}
|
||||||
end
|
end
|
||||||
|
|
||||||
@impl true
|
|
||||||
def handle_cast(:run_update, _state) do
|
|
||||||
new_stats = calculate_stat_data()
|
|
||||||
|
|
||||||
{:noreply, new_stats}
|
|
||||||
end
|
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def handle_info(:run_update, _) do
|
def handle_info(:run_update, _) do
|
||||||
new_stats = calculate_stat_data()
|
new_stats = calculate_stat_data()
|
||||||
|
|
|
@ -1324,14 +1324,48 @@ def get_recipients_from_activity(%Activity{recipients: to, actor: actor}) do
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec mute(User.t(), User.t(), boolean()) ::
|
@spec mute(User.t(), User.t(), map()) ::
|
||||||
{:ok, list(UserRelationship.t())} | {:error, String.t()}
|
{:ok, list(UserRelationship.t())} | {:error, String.t()}
|
||||||
def mute(%User{} = muter, %User{} = mutee, notifications? \\ true) do
|
def mute(%User{} = muter, %User{} = mutee, params \\ %{}) do
|
||||||
add_to_mutes(muter, mutee, notifications?)
|
notifications? = Map.get(params, :notifications, true)
|
||||||
|
expires_in = Map.get(params, :expires_in, 0)
|
||||||
|
|
||||||
|
with {:ok, user_mute} <- UserRelationship.create_mute(muter, mutee),
|
||||||
|
{:ok, user_notification_mute} <-
|
||||||
|
(notifications? && UserRelationship.create_notification_mute(muter, mutee)) ||
|
||||||
|
{:ok, nil} do
|
||||||
|
if expires_in > 0 do
|
||||||
|
Pleroma.Workers.MuteExpireWorker.enqueue(
|
||||||
|
"unmute_user",
|
||||||
|
%{"muter_id" => muter.id, "mutee_id" => mutee.id},
|
||||||
|
schedule_in: expires_in
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def unmute(%User{} = muter, %User{} = mutee) do
|
def unmute(%User{} = muter, %User{} = mutee) do
|
||||||
remove_from_mutes(muter, mutee)
|
with {:ok, user_mute} <- UserRelationship.delete_mute(muter, mutee),
|
||||||
|
{:ok, user_notification_mute} <-
|
||||||
|
UserRelationship.delete_notification_mute(muter, mutee) do
|
||||||
|
{:ok, [user_mute, user_notification_mute]}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def unmute(muter_id, mutee_id) do
|
||||||
|
with {:muter, %User{} = muter} <- {:muter, User.get_by_id(muter_id)},
|
||||||
|
{:mutee, %User{} = mutee} <- {:mutee, User.get_by_id(mutee_id)} do
|
||||||
|
unmute(muter, mutee)
|
||||||
|
else
|
||||||
|
{who, result} = error ->
|
||||||
|
Logger.warn(
|
||||||
|
"User.unmute/2 failed. #{who}: #{result}, muter_id: #{muter_id}, mutee_id: #{mutee_id}"
|
||||||
|
)
|
||||||
|
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def subscribe(%User{} = subscriber, %User{} = target) do
|
def subscribe(%User{} = subscriber, %User{} = target) do
|
||||||
|
@ -2320,23 +2354,6 @@ defp remove_from_block(%User{} = user, %User{} = blocked) do
|
||||||
UserRelationship.delete_block(user, blocked)
|
UserRelationship.delete_block(user, blocked)
|
||||||
end
|
end
|
||||||
|
|
||||||
defp add_to_mutes(%User{} = user, %User{} = muted_user, notifications?) do
|
|
||||||
with {:ok, user_mute} <- UserRelationship.create_mute(user, muted_user),
|
|
||||||
{:ok, user_notification_mute} <-
|
|
||||||
(notifications? && UserRelationship.create_notification_mute(user, muted_user)) ||
|
|
||||||
{:ok, nil} do
|
|
||||||
{:ok, Enum.filter([user_mute, user_notification_mute], & &1)}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp remove_from_mutes(user, %User{} = muted_user) do
|
|
||||||
with {:ok, user_mute} <- UserRelationship.delete_mute(user, muted_user),
|
|
||||||
{:ok, user_notification_mute} <-
|
|
||||||
UserRelationship.delete_notification_mute(user, muted_user) do
|
|
||||||
{:ok, [user_mute, user_notification_mute]}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_invisible(user, invisible) do
|
def set_invisible(user, invisible) do
|
||||||
params = %{invisible: invisible}
|
params = %{invisible: invisible}
|
||||||
|
|
||||||
|
|
|
@ -937,16 +937,11 @@ defp restrict_muted_reblogs(query, %{muting_user: %User{} = user} = opts) do
|
||||||
|
|
||||||
defp restrict_muted_reblogs(query, _), do: query
|
defp restrict_muted_reblogs(query, _), do: query
|
||||||
|
|
||||||
defp restrict_instance(query, %{instance: instance}) do
|
defp restrict_instance(query, %{instance: instance}) when is_binary(instance) do
|
||||||
users =
|
|
||||||
from(
|
from(
|
||||||
u in User,
|
activity in query,
|
||||||
select: u.ap_id,
|
where: fragment("split_part(actor::text, '/'::text, 3) = ?", ^instance)
|
||||||
where: fragment("? LIKE ?", u.nickname, ^"%@#{instance}")
|
|
||||||
)
|
)
|
||||||
|> Repo.all()
|
|
||||||
|
|
||||||
from(activity in query, where: activity.actor in ^users)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp restrict_instance(query, _), do: query
|
defp restrict_instance(query, _), do: query
|
||||||
|
|
|
@ -3,7 +3,62 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF do
|
defmodule Pleroma.Web.ActivityPub.MRF do
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
@mrf_config_descriptions [
|
||||||
|
%{
|
||||||
|
group: :pleroma,
|
||||||
|
key: :mrf,
|
||||||
|
tab: :mrf,
|
||||||
|
label: "MRF",
|
||||||
|
type: :group,
|
||||||
|
description: "General MRF settings",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :policies,
|
||||||
|
type: [:module, {:list, :module}],
|
||||||
|
description:
|
||||||
|
"A list of MRF policies enabled. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
|
||||||
|
suggestions: {:list_behaviour_implementations, Pleroma.Web.ActivityPub.MRF}
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :transparency,
|
||||||
|
label: "MRF transparency",
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :transparency_exclusions,
|
||||||
|
label: "MRF transparency exclusions",
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.",
|
||||||
|
suggestions: [
|
||||||
|
"exclusion.com"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
@default_description %{
|
||||||
|
label: "",
|
||||||
|
description: ""
|
||||||
|
}
|
||||||
|
|
||||||
|
@required_description_keys [:key, :related_policy]
|
||||||
|
|
||||||
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
|
@callback filter(Map.t()) :: {:ok | :reject, Map.t()}
|
||||||
|
@callback describe() :: {:ok | :error, Map.t()}
|
||||||
|
@callback config_description() :: %{
|
||||||
|
optional(:children) => [map()],
|
||||||
|
key: atom(),
|
||||||
|
related_policy: String.t(),
|
||||||
|
label: String.t(),
|
||||||
|
description: String.t()
|
||||||
|
}
|
||||||
|
@optional_callbacks config_description: 0
|
||||||
|
|
||||||
def filter(policies, %{} = message) do
|
def filter(policies, %{} = message) do
|
||||||
policies
|
policies
|
||||||
|
@ -51,8 +106,6 @@ def subdomain_match?(domains, host) do
|
||||||
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
Enum.any?(domains, fn domain -> Regex.match?(domain, host) end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@callback describe() :: {:ok | :error, Map.t()}
|
|
||||||
|
|
||||||
def describe(policies) do
|
def describe(policies) do
|
||||||
{:ok, policy_configs} =
|
{:ok, policy_configs} =
|
||||||
policies
|
policies
|
||||||
|
@ -82,4 +135,41 @@ def describe(policies) do
|
||||||
end
|
end
|
||||||
|
|
||||||
def describe, do: get_policies() |> describe()
|
def describe, do: get_policies() |> describe()
|
||||||
|
|
||||||
|
def config_descriptions do
|
||||||
|
Pleroma.Web.ActivityPub.MRF
|
||||||
|
|> Pleroma.Docs.Generator.list_behaviour_implementations()
|
||||||
|
|> config_descriptions()
|
||||||
|
end
|
||||||
|
|
||||||
|
def config_descriptions(policies) do
|
||||||
|
Enum.reduce(policies, @mrf_config_descriptions, fn policy, acc ->
|
||||||
|
if function_exported?(policy, :config_description, 0) do
|
||||||
|
description =
|
||||||
|
@default_description
|
||||||
|
|> Map.merge(policy.config_description)
|
||||||
|
|> Map.put(:group, :pleroma)
|
||||||
|
|> Map.put(:tab, :mrf)
|
||||||
|
|> Map.put(:type, :group)
|
||||||
|
|
||||||
|
if Enum.all?(@required_description_keys, &Map.has_key?(description, &1)) do
|
||||||
|
[description | acc]
|
||||||
|
else
|
||||||
|
Logger.warn(
|
||||||
|
"#{policy} config description doesn't have one or all required keys #{
|
||||||
|
inspect(@required_description_keys)
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
else
|
||||||
|
Logger.info(
|
||||||
|
"#{policy} is excluded from config descriptions, because does not implement `config_description/0` method."
|
||||||
|
)
|
||||||
|
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -40,4 +40,22 @@ defp maybe_add_expiration(activity) do
|
||||||
_ -> Map.put(activity, "expires_at", expires_at)
|
_ -> Map.put(activity, "expires_at", expires_at)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_activity_expiration,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy",
|
||||||
|
label: "MRF Activity Expiration Policy",
|
||||||
|
description: "Adds automatic expiration to all local activities",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :days,
|
||||||
|
type: :integer,
|
||||||
|
description: "Default global expiration time for all local activities (in days)",
|
||||||
|
suggestions: [90, 365]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -97,4 +97,31 @@ def filter(message), do: {:ok, message}
|
||||||
@impl true
|
@impl true
|
||||||
def describe,
|
def describe,
|
||||||
do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
|
do: {:ok, %{mrf_hellthread: Pleroma.Config.get(:mrf_hellthread) |> Enum.into(%{})}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_hellthread,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.HellthreadPolicy",
|
||||||
|
label: "MRF Hellthread",
|
||||||
|
description: "Block messages with excessive user mentions",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :delist_threshold,
|
||||||
|
type: :integer,
|
||||||
|
description:
|
||||||
|
"Number of mentioned users after which the message gets removed from timelines and" <>
|
||||||
|
"disables notifications. Set to 0 to disable.",
|
||||||
|
suggestions: [10]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject_threshold,
|
||||||
|
type: :integer,
|
||||||
|
description:
|
||||||
|
"Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.",
|
||||||
|
suggestions: [20]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -126,4 +126,46 @@ def describe do
|
||||||
|
|
||||||
{:ok, %{mrf_keyword: mrf_keyword}}
|
{:ok, %{mrf_keyword: mrf_keyword}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_keyword,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.KeywordPolicy",
|
||||||
|
label: "MRF Keyword",
|
||||||
|
description:
|
||||||
|
"Reject or Word-Replace messages matching a keyword or [Regex](https://hexdocs.pm/elixir/Regex.html).",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :reject,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
A list of patterns which result in message being rejected.
|
||||||
|
|
||||||
|
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
""",
|
||||||
|
suggestions: ["foo", ~r/foo/iu]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :federated_timeline_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: """
|
||||||
|
A list of patterns which result in message being removed from federated timelines (a.k.a unlisted).
|
||||||
|
|
||||||
|
Each pattern can be a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
""",
|
||||||
|
suggestions: ["foo", ~r/foo/iu]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :replace,
|
||||||
|
type: {:list, :tuple},
|
||||||
|
description: """
|
||||||
|
**Pattern**: a string or [Regex](https://hexdocs.pm/elixir/Regex.html) in the format of `~r/PATTERN/`.
|
||||||
|
|
||||||
|
**Replacement**: a string. Leaving the field empty is permitted.
|
||||||
|
"""
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,4 +25,22 @@ def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe, do: {:ok, %{}}
|
def describe, do: {:ok, %{}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_mention,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.MentionPolicy",
|
||||||
|
label: "MRF Mention",
|
||||||
|
description: "Block messages which mention a specific user",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :actors,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "A list of actors for which any post mentioning them will be dropped",
|
||||||
|
suggestions: ["actor1", "actor2"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -8,6 +8,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.NormalizeMarkup do
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@impl true
|
||||||
def filter(%{"type" => "Create", "object" => child_object} = object) do
|
def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||||
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
scrub_policy = Pleroma.Config.get([:mrf_normalize_markup, :scrub_policy])
|
||||||
|
|
||||||
|
@ -22,5 +23,23 @@ def filter(%{"type" => "Create", "object" => child_object} = object) do
|
||||||
|
|
||||||
def filter(object), do: {:ok, object}
|
def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
|
@impl true
|
||||||
def describe, do: {:ok, %{}}
|
def describe, do: {:ok, %{}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_normalize_markup,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.NormalizeMarkup",
|
||||||
|
label: "MRF Normalize Markup",
|
||||||
|
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :scrub_policy,
|
||||||
|
type: :module,
|
||||||
|
suggestions: [Pleroma.HTML.Scrubber.Default]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -106,4 +106,32 @@ def describe do
|
||||||
|
|
||||||
{:ok, %{mrf_object_age: mrf_object_age}}
|
{:ok, %{mrf_object_age: mrf_object_age}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_object_age,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy",
|
||||||
|
label: "MRF Object Age",
|
||||||
|
description:
|
||||||
|
"Rejects or delists posts based on their timestamp deviance from your server's clock.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :threshold,
|
||||||
|
type: :integer,
|
||||||
|
description: "Required age (in seconds) of a post before actions are taken.",
|
||||||
|
suggestions: [172_800]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :actions,
|
||||||
|
type: {:list, :atom},
|
||||||
|
description:
|
||||||
|
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
|
||||||
|
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
|
||||||
|
"`:reject` rejects the message entirely",
|
||||||
|
suggestions: [:delist, :strip_followers, :reject]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -48,4 +48,27 @@ def filter(object), do: {:ok, object}
|
||||||
@impl true
|
@impl true
|
||||||
def describe,
|
def describe,
|
||||||
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
do: {:ok, %{mrf_rejectnonpublic: Config.get(:mrf_rejectnonpublic) |> Enum.into(%{})}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_rejectnonpublic,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.RejectNonPublic",
|
||||||
|
description: "RejectNonPublic drops posts with non-public visibility settings.",
|
||||||
|
label: "MRF Reject Non Public",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :allow_followersonly,
|
||||||
|
label: "Allow followers-only",
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to allow followers-only posts"
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :allow_direct,
|
||||||
|
type: :boolean,
|
||||||
|
description: "Whether to allow direct messages"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -244,4 +244,78 @@ def describe do
|
||||||
|
|
||||||
{:ok, %{mrf_simple: mrf_simple}}
|
{:ok, %{mrf_simple: mrf_simple}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_simple,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.SimplePolicy",
|
||||||
|
label: "MRF Simple",
|
||||||
|
description: "Simple ingress policies",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :media_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to strip media attachments from",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :media_nsfw,
|
||||||
|
label: "Media NSFW",
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to tag all media as NSFW (sensitive) from",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :federated_timeline_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to reject activities from (except deletes)",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :accept,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to only accept activities from (except deletes)",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :followers_only,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "Force posts from the given instances to be visible by followers only",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :report_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to reject reports from",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :avatar_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to strip avatars from",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :banner_removal,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to strip banners from",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject_deletes,
|
||||||
|
type: {:list, :string},
|
||||||
|
description: "List of instances to reject deletions from",
|
||||||
|
suggestions: ["example.com", "*.example.com"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -39,4 +39,28 @@ def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe, do: {:ok, %{}}
|
def describe, do: {:ok, %{}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_subchain,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.SubchainPolicy",
|
||||||
|
label: "MRF Subchain",
|
||||||
|
description:
|
||||||
|
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
|
||||||
|
" All criteria are configured as a map of regular expressions to lists of policy modules.",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :match_actor,
|
||||||
|
type: {:map, {:list, :string}},
|
||||||
|
description: "Matches a series of regular expressions against the actor field",
|
||||||
|
suggestions: [
|
||||||
|
%{
|
||||||
|
~r/https:\/\/example.com/s => [Pleroma.Web.ActivityPub.MRF.DropPolicy]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -41,4 +41,25 @@ def describe do
|
||||||
|
|
||||||
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
{:ok, %{mrf_user_allowlist: mrf_user_allowlist}}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# TODO: change way of getting settings on `lib/pleroma/web/activity_pub/mrf/user_allow_list_policy.ex:18` to use `hosts` subkey
|
||||||
|
# @impl true
|
||||||
|
# def config_description do
|
||||||
|
# %{
|
||||||
|
# key: :mrf_user_allowlist,
|
||||||
|
# related_policy: "Pleroma.Web.ActivityPub.MRF.UserAllowListPolicy",
|
||||||
|
# description: "Accept-list of users from specified instances",
|
||||||
|
# children: [
|
||||||
|
# %{
|
||||||
|
# key: :hosts,
|
||||||
|
# type: :map,
|
||||||
|
# description:
|
||||||
|
# "The keys in this section are the domain names that the policy should apply to." <>
|
||||||
|
# " Each key should be assigned a list of users that should be allowed " <>
|
||||||
|
# "through by their ActivityPub ID",
|
||||||
|
# suggestions: [%{"example.org" => ["https://example.org/users/admin"]}]
|
||||||
|
# }
|
||||||
|
# ]
|
||||||
|
# }
|
||||||
|
# end
|
||||||
end
|
end
|
||||||
|
|
|
@ -7,6 +7,7 @@ defmodule Pleroma.Web.ActivityPub.MRF.VocabularyPolicy do
|
||||||
|
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@impl true
|
||||||
def filter(%{"type" => "Undo", "object" => child_message} = message) do
|
def filter(%{"type" => "Undo", "object" => child_message} = message) do
|
||||||
with {:ok, _} <- filter(child_message) do
|
with {:ok, _} <- filter(child_message) do
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
|
@ -36,6 +37,33 @@ def filter(%{"type" => message_type} = message) do
|
||||||
|
|
||||||
def filter(message), do: {:ok, message}
|
def filter(message), do: {:ok, message}
|
||||||
|
|
||||||
|
@impl true
|
||||||
def describe,
|
def describe,
|
||||||
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
|
do: {:ok, %{mrf_vocabulary: Pleroma.Config.get(:mrf_vocabulary) |> Enum.into(%{})}}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :mrf_vocabulary,
|
||||||
|
related_policy: "Pleroma.Web.ActivityPub.MRF.VocabularyPolicy",
|
||||||
|
label: "MRF Vocabulary",
|
||||||
|
description: "Filter messages which belong to certain activity vocabularies",
|
||||||
|
children: [
|
||||||
|
%{
|
||||||
|
key: :accept,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
|
||||||
|
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
||||||
|
},
|
||||||
|
%{
|
||||||
|
key: :reject,
|
||||||
|
type: {:list, :string},
|
||||||
|
description:
|
||||||
|
"A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
|
||||||
|
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -262,6 +262,12 @@ def mute_operation do
|
||||||
:query,
|
:query,
|
||||||
%Schema{allOf: [BooleanLike], default: true},
|
%Schema{allOf: [BooleanLike], default: true},
|
||||||
"Mute notifications in addition to statuses? Defaults to `true`."
|
"Mute notifications in addition to statuses? Defaults to `true`."
|
||||||
|
),
|
||||||
|
Operation.parameter(
|
||||||
|
:expires_in,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :integer, default: 0},
|
||||||
|
"Expire the mute in `expires_in` seconds. Default 0 for infinity"
|
||||||
)
|
)
|
||||||
],
|
],
|
||||||
responses: %{
|
responses: %{
|
||||||
|
@ -723,10 +729,17 @@ defp mute_request do
|
||||||
nullable: true,
|
nullable: true,
|
||||||
description: "Mute notifications in addition to statuses? Defaults to true.",
|
description: "Mute notifications in addition to statuses? Defaults to true.",
|
||||||
default: true
|
default: true
|
||||||
|
},
|
||||||
|
expires_in: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
nullable: true,
|
||||||
|
description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
|
||||||
|
default: 0
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
example: %{
|
example: %{
|
||||||
"notifications" => true
|
"notifications" => true,
|
||||||
|
"expires_in" => 86_400
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ApiSpec.ChatOperation do
|
||||||
alias OpenApiSpex.Operation
|
alias OpenApiSpex.Operation
|
||||||
alias OpenApiSpex.Schema
|
alias OpenApiSpex.Schema
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
alias Pleroma.Web.ApiSpec.Schemas.ApiError
|
||||||
|
alias Pleroma.Web.ApiSpec.Schemas.BooleanLike
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.Chat
|
alias Pleroma.Web.ApiSpec.Schemas.Chat
|
||||||
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
|
alias Pleroma.Web.ApiSpec.Schemas.ChatMessage
|
||||||
|
|
||||||
|
@ -132,7 +133,10 @@ def index_operation do
|
||||||
tags: ["chat"],
|
tags: ["chat"],
|
||||||
summary: "Get a list of chats that you participated in",
|
summary: "Get a list of chats that you participated in",
|
||||||
operationId: "ChatController.index",
|
operationId: "ChatController.index",
|
||||||
parameters: pagination_params(),
|
parameters: [
|
||||||
|
Operation.parameter(:with_muted, :query, BooleanLike, "Include chats from muted users")
|
||||||
|
| pagination_params()
|
||||||
|
],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => Operation.response("The chats of the user", "application/json", chats_response())
|
200 => Operation.response("The chats of the user", "application/json", chats_response())
|
||||||
},
|
},
|
||||||
|
|
|
@ -0,0 +1,40 @@
|
||||||
|
# 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.PleromaInstancesOperation do
|
||||||
|
alias OpenApiSpex.Operation
|
||||||
|
alias OpenApiSpex.Schema
|
||||||
|
|
||||||
|
def open_api_operation(action) do
|
||||||
|
operation = String.to_existing_atom("#{action}_operation")
|
||||||
|
apply(__MODULE__, operation, [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def show_operation do
|
||||||
|
%Operation{
|
||||||
|
tags: ["PleromaInstances"],
|
||||||
|
summary: "Instances federation status",
|
||||||
|
description: "Information about instances deemed unreachable by the server",
|
||||||
|
operationId: "PleromaInstances.show",
|
||||||
|
responses: %{
|
||||||
|
200 => Operation.response("PleromaInstances", "application/json", pleroma_instances())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def pleroma_instances do
|
||||||
|
%Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
unreachable: %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{hostname: %Schema{type: :string, format: :"date-time"}}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
example: %{
|
||||||
|
"unreachable" => %{"consistently-unreachable.name" => "2020-10-14 22:07:58.216473"}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -223,7 +223,27 @@ def mute_conversation_operation do
|
||||||
security: [%{"oAuth" => ["write:mutes"]}],
|
security: [%{"oAuth" => ["write:mutes"]}],
|
||||||
description: "Do not receive notifications for the thread that this status is part of.",
|
description: "Do not receive notifications for the thread that this status is part of.",
|
||||||
operationId: "StatusController.mute_conversation",
|
operationId: "StatusController.mute_conversation",
|
||||||
parameters: [id_param()],
|
requestBody:
|
||||||
|
request_body("Parameters", %Schema{
|
||||||
|
type: :object,
|
||||||
|
properties: %{
|
||||||
|
expires_in: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
nullable: true,
|
||||||
|
description: "Expire the mute in `expires_in` seconds. Default 0 for infinity",
|
||||||
|
default: 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
parameters: [
|
||||||
|
id_param(),
|
||||||
|
Operation.parameter(
|
||||||
|
:expires_in,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :integer, default: 0},
|
||||||
|
"Expire the mute in `expires_in` seconds. Default 0 for infinity"
|
||||||
|
)
|
||||||
|
],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => status_response(),
|
200 => status_response(),
|
||||||
400 => Operation.response("Error", "application/json", ApiError)
|
400 => Operation.response("Error", "application/json", ApiError)
|
||||||
|
|
|
@ -59,6 +59,7 @@ def public_operation do
|
||||||
security: [%{"oAuth" => ["read:statuses"]}],
|
security: [%{"oAuth" => ["read:statuses"]}],
|
||||||
parameters: [
|
parameters: [
|
||||||
local_param(),
|
local_param(),
|
||||||
|
instance_param(),
|
||||||
only_media_param(),
|
only_media_param(),
|
||||||
with_muted_param(),
|
with_muted_param(),
|
||||||
exclude_visibilities_param(),
|
exclude_visibilities_param(),
|
||||||
|
@ -158,8 +159,17 @@ defp local_param do
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp instance_param do
|
||||||
|
Operation.parameter(
|
||||||
|
:instance,
|
||||||
|
:query,
|
||||||
|
%Schema{type: :string},
|
||||||
|
"Show only statuses from the given domain"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
defp with_muted_param do
|
defp with_muted_param do
|
||||||
Operation.parameter(:with_muted, :query, BooleanLike, "Includeactivities by muted users")
|
Operation.parameter(:with_muted, :query, BooleanLike, "Include activities by muted users")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp exclude_visibilities_param do
|
defp exclude_visibilities_param do
|
||||||
|
|
|
@ -28,8 +28,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
||||||
},
|
},
|
||||||
votes_count: %Schema{
|
votes_count: %Schema{
|
||||||
type: :integer,
|
type: :integer,
|
||||||
nullable: true,
|
description: "How many votes have been received. Number."
|
||||||
description: "How many votes have been received. Number, or null if `multiple` is false."
|
},
|
||||||
|
voters_count: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "How many unique accounts have voted. Number."
|
||||||
},
|
},
|
||||||
voted: %Schema{
|
voted: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -61,7 +64,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Poll do
|
||||||
expired: true,
|
expired: true,
|
||||||
multiple: false,
|
multiple: false,
|
||||||
votes_count: 10,
|
votes_count: 10,
|
||||||
voters_count: nil,
|
voters_count: 10,
|
||||||
voted: true,
|
voted: true,
|
||||||
own_votes: [
|
own_votes: [
|
||||||
1
|
1
|
||||||
|
|
|
@ -454,20 +454,46 @@ def unpin(id, user) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def add_mute(user, activity) do
|
def add_mute(user, activity, params \\ %{}) do
|
||||||
|
expires_in = Map.get(params, :expires_in, 0)
|
||||||
|
|
||||||
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
|
with {:ok, _} <- ThreadMute.add_mute(user.id, activity.data["context"]),
|
||||||
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
|
_ <- Pleroma.Notification.mark_context_as_read(user, activity.data["context"]) do
|
||||||
|
if expires_in > 0 do
|
||||||
|
Pleroma.Workers.MuteExpireWorker.enqueue(
|
||||||
|
"unmute_conversation",
|
||||||
|
%{"user_id" => user.id, "activity_id" => activity.id},
|
||||||
|
schedule_in: expires_in
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
else
|
else
|
||||||
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
{:error, _} -> {:error, dgettext("errors", "conversation is already muted")}
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_mute(user, activity) do
|
def remove_mute(%User{} = user, %Activity{} = activity) do
|
||||||
ThreadMute.remove_mute(user.id, activity.data["context"])
|
ThreadMute.remove_mute(user.id, activity.data["context"])
|
||||||
{:ok, activity}
|
{:ok, activity}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def remove_mute(user_id, activity_id) do
|
||||||
|
with {:user, %User{} = user} <- {:user, User.get_by_id(user_id)},
|
||||||
|
{:activity, %Activity{} = activity} <- {:activity, Activity.get_by_id(activity_id)} do
|
||||||
|
remove_mute(user, activity)
|
||||||
|
else
|
||||||
|
{what, result} = error ->
|
||||||
|
Logger.warn(
|
||||||
|
"CommonAPI.remove_mute/2 failed. #{what}: #{result}, user_id: #{user_id}, activity_id: #{
|
||||||
|
activity_id
|
||||||
|
}"
|
||||||
|
)
|
||||||
|
|
||||||
|
{:error, error}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
|
def thread_muted?(%User{id: user_id}, %{data: %{"context" => context}})
|
||||||
when is_binary(context) do
|
when is_binary(context) do
|
||||||
ThreadMute.exists?(user_id, context)
|
ThreadMute.exists?(user_id, context)
|
||||||
|
|
|
@ -37,9 +37,11 @@ def redirector_with_meta(conn, params) do
|
||||||
|
|
||||||
tags = build_tags(conn, params)
|
tags = build_tags(conn, params)
|
||||||
preloads = preload_data(conn, params)
|
preloads = preload_data(conn, params)
|
||||||
|
title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
|
||||||
|
|
||||||
response =
|
response =
|
||||||
index_content
|
index_content
|
||||||
|
|> String.replace(~r/<title>.+?<\/title>/, title)
|
||||||
|> String.replace("<!--server-generated-meta-->", tags <> preloads)
|
|> String.replace("<!--server-generated-meta-->", tags <> preloads)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
@ -54,9 +56,11 @@ def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do
|
||||||
def redirector_with_preload(conn, params) do
|
def redirector_with_preload(conn, params) do
|
||||||
{:ok, index_content} = File.read(index_file_path())
|
{:ok, index_content} = File.read(index_file_path())
|
||||||
preloads = preload_data(conn, params)
|
preloads = preload_data(conn, params)
|
||||||
|
title = "<title>#{Pleroma.Config.get([:instance, :name])}</title>"
|
||||||
|
|
||||||
response =
|
response =
|
||||||
index_content
|
index_content
|
||||||
|
|> String.replace(~r/<title>.+?<\/title>/, title)
|
||||||
|> String.replace("<!--server-generated-meta-->", preloads)
|
|> String.replace("<!--server-generated-meta-->", preloads)
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -83,7 +83,7 @@ def activity_content(%{"content" => content}) do
|
||||||
|
|
||||||
def activity_content(_), do: ""
|
def activity_content(_), do: ""
|
||||||
|
|
||||||
def activity_context(activity), do: activity.data["context"]
|
def activity_context(activity), do: escape(activity.data["context"])
|
||||||
|
|
||||||
def attachment_href(attachment) do
|
def attachment_href(attachment) do
|
||||||
attachment["url"]
|
attachment["url"]
|
||||||
|
|
|
@ -394,7 +394,7 @@ def unfollow(%{assigns: %{user: follower, account: followed}} = conn, _params) d
|
||||||
|
|
||||||
@doc "POST /api/v1/accounts/:id/mute"
|
@doc "POST /api/v1/accounts/:id/mute"
|
||||||
def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
|
def mute(%{assigns: %{user: muter, account: muted}, body_params: params} = conn, _params) do
|
||||||
with {:ok, _user_relationships} <- User.mute(muter, muted, params.notifications) do
|
with {:ok, _user_relationships} <- User.mute(muter, muted, params) do
|
||||||
render(conn, "relationship.json", user: muter, target: muted)
|
render(conn, "relationship.json", user: muter, target: muted)
|
||||||
else
|
else
|
||||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||||
|
|
|
@ -284,9 +284,9 @@ def unbookmark(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
end
|
end
|
||||||
|
|
||||||
@doc "POST /api/v1/statuses/:id/mute"
|
@doc "POST /api/v1/statuses/:id/mute"
|
||||||
def mute_conversation(%{assigns: %{user: user}} = conn, %{id: id}) do
|
def mute_conversation(%{assigns: %{user: user}, body_params: params} = conn, %{id: id}) do
|
||||||
with %Activity{} = activity <- Activity.get_by_id(id),
|
with %Activity{} = activity <- Activity.get_by_id(id),
|
||||||
{:ok, activity} <- CommonAPI.add_mute(user, activity) do
|
{:ok, activity} <- CommonAPI.add_mute(user, activity, params) do
|
||||||
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
try_render(conn, "show.json", activity: activity, for: user, as: :activity)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -111,6 +111,7 @@ def public(%{assigns: %{user: user}} = conn, params) do
|
||||||
|> Map.put(:blocking_user, user)
|
|> Map.put(:blocking_user, user)
|
||||||
|> Map.put(:muting_user, user)
|
|> Map.put(:muting_user, user)
|
||||||
|> Map.put(:reply_filtering_user, user)
|
|> Map.put(:reply_filtering_user, user)
|
||||||
|
|> Map.put(:instance, params[:instance])
|
||||||
|> ActivityPub.fetch_public_activities()
|
|> ActivityPub.fetch_public_activities()
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|
|
|
@ -19,7 +19,7 @@ def render("show.json", %{object: object, multiple: multiple, options: options}
|
||||||
expired: expired,
|
expired: expired,
|
||||||
multiple: multiple,
|
multiple: multiple,
|
||||||
votes_count: votes_count,
|
votes_count: votes_count,
|
||||||
voters_count: (multiple || nil) && voters_count(object),
|
voters_count: voters_count(object),
|
||||||
options: options,
|
options: options,
|
||||||
voted: voted?(params),
|
voted: voted?(params),
|
||||||
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
|
emojis: Pleroma.Web.MastodonAPI.StatusView.build_emojis(object.data["emoji"])
|
||||||
|
|
|
@ -15,7 +15,6 @@ defmodule Pleroma.Web.PleromaAPI.ChatController do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
alias Pleroma.Web.PleromaAPI.Chat.MessageReferenceView
|
||||||
alias Pleroma.Web.PleromaAPI.ChatView
|
|
||||||
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
alias Pleroma.Web.Plugs.OAuthScopesPlug
|
||||||
|
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
@ -121,9 +120,7 @@ def mark_as_read(
|
||||||
) do
|
) do
|
||||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
|
with {:ok, chat} <- Chat.get_by_user_and_id(user, id),
|
||||||
{_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
|
{_n, _} <- MessageReference.set_all_seen_for_chat(chat, last_read_id) do
|
||||||
conn
|
render(conn, "show.json", chat: chat)
|
||||||
|> put_view(ChatView)
|
|
||||||
|> render("show.json", chat: chat)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -141,33 +138,30 @@ def messages(%{assigns: %{user: user}} = conn, %{id: id} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def index(%{assigns: %{user: %{id: user_id} = user}} = conn, _params) do
|
def index(%{assigns: %{user: %{id: user_id} = user}} = conn, params) do
|
||||||
blocked_ap_ids = User.blocked_users_ap_ids(user)
|
exclude_users =
|
||||||
|
User.blocked_users_ap_ids(user) ++
|
||||||
|
if params[:with_muted], do: [], else: User.muted_users_ap_ids(user)
|
||||||
|
|
||||||
chats =
|
chats =
|
||||||
Chat.for_user_query(user_id)
|
user_id
|
||||||
|> where([c], c.recipient not in ^blocked_ap_ids)
|
|> Chat.for_user_query()
|
||||||
|
|> where([c], c.recipient not in ^exclude_users)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
|
|
||||||
conn
|
render(conn, "index.json", chats: chats)
|
||||||
|> put_view(ChatView)
|
|
||||||
|> render("index.json", chats: chats)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def create(%{assigns: %{user: user}} = conn, %{id: id}) do
|
def create(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
|
with %User{ap_id: recipient} <- User.get_cached_by_id(id),
|
||||||
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
|
{:ok, %Chat{} = chat} <- Chat.get_or_create(user.id, recipient) do
|
||||||
conn
|
render(conn, "show.json", chat: chat)
|
||||||
|> put_view(ChatView)
|
|
||||||
|> render("show.json", chat: chat)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
|
with {:ok, chat} <- Chat.get_by_user_and_id(user, id) do
|
||||||
conn
|
render(conn, "show.json", chat: chat)
|
||||||
|> put_view(ChatView)
|
|
||||||
|> render("show.json", chat: chat)
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaAPI.InstancesController do
|
||||||
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
|
alias Pleroma.Instances
|
||||||
|
|
||||||
|
plug(Pleroma.Web.ApiSpec.CastAndValidate)
|
||||||
|
|
||||||
|
defdelegate open_api_operation(action), to: Pleroma.Web.ApiSpec.PleromaInstancesOperation
|
||||||
|
|
||||||
|
def show(conn, _params) do
|
||||||
|
unreachable =
|
||||||
|
Instances.get_consistently_unreachable()
|
||||||
|
|> Map.new(fn {host, date} -> {host, to_string(date)} end)
|
||||||
|
|
||||||
|
json(conn, %{"unreachable" => unreachable})
|
||||||
|
end
|
||||||
|
end
|
|
@ -401,6 +401,7 @@ defmodule Pleroma.Web.Router do
|
||||||
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
scope "/api/v1/pleroma", Pleroma.Web.PleromaAPI do
|
||||||
pipe_through(:api)
|
pipe_through(:api)
|
||||||
get("/accounts/:id/scrobbles", ScrobbleController, :index)
|
get("/accounts/:id/scrobbles", ScrobbleController, :index)
|
||||||
|
get("/federation_status", InstancesController, :show)
|
||||||
end
|
end
|
||||||
|
|
||||||
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
scope "/api/v1", Pleroma.Web.MastodonAPI do
|
||||||
|
|
|
@ -57,6 +57,15 @@ def get_topic("hashtag", _user, _oauth_token, %{"tag" => tag} = _params) do
|
||||||
{:ok, "hashtag:" <> tag}
|
{:ok, "hashtag:" <> tag}
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Allow remote instance streams.
|
||||||
|
def get_topic("public:remote", _user, _oauth_token, %{"instance" => instance} = _params) do
|
||||||
|
{:ok, "public:remote:" <> instance}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_topic("public:remote:media", _user, _oauth_token, %{"instance" => instance} = _params) do
|
||||||
|
{:ok, "public:remote:media:" <> instance}
|
||||||
|
end
|
||||||
|
|
||||||
# Expand user streams.
|
# Expand user streams.
|
||||||
def get_topic(
|
def get_topic(
|
||||||
stream,
|
stream,
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
|
<link href="<%= activity_context(@activity) %>" rel="ostatus:conversation"/>
|
||||||
|
|
||||||
<%= if @data["summary"] do %>
|
<%= if @data["summary"] do %>
|
||||||
<summary><%= @data["summary"] %></summary>
|
<summary><%= escape(@data["summary"]) %></summary>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @activity.local do %>
|
<%= if @activity.local do %>
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
|
<link rel="ostatus:conversation"><%= activity_context(@activity) %></link>
|
||||||
|
|
||||||
<%= if @data["summary"] do %>
|
<%= if @data["summary"] do %>
|
||||||
<description><%= @data["summary"] %></description>
|
<description><%= escape(@data["summary"]) %></description>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
||||||
<%= if @activity.local do %>
|
<%= if @activity.local do %>
|
||||||
|
|
20
lib/pleroma/workers/mute_expire_worker.ex
Normal file
20
lib/pleroma/workers/mute_expire_worker.ex
Normal file
|
@ -0,0 +1,20 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Workers.MuteExpireWorker do
|
||||||
|
use Pleroma.Workers.WorkerHelper, queue: "mute_expire"
|
||||||
|
|
||||||
|
@impl Oban.Worker
|
||||||
|
def perform(%Job{args: %{"op" => "unmute_user", "muter_id" => muter_id, "mutee_id" => mutee_id}}) do
|
||||||
|
Pleroma.User.unmute(muter_id, mutee_id)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
|
||||||
|
def perform(%Job{
|
||||||
|
args: %{"op" => "unmute_conversation", "user_id" => user_id, "activity_id" => activity_id}
|
||||||
|
}) do
|
||||||
|
Pleroma.Web.CommonAPI.remove_mute(user_id, activity_id)
|
||||||
|
:ok
|
||||||
|
end
|
||||||
|
end
|
19
test/fixtures/modules/good_mrf.ex
vendored
Normal file
19
test/fixtures/modules/good_mrf.ex
vendored
Normal file
|
@ -0,0 +1,19 @@
|
||||||
|
defmodule Fixtures.Modules.GoodMRF do
|
||||||
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def filter(a), do: {:ok, a}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def describe, do: %{}
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def config_description do
|
||||||
|
%{
|
||||||
|
key: :good_mrf,
|
||||||
|
related_policy: "Fixtures.Modules.GoodMRF",
|
||||||
|
label: "Good MRF",
|
||||||
|
description: "Some description"
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,8 +5,6 @@
|
||||||
defmodule Mix.Tasks.Pleroma.InstanceTest do
|
defmodule Mix.Tasks.Pleroma.InstanceTest do
|
||||||
use ExUnit.Case
|
use ExUnit.Case
|
||||||
|
|
||||||
@release_env_file "./test/pleroma.test.env"
|
|
||||||
|
|
||||||
setup do
|
setup do
|
||||||
File.mkdir_p!(tmp_path())
|
File.mkdir_p!(tmp_path())
|
||||||
|
|
||||||
|
@ -18,8 +16,6 @@ defmodule Mix.Tasks.Pleroma.InstanceTest do
|
||||||
File.rm_rf(Path.join(static_dir, "robots.txt"))
|
File.rm_rf(Path.join(static_dir, "robots.txt"))
|
||||||
end
|
end
|
||||||
|
|
||||||
if File.exists?(@release_env_file), do: File.rm_rf(@release_env_file)
|
|
||||||
|
|
||||||
Pleroma.Config.put([:instance, :static_dir], static_dir)
|
Pleroma.Config.put([:instance, :static_dir], static_dir)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
|
@ -73,9 +69,7 @@ test "running gen" do
|
||||||
"--dedupe-uploads",
|
"--dedupe-uploads",
|
||||||
"n",
|
"n",
|
||||||
"--anonymize-uploads",
|
"--anonymize-uploads",
|
||||||
"n",
|
"n"
|
||||||
"--release-env-file",
|
|
||||||
@release_env_file
|
|
||||||
])
|
])
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -97,9 +91,6 @@ test "running gen" do
|
||||||
assert generated_config =~ "filters: [Pleroma.Upload.Filter.ExifTool]"
|
assert generated_config =~ "filters: [Pleroma.Upload.Filter.ExifTool]"
|
||||||
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
assert File.read!(tmp_path() <> "setup.psql") == generated_setup_psql()
|
||||||
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
|
assert File.exists?(Path.expand("./test/instance/static/robots.txt"))
|
||||||
assert File.exists?(@release_env_file)
|
|
||||||
|
|
||||||
assert File.read!(@release_env_file) =~ ~r/^RELEASE_COOKIE=.*/
|
|
||||||
end
|
end
|
||||||
|
|
||||||
defp generated_setup_psql do
|
defp generated_setup_psql do
|
||||||
|
|
|
@ -1,30 +0,0 @@
|
||||||
# Pleroma: A lightweight social networking server
|
|
||||||
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
|
||||||
|
|
||||||
defmodule Mix.Tasks.Pleroma.ReleaseEnvTest do
|
|
||||||
use ExUnit.Case
|
|
||||||
import ExUnit.CaptureIO, only: [capture_io: 1]
|
|
||||||
|
|
||||||
@path "config/pleroma.test.env"
|
|
||||||
|
|
||||||
def do_clean do
|
|
||||||
if File.exists?(@path) do
|
|
||||||
File.rm_rf(@path)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
setup do
|
|
||||||
do_clean()
|
|
||||||
on_exit(fn -> do_clean() end)
|
|
||||||
:ok
|
|
||||||
end
|
|
||||||
|
|
||||||
test "generate pleroma.env" do
|
|
||||||
assert capture_io(fn ->
|
|
||||||
Mix.Tasks.Pleroma.ReleaseEnv.run(["gen", "--path", @path, "--force"])
|
|
||||||
end) =~ "The file generated"
|
|
||||||
|
|
||||||
assert File.read!(@path) =~ "RELEASE_COOKIE="
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -97,6 +97,20 @@ test "only converts strings to hash tags", %{
|
||||||
|
|
||||||
refute Enum.member?(topics, "hashtag:2")
|
refute Enum.member?(topics, "hashtag:2")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "non-local action produces public:remote topic", %{activity: activity} do
|
||||||
|
activity = %{activity | local: false, actor: "https://lain.com/users/lain"}
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
assert Enum.member?(topics, "public:remote:lain.com")
|
||||||
|
end
|
||||||
|
|
||||||
|
test "local action doesn't produce public:remote topic", %{activity: activity} do
|
||||||
|
activity = %{activity | local: true, actor: "https://lain.com/users/lain"}
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
refute Enum.member?(topics, "public:remote:lain.com")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "public visibility create events with attachments" do
|
describe "public visibility create events with attachments" do
|
||||||
|
@ -128,6 +142,13 @@ test "non-local doesn't produce public:local:media topics", %{activity: activity
|
||||||
|
|
||||||
refute Enum.member?(topics, "public:local:media")
|
refute Enum.member?(topics, "public:local:media")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "non-local action produces public:remote:media topic", %{activity: activity} do
|
||||||
|
activity = %{activity | local: false, actor: "https://lain.com/users/lain"}
|
||||||
|
topics = Topics.get_activity_topics(activity)
|
||||||
|
|
||||||
|
assert Enum.member?(topics, "public:remote:media:lain.com")
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "non-public visibility" do
|
describe "non-public visibility" do
|
||||||
|
|
|
@ -19,8 +19,8 @@ test "build report email" do
|
||||||
AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
|
AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment")
|
||||||
|
|
||||||
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
|
status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12")
|
||||||
reporter_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id)
|
reporter_url = reporter.ap_id
|
||||||
account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id)
|
account_url = account.ap_id
|
||||||
|
|
||||||
assert res.to == [{to_user.name, to_user.email}]
|
assert res.to == [{to_user.name, to_user.email}]
|
||||||
assert res.from == {config[:name], config[:notify_email]}
|
assert res.from == {config[:name], config[:notify_email]}
|
||||||
|
@ -54,7 +54,7 @@ test "new unapproved registration email" do
|
||||||
|
|
||||||
res = AdminEmail.new_unapproved_registration(to_user, account)
|
res = AdminEmail.new_unapproved_registration(to_user, account)
|
||||||
|
|
||||||
account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id)
|
account_url = account.ap_id
|
||||||
|
|
||||||
assert res.to == [{to_user.name, to_user.email}]
|
assert res.to == [{to_user.name, to_user.email}]
|
||||||
assert res.from == {config[:name], config[:notify_email]}
|
assert res.from == {config[:name], config[:notify_email]}
|
||||||
|
|
|
@ -49,6 +49,7 @@ test "requires authentication and a valid token for protected streams" do
|
||||||
test "allows public streams without authentication" do
|
test "allows public streams without authentication" do
|
||||||
assert {:ok, _} = start_socket("?stream=public")
|
assert {:ok, _} = start_socket("?stream=public")
|
||||||
assert {:ok, _} = start_socket("?stream=public:local")
|
assert {:ok, _} = start_socket("?stream=public:local")
|
||||||
|
assert {:ok, _} = start_socket("?stream=public:remote&instance=lain.com")
|
||||||
assert {:ok, _} = start_socket("?stream=hashtag&tag=lain")
|
assert {:ok, _} = start_socket("?stream=hashtag&tag=lain")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -229,7 +229,7 @@ test "notification created if user is muted without notifications" do
|
||||||
muter = insert(:user)
|
muter = insert(:user)
|
||||||
muted = insert(:user)
|
muted = insert(:user)
|
||||||
|
|
||||||
{:ok, _user_relationships} = User.mute(muter, muted, false)
|
{:ok, _user_relationships} = User.mute(muter, muted, %{notifications: false})
|
||||||
|
|
||||||
{:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
|
{:ok, activity} = CommonAPI.post(muted, %{status: "Hi @#{muter.nickname}"})
|
||||||
|
|
||||||
|
@ -1015,7 +1015,7 @@ test "move activity generates a notification" do
|
||||||
|
|
||||||
test "it returns notifications for muted user without notifications", %{user: user} do
|
test "it returns notifications for muted user without notifications", %{user: user} do
|
||||||
muted = insert(:user)
|
muted = insert(:user)
|
||||||
{:ok, _user_relationships} = User.mute(user, muted, false)
|
{:ok, _user_relationships} = User.mute(user, muted, %{notifications: false})
|
||||||
|
|
||||||
{:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
|
{:ok, _activity} = CommonAPI.post(muted, %{status: "hey @#{user.nickname}"})
|
||||||
|
|
||||||
|
|
|
@ -1008,6 +1008,27 @@ test "it mutes people" do
|
||||||
assert User.muted_notifications?(user, muted_user)
|
assert User.muted_notifications?(user, muted_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "expiring" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _user_relationships} = User.mute(user, muted_user, %{expires_in: 60})
|
||||||
|
assert User.mutes?(user, muted_user)
|
||||||
|
|
||||||
|
worker = Pleroma.Workers.MuteExpireWorker
|
||||||
|
args = %{"op" => "unmute_user", "muter_id" => user.id, "mutee_id" => muted_user.id}
|
||||||
|
|
||||||
|
assert_enqueued(
|
||||||
|
worker: worker,
|
||||||
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
|
assert :ok = perform_job(worker, args)
|
||||||
|
|
||||||
|
refute User.mutes?(user, muted_user)
|
||||||
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
end
|
||||||
|
|
||||||
test "it unmutes users" do
|
test "it unmutes users" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
muted_user = insert(:user)
|
muted_user = insert(:user)
|
||||||
|
@ -1019,6 +1040,17 @@ test "it unmutes users" do
|
||||||
refute User.muted_notifications?(user, muted_user)
|
refute User.muted_notifications?(user, muted_user)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it unmutes users by id" do
|
||||||
|
user = insert(:user)
|
||||||
|
muted_user = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _user_relationships} = User.mute(user, muted_user)
|
||||||
|
{:ok, _user_mute} = User.unmute(user.id, muted_user.id)
|
||||||
|
|
||||||
|
refute User.mutes?(user, muted_user)
|
||||||
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
end
|
||||||
|
|
||||||
test "it mutes user without notifications" do
|
test "it mutes user without notifications" do
|
||||||
user = insert(:user)
|
user = insert(:user)
|
||||||
muted_user = insert(:user)
|
muted_user = insert(:user)
|
||||||
|
@ -1026,7 +1058,7 @@ test "it mutes user without notifications" do
|
||||||
refute User.mutes?(user, muted_user)
|
refute User.mutes?(user, muted_user)
|
||||||
refute User.muted_notifications?(user, muted_user)
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
|
||||||
{:ok, _user_relationships} = User.mute(user, muted_user, false)
|
{:ok, _user_relationships} = User.mute(user, muted_user, %{notifications: false})
|
||||||
|
|
||||||
assert User.mutes?(user, muted_user)
|
assert User.mutes?(user, muted_user)
|
||||||
refute User.muted_notifications?(user, muted_user)
|
refute User.muted_notifications?(user, muted_user)
|
||||||
|
|
|
@ -87,4 +87,20 @@ test "it works as expected with mock policy" do
|
||||||
{:ok, ^expected} = MRF.describe()
|
{:ok, ^expected} = MRF.describe()
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "config_descriptions/0" do
|
||||||
|
descriptions = MRF.config_descriptions()
|
||||||
|
|
||||||
|
good_mrf = Enum.find(descriptions, fn %{key: key} -> key == :good_mrf end)
|
||||||
|
|
||||||
|
assert good_mrf == %{
|
||||||
|
key: :good_mrf,
|
||||||
|
related_policy: "Fixtures.Modules.GoodMRF",
|
||||||
|
label: "Good MRF",
|
||||||
|
description: "Some description",
|
||||||
|
group: :pleroma,
|
||||||
|
tab: :mrf,
|
||||||
|
type: :group
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -844,8 +844,8 @@ test "sets password_reset_pending to true", %{conn: conn} do
|
||||||
|
|
||||||
describe "instances" do
|
describe "instances" do
|
||||||
test "GET /instances/:instance/statuses", %{conn: conn} do
|
test "GET /instances/:instance/statuses", %{conn: conn} do
|
||||||
user = insert(:user, local: false, nickname: "archaeme@archae.me")
|
user = insert(:user, local: false, ap_id: "https://archae.me/users/archaeme")
|
||||||
user2 = insert(:user, local: false, nickname: "test@test.com")
|
user2 = insert(:user, local: false, ap_id: "https://test.com/users/test")
|
||||||
insert_pair(:note_activity, user: user)
|
insert_pair(:note_activity, user: user)
|
||||||
activity = insert(:note_activity, user: user2)
|
activity = insert(:note_activity, user: user2)
|
||||||
|
|
||||||
|
|
|
@ -3,8 +3,8 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.CommonAPITest do
|
defmodule Pleroma.Web.CommonAPITest do
|
||||||
use Pleroma.DataCase
|
|
||||||
use Oban.Testing, repo: Pleroma.Repo
|
use Oban.Testing, repo: Pleroma.Repo
|
||||||
|
use Pleroma.DataCase
|
||||||
|
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
|
@ -922,12 +922,34 @@ test "add mute", %{user: user, activity: activity} do
|
||||||
assert CommonAPI.thread_muted?(user, activity)
|
assert CommonAPI.thread_muted?(user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "add expiring mute", %{user: user, activity: activity} do
|
||||||
|
{:ok, _} = CommonAPI.add_mute(user, activity, %{expires_in: 60})
|
||||||
|
assert CommonAPI.thread_muted?(user, activity)
|
||||||
|
|
||||||
|
worker = Pleroma.Workers.MuteExpireWorker
|
||||||
|
args = %{"op" => "unmute_conversation", "user_id" => user.id, "activity_id" => activity.id}
|
||||||
|
|
||||||
|
assert_enqueued(
|
||||||
|
worker: worker,
|
||||||
|
args: args
|
||||||
|
)
|
||||||
|
|
||||||
|
assert :ok = perform_job(worker, args)
|
||||||
|
refute CommonAPI.thread_muted?(user, activity)
|
||||||
|
end
|
||||||
|
|
||||||
test "remove mute", %{user: user, activity: activity} do
|
test "remove mute", %{user: user, activity: activity} do
|
||||||
CommonAPI.add_mute(user, activity)
|
CommonAPI.add_mute(user, activity)
|
||||||
{:ok, _} = CommonAPI.remove_mute(user, activity)
|
{:ok, _} = CommonAPI.remove_mute(user, activity)
|
||||||
refute CommonAPI.thread_muted?(user, activity)
|
refute CommonAPI.thread_muted?(user, activity)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "remove mute by ids", %{user: user, activity: activity} do
|
||||||
|
CommonAPI.add_mute(user, activity)
|
||||||
|
{:ok, _} = CommonAPI.remove_mute(user.id, activity.id)
|
||||||
|
refute CommonAPI.thread_muted?(user, activity)
|
||||||
|
end
|
||||||
|
|
||||||
test "check that mutes can't be duplicate", %{user: user, activity: activity} do
|
test "check that mutes can't be duplicate", %{user: user, activity: activity} do
|
||||||
CommonAPI.add_mute(user, activity)
|
CommonAPI.add_mute(user, activity)
|
||||||
{:error, _} = CommonAPI.add_mute(user, activity)
|
{:error, _} = CommonAPI.add_mute(user, activity)
|
||||||
|
|
|
@ -12,16 +12,17 @@ defmodule Pleroma.Web.Feed.UserControllerTest do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.Feed.FeedView
|
||||||
|
|
||||||
setup do: clear_config([:static_fe, :enabled], false)
|
setup do: clear_config([:static_fe, :enabled], false)
|
||||||
|
|
||||||
describe "feed" do
|
describe "feed" do
|
||||||
setup do: clear_config([:feed])
|
setup do: clear_config([:feed])
|
||||||
|
|
||||||
test "gets an atom feed", %{conn: conn} do
|
setup do
|
||||||
Config.put(
|
Config.put(
|
||||||
[:feed, :post_title],
|
[:feed, :post_title],
|
||||||
%{max_length: 10, omission: "..."}
|
%{max_length: 15, omission: "..."}
|
||||||
)
|
)
|
||||||
|
|
||||||
activity = insert(:note_activity)
|
activity = insert(:note_activity)
|
||||||
|
@ -29,7 +30,8 @@ test "gets an atom feed", %{conn: conn} do
|
||||||
note =
|
note =
|
||||||
insert(:note,
|
insert(:note,
|
||||||
data: %{
|
data: %{
|
||||||
"content" => "This is :moominmamma: note ",
|
"content" => "This & this is :moominmamma: note ",
|
||||||
|
"source" => "This & this is :moominmamma: note ",
|
||||||
"attachment" => [
|
"attachment" => [
|
||||||
%{
|
%{
|
||||||
"url" => [
|
"url" => [
|
||||||
|
@ -37,7 +39,9 @@ test "gets an atom feed", %{conn: conn} do
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"inReplyTo" => activity.data["id"]
|
"inReplyTo" => activity.data["id"],
|
||||||
|
"context" => "2hu & as",
|
||||||
|
"summary" => "2hu & as"
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -48,7 +52,7 @@ test "gets an atom feed", %{conn: conn} do
|
||||||
insert(:note,
|
insert(:note,
|
||||||
user: user,
|
user: user,
|
||||||
data: %{
|
data: %{
|
||||||
"content" => "42 This is :moominmamma: note ",
|
"content" => "42 & This is :moominmamma: note ",
|
||||||
"inReplyTo" => activity.data["id"]
|
"inReplyTo" => activity.data["id"]
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
@ -56,6 +60,10 @@ test "gets an atom feed", %{conn: conn} do
|
||||||
note_activity2 = insert(:note_activity, note: note2)
|
note_activity2 = insert(:note_activity, note: note2)
|
||||||
object = Object.normalize(note_activity)
|
object = Object.normalize(note_activity)
|
||||||
|
|
||||||
|
[user: user, object: object, max_id: note_activity2.id]
|
||||||
|
end
|
||||||
|
|
||||||
|
test "gets an atom feed", %{conn: conn, user: user, object: object, max_id: max_id} do
|
||||||
resp =
|
resp =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/atom+xml")
|
|> put_req_header("accept", "application/atom+xml")
|
||||||
|
@ -67,13 +75,15 @@ test "gets an atom feed", %{conn: conn} do
|
||||||
|> SweetXml.parse()
|
|> SweetXml.parse()
|
||||||
|> SweetXml.xpath(~x"//entry/title/text()"l)
|
|> SweetXml.xpath(~x"//entry/title/text()"l)
|
||||||
|
|
||||||
assert activity_titles == ['42 This...', 'This is...']
|
assert activity_titles == ['42 & Thi...', 'This & t...']
|
||||||
assert resp =~ object.data["content"]
|
assert resp =~ FeedView.escape(object.data["content"])
|
||||||
|
assert resp =~ FeedView.escape(object.data["summary"])
|
||||||
|
assert resp =~ FeedView.escape(object.data["context"])
|
||||||
|
|
||||||
resp =
|
resp =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/atom+xml")
|
|> put_req_header("accept", "application/atom+xml")
|
||||||
|> get("/users/#{user.nickname}/feed", %{"max_id" => note_activity2.id})
|
|> get("/users/#{user.nickname}/feed", %{"max_id" => max_id})
|
||||||
|> response(200)
|
|> response(200)
|
||||||
|
|
||||||
activity_titles =
|
activity_titles =
|
||||||
|
@ -81,47 +91,10 @@ test "gets an atom feed", %{conn: conn} do
|
||||||
|> SweetXml.parse()
|
|> SweetXml.parse()
|
||||||
|> SweetXml.xpath(~x"//entry/title/text()"l)
|
|> SweetXml.xpath(~x"//entry/title/text()"l)
|
||||||
|
|
||||||
assert activity_titles == ['This is...']
|
assert activity_titles == ['This & t...']
|
||||||
end
|
end
|
||||||
|
|
||||||
test "gets a rss feed", %{conn: conn} do
|
test "gets a rss feed", %{conn: conn, user: user, object: object, max_id: max_id} do
|
||||||
Pleroma.Config.put(
|
|
||||||
[:feed, :post_title],
|
|
||||||
%{max_length: 10, omission: "..."}
|
|
||||||
)
|
|
||||||
|
|
||||||
activity = insert(:note_activity)
|
|
||||||
|
|
||||||
note =
|
|
||||||
insert(:note,
|
|
||||||
data: %{
|
|
||||||
"content" => "This is :moominmamma: note ",
|
|
||||||
"attachment" => [
|
|
||||||
%{
|
|
||||||
"url" => [
|
|
||||||
%{"mediaType" => "image/png", "href" => "https://pleroma.gov/image.png"}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"inReplyTo" => activity.data["id"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
note_activity = insert(:note_activity, note: note)
|
|
||||||
user = User.get_cached_by_ap_id(note_activity.data["actor"])
|
|
||||||
|
|
||||||
note2 =
|
|
||||||
insert(:note,
|
|
||||||
user: user,
|
|
||||||
data: %{
|
|
||||||
"content" => "42 This is :moominmamma: note ",
|
|
||||||
"inReplyTo" => activity.data["id"]
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
note_activity2 = insert(:note_activity, note: note2)
|
|
||||||
object = Object.normalize(note_activity)
|
|
||||||
|
|
||||||
resp =
|
resp =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/rss+xml")
|
|> put_req_header("accept", "application/rss+xml")
|
||||||
|
@ -133,13 +106,15 @@ test "gets a rss feed", %{conn: conn} do
|
||||||
|> SweetXml.parse()
|
|> SweetXml.parse()
|
||||||
|> SweetXml.xpath(~x"//item/title/text()"l)
|
|> SweetXml.xpath(~x"//item/title/text()"l)
|
||||||
|
|
||||||
assert activity_titles == ['42 This...', 'This is...']
|
assert activity_titles == ['42 & Thi...', 'This & t...']
|
||||||
assert resp =~ object.data["content"]
|
assert resp =~ FeedView.escape(object.data["content"])
|
||||||
|
assert resp =~ FeedView.escape(object.data["summary"])
|
||||||
|
assert resp =~ FeedView.escape(object.data["context"])
|
||||||
|
|
||||||
resp =
|
resp =
|
||||||
conn
|
conn
|
||||||
|> put_req_header("accept", "application/rss+xml")
|
|> put_req_header("accept", "application/rss+xml")
|
||||||
|> get("/users/#{user.nickname}/feed.rss", %{"max_id" => note_activity2.id})
|
|> get("/users/#{user.nickname}/feed.rss", %{"max_id" => max_id})
|
||||||
|> response(200)
|
|> response(200)
|
||||||
|
|
||||||
activity_titles =
|
activity_titles =
|
||||||
|
@ -147,7 +122,7 @@ test "gets a rss feed", %{conn: conn} do
|
||||||
|> SweetXml.parse()
|
|> SweetXml.parse()
|
||||||
|> SweetXml.xpath(~x"//item/title/text()"l)
|
|> SweetXml.xpath(~x"//item/title/text()"l)
|
||||||
|
|
||||||
assert activity_titles == ['This is...']
|
assert activity_titles == ['This & t...']
|
||||||
end
|
end
|
||||||
|
|
||||||
test "returns 404 for a missing feed", %{conn: conn} do
|
test "returns 404 for a missing feed", %{conn: conn} do
|
||||||
|
|
|
@ -502,7 +502,7 @@ test "see notifications after muting user without notifications" do
|
||||||
|
|
||||||
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
|
assert length(json_response_and_validate_schema(ret_conn, 200)) == 1
|
||||||
|
|
||||||
{:ok, _user_relationships} = User.mute(user, user2, false)
|
{:ok, _user_relationships} = User.mute(user, user2, %{notifications: false})
|
||||||
|
|
||||||
conn = get(conn, "/api/v1/notifications")
|
conn = get(conn, "/api/v1/notifications")
|
||||||
|
|
||||||
|
|
|
@ -147,6 +147,18 @@ test "doesn't return replies if follow is posting with users from blocked domain
|
||||||
activities = json_response_and_validate_schema(res_conn, 200)
|
activities = json_response_and_validate_schema(res_conn, 200)
|
||||||
[%{"id" => ^activity_id}] = activities
|
[%{"id" => ^activity_id}] = activities
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "can be filtered by instance", %{conn: conn} do
|
||||||
|
user = insert(:user, ap_id: "https://lain.com/users/lain")
|
||||||
|
insert(:note_activity, local: false)
|
||||||
|
insert(:note_activity, local: false)
|
||||||
|
|
||||||
|
{:ok, _} = CommonAPI.post(user, %{status: "test"})
|
||||||
|
|
||||||
|
conn = get(conn, "/api/v1/timelines/public?instance=lain.com")
|
||||||
|
|
||||||
|
assert length(json_response_and_validate_schema(conn, :ok)) == 1
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
defp local_and_remote_activities do
|
defp local_and_remote_activities do
|
||||||
|
|
|
@ -277,7 +277,7 @@ test "represent a relationship for the following and followed user" do
|
||||||
{:ok, user} = User.follow(user, other_user)
|
{:ok, user} = User.follow(user, other_user)
|
||||||
{:ok, other_user} = User.follow(other_user, user)
|
{:ok, other_user} = User.follow(other_user, user)
|
||||||
{:ok, _subscription} = User.subscribe(user, other_user)
|
{:ok, _subscription} = User.subscribe(user, other_user)
|
||||||
{:ok, _user_relationships} = User.mute(user, other_user, true)
|
{:ok, _user_relationships} = User.mute(user, other_user, %{notifications: true})
|
||||||
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
|
{:ok, _reblog_mute} = CommonAPI.hide_reblogs(user, other_user)
|
||||||
|
|
||||||
expected =
|
expected =
|
||||||
|
|
|
@ -44,7 +44,7 @@ test "renders a poll" do
|
||||||
],
|
],
|
||||||
voted: false,
|
voted: false,
|
||||||
votes_count: 0,
|
votes_count: 0,
|
||||||
voters_count: nil
|
voters_count: 0
|
||||||
}
|
}
|
||||||
|
|
||||||
result = PollView.render("show.json", %{object: object})
|
result = PollView.render("show.json", %{object: object})
|
||||||
|
|
|
@ -343,6 +343,35 @@ test "it does not return chats with users you blocked", %{conn: conn, user: user
|
||||||
assert length(result) == 0
|
assert length(result) == 0
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "it does not return chats with users you muted", %{conn: conn, user: user} do
|
||||||
|
recipient = insert(:user)
|
||||||
|
|
||||||
|
{:ok, _} = Chat.get_or_create(user.id, recipient.ap_id)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 1
|
||||||
|
|
||||||
|
User.mute(user, recipient)
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/chats")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 0
|
||||||
|
|
||||||
|
result =
|
||||||
|
conn
|
||||||
|
|> get("/api/v1/pleroma/chats?with_muted=true")
|
||||||
|
|> json_response_and_validate_schema(200)
|
||||||
|
|
||||||
|
assert length(result) == 1
|
||||||
|
end
|
||||||
|
|
||||||
test "it returns all chats", %{conn: conn, user: user} do
|
test "it returns all chats", %{conn: conn, user: user} do
|
||||||
Enum.each(1..30, fn _ ->
|
Enum.each(1..30, fn _ ->
|
||||||
recipient = insert(:user)
|
recipient = insert(:user)
|
||||||
|
|
|
@ -0,0 +1,38 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.PleromaApi.InstancesControllerTest do
|
||||||
|
use Pleroma.Web.ConnCase
|
||||||
|
|
||||||
|
alias Pleroma.Instances
|
||||||
|
|
||||||
|
setup_all do: clear_config([:instance, :federation_reachability_timeout_days], 1)
|
||||||
|
|
||||||
|
setup do
|
||||||
|
constant = "http://consistently-unreachable.name/"
|
||||||
|
eventual = "http://eventually-unreachable.com/path"
|
||||||
|
|
||||||
|
{:ok, %Pleroma.Instances.Instance{unreachable_since: constant_unreachable}} =
|
||||||
|
Instances.set_consistently_unreachable(constant)
|
||||||
|
|
||||||
|
_eventual_unrechable = Instances.set_unreachable(eventual)
|
||||||
|
|
||||||
|
%{constant_unreachable: constant_unreachable, constant: constant}
|
||||||
|
end
|
||||||
|
|
||||||
|
test "GET /api/v1/pleroma/federation_status", %{
|
||||||
|
conn: conn,
|
||||||
|
constant_unreachable: constant_unreachable,
|
||||||
|
constant: constant
|
||||||
|
} do
|
||||||
|
constant_host = URI.parse(constant).host
|
||||||
|
|
||||||
|
assert conn
|
||||||
|
|> put_req_header("content-type", "application/json")
|
||||||
|
|> get("/api/v1/pleroma/federation_status")
|
||||||
|
|> json_response_and_validate_schema(200) == %{
|
||||||
|
"unreachable" => %{constant_host => to_string(constant_unreachable)}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
end
|
|
@ -29,6 +29,14 @@ test "allows public" do
|
||||||
assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
|
assert {:ok, "public:local:media"} = Streamer.get_topic("public:local:media", nil, nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
test "allows instance streams" do
|
||||||
|
assert {:ok, "public:remote:lain.com"} =
|
||||||
|
Streamer.get_topic("public:remote", nil, nil, %{"instance" => "lain.com"})
|
||||||
|
|
||||||
|
assert {:ok, "public:remote:media:lain.com"} =
|
||||||
|
Streamer.get_topic("public:remote:media", nil, nil, %{"instance" => "lain.com"})
|
||||||
|
end
|
||||||
|
|
||||||
test "allows hashtag streams" do
|
test "allows hashtag streams" do
|
||||||
assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, nil, %{"tag" => "cofe"})
|
assert {:ok, "hashtag:cofe"} = Streamer.get_topic("hashtag", nil, nil, %{"tag" => "cofe"})
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in a new issue