Merge branch 'develop' into issue/1790-updated-oban
This commit is contained in:
commit
4695bdd81b
|
@ -14,7 +14,7 @@
|
||||||
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
* Pleroma version (could be found in the "Version" tab of settings in Pleroma-FE):
|
||||||
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
* Elixir version (`elixir -v` for from source installations, N/A for OTP):
|
||||||
* Operating system:
|
* Operating system:
|
||||||
* PostgreSQL version (`postgres -V`):
|
* PostgreSQL version (`psql -V`):
|
||||||
|
|
||||||
|
|
||||||
### Bug description
|
### Bug description
|
||||||
|
|
18
CHANGELOG.md
18
CHANGELOG.md
|
@ -11,9 +11,23 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
- Using the `only_media` filter on timelines will now exclude reblog media
|
- Using the `only_media` filter on timelines will now exclude reblog media
|
||||||
- MFR policy to set global expiration for all local Create activities
|
- MFR policy to set global expiration for all local Create activities
|
||||||
- OGP rich media parser merged with TwitterCard
|
- OGP rich media parser merged with TwitterCard
|
||||||
|
- Configuration: `:instance, rewrite_policy` moved to `:mrf, policies`, `:instance, :mrf_transparency` moved to `:mrf, :transparency`, `:instance, :mrf_transparency_exclusions` moved to `:mrf, :transparency_exclusions`. Old config namespace is deprecated.
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
|
||||||
- **Breaking:** Emoji API: changed methods and renamed routes.
|
- **Breaking:** Emoji API: changed methods and renamed routes.
|
||||||
|
- Streaming: Repeats of a user's posts will no longer be pushed to the user's stream.
|
||||||
|
- Mastodon API: Added `pleroma.metadata.fields_limits` to /api/v1/instance
|
||||||
|
- Mastodon API: On deletion, returns the original post text.
|
||||||
|
</details>
|
||||||
|
|
||||||
|
<details>
|
||||||
|
<summary>Admin API Changes</summary>
|
||||||
|
|
||||||
|
- Status visibility stats: now can return stats per instance.
|
||||||
|
|
||||||
|
- Mix task to refresh counter cache (`mix pleroma.refresh_counter_cache`)
|
||||||
</details>
|
</details>
|
||||||
|
|
||||||
### Removed
|
### Removed
|
||||||
|
@ -40,6 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
<summary>API Changes</summary>
|
<summary>API Changes</summary>
|
||||||
|
- Mastodon API: Add pleroma.parents_visible field to statuses.
|
||||||
- Mastodon API: Extended `/api/v1/instance`.
|
- Mastodon API: Extended `/api/v1/instance`.
|
||||||
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
|
||||||
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
- Mastodon API: Added `/api/v1/notifications/:id/dismiss` endpoint.
|
||||||
|
@ -96,6 +111,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
|
||||||
2. Run database migrations (inside Pleroma directory):
|
2. Run database migrations (inside Pleroma directory):
|
||||||
- OTP: `./bin/pleroma_ctl migrate`
|
- OTP: `./bin/pleroma_ctl migrate`
|
||||||
- From Source: `mix ecto.migrate`
|
- From Source: `mix ecto.migrate`
|
||||||
|
3. Reset status visibility counters (inside Pleroma directory):
|
||||||
|
- OTP: `./bin/pleroma_ctl refresh_counter_cache`
|
||||||
|
- From Source: `mix pleroma.refresh_counter_cache`
|
||||||
|
|
||||||
|
|
||||||
## [2.0.2] - 2020-04-08
|
## [2.0.2] - 2020-04-08
|
||||||
|
|
|
@ -210,7 +210,6 @@
|
||||||
Pleroma.Web.ActivityPub.Publisher
|
Pleroma.Web.ActivityPub.Publisher
|
||||||
],
|
],
|
||||||
allow_relay: true,
|
allow_relay: true,
|
||||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
|
||||||
public: true,
|
public: true,
|
||||||
quarantined_instances: [],
|
quarantined_instances: [],
|
||||||
managed_config: true,
|
managed_config: true,
|
||||||
|
@ -221,8 +220,6 @@
|
||||||
"text/markdown",
|
"text/markdown",
|
||||||
"text/bbcode"
|
"text/bbcode"
|
||||||
],
|
],
|
||||||
mrf_transparency: true,
|
|
||||||
mrf_transparency_exclusions: [],
|
|
||||||
autofollowed_nicknames: [],
|
autofollowed_nicknames: [],
|
||||||
max_pinned_statuses: 1,
|
max_pinned_statuses: 1,
|
||||||
attachment_links: false,
|
attachment_links: false,
|
||||||
|
@ -437,6 +434,12 @@
|
||||||
],
|
],
|
||||||
unfurl_nsfw: false
|
unfurl_nsfw: false
|
||||||
|
|
||||||
|
config :pleroma, Pleroma.Web.Preload,
|
||||||
|
providers: [
|
||||||
|
Pleroma.Web.Preload.Providers.Instance,
|
||||||
|
Pleroma.Web.Preload.Providers.StatusNet
|
||||||
|
]
|
||||||
|
|
||||||
config :pleroma, :http_security,
|
config :pleroma, :http_security,
|
||||||
enabled: true,
|
enabled: true,
|
||||||
sts: false,
|
sts: false,
|
||||||
|
@ -692,6 +695,15 @@
|
||||||
|
|
||||||
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
config :pleroma, Pleroma.Web.ApiSpec.CastAndValidate, strict: false
|
||||||
|
|
||||||
|
config :pleroma, :mrf,
|
||||||
|
policies: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
|
||||||
|
transparency: true,
|
||||||
|
transparency_exclusions: []
|
||||||
|
|
||||||
|
config :tzdata, :http_client, Pleroma.HTTP.Tzdata
|
||||||
|
|
||||||
|
config :ex_aws, http_client: Pleroma.HTTP.ExAws
|
||||||
|
|
||||||
# Import environment specific config. This must remain at the bottom
|
# Import environment specific config. This must remain at the bottom
|
||||||
# of this file so it overrides the configuration defined above.
|
# of this file so it overrides the configuration defined above.
|
||||||
import_config "#{Mix.env()}.exs"
|
import_config "#{Mix.env()}.exs"
|
||||||
|
|
|
@ -40,12 +40,13 @@
|
||||||
key: :link_name,
|
key: :link_name,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"If enabled, a name parameter will be added to the url of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
|
"If enabled, a name parameter will be added to the URL of the upload. For example `https://instance.tld/media/imagehash.png?name=realname.png`."
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :base_url,
|
key: :base_url,
|
||||||
|
label: "Base URL",
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Base url for the uploads, needed if you use CDN",
|
description: "Base URL for the uploads, needed if you use CDN",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
"https://cdn-host.com"
|
"https://cdn-host.com"
|
||||||
]
|
]
|
||||||
|
@ -58,6 +59,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :proxy_opts,
|
key: :proxy_opts,
|
||||||
|
label: "Proxy Options",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "Options for Pleroma.ReverseProxy",
|
description: "Options for Pleroma.ReverseProxy",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
|
@ -85,6 +87,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :http,
|
key: :http,
|
||||||
|
label: "HTTP",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "HTTP options",
|
description: "HTTP options",
|
||||||
children: [
|
children: [
|
||||||
|
@ -193,7 +196,9 @@
|
||||||
%{
|
%{
|
||||||
key: :args,
|
key: :args,
|
||||||
type: [:string, {:list, :string}, {:list, :tuple}],
|
type: [:string, {:list, :string}, {:list, :tuple}],
|
||||||
description: "List of actions for the mogrify command",
|
description:
|
||||||
|
"List of actions for the mogrify command. It's possible to add self-written settings as string. " <>
|
||||||
|
"For example `[\"auto-orient\", \"strip\", {\"resize\", \"3840x1080>\"}]` string will be parsed into list of the settings.",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
"strip",
|
"strip",
|
||||||
"auto-orient",
|
"auto-orient",
|
||||||
|
@ -479,6 +484,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :uri_schemes,
|
key: :uri_schemes,
|
||||||
|
label: "URI Schemes",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "URI schemes related settings",
|
description: "URI schemes related settings",
|
||||||
children: [
|
children: [
|
||||||
|
@ -651,17 +657,17 @@
|
||||||
key: :invites_enabled,
|
key: :invites_enabled,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"Enable user invitations for admins (depends on `registrations_open` being disabled)."
|
"Enable user invitations for admins (depends on `registrations_open` being disabled)"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :account_activation_required,
|
key: :account_activation_required,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Require users to confirm their emails before signing in."
|
description: "Require users to confirm their emails before signing in"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :federating,
|
key: :federating,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Enable federation with other instances."
|
description: "Enable federation with other instances"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :federation_incoming_replies_max_depth,
|
key: :federation_incoming_replies_max_depth,
|
||||||
|
@ -679,7 +685,7 @@
|
||||||
label: "Fed. reachability timeout days",
|
label: "Fed. reachability timeout days",
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description:
|
description:
|
||||||
"Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.",
|
"Timeout (in days) of each external federation target being unreachable prior to pausing federating to it",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
7
|
7
|
||||||
]
|
]
|
||||||
|
@ -689,17 +695,6 @@
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
|
description: "Enable Pleroma's Relay, which makes it possible to follow a whole instance"
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
key: :rewrite_policy,
|
|
||||||
type: [:module, {:list, :module}],
|
|
||||||
description:
|
|
||||||
"A list of enabled MRF policies. Module names are shortened (removed leading `Pleroma.Web.ActivityPub.MRF.` part), but on adding custom module you need to use full name.",
|
|
||||||
suggestions:
|
|
||||||
Generator.list_modules_in_dir(
|
|
||||||
"lib/pleroma/web/activity_pub/mrf",
|
|
||||||
"Elixir.Pleroma.Web.ActivityPub.MRF."
|
|
||||||
)
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :public,
|
key: :public,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -742,23 +737,6 @@
|
||||||
"text/bbcode"
|
"text/bbcode"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
%{
|
|
||||||
key: :mrf_transparency,
|
|
||||||
label: "MRF transparency",
|
|
||||||
type: :boolean,
|
|
||||||
description:
|
|
||||||
"Make the content of your Message Rewrite Facility settings public (via nodeinfo)"
|
|
||||||
},
|
|
||||||
%{
|
|
||||||
key: :mrf_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"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
%{
|
%{
|
||||||
key: :extended_nickname_format,
|
key: :extended_nickname_format,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
|
@ -829,6 +807,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :safe_dm_mentions,
|
key: :safe_dm_mentions,
|
||||||
|
label: "Safe DM mentions",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"If enabled, only mentions at the beginning of a post will be used to address people in direct messages." <>
|
"If enabled, only mentions at the beginning of a post will be used to address people in direct messages." <>
|
||||||
|
@ -868,7 +847,7 @@
|
||||||
%{
|
%{
|
||||||
key: :skip_thread_containment,
|
key: :skip_thread_containment,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Skip filtering out broken threads. Default: enabled"
|
description: "Skip filtering out broken threads. Default: enabled."
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :limit_to_local_content,
|
key: :limit_to_local_content,
|
||||||
|
@ -932,6 +911,7 @@
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :totp,
|
key: :totp,
|
||||||
|
label: "TOTP settings",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "TOTP settings",
|
description: "TOTP settings",
|
||||||
suggestions: [digits: 6, period: 30],
|
suggestions: [digits: 6, period: 30],
|
||||||
|
@ -948,7 +928,7 @@
|
||||||
type: :integer,
|
type: :integer,
|
||||||
suggestions: [30],
|
suggestions: [30],
|
||||||
description:
|
description:
|
||||||
"a period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
|
"A period for which the TOTP code will be valid, in seconds. Defaults to 30 seconds."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -962,7 +942,7 @@
|
||||||
key: :number,
|
key: :number,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
suggestions: [5],
|
suggestions: [5],
|
||||||
description: "number of backup codes to generate."
|
description: "Number of backup codes to generate."
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :length,
|
key: :length,
|
||||||
|
@ -979,7 +959,7 @@
|
||||||
key: :instance_thumbnail,
|
key: :instance_thumbnail,
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"The instance thumbnail image. It will appear in [Pleroma Instances](http://distsn.org/pleroma-instances.html)",
|
"The instance thumbnail can be any image that represents your instance and is used by some apps or services when they display information about your instance.",
|
||||||
suggestions: ["/instance/thumbnail.jpeg"]
|
suggestions: ["/instance/thumbnail.jpeg"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1002,6 +982,7 @@
|
||||||
group: :logger,
|
group: :logger,
|
||||||
type: :group,
|
type: :group,
|
||||||
key: :ex_syslogger,
|
key: :ex_syslogger,
|
||||||
|
label: "ExSyslogger",
|
||||||
description: "ExSyslogger-related settings",
|
description: "ExSyslogger-related settings",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -1020,7 +1001,7 @@
|
||||||
%{
|
%{
|
||||||
key: :format,
|
key: :format,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
|
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||||
suggestions: ["$metadata[$level] $message"]
|
suggestions: ["$metadata[$level] $message"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -1034,6 +1015,7 @@
|
||||||
group: :logger,
|
group: :logger,
|
||||||
type: :group,
|
type: :group,
|
||||||
key: :console,
|
key: :console,
|
||||||
|
label: "Console Logger",
|
||||||
description: "Console logger settings",
|
description: "Console logger settings",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -1045,7 +1027,7 @@
|
||||||
%{
|
%{
|
||||||
key: :format,
|
key: :format,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
|
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
|
||||||
suggestions: ["$metadata[$level] $message"]
|
suggestions: ["$metadata[$level] $message"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -1058,6 +1040,7 @@
|
||||||
%{
|
%{
|
||||||
group: :quack,
|
group: :quack,
|
||||||
type: :group,
|
type: :group,
|
||||||
|
label: "Quack Logger",
|
||||||
description: "Quack-related settings",
|
description: "Quack-related settings",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -1168,19 +1151,19 @@
|
||||||
key: :greentext,
|
key: :greentext,
|
||||||
label: "Greentext",
|
label: "Greentext",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Enables green text on lines prefixed with the > character."
|
description: "Enables green text on lines prefixed with the > character"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :hideFilteredStatuses,
|
key: :hideFilteredStatuses,
|
||||||
label: "Hide Filtered Statuses",
|
label: "Hide Filtered Statuses",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Hides filtered statuses from timelines."
|
description: "Hides filtered statuses from timelines"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :hideMutedPosts,
|
key: :hideMutedPosts,
|
||||||
label: "Hide Muted Posts",
|
label: "Hide Muted Posts",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Hides muted statuses from timelines."
|
description: "Hides muted statuses from timelines"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :hidePostStats,
|
key: :hidePostStats,
|
||||||
|
@ -1192,7 +1175,7 @@
|
||||||
key: :hideSitename,
|
key: :hideSitename,
|
||||||
label: "Hide Sitename",
|
label: "Hide Sitename",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Hides instance name from PleromaFE banner."
|
description: "Hides instance name from PleromaFE banner"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :hideUserStats,
|
key: :hideUserStats,
|
||||||
|
@ -1237,14 +1220,14 @@
|
||||||
label: "NSFW Censor Image",
|
label: "NSFW Censor Image",
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"URL of the image to use for hiding NSFW media attachments in the timeline.",
|
"URL of the image to use for hiding NSFW media attachments in the timeline",
|
||||||
suggestions: ["/static/img/nsfw.74818f9.png"]
|
suggestions: ["/static/img/nsfw.74818f9.png"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :postContentType,
|
key: :postContentType,
|
||||||
label: "Post Content Type",
|
label: "Post Content Type",
|
||||||
type: {:dropdown, :atom},
|
type: {:dropdown, :atom},
|
||||||
description: "Default post formatting option.",
|
description: "Default post formatting option",
|
||||||
suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
|
suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -1273,14 +1256,14 @@
|
||||||
key: :sidebarRight,
|
key: :sidebarRight,
|
||||||
label: "Sidebar on Right",
|
label: "Sidebar on Right",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Change alignment of sidebar and panels to the right."
|
description: "Change alignment of sidebar and panels to the right"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :showFeaturesPanel,
|
key: :showFeaturesPanel,
|
||||||
label: "Show instance features panel",
|
label: "Show instance features panel",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"Enables panel displaying functionality of the instance on the About page."
|
"Enables panel displaying functionality of the instance on the About page"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :showInstanceSpecificPanel,
|
key: :showInstanceSpecificPanel,
|
||||||
|
@ -1338,7 +1321,7 @@
|
||||||
key: :mascots,
|
key: :mascots,
|
||||||
type: {:keyword, :map},
|
type: {:keyword, :map},
|
||||||
description:
|
description:
|
||||||
"Keyword of mascots, each element must contain both an url and a mime_type key",
|
"Keyword of mascots, each element must contain both an URL and a mime_type key",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
pleroma_fox_tan: %{
|
pleroma_fox_tan: %{
|
||||||
url: "/images/pleroma-fox-tan-smol.png",
|
url: "/images/pleroma-fox-tan-smol.png",
|
||||||
|
@ -1362,7 +1345,7 @@
|
||||||
%{
|
%{
|
||||||
key: :default_user_avatar,
|
key: :default_user_avatar,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "URL of the default user avatar.",
|
description: "URL of the default user avatar",
|
||||||
suggestions: ["/images/avi.png"]
|
suggestions: ["/images/avi.png"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1372,7 +1355,7 @@
|
||||||
key: :manifest,
|
key: :manifest,
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE",
|
"This section describe PWA manifest instance-specific values. Currently this option relate only for MastoFE.",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :icons,
|
key: :icons,
|
||||||
|
@ -1408,10 +1391,49 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_simple,
|
key: :mrf,
|
||||||
label: "MRF simple",
|
tab: :mrf,
|
||||||
|
label: "MRF",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Message Rewrite Facility",
|
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:
|
||||||
|
Generator.list_modules_in_dir(
|
||||||
|
"lib/pleroma/web/activity_pub/mrf",
|
||||||
|
"Elixir.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,
|
||||||
|
label: "MRF Simple",
|
||||||
|
type: :group,
|
||||||
|
description: "Simple ingress policies",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :media_removal,
|
key: :media_removal,
|
||||||
|
@ -1430,7 +1452,7 @@
|
||||||
key: :federated_timeline_removal,
|
key: :federated_timeline_removal,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description:
|
description:
|
||||||
"List of instances to remove from Federated (aka The Whole Known Network) Timeline",
|
"List of instances to remove from the Federated (aka The Whole Known Network) Timeline",
|
||||||
suggestions: ["example.com", "*.example.com"]
|
suggestions: ["example.com", "*.example.com"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -1474,14 +1496,15 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_activity_expiration,
|
key: :mrf_activity_expiration,
|
||||||
|
tab: :mrf,
|
||||||
label: "MRF Activity Expiration Policy",
|
label: "MRF Activity Expiration Policy",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Adds expiration to all local Create Note activities",
|
description: "Adds automatic expiration to all local activities",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :days,
|
key: :days,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Default global expiration time for all local Create activities (in days)",
|
description: "Default global expiration time for all local activities (in days)",
|
||||||
suggestions: [90, 365]
|
suggestions: [90, 365]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1489,7 +1512,8 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_subchain,
|
key: :mrf_subchain,
|
||||||
label: "MRF subchain",
|
tab: :mrf,
|
||||||
|
label: "MRF Subchain",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
|
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
|
||||||
|
@ -1510,9 +1534,9 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_rejectnonpublic,
|
key: :mrf_rejectnonpublic,
|
||||||
description:
|
tab: :mrf,
|
||||||
"MRF RejectNonPublic settings. RejectNonPublic drops posts with non-public visibility settings.",
|
description: "RejectNonPublic drops posts with non-public visibility settings.",
|
||||||
label: "MRF reject non public",
|
label: "MRF Reject Non Public",
|
||||||
type: :group,
|
type: :group,
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -1531,16 +1555,17 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_hellthread,
|
key: :mrf_hellthread,
|
||||||
label: "MRF hellthread",
|
tab: :mrf,
|
||||||
|
label: "MRF Hellthread",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Block messages with too much mentions",
|
description: "Block messages with excessive user mentions",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :delist_threshold,
|
key: :delist_threshold,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description:
|
description:
|
||||||
"Number of mentioned users after which the message gets delisted (the message can still be seen, " <>
|
"Number of mentioned users after which the message gets removed from timelines and" <>
|
||||||
" but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.",
|
"disables notifications. Set to 0 to disable.",
|
||||||
suggestions: [10]
|
suggestions: [10]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -1555,7 +1580,8 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_keyword,
|
key: :mrf_keyword,
|
||||||
label: "MRF keyword",
|
tab: :mrf,
|
||||||
|
label: "MRF Keyword",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Reject or Word-Replace messages with a keyword or regex",
|
description: "Reject or Word-Replace messages with a keyword or regex",
|
||||||
children: [
|
children: [
|
||||||
|
@ -1585,14 +1611,15 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_mention,
|
key: :mrf_mention,
|
||||||
label: "MRF mention",
|
tab: :mrf,
|
||||||
|
label: "MRF Mention",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Block messages which mention a user",
|
description: "Block messages which mention a specific user",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :actors,
|
key: :actors,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description: "A list of actors for which any post mentioning them will be dropped.",
|
description: "A list of actors for which any post mentioning them will be dropped",
|
||||||
suggestions: ["actor1", "actor2"]
|
suggestions: ["actor1", "actor2"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1600,7 +1627,8 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_vocabulary,
|
key: :mrf_vocabulary,
|
||||||
label: "MRF vocabulary",
|
tab: :mrf,
|
||||||
|
label: "MRF Vocabulary",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Filter messages which belong to certain activity vocabularies",
|
description: "Filter messages which belong to certain activity vocabularies",
|
||||||
children: [
|
children: [
|
||||||
|
@ -1608,14 +1636,14 @@
|
||||||
key: :accept,
|
key: :accept,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description:
|
description:
|
||||||
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted",
|
"A list of ActivityStreams terms to accept. If empty, all supported messages are accepted.",
|
||||||
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :reject,
|
key: :reject,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description:
|
description:
|
||||||
"A list of ActivityStreams terms to reject. If empty, no messages are rejected",
|
"A list of ActivityStreams terms to reject. If empty, no messages are rejected.",
|
||||||
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
suggestions: ["Create", "Follow", "Mention", "Announce", "Like"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1645,6 +1673,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :base_url,
|
key: :base_url,
|
||||||
|
label: "Base URL",
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
|
"The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.",
|
||||||
|
@ -1677,6 +1706,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :proxy_opts,
|
key: :proxy_opts,
|
||||||
|
label: "Proxy Options",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "Options for Pleroma.ReverseProxy",
|
description: "Options for Pleroma.ReverseProxy",
|
||||||
suggestions: [
|
suggestions: [
|
||||||
|
@ -1704,6 +1734,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :http,
|
key: :http,
|
||||||
|
label: "HTTP",
|
||||||
type: :keyword,
|
type: :keyword,
|
||||||
description: "HTTP options",
|
description: "HTTP options",
|
||||||
children: [
|
children: [
|
||||||
|
@ -1799,6 +1830,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :ip,
|
key: :ip,
|
||||||
|
label: "IP",
|
||||||
type: :tuple,
|
type: :tuple,
|
||||||
description: "IP address to bind to",
|
description: "IP address to bind to",
|
||||||
suggestions: [{0, 0, 0, 0}]
|
suggestions: [{0, 0, 0, 0}]
|
||||||
|
@ -1812,7 +1844,7 @@
|
||||||
%{
|
%{
|
||||||
key: :dstport,
|
key: :dstport,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Port advertised in urls (optional, defaults to port)",
|
description: "Port advertised in URLs (optional, defaults to port)",
|
||||||
suggestions: [9999]
|
suggestions: [9999]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1820,6 +1852,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :activitypub,
|
key: :activitypub,
|
||||||
|
label: "ActivityPub",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "ActivityPub-related settings",
|
description: "ActivityPub-related settings",
|
||||||
children: [
|
children: [
|
||||||
|
@ -1842,7 +1875,7 @@
|
||||||
key: :note_replies_output_limit,
|
key: :note_replies_output_limit,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description:
|
description:
|
||||||
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)."
|
"The number of Note replies' URIs to be included with outgoing federation (`5` to match Mastodon hardcoded value, `0` to disable the output)"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :follow_handshake_timeout,
|
key: :follow_handshake_timeout,
|
||||||
|
@ -1855,6 +1888,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :http_security,
|
key: :http_security,
|
||||||
|
label: "HTTP security",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "HTTP security settings",
|
description: "HTTP security settings",
|
||||||
children: [
|
children: [
|
||||||
|
@ -1893,7 +1927,7 @@
|
||||||
key: :report_uri,
|
key: :report_uri,
|
||||||
label: "Report URI",
|
label: "Report URI",
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Adds the specified url to report-uri and report-to group in CSP header",
|
description: "Adds the specified URL to report-uri and report-to group in CSP header",
|
||||||
suggestions: ["https://example.com/report-uri"]
|
suggestions: ["https://example.com/report-uri"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -1901,9 +1935,10 @@
|
||||||
%{
|
%{
|
||||||
group: :web_push_encryption,
|
group: :web_push_encryption,
|
||||||
key: :vapid_details,
|
key: :vapid_details,
|
||||||
|
label: "Vapid Details",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it",
|
"Web Push Notifications configuration. You can use the mix task mix web_push.gen.keypair to generate it.",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :subject,
|
key: :subject,
|
||||||
|
@ -1970,6 +2005,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
|
label: "Pleroma Admin Token",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter",
|
"Allows to set a token that can be used to authenticate with the admin api without using an actual user by giving it as the `admin_token` parameter",
|
||||||
|
@ -1977,7 +2013,7 @@
|
||||||
%{
|
%{
|
||||||
key: :admin_token,
|
key: :admin_token,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Token",
|
description: "Admin token",
|
||||||
suggestions: ["We recommend a secure random string or UUID"]
|
suggestions: ["We recommend a secure random string or UUID"]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2135,24 +2171,24 @@
|
||||||
key: :rich_media,
|
key: :rich_media,
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"If enabled the instance will parse metadata from attached links to generate link previews.",
|
"If enabled the instance will parse metadata from attached links to generate link previews",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :enabled,
|
key: :enabled,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Enables RichMedia parsing of URLs."
|
description: "Enables RichMedia parsing of URLs"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :ignore_hosts,
|
key: :ignore_hosts,
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description: "List of hosts which will be ignored by the metadata parser.",
|
description: "List of hosts which will be ignored by the metadata parser",
|
||||||
suggestions: ["accounts.google.com", "xss.website"]
|
suggestions: ["accounts.google.com", "xss.website"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :ignore_tld,
|
key: :ignore_tld,
|
||||||
label: "Ignore TLD",
|
label: "Ignore TLD",
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description: "List TLDs (top-level domains) which will ignore for parse metadata.",
|
description: "List TLDs (top-level domains) which will ignore for parse metadata",
|
||||||
suggestions: ["local", "localdomain", "lan"]
|
suggestions: ["local", "localdomain", "lan"]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -2180,31 +2216,32 @@
|
||||||
%{
|
%{
|
||||||
group: :auto_linker,
|
group: :auto_linker,
|
||||||
key: :opts,
|
key: :opts,
|
||||||
|
label: "Auto Linker",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Configuration for the auto_linker library",
|
description: "Configuration for the auto_linker library",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :class,
|
key: :class,
|
||||||
type: [:string, false],
|
type: [:string, false],
|
||||||
description: "Specify the class to be added to the generated link. Disable to clear",
|
description: "Specify the class to be added to the generated link. Disable to clear.",
|
||||||
suggestions: ["auto-linker", false]
|
suggestions: ["auto-linker", false]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :rel,
|
key: :rel,
|
||||||
type: [:string, false],
|
type: [:string, false],
|
||||||
description: "Override the rel attribute. Disable to clear",
|
description: "Override the rel attribute. Disable to clear.",
|
||||||
suggestions: ["ugc", "noopener noreferrer", false]
|
suggestions: ["ugc", "noopener noreferrer", false]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :new_window,
|
key: :new_window,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Link urls will open in new window/tab"
|
description: "Link URLs will open in new window/tab"
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :truncate,
|
key: :truncate,
|
||||||
type: [:integer, false],
|
type: [:integer, false],
|
||||||
description:
|
description:
|
||||||
"Set to a number to truncate urls longer then the number. Truncated urls will end in `..`",
|
"Set to a number to truncate URLs longer then the number. Truncated URLs will end in `..`",
|
||||||
suggestions: [15, false]
|
suggestions: [15, false]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -2215,7 +2252,7 @@
|
||||||
%{
|
%{
|
||||||
key: :extra,
|
key: :extra,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Link urls with rarely used schemes (magnet, ipfs, irc, etc.)"
|
description: "Link URLs with rarely used schemes (magnet, ipfs, irc, etc.)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2261,6 +2298,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
|
label: "Pleroma Authenticator",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Authenticator",
|
description: "Authenticator",
|
||||||
children: [
|
children: [
|
||||||
|
@ -2274,6 +2312,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :ldap,
|
key: :ldap,
|
||||||
|
label: "LDAP",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
|
"Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
|
||||||
|
@ -2360,6 +2399,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :uid,
|
key: :uid,
|
||||||
|
label: "UID",
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
|
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
|
||||||
|
@ -2375,11 +2415,12 @@
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :enforce_oauth_admin_scope_usage,
|
key: :enforce_oauth_admin_scope_usage,
|
||||||
|
label: "Enforce OAuth admin scope usage",
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description:
|
description:
|
||||||
"OAuth admin scope requirement toggle. " <>
|
"OAuth admin scope requirement toggle. " <>
|
||||||
"If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
|
"If enabled, admin actions explicitly demand admin OAuth scope(s) presence in OAuth token " <>
|
||||||
"(client app must support admin scopes). If disabled and token doesn't have admin scope(s)," <>
|
"(client app must support admin scopes). If disabled and token doesn't have admin scope(s), " <>
|
||||||
"`is_admin` user flag grants access to admin-specific actions."
|
"`is_admin` user flag grants access to admin-specific actions."
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
|
@ -2391,6 +2432,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :oauth_consumer_template,
|
key: :oauth_consumer_template,
|
||||||
|
label: "OAuth consumer template",
|
||||||
type: :string,
|
type: :string,
|
||||||
description:
|
description:
|
||||||
"OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
|
"OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
|
||||||
|
@ -2399,6 +2441,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :oauth_consumer_strategies,
|
key: :oauth_consumer_strategies,
|
||||||
|
label: "OAuth consumer strategies",
|
||||||
type: {:list, :string},
|
type: {:list, :string},
|
||||||
description:
|
description:
|
||||||
"The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
|
"The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
|
||||||
|
@ -2527,7 +2570,7 @@
|
||||||
%{
|
%{
|
||||||
key: :enabled,
|
key: :enabled,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "enables new users admin digest email when `true`",
|
description: "Enables new users admin digest email when `true`",
|
||||||
suggestions: [false]
|
suggestions: [false]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -2535,6 +2578,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :oauth2,
|
key: :oauth2,
|
||||||
|
label: "OAuth2",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Configure OAuth 2 provider capabilities",
|
description: "Configure OAuth 2 provider capabilities",
|
||||||
children: [
|
children: [
|
||||||
|
@ -2553,7 +2597,7 @@
|
||||||
%{
|
%{
|
||||||
key: :clean_expired_tokens,
|
key: :clean_expired_tokens,
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "Enable a background job to clean expired oauth tokens. Default: disabled."
|
description: "Enable a background job to clean expired OAuth tokens. Default: disabled."
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
@ -2637,6 +2681,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :relation_id_action,
|
key: :relation_id_action,
|
||||||
|
label: "Relation ID action",
|
||||||
type: [:tuple, {:list, :tuple}],
|
type: [:tuple, {:list, :tuple}],
|
||||||
description: "For actions on relation with a specific user (follow, unfollow)",
|
description: "For actions on relation with a specific user (follow, unfollow)",
|
||||||
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
|
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
|
||||||
|
@ -2650,6 +2695,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :status_id_action,
|
key: :status_id_action,
|
||||||
|
label: "Status ID action",
|
||||||
type: [:tuple, {:list, :tuple}],
|
type: [:tuple, {:list, :tuple}],
|
||||||
description:
|
description:
|
||||||
"For fav / unfav or reblog / unreblog actions on the same status by the same user",
|
"For fav / unfav or reblog / unreblog actions on the same status by the same user",
|
||||||
|
@ -2665,6 +2711,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :esshd,
|
group: :esshd,
|
||||||
|
label: "ESSHD",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <>
|
"Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <>
|
||||||
|
@ -2703,8 +2750,9 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :mime,
|
group: :mime,
|
||||||
|
label: "Mime Types",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Mime types",
|
description: "Mime Types settings",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :types,
|
key: :types,
|
||||||
|
@ -2763,6 +2811,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :http,
|
key: :http,
|
||||||
|
label: "HTTP",
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "HTTP settings",
|
description: "HTTP settings",
|
||||||
children: [
|
children: [
|
||||||
|
@ -2811,6 +2860,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :markup,
|
key: :markup,
|
||||||
|
label: "Markup Settings",
|
||||||
type: :group,
|
type: :group,
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -2851,8 +2901,9 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
|
tab: :mrf,
|
||||||
key: :mrf_normalize_markup,
|
key: :mrf_normalize_markup,
|
||||||
label: "MRF normalize markup",
|
label: "MRF Normalize Markup",
|
||||||
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
|
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
|
||||||
type: :group,
|
type: :group,
|
||||||
children: [
|
children: [
|
||||||
|
@ -2908,6 +2959,7 @@
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
group: :cors_plug,
|
group: :cors_plug,
|
||||||
|
label: "CORS plug config",
|
||||||
type: :group,
|
type: :group,
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
|
@ -2980,6 +3032,7 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :web_cache_ttl,
|
key: :web_cache_ttl,
|
||||||
|
label: "Web cache TTL",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
|
"The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
|
||||||
|
@ -3002,9 +3055,10 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :static_fe,
|
key: :static_fe,
|
||||||
|
label: "Static FE",
|
||||||
type: :group,
|
type: :group,
|
||||||
description:
|
description:
|
||||||
"Render profiles and posts using server-generated HTML that is viewable without using JavaScript.",
|
"Render profiles and posts using server-generated HTML that is viewable without using JavaScript",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :enabled,
|
key: :enabled,
|
||||||
|
@ -3022,18 +3076,18 @@
|
||||||
%{
|
%{
|
||||||
key: :post_title,
|
key: :post_title,
|
||||||
type: :map,
|
type: :map,
|
||||||
description: "Configure title rendering.",
|
description: "Configure title rendering",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :max_length,
|
key: :max_length,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Maximum number of characters before truncating title.",
|
description: "Maximum number of characters before truncating title",
|
||||||
suggestions: [100]
|
suggestions: [100]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :omission,
|
key: :omission,
|
||||||
type: :string,
|
type: :string,
|
||||||
description: "Replacement which will be used after truncating string.",
|
description: "Replacement which will be used after truncating string",
|
||||||
suggestions: ["..."]
|
suggestions: ["..."]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
@ -3043,8 +3097,11 @@
|
||||||
%{
|
%{
|
||||||
group: :pleroma,
|
group: :pleroma,
|
||||||
key: :mrf_object_age,
|
key: :mrf_object_age,
|
||||||
|
label: "MRF Object Age",
|
||||||
|
tab: :mrf,
|
||||||
type: :group,
|
type: :group,
|
||||||
description: "Rejects or delists posts based on their age when received.",
|
description:
|
||||||
|
"Rejects or delists posts based on their timestamp deviance from your server's clock.",
|
||||||
children: [
|
children: [
|
||||||
%{
|
%{
|
||||||
key: :threshold,
|
key: :threshold,
|
||||||
|
@ -3057,7 +3114,7 @@
|
||||||
type: {:list, :atom},
|
type: {:list, :atom},
|
||||||
description:
|
description:
|
||||||
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
|
"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; " <>
|
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
|
||||||
"`:reject` rejects the message entirely",
|
"`:reject` rejects the message entirely",
|
||||||
suggestions: [:delist, :strip_followers, :reject]
|
suggestions: [:delist, :strip_followers, :reject]
|
||||||
}
|
}
|
||||||
|
@ -3085,13 +3142,13 @@
|
||||||
%{
|
%{
|
||||||
key: :workers,
|
key: :workers,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Number of workers to send notifications.",
|
description: "Number of workers to send notifications",
|
||||||
suggestions: [3]
|
suggestions: [3]
|
||||||
},
|
},
|
||||||
%{
|
%{
|
||||||
key: :overflow_workers,
|
key: :overflow_workers,
|
||||||
type: :integer,
|
type: :integer,
|
||||||
description: "Maximum number of workers created if pool is empty.",
|
description: "Maximum number of workers created if pool is empty",
|
||||||
suggestions: [2]
|
suggestions: [2]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
|
@ -1118,6 +1118,10 @@ Loads json generated from `config/descriptions.exs`.
|
||||||
|
|
||||||
### Stats
|
### Stats
|
||||||
|
|
||||||
|
- Query Params:
|
||||||
|
- *optional* `instance`: **string** instance hostname (without protocol) to get stats for
|
||||||
|
- Example: `https://mypleroma.org/api/pleroma/admin/stats?instance=lain.com`
|
||||||
|
|
||||||
- Response:
|
- Response:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
|
|
|
@ -27,6 +27,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
- `expires_at`: a datetime (iso8601) that states when the post will expire (be deleted automatically), or empty if the post won't expire
|
||||||
- `thread_muted`: true if the thread the post belongs to is muted
|
- `thread_muted`: true if the thread the post belongs to is muted
|
||||||
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
- `emoji_reactions`: A list with emoji / reaction maps. The format is `{name: "☕", count: 1, me: true}`. Contains no information about the reacting users, for that use the `/statuses/:id/reactions` endpoint.
|
||||||
|
- `parent_visible`: If the parent of this post is visible to the user or not.
|
||||||
|
|
||||||
## Media Attachments
|
## Media Attachments
|
||||||
|
|
||||||
|
@ -51,11 +52,14 @@ The `id` parameter can also be the `nickname` of the user. This only works in th
|
||||||
|
|
||||||
Has these additional fields under the `pleroma` object:
|
Has these additional fields under the `pleroma` object:
|
||||||
|
|
||||||
|
- `ap_id`: nullable URL string, ActivityPub id of the user
|
||||||
|
- `background_image`: nullable URL string, background image of the user
|
||||||
- `tags`: Lists an array of tags for the user
|
- `tags`: Lists an array of tags for the user
|
||||||
- `relationship{}`: Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
- `relationship` (object): Includes fields as documented for Mastodon API https://docs.joinmastodon.org/entities/relationship/
|
||||||
- `is_moderator`: boolean, nullable, true if user is a moderator
|
- `is_moderator`: boolean, nullable, true if user is a moderator
|
||||||
- `is_admin`: boolean, nullable, true if user is an admin
|
- `is_admin`: boolean, nullable, true if user is an admin
|
||||||
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
- `confirmation_pending`: boolean, true if a new user account is waiting on email confirmation to be activated
|
||||||
|
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
|
||||||
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
- `hide_followers`: boolean, true when the user has follower hiding enabled
|
||||||
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
- `hide_follows`: boolean, true when the user has follow hiding enabled
|
||||||
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
- `hide_followers_count`: boolean, true when the user has follower stat hiding enabled
|
||||||
|
@ -66,6 +70,7 @@ Has these additional fields under the `pleroma` object:
|
||||||
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
- `allow_following_move`: boolean, true when the user allows automatically follow moved following accounts
|
||||||
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
- `unread_conversation_count`: The count of unread conversations. Only returned to the account owner.
|
||||||
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
- `unread_notifications_count`: The count of unread notifications. Only returned to the account owner.
|
||||||
|
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
|
||||||
|
|
||||||
### Source
|
### Source
|
||||||
|
|
||||||
|
@ -223,6 +228,7 @@ Has theses additional parameters (which are the same as in Pleroma-API):
|
||||||
- `background_image`: A background image that frontends can use
|
- `background_image`: A background image that frontends can use
|
||||||
- `pleroma.metadata.features`: A list of supported features
|
- `pleroma.metadata.features`: A list of supported features
|
||||||
- `pleroma.metadata.federation`: The federation restrictions of this instance
|
- `pleroma.metadata.federation`: The federation restrictions of this instance
|
||||||
|
- `pleroma.metadata.fields_limits`: A list of values detailing the length and count limitation for various instance-configurable fields.
|
||||||
- `vapid_public_key`: The public key needed for push messages
|
- `vapid_public_key`: The public key needed for push messages
|
||||||
|
|
||||||
## Markers
|
## Markers
|
||||||
|
@ -234,3 +240,43 @@ Has these additional fields under the `pleroma` object:
|
||||||
## Streaming
|
## Streaming
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
## 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.
|
||||||
|
|
||||||
|
### Suggestions
|
||||||
|
|
||||||
|
*Added in Mastodon 2.4.3*
|
||||||
|
|
||||||
|
- `GET /api/v1/suggestions`: Returns an empty array, `[]`
|
||||||
|
|
||||||
|
### Trends
|
||||||
|
|
||||||
|
*Added in Mastodon 3.0.0*
|
||||||
|
|
||||||
|
- `GET /api/v1/trends`: Returns an empty array, `[]`
|
||||||
|
|
||||||
|
### Identity proofs
|
||||||
|
|
||||||
|
*Added in Mastodon 2.8.0*
|
||||||
|
|
||||||
|
- `GET /api/v1/identity_proofs`: Returns an empty array, `[]`
|
||||||
|
|
||||||
|
### Endorsements
|
||||||
|
|
||||||
|
*Added in Mastodon 2.5.0*
|
||||||
|
|
||||||
|
- `GET /api/v1/endorsements`: Returns an empty array, `[]`
|
||||||
|
|
||||||
|
### Profile directory
|
||||||
|
|
||||||
|
*Added in Mastodon 3.0.0*
|
||||||
|
|
||||||
|
- `GET /api/v1/directory`: Returns HTTP 404
|
||||||
|
|
||||||
|
### Featured tags
|
||||||
|
|
||||||
|
*Added in Mastodon 3.0.0*
|
||||||
|
|
||||||
|
- `GET /api/v1/featured_tags`: Returns HTTP 404
|
||||||
|
|
|
@ -36,26 +36,10 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
* `federation_incoming_replies_max_depth`: Max. depth of reply-to activities fetching on incoming federation, to prevent out-of-memory situations while fetching very long threads. If set to `nil`, threads of any depth will be fetched. Lower this value if you experience out-of-memory crashes.
|
||||||
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
* `federation_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
|
||||||
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
* `allow_relay`: Enable Pleroma’s Relay, which makes it possible to follow a whole instance.
|
||||||
* `rewrite_policy`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production.
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certain instances (See [`:mrf_simple`](#mrf_simple)).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
|
|
||||||
* `Pleroma.Web.ActivityPub.MRF.ActivityExpirationPolicy`: Adds expiration to all local Create activities (see [`:mrf_activity_expiration`](#mrf_activity_expiration)).
|
|
||||||
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
* `public`: Makes the client API in authenticated mode-only except for user-profiles. Useful for disabling the Local Timeline and The Whole Known Network.
|
||||||
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
* `quarantined_instances`: List of ActivityPub instances where private(DMs, followers-only) activities will not be send.
|
||||||
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
|
* `managed_config`: Whenether the config for pleroma-fe is configured in [:frontend_configurations](#frontend_configurations) or in ``static/config.json``.
|
||||||
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
* `allowed_post_formats`: MIME-type list of formats allowed to be posted (transformed into HTML).
|
||||||
* `mrf_transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
|
||||||
* `mrf_transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
|
||||||
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
* `extended_nickname_format`: Set to `true` to use extended local nicknames format (allows underscores/dashes). This will break federation with
|
||||||
older software for theses nicknames.
|
older software for theses nicknames.
|
||||||
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
|
* `max_pinned_statuses`: The maximum number of pinned statuses. `0` will disable the feature.
|
||||||
|
@ -78,11 +62,30 @@ To add configuration to your config file, you can copy it from the base config.
|
||||||
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
* `external_user_synchronization`: Enabling following/followers counters synchronization for external users.
|
||||||
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
* `cleanup_attachments`: Remove attachments along with statuses. Does not affect duplicate files and attachments without status. Enabling this will increase load to database when deleting statuses on larger instances.
|
||||||
|
|
||||||
|
## Message rewrite facility
|
||||||
|
|
||||||
|
### :mrf
|
||||||
|
* `policies`: Message Rewrite Policy, either one or a list. Here are the ones available by default:
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.NoOpPolicy`: Doesn’t modify activities (default).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesn’t makes sense to use in production.
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.SimplePolicy`: Restrict the visibility of activities from certains instances (See [`:mrf_simple`](#mrf_simple)).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.TagPolicy`: Applies policies to individual users based on tags, which can be set using pleroma-fe/admin-fe/any other app that supports Pleroma Admin API. For example it allows marking posts from individual users nsfw (sensitive).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.SubchainPolicy`: Selectively runs other MRF policies when messages match (See [`:mrf_subchain`](#mrf_subchain)).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.RejectNonPublic`: Drops posts with non-public visibility settings (See [`:mrf_rejectnonpublic`](#mrf_rejectnonpublic)).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.EnsureRePrepended`: Rewrites posts to ensure that replies to posts with subjects do not have an identical subject and instead begin with re:.
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.AntiLinkSpamPolicy`: Rejects posts from likely spambots by rejecting posts from new users that contain links.
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy`: Crawls attachments using their MediaProxy URLs so that the MediaProxy cache is primed.
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.MentionPolicy`: Drops posts mentioning configurable users. (See [`:mrf_mention`](#mrf_mention)).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.VocabularyPolicy`: Restricts activities to a configured set of vocabulary. (See [`:mrf_vocabulary`](#mrf_vocabulary)).
|
||||||
|
* `Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy`: Rejects or delists posts based on their age when received. (See [`:mrf_object_age`](#mrf_object_age)).
|
||||||
|
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
|
||||||
|
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
|
||||||
|
|
||||||
## Federation
|
## Federation
|
||||||
### MRF policies
|
### MRF policies
|
||||||
|
|
||||||
!!! note
|
!!! note
|
||||||
Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `rewrite_policy` under [:instance](#instance) section.
|
Configuring MRF policies is not enough for them to take effect. You have to enable them by specifying their module in `policies` under [:mrf](#mrf) section.
|
||||||
|
|
||||||
#### :mrf_simple
|
#### :mrf_simple
|
||||||
* `media_removal`: List of instances to remove media from.
|
* `media_removal`: List of instances to remove media from.
|
||||||
|
@ -969,13 +972,13 @@ config :pleroma, :database_config_whitelist, [
|
||||||
|
|
||||||
Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
Restrict access for unauthenticated users to timelines (public and federate), user profiles and statuses.
|
||||||
|
|
||||||
* `timelines` - public and federated timelines
|
* `timelines`: public and federated timelines
|
||||||
* `local` - public timeline
|
* `local`: public timeline
|
||||||
* `federated`
|
* `federated`
|
||||||
* `profiles` - user profiles
|
* `profiles`: user profiles
|
||||||
* `local`
|
* `local`
|
||||||
* `remote`
|
* `remote`
|
||||||
* `activities` - statuses
|
* `activities`: statuses
|
||||||
* `local`
|
* `local`
|
||||||
* `remote`
|
* `remote`
|
||||||
|
|
||||||
|
|
|
@ -34,9 +34,9 @@ config :pleroma, :instance,
|
||||||
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
|
To use `SimplePolicy`, you must enable it. Do so by adding the following to your `:instance` config object, so that it looks like this:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, :instance,
|
config :pleroma, :mrf,
|
||||||
[...]
|
[...]
|
||||||
rewrite_policy: Pleroma.Web.ActivityPub.MRF.SimplePolicy
|
policies: Pleroma.Web.ActivityPub.MRF.SimplePolicy
|
||||||
```
|
```
|
||||||
|
|
||||||
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
|
Once `SimplePolicy` is enabled, you can configure various groups in the `:mrf_simple` config object. These groups are:
|
||||||
|
@ -58,8 +58,8 @@ Servers should be configured as lists.
|
||||||
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
|
This example will enable `SimplePolicy`, block media from `illegalporn.biz`, mark media as NSFW from `porn.biz` and `porn.business`, reject messages from `spam.com`, remove messages from `spam.university` from the federated timeline and block reports (flags) from `whiny.whiner`:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, :instance,
|
config :pleroma, :mrf,
|
||||||
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
|
policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
|
||||||
|
|
||||||
config :pleroma, :mrf_simple,
|
config :pleroma, :mrf_simple,
|
||||||
media_removal: ["illegalporn.biz"],
|
media_removal: ["illegalporn.biz"],
|
||||||
|
@ -75,7 +75,7 @@ The effects of MRF policies can be very drastic. It is important to use this fun
|
||||||
|
|
||||||
## Writing your own MRF Policy
|
## Writing your own MRF Policy
|
||||||
|
|
||||||
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `rewrite_policy` config setting.
|
As discussed above, the MRF system is a modular system that supports pluggable policies. This means that an admin may write a custom MRF policy in Elixir or any other language that runs on the Erlang VM, by specifying the module name in the `policies` config setting.
|
||||||
|
|
||||||
For example, here is a sample policy module which rewrites all messages to "new message content":
|
For example, here is a sample policy module which rewrites all messages to "new message content":
|
||||||
|
|
||||||
|
@ -125,8 +125,8 @@ end
|
||||||
If you save this file as `lib/pleroma/web/activity_pub/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
|
If you save this file as `lib/pleroma/web/activity_pub/mrf/rewrite_policy.ex`, it will be included when you next rebuild Pleroma. You can enable it in the configuration like so:
|
||||||
|
|
||||||
```elixir
|
```elixir
|
||||||
config :pleroma, :instance,
|
config :pleroma, :mrf,
|
||||||
rewrite_policy: [
|
policies: [
|
||||||
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
|
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
|
||||||
Pleroma.Web.ActivityPub.MRF.RewritePolicy
|
Pleroma.Web.ActivityPub.MRF.RewritePolicy
|
||||||
]
|
]
|
||||||
|
|
|
@ -33,6 +33,6 @@ as soon as the post is received by your instance.
|
||||||
Add to your `prod.secret.exs`:
|
Add to your `prod.secret.exs`:
|
||||||
|
|
||||||
```
|
```
|
||||||
config :pleroma, :instance,
|
config :pleroma, :mrf,
|
||||||
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
|
policies: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
|
||||||
```
|
```
|
||||||
|
|
|
@ -17,30 +17,53 @@ defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
|
||||||
def run([]) do
|
def run([]) do
|
||||||
Mix.Pleroma.start_pleroma()
|
Mix.Pleroma.start_pleroma()
|
||||||
|
|
||||||
["public", "unlisted", "private", "direct"]
|
instances =
|
||||||
|> Enum.each(fn visibility ->
|
Activity
|
||||||
count = status_visibility_count_query(visibility)
|
|> distinct([a], true)
|
||||||
name = "status_visibility_#{visibility}"
|
|> select([a], fragment("split_part(?, '/', 3)", a.actor))
|
||||||
CounterCache.set(name, count)
|
|> Repo.all()
|
||||||
Mix.Pleroma.shell_info("Set #{name} to #{count}")
|
|
||||||
|
instances
|
||||||
|
|> Enum.with_index(1)
|
||||||
|
|> Enum.each(fn {instance, i} ->
|
||||||
|
counters = instance_counters(instance)
|
||||||
|
CounterCache.set(instance, counters)
|
||||||
|
|
||||||
|
Mix.Pleroma.shell_info(
|
||||||
|
"[#{i}/#{length(instances)}] Setting #{instance} counters: #{inspect(counters)}"
|
||||||
|
)
|
||||||
end)
|
end)
|
||||||
|
|
||||||
Mix.Pleroma.shell_info("Done")
|
Mix.Pleroma.shell_info("Done")
|
||||||
end
|
end
|
||||||
|
|
||||||
defp status_visibility_count_query(visibility) do
|
defp instance_counters(instance) do
|
||||||
|
counters = %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
|
||||||
|
|
||||||
Activity
|
Activity
|
||||||
|> where(
|
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
||||||
|
|> where([a], fragment("split_part(?, '/', 3) = ?", a.actor, ^instance))
|
||||||
|
|> select(
|
||||||
[a],
|
[a],
|
||||||
fragment(
|
{fragment(
|
||||||
"activity_visibility(?, ?, ?) = ?",
|
"activity_visibility(?, ?, ?)",
|
||||||
a.actor,
|
a.actor,
|
||||||
a.recipients,
|
a.recipients,
|
||||||
a.data,
|
a.data
|
||||||
^visibility
|
), count(a.id)}
|
||||||
|
)
|
||||||
|
|> group_by(
|
||||||
|
[a],
|
||||||
|
fragment(
|
||||||
|
"activity_visibility(?, ?, ?)",
|
||||||
|
a.actor,
|
||||||
|
a.recipients,
|
||||||
|
a.data
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|
|> Repo.all(timeout: :timer.minutes(30))
|
||||||
|> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
|
|> Enum.reduce(counters, fn {visibility, count}, acc ->
|
||||||
|
Map.put(acc, visibility, count)
|
||||||
|
end)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -167,7 +167,9 @@ defp only_full_update?(%ConfigDB{group: group, key: key}) do
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec delete(map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
@spec delete(ConfigDB.t() | map()) :: {:ok, ConfigDB.t()} | {:error, Changeset.t()}
|
||||||
|
def delete(%ConfigDB{} = config), do: Repo.delete(config)
|
||||||
|
|
||||||
def delete(params) do
|
def delete(params) do
|
||||||
search_opts = Map.delete(params, :subkeys)
|
search_opts = Map.delete(params, :subkeys)
|
||||||
|
|
||||||
|
|
|
@ -3,9 +3,23 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Config.DeprecationWarnings do
|
defmodule Pleroma.Config.DeprecationWarnings do
|
||||||
|
alias Pleroma.Config
|
||||||
|
|
||||||
require Logger
|
require Logger
|
||||||
alias Pleroma.Config
|
alias Pleroma.Config
|
||||||
|
|
||||||
|
@type config_namespace() :: [atom()]
|
||||||
|
@type config_map() :: {config_namespace(), config_namespace(), String.t()}
|
||||||
|
|
||||||
|
@mrf_config_map [
|
||||||
|
{[:instance, :rewrite_policy], [:mrf, :policies],
|
||||||
|
"\n* `config :pleroma, :instance, rewrite_policy` is now `config :pleroma, :mrf, policies`"},
|
||||||
|
{[:instance, :mrf_transparency], [:mrf, :transparency],
|
||||||
|
"\n* `config :pleroma, :instance, mrf_transparency` is now `config :pleroma, :mrf, transparency`"},
|
||||||
|
{[:instance, :mrf_transparency_exclusions], [:mrf, :transparency_exclusions],
|
||||||
|
"\n* `config :pleroma, :instance, mrf_transparency_exclusions` is now `config :pleroma, :mrf, transparency_exclusions`"}
|
||||||
|
]
|
||||||
|
|
||||||
def check_hellthread_threshold do
|
def check_hellthread_threshold do
|
||||||
if Config.get([:mrf_hellthread, :threshold]) do
|
if Config.get([:mrf_hellthread, :threshold]) do
|
||||||
Logger.warn("""
|
Logger.warn("""
|
||||||
|
@ -39,5 +53,35 @@ def mrf_user_allowlist do
|
||||||
def warn do
|
def warn do
|
||||||
check_hellthread_threshold()
|
check_hellthread_threshold()
|
||||||
mrf_user_allowlist()
|
mrf_user_allowlist()
|
||||||
|
check_old_mrf_config()
|
||||||
|
end
|
||||||
|
|
||||||
|
def check_old_mrf_config do
|
||||||
|
warning_preface = """
|
||||||
|
!!!DEPRECATION WARNING!!!
|
||||||
|
Your config is using old namespaces for MRF configuration. They should work for now, but you are advised to change to new namespaces to prevent possible issues later:
|
||||||
|
"""
|
||||||
|
|
||||||
|
move_namespace_and_warn(@mrf_config_map, warning_preface)
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec move_namespace_and_warn([config_map()], String.t()) :: :ok
|
||||||
|
def move_namespace_and_warn(config_map, warning_preface) do
|
||||||
|
warning =
|
||||||
|
Enum.reduce(config_map, "", fn
|
||||||
|
{old, new, err_msg}, acc ->
|
||||||
|
old_config = Config.get(old)
|
||||||
|
|
||||||
|
if old_config do
|
||||||
|
Config.put(new, old_config)
|
||||||
|
acc <> err_msg
|
||||||
|
else
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end)
|
||||||
|
|
||||||
|
if warning != "" do
|
||||||
|
Logger.warn(warning_preface <> warning)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -10,32 +10,70 @@ defmodule Pleroma.CounterCache do
|
||||||
import Ecto.Query
|
import Ecto.Query
|
||||||
|
|
||||||
schema "counter_cache" do
|
schema "counter_cache" do
|
||||||
field(:name, :string)
|
field(:instance, :string)
|
||||||
field(:count, :integer)
|
field(:public, :integer)
|
||||||
|
field(:unlisted, :integer)
|
||||||
|
field(:private, :integer)
|
||||||
|
field(:direct, :integer)
|
||||||
end
|
end
|
||||||
|
|
||||||
def changeset(struct, params) do
|
def changeset(struct, params) do
|
||||||
struct
|
struct
|
||||||
|> cast(params, [:name, :count])
|
|> cast(params, [:instance, :public, :unlisted, :private, :direct])
|
||||||
|> validate_required([:name])
|
|> validate_required([:instance])
|
||||||
|> unique_constraint(:name)
|
|> unique_constraint(:instance)
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_as_map(names) when is_list(names) do
|
def get_by_instance(instance) do
|
||||||
CounterCache
|
CounterCache
|
||||||
|> where([cc], cc.name in ^names)
|
|> select([c], %{
|
||||||
|> Repo.all()
|
"public" => c.public,
|
||||||
|> Enum.group_by(& &1.name, & &1.count)
|
"unlisted" => c.unlisted,
|
||||||
|> Map.new(fn {k, v} -> {k, hd(v)} end)
|
"private" => c.private,
|
||||||
|
"direct" => c.direct
|
||||||
|
})
|
||||||
|
|> where([c], c.instance == ^instance)
|
||||||
|
|> Repo.one()
|
||||||
|
|> case do
|
||||||
|
nil -> %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
|
||||||
|
val -> val
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set(name, count) do
|
def get_sum do
|
||||||
|
CounterCache
|
||||||
|
|> select([c], %{
|
||||||
|
"public" => type(sum(c.public), :integer),
|
||||||
|
"unlisted" => type(sum(c.unlisted), :integer),
|
||||||
|
"private" => type(sum(c.private), :integer),
|
||||||
|
"direct" => type(sum(c.direct), :integer)
|
||||||
|
})
|
||||||
|
|> Repo.one()
|
||||||
|
end
|
||||||
|
|
||||||
|
def set(instance, values) do
|
||||||
|
params =
|
||||||
|
Enum.reduce(
|
||||||
|
["public", "private", "unlisted", "direct"],
|
||||||
|
%{"instance" => instance},
|
||||||
|
fn param, acc ->
|
||||||
|
Map.put_new(acc, param, Map.get(values, param, 0))
|
||||||
|
end
|
||||||
|
)
|
||||||
|
|
||||||
%CounterCache{}
|
%CounterCache{}
|
||||||
|> changeset(%{"name" => name, "count" => count})
|
|> changeset(params)
|
||||||
|> Repo.insert(
|
|> Repo.insert(
|
||||||
on_conflict: [set: [count: count]],
|
on_conflict: [
|
||||||
|
set: [
|
||||||
|
public: params["public"],
|
||||||
|
private: params["private"],
|
||||||
|
unlisted: params["unlisted"],
|
||||||
|
direct: params["direct"]
|
||||||
|
]
|
||||||
|
],
|
||||||
returning: true,
|
returning: true,
|
||||||
conflict_target: :name
|
conflict_target: :instance
|
||||||
)
|
)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -45,6 +45,7 @@ def show(opts) do
|
||||||
shortcodes =
|
shortcodes =
|
||||||
pack.files
|
pack.files
|
||||||
|> Map.keys()
|
|> Map.keys()
|
||||||
|
|> Enum.sort()
|
||||||
|> paginate(opts[:page], opts[:page_size])
|
|> paginate(opts[:page], opts[:page_size])
|
||||||
|
|
||||||
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
|
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))
|
||||||
|
|
|
@ -124,6 +124,7 @@ def get_follow_requests(%User{id: id}) do
|
||||||
|> join(:inner, [r], f in assoc(r, :follower))
|
|> join(:inner, [r], f in assoc(r, :follower))
|
||||||
|> where([r], r.state == ^:follow_pending)
|
|> where([r], r.state == ^:follow_pending)
|
||||||
|> where([r], r.following_id == ^id)
|
|> where([r], r.following_id == ^id)
|
||||||
|
|> where([r, f], f.deactivated != true)
|
||||||
|> select([r, f], f)
|
|> select([r, f], f)
|
||||||
|> Repo.all()
|
|> Repo.all()
|
||||||
end
|
end
|
||||||
|
|
|
@ -109,7 +109,7 @@ def extract_first_external_url(object, content) do
|
||||||
result =
|
result =
|
||||||
content
|
content
|
||||||
|> Floki.parse_fragment!()
|
|> Floki.parse_fragment!()
|
||||||
|> Floki.filter_out("a.mention,a.hashtag,a[rel~=\"tag\"]")
|
|> Floki.filter_out("a.mention,a.hashtag,a.attachment,a[rel~=\"tag\"]")
|
||||||
|> Floki.attribute("a", "href")
|
|> Floki.attribute("a", "href")
|
||||||
|> Enum.at(0)
|
|> Enum.at(0)
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,22 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.ExAws do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
@behaviour ExAws.Request.HttpClient
|
||||||
|
|
||||||
|
alias Pleroma.HTTP
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def request(method, url, body \\ "", headers \\ [], http_opts \\ []) do
|
||||||
|
case HTTP.request(method, url, body, headers, http_opts) do
|
||||||
|
{:ok, env} ->
|
||||||
|
{:ok, %{status_code: env.status, headers: env.headers, body: env.body}}
|
||||||
|
|
||||||
|
{:error, reason} ->
|
||||||
|
{:error, %{reason: reason}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -16,6 +16,7 @@ defmodule Pleroma.HTTP do
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
@type t :: __MODULE__
|
@type t :: __MODULE__
|
||||||
|
@type method() :: :get | :post | :put | :delete | :head
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Performs GET request.
|
Performs GET request.
|
||||||
|
@ -28,6 +29,9 @@ def get(url, headers \\ [], options \\ [])
|
||||||
def get(nil, _, _), do: nil
|
def get(nil, _, _), do: nil
|
||||||
def get(url, headers, options), do: request(:get, url, "", headers, options)
|
def get(url, headers, options), do: request(:get, url, "", headers, options)
|
||||||
|
|
||||||
|
@spec head(Request.url(), Request.headers(), keyword()) :: {:ok, Env.t()} | {:error, any()}
|
||||||
|
def head(url, headers \\ [], options \\ []), do: request(:head, url, "", headers, options)
|
||||||
|
|
||||||
@doc """
|
@doc """
|
||||||
Performs POST request.
|
Performs POST request.
|
||||||
|
|
||||||
|
@ -42,7 +46,7 @@ def post(url, body, headers \\ [], options \\ []),
|
||||||
Builds and performs http request.
|
Builds and performs http request.
|
||||||
|
|
||||||
# Arguments:
|
# Arguments:
|
||||||
`method` - :get, :post, :put, :delete
|
`method` - :get, :post, :put, :delete, :head
|
||||||
`url` - full url
|
`url` - full url
|
||||||
`body` - request body
|
`body` - request body
|
||||||
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
|
`headers` - a keyworld list of headers, e.g. `[{"content-type", "text/plain"}]`
|
||||||
|
@ -52,7 +56,7 @@ def post(url, body, headers \\ [], options \\ []),
|
||||||
`{:ok, %Tesla.Env{}}` or `{:error, error}`
|
`{:ok, %Tesla.Env{}}` or `{:error, error}`
|
||||||
|
|
||||||
"""
|
"""
|
||||||
@spec request(atom(), Request.url(), String.t(), Request.headers(), keyword()) ::
|
@spec request(method(), Request.url(), String.t(), Request.headers(), keyword()) ::
|
||||||
{:ok, Env.t()} | {:error, any()}
|
{:ok, Env.t()} | {:error, any()}
|
||||||
def request(method, url, body, headers, options) when is_binary(url) do
|
def request(method, url, body, headers, options) when is_binary(url) do
|
||||||
uri = URI.parse(url)
|
uri = URI.parse(url)
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.HTTP.Tzdata do
|
||||||
|
@moduledoc false
|
||||||
|
|
||||||
|
@behaviour Tzdata.HTTPClient
|
||||||
|
|
||||||
|
alias Pleroma.HTTP
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def get(url, headers, options) do
|
||||||
|
with {:ok, %Tesla.Env{} = env} <- HTTP.get(url, headers, options) do
|
||||||
|
{:ok, {env.status, env.headers, env.body}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
@impl true
|
||||||
|
def head(url, headers, options) do
|
||||||
|
with {:ok, %Tesla.Env{} = env} <- HTTP.head(url, headers, options) do
|
||||||
|
{:ok, {env.status, env.headers}}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -3,7 +3,6 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
defmodule Pleroma.MigrationHelper.NotificationBackfill do
|
||||||
alias Pleroma.Notification
|
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.Repo
|
alias Pleroma.Repo
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
|
@ -25,18 +24,27 @@ def fill_in_notification_types do
|
||||||
|> type_from_activity()
|
|> type_from_activity()
|
||||||
|
|
||||||
notification
|
notification
|
||||||
|> Notification.changeset(%{type: type})
|
|> Ecto.Changeset.change(%{type: type})
|
||||||
|> Repo.update()
|
|> Repo.update()
|
||||||
end)
|
end)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp get_by_ap_id(ap_id) do
|
||||||
|
q =
|
||||||
|
from(u in User,
|
||||||
|
select: u.id
|
||||||
|
)
|
||||||
|
|
||||||
|
Repo.get_by(q, ap_id: ap_id)
|
||||||
|
end
|
||||||
|
|
||||||
# This is copied over from Notifications to keep this stable.
|
# This is copied over from Notifications to keep this stable.
|
||||||
defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
defp type_from_activity(%{data: %{"type" => type}} = activity) do
|
||||||
case type do
|
case type do
|
||||||
"Follow" ->
|
"Follow" ->
|
||||||
accepted_function = fn activity ->
|
accepted_function = fn activity ->
|
||||||
with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
|
with %User{} = follower <- get_by_ap_id(activity.data["actor"]),
|
||||||
%User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
|
%User{} = followed <- get_by_ap_id(activity.data["object"]) do
|
||||||
Pleroma.FollowingRelationship.following?(follower, followed)
|
Pleroma.FollowingRelationship.following?(follower, followed)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -367,6 +367,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
|
||||||
do_send = do_send && user in enabled_receivers
|
do_send = do_send && user in enabled_receivers
|
||||||
create_notification(activity, user, do_send)
|
create_notification(activity, user, do_send)
|
||||||
end)
|
end)
|
||||||
|
|> Enum.reject(&is_nil/1)
|
||||||
|
|
||||||
{:ok, notifications}
|
{:ok, notifications}
|
||||||
end
|
end
|
||||||
|
|
|
@ -83,8 +83,8 @@ def fetch_object_from_id(id, options \\ []) do
|
||||||
{:transmogrifier, {:error, {:reject, nil}}} ->
|
{:transmogrifier, {:error, {:reject, nil}}} ->
|
||||||
{:reject, nil}
|
{:reject, nil}
|
||||||
|
|
||||||
{:transmogrifier, _} ->
|
{:transmogrifier, _} = e ->
|
||||||
{:error, "Transmogrifier failure."}
|
{:error, e}
|
||||||
|
|
||||||
{:object, data, nil} ->
|
{:object, data, nil} ->
|
||||||
reinject_object(%Object{}, data)
|
reinject_object(%Object{}, data)
|
||||||
|
|
|
@ -97,20 +97,11 @@ def calculate_stat_data do
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
||||||
def get_status_visibility_count do
|
def get_status_visibility_count(instance \\ nil) do
|
||||||
counter_cache =
|
if is_nil(instance) do
|
||||||
CounterCache.get_as_map([
|
CounterCache.get_sum()
|
||||||
"status_visibility_public",
|
else
|
||||||
"status_visibility_private",
|
CounterCache.get_by_instance(instance)
|
||||||
"status_visibility_unlisted",
|
end
|
||||||
"status_visibility_direct"
|
|
||||||
])
|
|
||||||
|
|
||||||
%{
|
|
||||||
public: counter_cache["status_visibility_public"] || 0,
|
|
||||||
unlisted: counter_cache["status_visibility_unlisted"] || 0,
|
|
||||||
private: counter_cache["status_visibility_private"] || 0,
|
|
||||||
direct: counter_cache["status_visibility_direct"] || 0
|
|
||||||
}
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -115,7 +115,7 @@ defmodule Pleroma.User do
|
||||||
field(:is_moderator, :boolean, default: false)
|
field(:is_moderator, :boolean, default: false)
|
||||||
field(:is_admin, :boolean, default: false)
|
field(:is_admin, :boolean, default: false)
|
||||||
field(:show_role, :boolean, default: true)
|
field(:show_role, :boolean, default: true)
|
||||||
field(:settings, :map, default: nil)
|
field(:mastofe_settings, :map, default: nil)
|
||||||
field(:uri, ObjectValidators.Uri, default: nil)
|
field(:uri, ObjectValidators.Uri, default: nil)
|
||||||
field(:hide_followers_count, :boolean, default: false)
|
field(:hide_followers_count, :boolean, default: false)
|
||||||
field(:hide_follows_count, :boolean, default: false)
|
field(:hide_follows_count, :boolean, default: false)
|
||||||
|
@ -1309,7 +1309,8 @@ def block(%User{} = blocker, %User{} = blocked) do
|
||||||
|
|
||||||
unsubscribe(blocked, blocker)
|
unsubscribe(blocked, blocker)
|
||||||
|
|
||||||
if following?(blocked, blocker), do: unfollow(blocked, blocker)
|
unfollowing_blocked = Config.get([:activitypub, :unfollow_blocked], true)
|
||||||
|
if unfollowing_blocked && following?(blocked, blocker), do: unfollow(blocked, blocker)
|
||||||
|
|
||||||
{:ok, blocker} = update_follower_count(blocker)
|
{:ok, blocker} = update_follower_count(blocker)
|
||||||
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
{:ok, blocker, _} = Participation.mark_all_as_read(blocker, blocked)
|
||||||
|
@ -1527,8 +1528,7 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
|
||||||
blocked_identifiers,
|
blocked_identifiers,
|
||||||
fn blocked_identifier ->
|
fn blocked_identifier ->
|
||||||
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
|
||||||
{:ok, _user_block} <- block(blocker, blocked),
|
{:ok, _block} <- CommonAPI.block(blocker, blocked) do
|
||||||
{:ok, _} <- ActivityPub.block(blocker, blocked) do
|
|
||||||
blocked
|
blocked
|
||||||
else
|
else
|
||||||
err ->
|
err ->
|
||||||
|
@ -2118,8 +2118,8 @@ def mascot_update(user, url) do
|
||||||
|
|
||||||
def mastodon_settings_update(user, settings) do
|
def mastodon_settings_update(user, settings) do
|
||||||
user
|
user
|
||||||
|> cast(%{settings: settings}, [:settings])
|
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|
||||||
|> validate_required([:settings])
|
|> validate_required([:mastofe_settings])
|
||||||
|> update_and_set_cache()
|
|> update_and_set_cache()
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,7 @@ defp search_query(query_string, for_user, following) do
|
||||||
|> base_query(following)
|
|> base_query(following)
|
||||||
|> filter_blocked_user(for_user)
|
|> filter_blocked_user(for_user)
|
||||||
|> filter_invisible_users()
|
|> filter_invisible_users()
|
||||||
|
|> filter_internal_users()
|
||||||
|> filter_blocked_domains(for_user)
|
|> filter_blocked_domains(for_user)
|
||||||
|> fts_search(query_string)
|
|> fts_search(query_string)
|
||||||
|> trigram_rank(query_string)
|
|> trigram_rank(query_string)
|
||||||
|
@ -109,6 +110,10 @@ defp filter_invisible_users(query) do
|
||||||
from(q in query, where: q.invisible == false)
|
from(q in query, where: q.invisible == false)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp filter_internal_users(query) do
|
||||||
|
from(q in query, where: q.actor_type != "Application")
|
||||||
|
end
|
||||||
|
|
||||||
defp filter_blocked_user(query, %User{} = blocker) do
|
defp filter_blocked_user(query, %User{} = blocker) do
|
||||||
query
|
query
|
||||||
|> join(:left, [u], b in Pleroma.UserRelationship,
|
|> join(:left, [u], b in Pleroma.UserRelationship,
|
||||||
|
|
|
@ -321,28 +321,6 @@ defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec update(map()) :: {:ok, Activity.t()} | {:error, any()}
|
|
||||||
def update(%{to: to, cc: cc, actor: actor, object: object} = params) do
|
|
||||||
local = !(params[:local] == false)
|
|
||||||
activity_id = params[:activity_id]
|
|
||||||
|
|
||||||
data =
|
|
||||||
%{
|
|
||||||
"to" => to,
|
|
||||||
"cc" => cc,
|
|
||||||
"type" => "Update",
|
|
||||||
"actor" => actor,
|
|
||||||
"object" => object
|
|
||||||
}
|
|
||||||
|> Maps.put_if_present("id", activity_id)
|
|
||||||
|
|
||||||
with {:ok, activity} <- insert(data, local),
|
|
||||||
_ <- notify_and_stream(activity),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, activity}
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
|
@spec follow(User.t(), User.t(), String.t() | nil, boolean(), keyword()) ::
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
{:ok, Activity.t()} | {:error, any()}
|
||||||
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
|
def follow(follower, followed, activity_id \\ nil, local \\ true, opts \\ []) do
|
||||||
|
@ -388,33 +366,6 @@ defp do_unfollow(follower, followed, activity_id, local) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
@spec block(User.t(), User.t(), String.t() | nil, boolean()) ::
|
|
||||||
{:ok, Activity.t()} | {:error, any()}
|
|
||||||
def block(blocker, blocked, activity_id \\ nil, local \\ true) do
|
|
||||||
with {:ok, result} <-
|
|
||||||
Repo.transaction(fn -> do_block(blocker, blocked, activity_id, local) end) do
|
|
||||||
result
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
defp do_block(blocker, blocked, activity_id, local) do
|
|
||||||
unfollow_blocked = Config.get([:activitypub, :unfollow_blocked])
|
|
||||||
|
|
||||||
if unfollow_blocked and fetch_latest_follow(blocker, blocked) do
|
|
||||||
unfollow(blocker, blocked, nil, local)
|
|
||||||
end
|
|
||||||
|
|
||||||
block_data = make_block_data(blocker, blocked, activity_id)
|
|
||||||
|
|
||||||
with {:ok, activity} <- insert(block_data, local),
|
|
||||||
_ <- notify_and_stream(activity),
|
|
||||||
:ok <- maybe_federate(activity) do
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
{:error, error} -> Repo.rollback(error)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
@spec flag(map()) :: {:ok, Activity.t()} | {:error, any()}
|
||||||
def flag(
|
def flag(
|
||||||
%{
|
%{
|
||||||
|
@ -1420,6 +1371,16 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def maybe_handle_clashing_nickname(nickname) do
|
||||||
|
with %User{} = old_user <- User.get_by_nickname(nickname) do
|
||||||
|
Logger.info("Found an old user for #{nickname}, ap id is #{old_user.ap_id}, renaming.")
|
||||||
|
|
||||||
|
old_user
|
||||||
|
|> User.remote_user_changeset(%{nickname: "#{old_user.id}.#{old_user.nickname}"})
|
||||||
|
|> User.update_and_set_cache()
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def make_user_from_ap_id(ap_id) do
|
def make_user_from_ap_id(ap_id) do
|
||||||
user = User.get_cached_by_ap_id(ap_id)
|
user = User.get_cached_by_ap_id(ap_id)
|
||||||
|
|
||||||
|
@ -1432,6 +1393,8 @@ def make_user_from_ap_id(ap_id) do
|
||||||
|> User.remote_user_changeset(data)
|
|> User.remote_user_changeset(data)
|
||||||
|> User.update_and_set_cache()
|
|> User.update_and_set_cache()
|
||||||
else
|
else
|
||||||
|
maybe_handle_clashing_nickname(data[:nickname])
|
||||||
|
|
||||||
data
|
data
|
||||||
|> User.remote_user_changeset()
|
|> User.remote_user_changeset()
|
||||||
|> Repo.insert()
|
|> Repo.insert()
|
||||||
|
|
|
@ -123,6 +123,33 @@ def like(actor, object) do
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
# Retricted to user updates for now, always public
|
||||||
|
@spec update(User.t(), Object.t()) :: {:ok, map(), keyword()}
|
||||||
|
def update(actor, object) do
|
||||||
|
to = [Pleroma.Constants.as_public(), actor.follower_address]
|
||||||
|
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"type" => "Update",
|
||||||
|
"actor" => actor.ap_id,
|
||||||
|
"object" => object,
|
||||||
|
"to" => to
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
|
@spec block(User.t(), User.t()) :: {:ok, map(), keyword()}
|
||||||
|
def block(blocker, blocked) do
|
||||||
|
{:ok,
|
||||||
|
%{
|
||||||
|
"id" => Utils.generate_activity_id(),
|
||||||
|
"type" => "Block",
|
||||||
|
"actor" => blocker.ap_id,
|
||||||
|
"object" => blocked.ap_id,
|
||||||
|
"to" => [blocked.ap_id]
|
||||||
|
}, []}
|
||||||
|
end
|
||||||
|
|
||||||
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
@spec announce(User.t(), Object.t(), keyword()) :: {:ok, map(), keyword()}
|
||||||
def announce(actor, object, options \\ []) do
|
def announce(actor, object, options \\ []) do
|
||||||
public? = Keyword.get(options, :public, false)
|
public? = Keyword.get(options, :public, false)
|
||||||
|
|
|
@ -16,7 +16,7 @@ def filter(policies, %{} = object) do
|
||||||
def filter(%{} = object), do: get_policies() |> filter(object)
|
def filter(%{} = object), do: get_policies() |> filter(object)
|
||||||
|
|
||||||
def get_policies do
|
def get_policies do
|
||||||
Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
|
Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
|
||||||
end
|
end
|
||||||
|
|
||||||
defp get_policies(policy) when is_atom(policy), do: [policy]
|
defp get_policies(policy) when is_atom(policy), do: [policy]
|
||||||
|
@ -51,7 +51,7 @@ def describe(policies) do
|
||||||
get_policies()
|
get_policies()
|
||||||
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
|> Enum.map(fn policy -> to_string(policy) |> String.split(".") |> List.last() end)
|
||||||
|
|
||||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
exclusions = Pleroma.Config.get([:mrf, :transparency_exclusions])
|
||||||
|
|
||||||
base =
|
base =
|
||||||
%{
|
%{
|
||||||
|
|
|
@ -27,11 +27,14 @@ defp contains_links?(_), do: false
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
def filter(%{"type" => "Create", "actor" => actor, "object" => object} = message) do
|
||||||
with {:ok, %User{} = u} <- User.get_or_fetch_by_ap_id(actor),
|
with {:ok, %User{local: false} = u} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
{:contains_links, true} <- {:contains_links, contains_links?(object)},
|
||||||
{:old_user, true} <- {:old_user, old_user?(u)} do
|
{:old_user, true} <- {:old_user, old_user?(u)} do
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
else
|
else
|
||||||
|
{:ok, %User{local: true}} ->
|
||||||
|
{:ok, message}
|
||||||
|
|
||||||
{:contains_links, false} ->
|
{:contains_links, false} ->
|
||||||
{:ok, message}
|
{:ok, message}
|
||||||
|
|
||||||
|
|
|
@ -3,21 +3,23 @@
|
||||||
# SPDX-License-Identifier: AGPL-3.0-only
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web.ActivityPub.MRF
|
|
||||||
@moduledoc "Filter activities depending on their origin instance"
|
@moduledoc "Filter activities depending on their origin instance"
|
||||||
@behaviour Pleroma.Web.ActivityPub.MRF
|
@behaviour Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.ActivityPub.MRF
|
||||||
|
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
|
|
||||||
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||||
accepts =
|
accepts =
|
||||||
Pleroma.Config.get([:mrf_simple, :accept])
|
Config.get([:mrf_simple, :accept])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
cond do
|
cond do
|
||||||
accepts == [] -> {:ok, object}
|
accepts == [] -> {:ok, object}
|
||||||
actor_host == Pleroma.Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
|
actor_host == Config.get([Pleroma.Web.Endpoint, :url, :host]) -> {:ok, object}
|
||||||
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
|
MRF.subdomain_match?(accepts, actor_host) -> {:ok, object}
|
||||||
true -> {:reject, nil}
|
true -> {:reject, nil}
|
||||||
end
|
end
|
||||||
|
@ -25,7 +27,7 @@ defp check_accept(%{host: actor_host} = _actor_info, object) do
|
||||||
|
|
||||||
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
defp check_reject(%{host: actor_host} = _actor_info, object) do
|
||||||
rejects =
|
rejects =
|
||||||
Pleroma.Config.get([:mrf_simple, :reject])
|
Config.get([:mrf_simple, :reject])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(rejects, actor_host) do
|
if MRF.subdomain_match?(rejects, actor_host) do
|
||||||
|
@ -41,7 +43,7 @@ defp check_media_removal(
|
||||||
)
|
)
|
||||||
when length(child_attachment) > 0 do
|
when length(child_attachment) > 0 do
|
||||||
media_removal =
|
media_removal =
|
||||||
Pleroma.Config.get([:mrf_simple, :media_removal])
|
Config.get([:mrf_simple, :media_removal])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -65,7 +67,7 @@ defp check_media_nsfw(
|
||||||
} = object
|
} = object
|
||||||
) do
|
) do
|
||||||
media_nsfw =
|
media_nsfw =
|
||||||
Pleroma.Config.get([:mrf_simple, :media_nsfw])
|
Config.get([:mrf_simple, :media_nsfw])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -85,7 +87,7 @@ defp check_media_nsfw(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||||
timeline_removal =
|
timeline_removal =
|
||||||
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
|
Config.get([:mrf_simple, :federated_timeline_removal])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
object =
|
object =
|
||||||
|
@ -108,7 +110,7 @@ defp check_ftl_removal(%{host: actor_host} = _actor_info, object) do
|
||||||
|
|
||||||
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
|
defp check_report_removal(%{host: actor_host} = _actor_info, %{"type" => "Flag"} = object) do
|
||||||
report_removal =
|
report_removal =
|
||||||
Pleroma.Config.get([:mrf_simple, :report_removal])
|
Config.get([:mrf_simple, :report_removal])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(report_removal, actor_host) do
|
if MRF.subdomain_match?(report_removal, actor_host) do
|
||||||
|
@ -122,7 +124,7 @@ defp check_report_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
|
defp check_avatar_removal(%{host: actor_host} = _actor_info, %{"icon" => _icon} = object) do
|
||||||
avatar_removal =
|
avatar_removal =
|
||||||
Pleroma.Config.get([:mrf_simple, :avatar_removal])
|
Config.get([:mrf_simple, :avatar_removal])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(avatar_removal, actor_host) do
|
if MRF.subdomain_match?(avatar_removal, actor_host) do
|
||||||
|
@ -136,7 +138,7 @@ defp check_avatar_removal(_actor_info, object), do: {:ok, object}
|
||||||
|
|
||||||
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
|
defp check_banner_removal(%{host: actor_host} = _actor_info, %{"image" => _image} = object) do
|
||||||
banner_removal =
|
banner_removal =
|
||||||
Pleroma.Config.get([:mrf_simple, :banner_removal])
|
Config.get([:mrf_simple, :banner_removal])
|
||||||
|> MRF.subdomains_regex()
|
|> MRF.subdomains_regex()
|
||||||
|
|
||||||
if MRF.subdomain_match?(banner_removal, actor_host) do
|
if MRF.subdomain_match?(banner_removal, actor_host) do
|
||||||
|
@ -197,10 +199,10 @@ def filter(object), do: {:ok, object}
|
||||||
|
|
||||||
@impl true
|
@impl true
|
||||||
def describe do
|
def describe do
|
||||||
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
|
exclusions = Config.get([:mrf, :transparency_exclusions])
|
||||||
|
|
||||||
mrf_simple =
|
mrf_simple =
|
||||||
Pleroma.Config.get(:mrf_simple)
|
Config.get(:mrf_simple)
|
||||||
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
|> Enum.map(fn {k, v} -> {k, Enum.reject(v, fn v -> v in exclusions end)} end)
|
||||||
|> Enum.into(%{})
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
|
|
@ -13,16 +13,47 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
|
||||||
alias Pleroma.Object
|
alias Pleroma.Object
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
|
||||||
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
|
||||||
|
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
|
||||||
|
|
||||||
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
|
||||||
def validate(object, meta)
|
def validate(object, meta)
|
||||||
|
|
||||||
|
def validate(%{"type" => "Block"} = block_activity, meta) do
|
||||||
|
with {:ok, block_activity} <-
|
||||||
|
block_activity
|
||||||
|
|> BlockValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
block_activity = stringify_keys(block_activity)
|
||||||
|
outgoing_blocks = Pleroma.Config.get([:activitypub, :outgoing_blocks])
|
||||||
|
|
||||||
|
meta =
|
||||||
|
if !outgoing_blocks do
|
||||||
|
Keyword.put(meta, :do_not_federate, true)
|
||||||
|
else
|
||||||
|
meta
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, block_activity, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate(%{"type" => "Update"} = update_activity, meta) do
|
||||||
|
with {:ok, update_activity} <-
|
||||||
|
update_activity
|
||||||
|
|> UpdateValidator.cast_and_validate()
|
||||||
|
|> Ecto.Changeset.apply_action(:insert) do
|
||||||
|
update_activity = stringify_keys(update_activity)
|
||||||
|
{:ok, update_activity, meta}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def validate(%{"type" => "Undo"} = object, meta) do
|
def validate(%{"type" => "Undo"} = object, meta) do
|
||||||
with {:ok, object} <-
|
with {:ok, object} <-
|
||||||
object
|
object
|
||||||
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:object, ObjectValidators.ObjectID)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|
|> validate_inclusion(:type, ["Block"])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_actor_presence(field_name: :object)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> validate_data
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,59 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator do
|
||||||
|
use Ecto.Schema
|
||||||
|
|
||||||
|
alias Pleroma.EctoType.ActivityPub.ObjectValidators
|
||||||
|
|
||||||
|
import Ecto.Changeset
|
||||||
|
import Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
|
||||||
|
|
||||||
|
@primary_key false
|
||||||
|
|
||||||
|
embedded_schema do
|
||||||
|
field(:id, ObjectValidators.ObjectID, primary_key: true)
|
||||||
|
field(:type, :string)
|
||||||
|
field(:actor, ObjectValidators.ObjectID)
|
||||||
|
field(:to, ObjectValidators.Recipients, default: [])
|
||||||
|
field(:cc, ObjectValidators.Recipients, default: [])
|
||||||
|
# In this case, we save the full object in this activity instead of just a
|
||||||
|
# reference, so we can always see what was actually changed by this.
|
||||||
|
field(:object, :map)
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_data(data) do
|
||||||
|
%__MODULE__{}
|
||||||
|
|> cast(data, __schema__(:fields))
|
||||||
|
end
|
||||||
|
|
||||||
|
def validate_data(cng) do
|
||||||
|
cng
|
||||||
|
|> validate_required([:id, :type, :actor, :to, :cc, :object])
|
||||||
|
|> validate_inclusion(:type, ["Update"])
|
||||||
|
|> validate_actor_presence()
|
||||||
|
|> validate_updating_rights()
|
||||||
|
end
|
||||||
|
|
||||||
|
def cast_and_validate(data) do
|
||||||
|
data
|
||||||
|
|> cast_data
|
||||||
|
|> validate_data
|
||||||
|
end
|
||||||
|
|
||||||
|
# For now we only support updating users, and here the rule is easy:
|
||||||
|
# object id == actor id
|
||||||
|
def validate_updating_rights(cng) do
|
||||||
|
with actor = get_field(cng, :actor),
|
||||||
|
object = get_field(cng, :object),
|
||||||
|
{:ok, object_id} <- ObjectValidators.ObjectID.cast(object),
|
||||||
|
true <- actor == object_id do
|
||||||
|
cng
|
||||||
|
else
|
||||||
|
_e ->
|
||||||
|
cng
|
||||||
|
|> add_error(:object, "Can't be updated by this actor")
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
collection, and so on.
|
collection, and so on.
|
||||||
"""
|
"""
|
||||||
alias Pleroma.Activity
|
alias Pleroma.Activity
|
||||||
|
alias Pleroma.Activity.Ir.Topics
|
||||||
alias Pleroma.Chat
|
alias Pleroma.Chat
|
||||||
alias Pleroma.Chat.MessageReference
|
alias Pleroma.Chat.MessageReference
|
||||||
alias Pleroma.Notification
|
alias Pleroma.Notification
|
||||||
|
@ -20,6 +21,41 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
|
||||||
|
|
||||||
def handle(object, meta \\ [])
|
def handle(object, meta \\ [])
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - Unfollow and block
|
||||||
|
def handle(
|
||||||
|
%{data: %{"type" => "Block", "object" => blocked_user, "actor" => blocking_user}} =
|
||||||
|
object,
|
||||||
|
meta
|
||||||
|
) do
|
||||||
|
with %User{} = blocker <- User.get_cached_by_ap_id(blocking_user),
|
||||||
|
%User{} = blocked <- User.get_cached_by_ap_id(blocked_user) do
|
||||||
|
User.block(blocker, blocked)
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
|
||||||
|
# Tasks this handles:
|
||||||
|
# - Update the user
|
||||||
|
#
|
||||||
|
# For a local user, we also get a changeset with the full information, so we
|
||||||
|
# can update non-federating, non-activitypub settings as well.
|
||||||
|
def handle(%{data: %{"type" => "Update", "object" => updated_object}} = object, meta) do
|
||||||
|
if changeset = Keyword.get(meta, :user_update_changeset) do
|
||||||
|
changeset
|
||||||
|
|> User.update_and_set_cache()
|
||||||
|
else
|
||||||
|
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(updated_object)
|
||||||
|
|
||||||
|
User.get_by_ap_id(updated_object["id"])
|
||||||
|
|> User.remote_user_changeset(new_user_data)
|
||||||
|
|> User.update_and_set_cache()
|
||||||
|
end
|
||||||
|
|
||||||
|
{:ok, object, meta}
|
||||||
|
end
|
||||||
|
|
||||||
# Tasks this handles:
|
# Tasks this handles:
|
||||||
# - Add like to object
|
# - Add like to object
|
||||||
# - Set up notification
|
# - Set up notification
|
||||||
|
@ -62,7 +98,10 @@ def handle(%{data: %{"type" => "Announce"}} = object, meta) do
|
||||||
|
|
||||||
if !User.is_internal_user?(user) do
|
if !User.is_internal_user?(user) do
|
||||||
Notification.create_notifications(object)
|
Notification.create_notifications(object)
|
||||||
ActivityPub.stream_out(object)
|
|
||||||
|
object
|
||||||
|
|> Topics.get_activity_topics()
|
||||||
|
|> Streamer.stream(object)
|
||||||
end
|
end
|
||||||
|
|
||||||
{:ok, object, meta}
|
{:ok, object, meta}
|
||||||
|
|
|
@ -446,12 +446,9 @@ def handle_incoming(
|
||||||
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
|
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
|
||||||
actor = Containment.get_actor(data)
|
actor = Containment.get_actor(data)
|
||||||
|
|
||||||
data =
|
|
||||||
Map.put(data, "actor", actor)
|
|
||||||
|> fix_addressing
|
|
||||||
|
|
||||||
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
with nil <- Activity.get_create_by_object_ap_id(object["id"]),
|
||||||
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(data["actor"]) do
|
{:ok, %User{} = user} <- User.get_or_fetch_by_ap_id(actor),
|
||||||
|
data <- Map.put(data, "actor", actor) |> fix_addressing() do
|
||||||
object = fix_object(object, options)
|
object = fix_object(object, options)
|
||||||
|
|
||||||
params = %{
|
params = %{
|
||||||
|
@ -673,7 +670,7 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(%{"type" => type} = data, _options)
|
def handle_incoming(%{"type" => type} = data, _options)
|
||||||
when type in ["Like", "EmojiReact", "Announce"] do
|
when type in ~w{Like EmojiReact Announce} do
|
||||||
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
with :ok <- ObjectValidator.fetch_actor_and_object(data),
|
||||||
{:ok, activity, _meta} <-
|
{:ok, activity, _meta} <-
|
||||||
Pipeline.common_pipeline(data, local: false) do
|
Pipeline.common_pipeline(data, local: false) do
|
||||||
|
@ -684,35 +681,13 @@ def handle_incoming(%{"type" => type} = data, _options)
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
|
%{"type" => type} = data,
|
||||||
data,
|
|
||||||
_options
|
_options
|
||||||
)
|
)
|
||||||
when object_type in [
|
when type in ~w{Update Block} do
|
||||||
"Person",
|
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
|
||||||
"Application",
|
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
|
||||||
"Service",
|
{:ok, activity}
|
||||||
"Organization"
|
|
||||||
] do
|
|
||||||
with %User{ap_id: ^actor_id} = actor <- User.get_cached_by_ap_id(object["id"]) do
|
|
||||||
{:ok, new_user_data} = ActivityPub.user_data_from_user_object(object)
|
|
||||||
|
|
||||||
actor
|
|
||||||
|> User.remote_user_changeset(new_user_data)
|
|
||||||
|> User.update_and_set_cache()
|
|
||||||
|
|
||||||
ActivityPub.update(%{
|
|
||||||
local: false,
|
|
||||||
to: data["to"] || [],
|
|
||||||
cc: data["cc"] || [],
|
|
||||||
object: object,
|
|
||||||
actor: actor_id,
|
|
||||||
activity_id: data["id"]
|
|
||||||
})
|
|
||||||
else
|
|
||||||
e ->
|
|
||||||
Logger.error(e)
|
|
||||||
:error
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -788,21 +763,6 @@ def handle_incoming(
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def handle_incoming(
|
|
||||||
%{"type" => "Block", "object" => blocked, "actor" => blocker, "id" => id} = _data,
|
|
||||||
_options
|
|
||||||
) do
|
|
||||||
with %User{local: true} = blocked = User.get_cached_by_ap_id(blocked),
|
|
||||||
{:ok, %User{} = blocker} = User.get_or_fetch_by_ap_id(blocker),
|
|
||||||
{:ok, activity} <- ActivityPub.block(blocker, blocked, id, false) do
|
|
||||||
User.unfollow(blocker, blocked)
|
|
||||||
User.block(blocker, blocked)
|
|
||||||
{:ok, activity}
|
|
||||||
else
|
|
||||||
_e -> :error
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def handle_incoming(
|
def handle_incoming(
|
||||||
%{
|
%{
|
||||||
"type" => "Move",
|
"type" => "Move",
|
||||||
|
|
|
@ -47,6 +47,10 @@ def is_list?(_), do: false
|
||||||
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
|
@spec visible_for_user?(Activity.t(), User.t() | nil) :: boolean()
|
||||||
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
|
def visible_for_user?(%{actor: ap_id}, %User{ap_id: ap_id}), do: true
|
||||||
|
|
||||||
|
def visible_for_user?(nil, _), do: false
|
||||||
|
|
||||||
|
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
||||||
|
|
||||||
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
|
def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{} = user) do
|
||||||
user.ap_id in activity.data["to"] ||
|
user.ap_id in activity.data["to"] ||
|
||||||
list_ap_id
|
list_ap_id
|
||||||
|
@ -54,8 +58,6 @@ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{
|
||||||
|> Pleroma.List.member?(user)
|
|> Pleroma.List.member?(user)
|
||||||
end
|
end
|
||||||
|
|
||||||
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
|
|
||||||
|
|
||||||
def visible_for_user?(%{local: local} = activity, nil) do
|
def visible_for_user?(%{local: local} = activity, nil) do
|
||||||
cfg_key =
|
cfg_key =
|
||||||
if local,
|
if local,
|
||||||
|
|
|
@ -643,10 +643,10 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
|
||||||
json(conn, "")
|
json(conn, "")
|
||||||
end
|
end
|
||||||
|
|
||||||
def stats(conn, _) do
|
def stats(conn, params) do
|
||||||
count = Stats.get_status_visibility_count()
|
counters = Stats.get_status_visibility_count(params["instance"])
|
||||||
|
|
||||||
json(conn, %{"status_visibility" => count})
|
json(conn, %{"status_visibility" => counters})
|
||||||
end
|
end
|
||||||
|
|
||||||
defp page_params(params) do
|
defp page_params(params) do
|
||||||
|
|
|
@ -40,7 +40,7 @@ def call(%{private: %{open_api_spex: private_data}} = conn, %{
|
||||||
|> List.first()
|
|> List.first()
|
||||||
|
|
||||||
_ ->
|
_ ->
|
||||||
nil
|
"application/json"
|
||||||
end
|
end
|
||||||
|
|
||||||
private_data = Map.put(private_data, :operation_id, operation_id)
|
private_data = Map.put(private_data, :operation_id, operation_id)
|
||||||
|
|
|
@ -84,7 +84,7 @@ def delete_operation do
|
||||||
operationId: "StatusController.delete",
|
operationId: "StatusController.delete",
|
||||||
parameters: [id_param()],
|
parameters: [id_param()],
|
||||||
responses: %{
|
responses: %{
|
||||||
200 => empty_object_response(),
|
200 => status_response(),
|
||||||
403 => Operation.response("Forbidden", "application/json", ApiError),
|
403 => Operation.response("Forbidden", "application/json", ApiError),
|
||||||
404 => Operation.response("Not Found", "application/json", ApiError)
|
404 => Operation.response("Not Found", "application/json", ApiError)
|
||||||
}
|
}
|
||||||
|
|
|
@ -40,20 +40,53 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
pleroma: %Schema{
|
pleroma: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
allow_following_move: %Schema{type: :boolean},
|
allow_following_move: %Schema{
|
||||||
background_image: %Schema{type: :string, nullable: true},
|
type: :boolean,
|
||||||
|
description: "whether the user allows automatically follow moved following accounts"
|
||||||
|
},
|
||||||
|
background_image: %Schema{type: :string, nullable: true, format: :uri},
|
||||||
chat_token: %Schema{type: :string},
|
chat_token: %Schema{type: :string},
|
||||||
confirmation_pending: %Schema{type: :boolean},
|
confirmation_pending: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"whether the user account is waiting on email confirmation to be activated"
|
||||||
|
},
|
||||||
hide_favorites: %Schema{type: :boolean},
|
hide_favorites: %Schema{type: :boolean},
|
||||||
hide_followers_count: %Schema{type: :boolean},
|
hide_followers_count: %Schema{
|
||||||
hide_followers: %Schema{type: :boolean},
|
type: :boolean,
|
||||||
hide_follows_count: %Schema{type: :boolean},
|
description: "whether the user has follower stat hiding enabled"
|
||||||
hide_follows: %Schema{type: :boolean},
|
},
|
||||||
is_admin: %Schema{type: :boolean},
|
hide_followers: %Schema{
|
||||||
is_moderator: %Schema{type: :boolean},
|
type: :boolean,
|
||||||
|
description: "whether the user has follower hiding enabled"
|
||||||
|
},
|
||||||
|
hide_follows_count: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user has follow stat hiding enabled"
|
||||||
|
},
|
||||||
|
hide_follows: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user has follow hiding enabled"
|
||||||
|
},
|
||||||
|
is_admin: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user is an admin of the local instance"
|
||||||
|
},
|
||||||
|
is_moderator: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "whether the user is a moderator of the local instance"
|
||||||
|
},
|
||||||
skip_thread_containment: %Schema{type: :boolean},
|
skip_thread_containment: %Schema{type: :boolean},
|
||||||
tags: %Schema{type: :array, items: %Schema{type: :string}},
|
tags: %Schema{
|
||||||
unread_conversation_count: %Schema{type: :integer},
|
type: :array,
|
||||||
|
items: %Schema{type: :string},
|
||||||
|
description:
|
||||||
|
"List of tags being used for things like extra roles or moderation(ie. marking all media as nsfw all)."
|
||||||
|
},
|
||||||
|
unread_conversation_count: %Schema{
|
||||||
|
type: :integer,
|
||||||
|
description: "The count of unread conversations. Only returned to the account owner."
|
||||||
|
},
|
||||||
notification_settings: %Schema{
|
notification_settings: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
|
@ -66,7 +99,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
},
|
},
|
||||||
relationship: AccountRelationship,
|
relationship: AccountRelationship,
|
||||||
settings_store: %Schema{
|
settings_store: %Schema{
|
||||||
type: :object
|
type: :object,
|
||||||
|
description:
|
||||||
|
"A generic map of settings for frontends. Opaque to the backend. Only returned in `verify_credentials` and `update_credentials`"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -74,16 +109,32 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
fields: %Schema{type: :array, items: AccountField},
|
fields: %Schema{type: :array, items: AccountField},
|
||||||
note: %Schema{type: :string},
|
note: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description:
|
||||||
|
"Plaintext version of the bio without formatting applied by the backend, used for editing the bio."
|
||||||
|
},
|
||||||
privacy: VisibilityScope,
|
privacy: VisibilityScope,
|
||||||
sensitive: %Schema{type: :boolean},
|
sensitive: %Schema{type: :boolean},
|
||||||
pleroma: %Schema{
|
pleroma: %Schema{
|
||||||
type: :object,
|
type: :object,
|
||||||
properties: %{
|
properties: %{
|
||||||
actor_type: ActorType,
|
actor_type: ActorType,
|
||||||
discoverable: %Schema{type: :boolean},
|
discoverable: %Schema{
|
||||||
no_rich_text: %Schema{type: :boolean},
|
type: :boolean,
|
||||||
show_role: %Schema{type: :boolean}
|
description:
|
||||||
|
"whether the user allows discovery of the account in search results and other services."
|
||||||
|
},
|
||||||
|
no_rich_text: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"whether the HTML tags for rich-text formatting are stripped from all statuses requested from the API."
|
||||||
|
},
|
||||||
|
show_role: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description:
|
||||||
|
"whether the user wants their role (e.g admin, moderator) to be shown"
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -62,6 +62,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
content: %Schema{type: :string, format: :html, description: "HTML-encoded status content"},
|
content: %Schema{type: :string, format: :html, description: "HTML-encoded status content"},
|
||||||
|
text: %Schema{
|
||||||
|
type: :string,
|
||||||
|
description: "Original unformatted content in plain text",
|
||||||
|
nullable: true
|
||||||
|
},
|
||||||
created_at: %Schema{
|
created_at: %Schema{
|
||||||
type: :string,
|
type: :string,
|
||||||
format: "date-time",
|
format: "date-time",
|
||||||
|
@ -184,6 +189,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
|
||||||
thread_muted: %Schema{
|
thread_muted: %Schema{
|
||||||
type: :boolean,
|
type: :boolean,
|
||||||
description: "`true` if the thread the post belongs to is muted"
|
description: "`true` if the thread the post belongs to is muted"
|
||||||
|
},
|
||||||
|
parent_visible: %Schema{
|
||||||
|
type: :boolean,
|
||||||
|
description: "`true` if the parent post is visible to the user"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -186,6 +186,7 @@ defp object(draft) do
|
||||||
draft.poll
|
draft.poll
|
||||||
)
|
)
|
||||||
|> Map.put("emoji", emoji)
|
|> Map.put("emoji", emoji)
|
||||||
|
|> Map.put("source", draft.status)
|
||||||
|
|
||||||
%__MODULE__{draft | object: object}
|
%__MODULE__{draft | object: object}
|
||||||
end
|
end
|
||||||
|
|
|
@ -25,6 +25,13 @@ defmodule Pleroma.Web.CommonAPI do
|
||||||
require Pleroma.Constants
|
require Pleroma.Constants
|
||||||
require Logger
|
require Logger
|
||||||
|
|
||||||
|
def block(blocker, blocked) do
|
||||||
|
with {:ok, block_data, _} <- Builder.block(blocker, blocked),
|
||||||
|
{:ok, block, _} <- Pipeline.common_pipeline(block_data, local: true) do
|
||||||
|
{:ok, block}
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
def post_chat_message(%User{} = user, %User{} = recipient, content, opts \\ []) do
|
||||||
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
|
||||||
:ok <- validate_chat_content_length(content, !!maybe_attachment),
|
:ok <- validate_chat_content_length(content, !!maybe_attachment),
|
||||||
|
|
|
@ -9,6 +9,7 @@ defmodule Fallback.RedirectController do
|
||||||
|
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.Metadata
|
alias Pleroma.Web.Metadata
|
||||||
|
alias Pleroma.Web.Preload
|
||||||
|
|
||||||
def api_not_implemented(conn, _params) do
|
def api_not_implemented(conn, _params) do
|
||||||
conn
|
conn
|
||||||
|
@ -16,16 +17,7 @@ def api_not_implemented(conn, _params) do
|
||||||
|> json(%{error: "Not implemented"})
|
|> json(%{error: "Not implemented"})
|
||||||
end
|
end
|
||||||
|
|
||||||
def redirector(conn, _params, code \\ 200)
|
def redirector(conn, _params, code \\ 200) do
|
||||||
|
|
||||||
# redirect to admin section
|
|
||||||
# /pleroma/admin -> /pleroma/admin/
|
|
||||||
#
|
|
||||||
def redirector(conn, %{"path" => ["pleroma", "admin"]} = _, _code) do
|
|
||||||
redirect(conn, to: "/pleroma/admin/")
|
|
||||||
end
|
|
||||||
|
|
||||||
def redirector(conn, _params, code) do
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_content_type("text/html")
|
||||||
|> send_file(code, index_file_path())
|
|> send_file(code, index_file_path())
|
||||||
|
@ -43,28 +35,33 @@ def redirector_with_meta(conn, %{"maybe_nickname_or_id" => maybe_nickname_or_id}
|
||||||
def redirector_with_meta(conn, params) do
|
def redirector_with_meta(conn, params) do
|
||||||
{:ok, index_content} = File.read(index_file_path())
|
{:ok, index_content} = File.read(index_file_path())
|
||||||
|
|
||||||
tags =
|
tags = build_tags(conn, params)
|
||||||
try do
|
preloads = preload_data(conn, params)
|
||||||
Metadata.build_tags(params)
|
|
||||||
rescue
|
|
||||||
e ->
|
|
||||||
Logger.error(
|
|
||||||
"Metadata rendering for #{conn.request_path} failed.\n" <>
|
|
||||||
Exception.format(:error, e, __STACKTRACE__)
|
|
||||||
)
|
|
||||||
|
|
||||||
""
|
response =
|
||||||
end
|
index_content
|
||||||
|
|> String.replace("<!--server-generated-meta-->", tags <> preloads)
|
||||||
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("text/html")
|
|> put_resp_content_type("text/html")
|
||||||
|> send_resp(200, response)
|
|> send_resp(200, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
def index_file_path do
|
def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do
|
||||||
Pleroma.Plugs.InstanceStatic.file_path("index.html")
|
redirect(conn, to: "/pleroma/admin/")
|
||||||
|
end
|
||||||
|
|
||||||
|
def redirector_with_preload(conn, params) do
|
||||||
|
{:ok, index_content} = File.read(index_file_path())
|
||||||
|
preloads = preload_data(conn, params)
|
||||||
|
|
||||||
|
response =
|
||||||
|
index_content
|
||||||
|
|> String.replace("<!--server-generated-meta-->", preloads)
|
||||||
|
|
||||||
|
conn
|
||||||
|
|> put_resp_content_type("text/html")
|
||||||
|
|> send_resp(200, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
def registration_page(conn, params) do
|
def registration_page(conn, params) do
|
||||||
|
@ -76,4 +73,36 @@ def empty(conn, _params) do
|
||||||
|> put_status(204)
|
|> put_status(204)
|
||||||
|> text("")
|
|> text("")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
defp index_file_path do
|
||||||
|
Pleroma.Plugs.InstanceStatic.file_path("index.html")
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_tags(conn, params) do
|
||||||
|
try do
|
||||||
|
Metadata.build_tags(params)
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.error(
|
||||||
|
"Metadata rendering for #{conn.request_path} failed.\n" <>
|
||||||
|
Exception.format(:error, e, __STACKTRACE__)
|
||||||
|
)
|
||||||
|
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp preload_data(conn, params) do
|
||||||
|
try do
|
||||||
|
Preload.build_tags(conn, params)
|
||||||
|
rescue
|
||||||
|
e ->
|
||||||
|
Logger.error(
|
||||||
|
"Preloading for #{conn.request_path} failed.\n" <>
|
||||||
|
Exception.format(:error, e, __STACKTRACE__)
|
||||||
|
)
|
||||||
|
|
||||||
|
""
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -20,6 +20,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
|
||||||
alias Pleroma.Plugs.RateLimiter
|
alias Pleroma.Plugs.RateLimiter
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web.ActivityPub.ActivityPub
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.ActivityPub.Builder
|
||||||
|
alias Pleroma.Web.ActivityPub.Pipeline
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
alias Pleroma.Web.MastodonAPI.ListView
|
alias Pleroma.Web.MastodonAPI.ListView
|
||||||
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
alias Pleroma.Web.MastodonAPI.MastodonAPI
|
||||||
|
@ -182,34 +184,39 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
|
||||||
end)
|
end)
|
||||||
|> Maps.put_if_present(:actor_type, params[:actor_type])
|
|> Maps.put_if_present(:actor_type, params[:actor_type])
|
||||||
|
|
||||||
changeset = User.update_changeset(user, user_params)
|
# What happens here:
|
||||||
|
#
|
||||||
with {:ok, user} <- User.update_and_set_cache(changeset) do
|
# We want to update the user through the pipeline, but the ActivityPub
|
||||||
user
|
# update information is not quite enough for this, because this also
|
||||||
|> build_update_activity_params()
|
# contains local settings that don't federate and don't even appear
|
||||||
|> ActivityPub.update()
|
# in the Update activity.
|
||||||
|
#
|
||||||
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
|
# So we first build the normal local changeset, then apply it to the
|
||||||
|
# user data, but don't persist it. With this, we generate the object
|
||||||
|
# data for our update activity. We feed this and the changeset as meta
|
||||||
|
# inforation into the pipeline, where they will be properly updated and
|
||||||
|
# federated.
|
||||||
|
with changeset <- User.update_changeset(user, user_params),
|
||||||
|
{:ok, unpersisted_user} <- Ecto.Changeset.apply_action(changeset, :update),
|
||||||
|
updated_object <-
|
||||||
|
Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
|
||||||
|
|> Map.delete("@context"),
|
||||||
|
{:ok, update_data, []} <- Builder.update(user, updated_object),
|
||||||
|
{:ok, _update, _} <-
|
||||||
|
Pipeline.common_pipeline(update_data,
|
||||||
|
local: true,
|
||||||
|
user_update_changeset: changeset
|
||||||
|
) do
|
||||||
|
render(conn, "show.json",
|
||||||
|
user: unpersisted_user,
|
||||||
|
for: unpersisted_user,
|
||||||
|
with_pleroma_settings: true
|
||||||
|
)
|
||||||
else
|
else
|
||||||
_e -> render_error(conn, :forbidden, "Invalid request")
|
_e -> render_error(conn, :forbidden, "Invalid request")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Hotfix, handling will be redone with the pipeline
|
|
||||||
defp build_update_activity_params(user) do
|
|
||||||
object =
|
|
||||||
Pleroma.Web.ActivityPub.UserView.render("user.json", user: user)
|
|
||||||
|> Map.delete("@context")
|
|
||||||
|
|
||||||
%{
|
|
||||||
local: true,
|
|
||||||
to: [user.follower_address],
|
|
||||||
cc: [],
|
|
||||||
object: object,
|
|
||||||
actor: user.ap_id
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
defp normalize_fields_attributes(fields) do
|
defp normalize_fields_attributes(fields) do
|
||||||
if Enum.all?(fields, &is_tuple/1) do
|
if Enum.all?(fields, &is_tuple/1) do
|
||||||
Enum.map(fields, fn {_, v} -> v end)
|
Enum.map(fields, fn {_, v} -> v end)
|
||||||
|
@ -378,8 +385,7 @@ def unmute(%{assigns: %{user: muter, account: muted}} = conn, _params) do
|
||||||
|
|
||||||
@doc "POST /api/v1/accounts/:id/block"
|
@doc "POST /api/v1/accounts/:id/block"
|
||||||
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
|
||||||
with {:ok, _user_block} <- User.block(blocker, blocked),
|
with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do
|
||||||
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
|
|
||||||
render(conn, "relationship.json", user: blocker, target: blocked)
|
render(conn, "relationship.json", user: blocker, target: blocked)
|
||||||
else
|
else
|
||||||
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
{:error, message} -> json_response(conn, :forbidden, %{error: message})
|
||||||
|
|
|
@ -44,6 +44,7 @@ def search2(conn, params), do: do_search(:v2, conn, params)
|
||||||
def search(conn, params), do: do_search(:v1, conn, params)
|
def search(conn, params), do: do_search(:v1, conn, params)
|
||||||
|
|
||||||
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
|
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
|
||||||
|
query = String.trim(query)
|
||||||
options = search_options(params, user)
|
options = search_options(params, user)
|
||||||
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
|
||||||
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}
|
||||||
|
|
|
@ -200,11 +200,18 @@ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
|
|
||||||
@doc "DELETE /api/v1/statuses/:id"
|
@doc "DELETE /api/v1/statuses/:id"
|
||||||
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
|
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
|
||||||
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
|
||||||
json(conn, %{})
|
render <-
|
||||||
|
try_render(conn, "show.json",
|
||||||
|
activity: activity,
|
||||||
|
for: user,
|
||||||
|
with_direct_conversation_id: true,
|
||||||
|
with_source: true
|
||||||
|
),
|
||||||
|
{:ok, %Activity{}} <- CommonAPI.delete(id, user) do
|
||||||
|
render
|
||||||
else
|
else
|
||||||
{:error, :not_found} = e -> e
|
_e -> {:error, :not_found}
|
||||||
_e -> render_error(conn, :forbidden, "Can't delete this post")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -36,8 +36,10 @@ def render("show.json", _) do
|
||||||
background_image: Keyword.get(instance, :background_image),
|
background_image: Keyword.get(instance, :background_image),
|
||||||
pleroma: %{
|
pleroma: %{
|
||||||
metadata: %{
|
metadata: %{
|
||||||
|
account_activation_required: Keyword.get(instance, :account_activation_required),
|
||||||
features: features(),
|
features: features(),
|
||||||
federation: federation()
|
federation: federation(),
|
||||||
|
fields_limits: fields_limits()
|
||||||
},
|
},
|
||||||
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
|
||||||
}
|
}
|
||||||
|
@ -78,7 +80,7 @@ def features do
|
||||||
def federation do
|
def federation do
|
||||||
quarantined = Config.get([:instance, :quarantined_instances], [])
|
quarantined = Config.get([:instance, :quarantined_instances], [])
|
||||||
|
|
||||||
if Config.get([:instance, :mrf_transparency]) do
|
if Config.get([:mrf, :transparency]) do
|
||||||
{:ok, data} = MRF.describe()
|
{:ok, data} = MRF.describe()
|
||||||
|
|
||||||
data
|
data
|
||||||
|
@ -88,4 +90,13 @@ def federation do
|
||||||
end
|
end
|
||||||
|> Map.put(:enabled, Config.get([:instance, :federating]))
|
|> Map.put(:enabled, Config.get([:instance, :federating]))
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def fields_limits do
|
||||||
|
%{
|
||||||
|
max_fields: Config.get([:instance, :max_account_fields]),
|
||||||
|
max_remote_fields: Config.get([:instance, :max_remote_account_fields]),
|
||||||
|
name_length: Config.get([:instance, :account_field_name_length]),
|
||||||
|
value_length: Config.get([:instance, :account_field_value_length])
|
||||||
|
}
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
|
||||||
alias Pleroma.Web.MastodonAPI.StatusView
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
alias Pleroma.Web.MediaProxy
|
alias Pleroma.Web.MediaProxy
|
||||||
|
|
||||||
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1]
|
import Pleroma.Web.ActivityPub.Visibility, only: [get_visibility: 1, visible_for_user?: 2]
|
||||||
|
|
||||||
# TODO: Add cached version.
|
# TODO: Add cached version.
|
||||||
defp get_replied_to_activities([]), do: %{}
|
defp get_replied_to_activities([]), do: %{}
|
||||||
|
@ -333,6 +333,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
reblog: nil,
|
reblog: nil,
|
||||||
card: card,
|
card: card,
|
||||||
content: content_html,
|
content: content_html,
|
||||||
|
text: opts[:with_source] && object.data["source"],
|
||||||
created_at: created_at,
|
created_at: created_at,
|
||||||
reblogs_count: announcement_count,
|
reblogs_count: announcement_count,
|
||||||
replies_count: object.data["repliesCount"] || 0,
|
replies_count: object.data["repliesCount"] || 0,
|
||||||
|
@ -364,7 +365,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
|
||||||
expires_at: expires_at,
|
expires_at: expires_at,
|
||||||
direct_conversation_id: direct_conversation_id,
|
direct_conversation_id: direct_conversation_id,
|
||||||
thread_muted: thread_muted?,
|
thread_muted: thread_muted?,
|
||||||
emoji_reactions: emoji_reactions
|
emoji_reactions: emoji_reactions,
|
||||||
|
parent_visible: visible_for_user?(reply_to, opts[:for])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,91 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Nodeinfo.Nodeinfo do
|
||||||
|
alias Pleroma.Config
|
||||||
|
alias Pleroma.Stats
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.Federator.Publisher
|
||||||
|
alias Pleroma.Web.MastodonAPI.InstanceView
|
||||||
|
|
||||||
|
# returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
|
||||||
|
# under software.
|
||||||
|
def get_nodeinfo("2.0") do
|
||||||
|
stats = Stats.get_stats()
|
||||||
|
|
||||||
|
staff_accounts =
|
||||||
|
User.all_superusers()
|
||||||
|
|> Enum.map(fn u -> u.ap_id end)
|
||||||
|
|
||||||
|
federation = InstanceView.federation()
|
||||||
|
features = InstanceView.features()
|
||||||
|
|
||||||
|
%{
|
||||||
|
version: "2.0",
|
||||||
|
software: %{
|
||||||
|
name: Pleroma.Application.name() |> String.downcase(),
|
||||||
|
version: Pleroma.Application.version()
|
||||||
|
},
|
||||||
|
protocols: Publisher.gather_nodeinfo_protocol_names(),
|
||||||
|
services: %{
|
||||||
|
inbound: [],
|
||||||
|
outbound: []
|
||||||
|
},
|
||||||
|
openRegistrations: Config.get([:instance, :registrations_open]),
|
||||||
|
usage: %{
|
||||||
|
users: %{
|
||||||
|
total: Map.get(stats, :user_count, 0)
|
||||||
|
},
|
||||||
|
localPosts: Map.get(stats, :status_count, 0)
|
||||||
|
},
|
||||||
|
metadata: %{
|
||||||
|
nodeName: Config.get([:instance, :name]),
|
||||||
|
nodeDescription: Config.get([:instance, :description]),
|
||||||
|
private: !Config.get([:instance, :public], true),
|
||||||
|
suggestions: %{
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
|
staffAccounts: staff_accounts,
|
||||||
|
federation: federation,
|
||||||
|
pollLimits: Config.get([:instance, :poll_limits]),
|
||||||
|
postFormats: Config.get([:instance, :allowed_post_formats]),
|
||||||
|
uploadLimits: %{
|
||||||
|
general: Config.get([:instance, :upload_limit]),
|
||||||
|
avatar: Config.get([:instance, :avatar_upload_limit]),
|
||||||
|
banner: Config.get([:instance, :banner_upload_limit]),
|
||||||
|
background: Config.get([:instance, :background_upload_limit])
|
||||||
|
},
|
||||||
|
fieldsLimits: %{
|
||||||
|
maxFields: Config.get([:instance, :max_account_fields]),
|
||||||
|
maxRemoteFields: Config.get([:instance, :max_remote_account_fields]),
|
||||||
|
nameLength: Config.get([:instance, :account_field_name_length]),
|
||||||
|
valueLength: Config.get([:instance, :account_field_value_length])
|
||||||
|
},
|
||||||
|
accountActivationRequired: Config.get([:instance, :account_activation_required], false),
|
||||||
|
invitesEnabled: Config.get([:instance, :invites_enabled], false),
|
||||||
|
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
|
||||||
|
features: features,
|
||||||
|
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
|
||||||
|
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_nodeinfo("2.1") do
|
||||||
|
raw_response = get_nodeinfo("2.0")
|
||||||
|
|
||||||
|
updated_software =
|
||||||
|
raw_response
|
||||||
|
|> Map.get(:software)
|
||||||
|
|> Map.put(:repository, Pleroma.Application.repository())
|
||||||
|
|
||||||
|
raw_response
|
||||||
|
|> Map.put(:software, updated_software)
|
||||||
|
|> Map.put(:version, "2.1")
|
||||||
|
end
|
||||||
|
|
||||||
|
def get_nodeinfo(_version) do
|
||||||
|
{:error, :missing}
|
||||||
|
end
|
||||||
|
end
|
|
@ -5,12 +5,8 @@
|
||||||
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
|
||||||
use Pleroma.Web, :controller
|
use Pleroma.Web, :controller
|
||||||
|
|
||||||
alias Pleroma.Config
|
|
||||||
alias Pleroma.Stats
|
|
||||||
alias Pleroma.User
|
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.Federator.Publisher
|
alias Pleroma.Web.Nodeinfo.Nodeinfo
|
||||||
alias Pleroma.Web.MastodonAPI.InstanceView
|
|
||||||
|
|
||||||
def schemas(conn, _params) do
|
def schemas(conn, _params) do
|
||||||
response = %{
|
response = %{
|
||||||
|
@ -29,102 +25,20 @@ def schemas(conn, _params) do
|
||||||
json(conn, response)
|
json(conn, response)
|
||||||
end
|
end
|
||||||
|
|
||||||
# returns a nodeinfo 2.0 map, since 2.1 just adds a repository field
|
|
||||||
# under software.
|
|
||||||
def raw_nodeinfo do
|
|
||||||
stats = Stats.get_stats()
|
|
||||||
|
|
||||||
staff_accounts =
|
|
||||||
User.all_superusers()
|
|
||||||
|> Enum.map(fn u -> u.ap_id end)
|
|
||||||
|
|
||||||
features = InstanceView.features()
|
|
||||||
federation = InstanceView.federation()
|
|
||||||
|
|
||||||
%{
|
|
||||||
version: "2.0",
|
|
||||||
software: %{
|
|
||||||
name: Pleroma.Application.name() |> String.downcase(),
|
|
||||||
version: Pleroma.Application.version()
|
|
||||||
},
|
|
||||||
protocols: Publisher.gather_nodeinfo_protocol_names(),
|
|
||||||
services: %{
|
|
||||||
inbound: [],
|
|
||||||
outbound: []
|
|
||||||
},
|
|
||||||
openRegistrations: Config.get([:instance, :registrations_open]),
|
|
||||||
usage: %{
|
|
||||||
users: %{
|
|
||||||
total: Map.get(stats, :user_count, 0)
|
|
||||||
},
|
|
||||||
localPosts: Map.get(stats, :status_count, 0)
|
|
||||||
},
|
|
||||||
metadata: %{
|
|
||||||
nodeName: Config.get([:instance, :name]),
|
|
||||||
nodeDescription: Config.get([:instance, :description]),
|
|
||||||
private: !Config.get([:instance, :public], true),
|
|
||||||
suggestions: %{
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
staffAccounts: staff_accounts,
|
|
||||||
federation: federation,
|
|
||||||
pollLimits: Config.get([:instance, :poll_limits]),
|
|
||||||
postFormats: Config.get([:instance, :allowed_post_formats]),
|
|
||||||
uploadLimits: %{
|
|
||||||
general: Config.get([:instance, :upload_limit]),
|
|
||||||
avatar: Config.get([:instance, :avatar_upload_limit]),
|
|
||||||
banner: Config.get([:instance, :banner_upload_limit]),
|
|
||||||
background: Config.get([:instance, :background_upload_limit])
|
|
||||||
},
|
|
||||||
fieldsLimits: %{
|
|
||||||
maxFields: Config.get([:instance, :max_account_fields]),
|
|
||||||
maxRemoteFields: Config.get([:instance, :max_remote_account_fields]),
|
|
||||||
nameLength: Config.get([:instance, :account_field_name_length]),
|
|
||||||
valueLength: Config.get([:instance, :account_field_value_length])
|
|
||||||
},
|
|
||||||
accountActivationRequired: Config.get([:instance, :account_activation_required], false),
|
|
||||||
invitesEnabled: Config.get([:instance, :invites_enabled], false),
|
|
||||||
mailerEnabled: Config.get([Pleroma.Emails.Mailer, :enabled], false),
|
|
||||||
features: features,
|
|
||||||
restrictedNicknames: Config.get([Pleroma.User, :restricted_nicknames]),
|
|
||||||
skipThreadContainment: Config.get([:instance, :skip_thread_containment], false)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
end
|
|
||||||
|
|
||||||
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
|
# Schema definition: https://github.com/jhass/nodeinfo/blob/master/schemas/2.0/schema.json
|
||||||
# and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json
|
# and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json
|
||||||
def nodeinfo(conn, %{"version" => "2.0"}) do
|
def nodeinfo(conn, %{"version" => version}) do
|
||||||
|
case Nodeinfo.get_nodeinfo(version) do
|
||||||
|
{:error, :missing} ->
|
||||||
|
render_error(conn, :not_found, "Nodeinfo schema version not handled")
|
||||||
|
|
||||||
|
node_info ->
|
||||||
conn
|
conn
|
||||||
|> put_resp_header(
|
|> put_resp_header(
|
||||||
"content-type",
|
"content-type",
|
||||||
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
|
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
|
||||||
)
|
)
|
||||||
|> json(raw_nodeinfo())
|
|> json(node_info)
|
||||||
end
|
end
|
||||||
|
|
||||||
def nodeinfo(conn, %{"version" => "2.1"}) do
|
|
||||||
raw_response = raw_nodeinfo()
|
|
||||||
|
|
||||||
updated_software =
|
|
||||||
raw_response
|
|
||||||
|> Map.get(:software)
|
|
||||||
|> Map.put(:repository, Pleroma.Application.repository())
|
|
||||||
|
|
||||||
response =
|
|
||||||
raw_response
|
|
||||||
|> Map.put(:software, updated_software)
|
|
||||||
|> Map.put(:version, "2.1")
|
|
||||||
|
|
||||||
conn
|
|
||||||
|> put_resp_header(
|
|
||||||
"content-type",
|
|
||||||
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.1#; charset=utf-8"
|
|
||||||
)
|
|
||||||
|> json(response)
|
|
||||||
end
|
|
||||||
|
|
||||||
def nodeinfo(conn, _) do
|
|
||||||
render_error(conn, :not_found, "Nodeinfo schema version not handled")
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Preload do
|
||||||
|
alias Phoenix.HTML
|
||||||
|
require Logger
|
||||||
|
|
||||||
|
def build_tags(_conn, params) do
|
||||||
|
preload_data =
|
||||||
|
Enum.reduce(Pleroma.Config.get([__MODULE__, :providers], []), %{}, fn parser, acc ->
|
||||||
|
terms =
|
||||||
|
params
|
||||||
|
|> parser.generate_terms()
|
||||||
|
|> Enum.map(fn {k, v} -> {k, Base.encode64(Jason.encode!(v))} end)
|
||||||
|
|> Enum.into(%{})
|
||||||
|
|
||||||
|
Map.merge(acc, terms)
|
||||||
|
end)
|
||||||
|
|
||||||
|
rendered_html =
|
||||||
|
preload_data
|
||||||
|
|> Jason.encode!()
|
||||||
|
|> build_script_tag()
|
||||||
|
|> HTML.safe_to_string()
|
||||||
|
|
||||||
|
rendered_html
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_script_tag(content) do
|
||||||
|
HTML.Tag.content_tag(:script, HTML.raw(content),
|
||||||
|
id: "initial-results",
|
||||||
|
type: "application/json"
|
||||||
|
)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,50 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Preload.Providers.Instance do
|
||||||
|
alias Pleroma.Plugs.InstanceStatic
|
||||||
|
alias Pleroma.Web.MastodonAPI.InstanceView
|
||||||
|
alias Pleroma.Web.Nodeinfo.Nodeinfo
|
||||||
|
alias Pleroma.Web.Preload.Providers.Provider
|
||||||
|
|
||||||
|
@behaviour Provider
|
||||||
|
@instance_url "/api/v1/instance"
|
||||||
|
@panel_url "/instance/panel.html"
|
||||||
|
@nodeinfo_url "/nodeinfo/2.0.json"
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def generate_terms(_params) do
|
||||||
|
%{}
|
||||||
|
|> build_info_tag()
|
||||||
|
|> build_panel_tag()
|
||||||
|
|> build_nodeinfo_tag()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_info_tag(acc) do
|
||||||
|
info_data = InstanceView.render("show.json", %{})
|
||||||
|
|
||||||
|
Map.put(acc, @instance_url, info_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_panel_tag(acc) do
|
||||||
|
instance_path = InstanceStatic.file_path(@panel_url |> to_string())
|
||||||
|
|
||||||
|
if File.exists?(instance_path) do
|
||||||
|
panel_data = File.read!(instance_path)
|
||||||
|
Map.put(acc, @panel_url, panel_data)
|
||||||
|
else
|
||||||
|
acc
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_nodeinfo_tag(acc) do
|
||||||
|
case Nodeinfo.get_nodeinfo("2.0") do
|
||||||
|
{:error, _} ->
|
||||||
|
acc
|
||||||
|
|
||||||
|
nodeinfo_data ->
|
||||||
|
Map.put(acc, @nodeinfo_url, nodeinfo_data)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Preload.Providers.Provider do
|
||||||
|
@callback generate_terms(map()) :: map()
|
||||||
|
end
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Preload.Providers.StatusNet do
|
||||||
|
alias Pleroma.Web.Preload.Providers.Provider
|
||||||
|
alias Pleroma.Web.TwitterAPI.UtilController
|
||||||
|
|
||||||
|
@behaviour Provider
|
||||||
|
@config_url "/api/statusnet/config.json"
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def generate_terms(_params) do
|
||||||
|
%{}
|
||||||
|
|> build_config_tag()
|
||||||
|
end
|
||||||
|
|
||||||
|
defp build_config_tag(acc) do
|
||||||
|
resp =
|
||||||
|
Plug.Test.conn(:get, @config_url |> to_string())
|
||||||
|
|> UtilController.config(nil)
|
||||||
|
|
||||||
|
Map.put(acc, @config_url, resp.resp_body)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,39 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Preload.Providers.Timelines do
|
||||||
|
alias Pleroma.Web.ActivityPub.ActivityPub
|
||||||
|
alias Pleroma.Web.MastodonAPI.StatusView
|
||||||
|
alias Pleroma.Web.Preload.Providers.Provider
|
||||||
|
|
||||||
|
@behaviour Provider
|
||||||
|
@public_url "/api/v1/timelines/public"
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def generate_terms(params) do
|
||||||
|
build_public_tag(%{}, params)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_public_tag(acc, params) do
|
||||||
|
if Pleroma.Config.get([:restrict_unauthenticated, :timelines, :federated], true) do
|
||||||
|
acc
|
||||||
|
else
|
||||||
|
Map.put(acc, @public_url, public_timeline(params))
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
defp public_timeline(%{"path" => ["main", "all"]}), do: get_public_timeline(false)
|
||||||
|
|
||||||
|
defp public_timeline(_params), do: get_public_timeline(true)
|
||||||
|
|
||||||
|
defp get_public_timeline(local_only) do
|
||||||
|
activities =
|
||||||
|
ActivityPub.fetch_public_activities(%{
|
||||||
|
type: ["Create"],
|
||||||
|
local_only: local_only
|
||||||
|
})
|
||||||
|
|
||||||
|
StatusView.render("index.json", activities: activities, for: nil, as: :activity)
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,26 @@
|
||||||
|
# Pleroma: A lightweight social networking server
|
||||||
|
# Copyright © 2017-2020 Pleroma Authors <https://pleroma.social/>
|
||||||
|
# SPDX-License-Identifier: AGPL-3.0-only
|
||||||
|
|
||||||
|
defmodule Pleroma.Web.Preload.Providers.User do
|
||||||
|
alias Pleroma.User
|
||||||
|
alias Pleroma.Web.MastodonAPI.AccountView
|
||||||
|
alias Pleroma.Web.Preload.Providers.Provider
|
||||||
|
|
||||||
|
@behaviour Provider
|
||||||
|
@account_url_base "/api/v1/accounts"
|
||||||
|
|
||||||
|
@impl Provider
|
||||||
|
def generate_terms(%{user: user}) do
|
||||||
|
build_accounts_tag(%{}, user)
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_terms(_params), do: %{}
|
||||||
|
|
||||||
|
def build_accounts_tag(acc, %User{} = user) do
|
||||||
|
account_data = AccountView.render("show.json", %{user: user, for: user})
|
||||||
|
Map.put(acc, "#{@account_url_base}/#{user.id}", account_data)
|
||||||
|
end
|
||||||
|
|
||||||
|
def build_accounts_tag(acc, _), do: acc
|
||||||
|
end
|
|
@ -726,7 +726,7 @@ defmodule Pleroma.Web.Router do
|
||||||
get("/registration/:token", RedirectController, :registration_page)
|
get("/registration/:token", RedirectController, :registration_page)
|
||||||
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
|
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
|
||||||
get("/api*path", RedirectController, :api_not_implemented)
|
get("/api*path", RedirectController, :api_not_implemented)
|
||||||
get("/*path", RedirectController, :redirector)
|
get("/*path", RedirectController, :redirector_with_preload)
|
||||||
|
|
||||||
options("/*path", RedirectController, :empty)
|
options("/*path", RedirectController, :empty)
|
||||||
end
|
end
|
||||||
|
|
|
@ -116,6 +116,7 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) do
|
||||||
true <-
|
true <-
|
||||||
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
|
Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)),
|
||||||
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
|
true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids,
|
||||||
|
true <- !(item.data["type"] == "Announce" && parent.data["actor"] == user.ap_id),
|
||||||
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
|
||||||
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
true <- MapSet.disjoint?(recipients, recipient_blocks),
|
||||||
%{host: item_host} <- URI.parse(item.actor),
|
%{host: item_host} <- URI.parse(item.actor),
|
||||||
|
|
|
@ -15,6 +15,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
|
||||||
alias Pleroma.User
|
alias Pleroma.User
|
||||||
alias Pleroma.Web
|
alias Pleroma.Web
|
||||||
alias Pleroma.Web.CommonAPI
|
alias Pleroma.Web.CommonAPI
|
||||||
|
alias Pleroma.Web.TwitterAPI.UtilView
|
||||||
alias Pleroma.Web.WebFinger
|
alias Pleroma.Web.WebFinger
|
||||||
|
|
||||||
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
|
plug(Pleroma.Web.FederatingPlug when action == :remote_subscribe)
|
||||||
|
@ -90,17 +91,7 @@ def notifications_read(%{assigns: %{user: user}} = conn, %{"id" => notification_
|
||||||
|
|
||||||
def config(%{assigns: %{format: "xml"}} = conn, _params) do
|
def config(%{assigns: %{format: "xml"}} = conn, _params) do
|
||||||
instance = Pleroma.Config.get(:instance)
|
instance = Pleroma.Config.get(:instance)
|
||||||
|
response = UtilView.status_net_config(instance)
|
||||||
response = """
|
|
||||||
<config>
|
|
||||||
<site>
|
|
||||||
<name>#{Keyword.get(instance, :name)}</name>
|
|
||||||
<site>#{Web.base_url()}</site>
|
|
||||||
<textlimit>#{Keyword.get(instance, :limit)}</textlimit>
|
|
||||||
<closed>#{!Keyword.get(instance, :registrations_open)}</closed>
|
|
||||||
</site>
|
|
||||||
</config>
|
|
||||||
"""
|
|
||||||
|
|
||||||
conn
|
conn
|
||||||
|> put_resp_content_type("application/xml")
|
|> put_resp_content_type("application/xml")
|
||||||
|
|
|
@ -5,4 +5,18 @@
|
||||||
defmodule Pleroma.Web.TwitterAPI.UtilView do
|
defmodule Pleroma.Web.TwitterAPI.UtilView do
|
||||||
use Pleroma.Web, :view
|
use Pleroma.Web, :view
|
||||||
import Phoenix.HTML.Form
|
import Phoenix.HTML.Form
|
||||||
|
alias Pleroma.Web
|
||||||
|
|
||||||
|
def status_net_config(instance) do
|
||||||
|
"""
|
||||||
|
<config>
|
||||||
|
<site>
|
||||||
|
<name>#{Keyword.get(instance, :name)}</name>
|
||||||
|
<site>#{Web.base_url()}</site>
|
||||||
|
<textlimit>#{Keyword.get(instance, :limit)}</textlimit>
|
||||||
|
<closed>#{!Keyword.get(instance, :registrations_open)}</closed>
|
||||||
|
</site>
|
||||||
|
</config>
|
||||||
|
"""
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -86,7 +86,7 @@ def initial_state(token, user, custom_emojis) do
|
||||||
"video\/mp4"
|
"video\/mp4"
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
settings: user.settings || @default_settings,
|
settings: user.mastofe_settings || @default_settings,
|
||||||
push_subscription: nil,
|
push_subscription: nil,
|
||||||
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
|
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
|
||||||
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
|
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),
|
||||||
|
|
7
mix.exs
7
mix.exs
|
@ -117,7 +117,7 @@ defp oauth_deps do
|
||||||
defp deps do
|
defp deps do
|
||||||
[
|
[
|
||||||
{:phoenix, "~> 1.4.8"},
|
{:phoenix, "~> 1.4.8"},
|
||||||
{:tzdata, "~> 0.5.21"},
|
{:tzdata, "~> 1.0.3"},
|
||||||
{:plug_cowboy, "~> 2.0"},
|
{:plug_cowboy, "~> 2.0"},
|
||||||
{:phoenix_pubsub, "~> 1.1"},
|
{:phoenix_pubsub, "~> 1.1"},
|
||||||
{:phoenix_ecto, "~> 4.0"},
|
{:phoenix_ecto, "~> 4.0"},
|
||||||
|
@ -159,7 +159,10 @@ defp deps do
|
||||||
{:cors_plug, "~> 1.5"},
|
{:cors_plug, "~> 1.5"},
|
||||||
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
|
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
|
||||||
{:web_push_encryption, "~> 0.2.1"},
|
{:web_push_encryption, "~> 0.2.1"},
|
||||||
{:swoosh, "~> 0.23.2"},
|
{:swoosh,
|
||||||
|
git: "https://github.com/swoosh/swoosh",
|
||||||
|
ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5",
|
||||||
|
override: true},
|
||||||
{:phoenix_swoosh, "~> 0.2"},
|
{:phoenix_swoosh, "~> 0.2"},
|
||||||
{:gen_smtp, "~> 0.13"},
|
{:gen_smtp, "~> 0.13"},
|
||||||
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},
|
||||||
|
|
4
mix.lock
4
mix.lock
|
@ -104,13 +104,13 @@
|
||||||
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
"sleeplocks": {:hex, :sleeplocks, "1.1.1", "3d462a0639a6ef36cc75d6038b7393ae537ab394641beb59830a1b8271faeed3", [:rebar3], [], "hexpm", "84ee37aeff4d0d92b290fff986d6a95ac5eedf9b383fadfd1d88e9b84a1c02e1"},
|
||||||
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
"ssl_verify_fun": {:hex, :ssl_verify_fun, "1.1.6", "cf344f5692c82d2cd7554f5ec8fd961548d4fd09e7d22f5b62482e5aeaebd4b0", [:make, :mix, :rebar3], [], "hexpm", "bdb0d2471f453c88ff3908e7686f86f9be327d065cc1ec16fa4540197ea04680"},
|
||||||
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
"sweet_xml": {:hex, :sweet_xml, "0.6.6", "fc3e91ec5dd7c787b6195757fbcf0abc670cee1e4172687b45183032221b66b8", [:mix], [], "hexpm", "2e1ec458f892ffa81f9f8386e3f35a1af6db7a7a37748a64478f13163a1f3573"},
|
||||||
"swoosh": {:hex, :swoosh, "0.23.5", "bfd9404bbf5069b1be2ffd317923ce57e58b332e25dbca2a35dedd7820dfee5a", [:mix], [{:cowboy, "~> 1.0.1 or ~> 1.1 or ~> 2.4", [hex: :cowboy, repo: "hexpm", optional: true]}, {:gen_smtp, "~> 0.13", [hex: :gen_smtp, repo: "hexpm", optional: true]}, {:hackney, "~> 1.9", [hex: :hackney, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: false]}, {:mail, "~> 0.2", [hex: :mail, repo: "hexpm", optional: true]}, {:mime, "~> 1.1", [hex: :mime, repo: "hexpm", optional: false]}, {:plug_cowboy, ">= 1.0.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}], "hexpm", "e3928e1d2889a308aaf3e42755809ac21cffd77cb58eef01cbfdab4ce2fd1e21"},
|
"swoosh": {:git, "https://github.com/swoosh/swoosh", "c96e0ca8a00d8f211ec1f042a4626b09f249caa5", [ref: "c96e0ca8a00d8f211ec1f042a4626b09f249caa5"]},
|
||||||
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
"syslog": {:hex, :syslog, "1.1.0", "6419a232bea84f07b56dc575225007ffe34d9fdc91abe6f1b2f254fd71d8efc2", [:rebar3], [], "hexpm", "4c6a41373c7e20587be33ef841d3de6f3beba08519809329ecc4d27b15b659e1"},
|
||||||
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
|
||||||
"tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
|
"tesla": {:git, "https://git.pleroma.social/pleroma/elixir-libraries/tesla.git", "61b7503cef33f00834f78ddfafe0d5d9dec2270b", [ref: "61b7503cef33f00834f78ddfafe0d5d9dec2270b"]},
|
||||||
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
|
"timex": {:hex, :timex, "3.6.1", "efdf56d0e67a6b956cc57774353b0329c8ab7726766a11547e529357ffdc1d56", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 0.1.8 or ~> 0.5 or ~> 1.0.0", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "f354efb2400dd7a80fd9eb6c8419068c4f632da4ac47f3d8822d6e33f08bc852"},
|
||||||
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
|
||||||
"tzdata": {:hex, :tzdata, "0.5.22", "f2ba9105117ee0360eae2eca389783ef7db36d533899b2e84559404dbc77ebb8", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "cd66c8a1e6a9e121d1f538b01bef459334bb4029a1ffb4eeeb5e4eae0337e7b6"},
|
"tzdata": {:hex, :tzdata, "1.0.3", "73470ad29dde46e350c60a66e6b360d3b99d2d18b74c4c349dbebbc27a09a3eb", [:mix], [{:hackney, "~> 1.0", [hex: :hackney, repo: "hexpm", optional: false]}], "hexpm", "a6e1ee7003c4d04ecbd21dd3ec690d4c6662db5d3bbdd7262d53cdf5e7c746c1"},
|
||||||
"ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"},
|
"ueberauth": {:hex, :ueberauth, "0.6.2", "25a31111249d60bad8b65438b2306a4dc91f3208faa62f5a8c33e8713989b2e8", [:mix], [{:plug, "~> 1.5", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "db9fbfb5ac707bc4f85a297758406340bf0358b4af737a88113c1a9eee120ac7"},
|
||||||
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
|
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
|
||||||
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
|
||||||
|
|
|
@ -0,0 +1,39 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.MrfConfigMoveFromInstanceNamespace do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
alias Pleroma.ConfigDB
|
||||||
|
|
||||||
|
@old_keys [:rewrite_policy, :mrf_transparency, :mrf_transparency_exclusions]
|
||||||
|
def change do
|
||||||
|
config = ConfigDB.get_by_params(%{group: :pleroma, key: :instance})
|
||||||
|
|
||||||
|
if config do
|
||||||
|
mrf =
|
||||||
|
config.value
|
||||||
|
|> Keyword.take(@old_keys)
|
||||||
|
|> Keyword.new(fn
|
||||||
|
{:rewrite_policy, policies} -> {:policies, policies}
|
||||||
|
{:mrf_transparency, transparency} -> {:transparency, transparency}
|
||||||
|
{:mrf_transparency_exclusions, exclusions} -> {:transparency_exclusions, exclusions}
|
||||||
|
end)
|
||||||
|
|
||||||
|
if mrf != [] do
|
||||||
|
{:ok, _} =
|
||||||
|
%ConfigDB{}
|
||||||
|
|> ConfigDB.changeset(%{group: :pleroma, key: :mrf, value: mrf})
|
||||||
|
|> Pleroma.Repo.insert()
|
||||||
|
|
||||||
|
new_instance = Keyword.drop(config.value, @old_keys)
|
||||||
|
|
||||||
|
if new_instance != [] do
|
||||||
|
{:ok, _} =
|
||||||
|
config
|
||||||
|
|> ConfigDB.changeset(%{value: new_instance})
|
||||||
|
|> Pleroma.Repo.update()
|
||||||
|
else
|
||||||
|
{:ok, _} = ConfigDB.delete(config)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,143 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.UpdateCounterCacheTable do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
@function_name "update_status_visibility_counter_cache"
|
||||||
|
@trigger_name "status_visibility_counter_cache_trigger"
|
||||||
|
|
||||||
|
def up do
|
||||||
|
execute("drop trigger if exists #{@trigger_name} on activities")
|
||||||
|
execute("drop function if exists #{@function_name}()")
|
||||||
|
drop_if_exists(unique_index(:counter_cache, [:name]))
|
||||||
|
drop_if_exists(table(:counter_cache))
|
||||||
|
|
||||||
|
create_if_not_exists table(:counter_cache) do
|
||||||
|
add(:instance, :string, null: false)
|
||||||
|
add(:direct, :bigint, null: false, default: 0)
|
||||||
|
add(:private, :bigint, null: false, default: 0)
|
||||||
|
add(:unlisted, :bigint, null: false, default: 0)
|
||||||
|
add(:public, :bigint, null: false, default: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:counter_cache, [:instance]))
|
||||||
|
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION #{@function_name}()
|
||||||
|
RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
hostname character varying(255);
|
||||||
|
visibility_new character varying(64);
|
||||||
|
visibility_old character varying(64);
|
||||||
|
actor character varying(255);
|
||||||
|
BEGIN
|
||||||
|
IF TG_OP = 'DELETE' THEN
|
||||||
|
actor := OLD.actor;
|
||||||
|
ELSE
|
||||||
|
actor := NEW.actor;
|
||||||
|
END IF;
|
||||||
|
hostname := split_part(actor, '/', 3);
|
||||||
|
IF TG_OP = 'INSERT' THEN
|
||||||
|
visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data);
|
||||||
|
IF NEW.data->>'type' = 'Create'
|
||||||
|
AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN
|
||||||
|
EXECUTE format('INSERT INTO "counter_cache" ("instance", %1$I) VALUES ($1, 1)
|
||||||
|
ON CONFLICT ("instance") DO
|
||||||
|
UPDATE SET %1$I = "counter_cache".%1$I + 1', visibility_new)
|
||||||
|
USING hostname;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF TG_OP = 'UPDATE' THEN
|
||||||
|
visibility_new := activity_visibility(NEW.actor, NEW.recipients, NEW.data);
|
||||||
|
visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data);
|
||||||
|
IF (NEW.data->>'type' = 'Create')
|
||||||
|
AND (OLD.data->>'type' = 'Create')
|
||||||
|
AND visibility_new != visibility_old
|
||||||
|
AND visibility_new IN ('public', 'unlisted', 'private', 'direct') THEN
|
||||||
|
EXECUTE format('UPDATE "counter_cache" SET
|
||||||
|
%1$I = greatest("counter_cache".%1$I - 1, 0),
|
||||||
|
%2$I = "counter_cache".%2$I + 1
|
||||||
|
WHERE "instance" = $1', visibility_old, visibility_new)
|
||||||
|
USING hostname;
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF TG_OP = 'DELETE' THEN
|
||||||
|
IF OLD.data->>'type' = 'Create' THEN
|
||||||
|
visibility_old := activity_visibility(OLD.actor, OLD.recipients, OLD.data);
|
||||||
|
EXECUTE format('UPDATE "counter_cache" SET
|
||||||
|
%1$I = greatest("counter_cache".%1$I - 1, 0)
|
||||||
|
WHERE "instance" = $1', visibility_old)
|
||||||
|
USING hostname;
|
||||||
|
END IF;
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$
|
||||||
|
LANGUAGE 'plpgsql';
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
execute("DROP TRIGGER IF EXISTS #{@trigger_name} ON activities")
|
||||||
|
|
||||||
|
"""
|
||||||
|
CREATE TRIGGER #{@trigger_name}
|
||||||
|
BEFORE
|
||||||
|
INSERT
|
||||||
|
OR UPDATE of recipients, data
|
||||||
|
OR DELETE
|
||||||
|
ON activities
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE #{@function_name}();
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
execute("DROP TRIGGER IF EXISTS #{@trigger_name} ON activities")
|
||||||
|
execute("DROP FUNCTION IF EXISTS #{@function_name}()")
|
||||||
|
drop_if_exists(unique_index(:counter_cache, [:instance]))
|
||||||
|
drop_if_exists(table(:counter_cache))
|
||||||
|
|
||||||
|
create_if_not_exists table(:counter_cache) do
|
||||||
|
add(:name, :string, null: false)
|
||||||
|
add(:count, :bigint, null: false, default: 0)
|
||||||
|
end
|
||||||
|
|
||||||
|
create_if_not_exists(unique_index(:counter_cache, [:name]))
|
||||||
|
|
||||||
|
"""
|
||||||
|
CREATE OR REPLACE FUNCTION #{@function_name}()
|
||||||
|
RETURNS TRIGGER AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
BEGIN
|
||||||
|
IF TG_OP = 'INSERT' THEN
|
||||||
|
IF NEW.data->>'type' = 'Create' THEN
|
||||||
|
EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF TG_OP = 'UPDATE' THEN
|
||||||
|
IF (NEW.data->>'type' = 'Create') and (OLD.data->>'type' = 'Create') and activity_visibility(NEW.actor, NEW.recipients, NEW.data) != activity_visibility(OLD.actor, OLD.recipients, OLD.data) THEN
|
||||||
|
EXECUTE 'INSERT INTO counter_cache (name, count) VALUES (''status_visibility_' || activity_visibility(NEW.actor, NEW.recipients, NEW.data) || ''', 1) ON CONFLICT (name) DO UPDATE SET count = counter_cache.count + 1';
|
||||||
|
EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
|
||||||
|
END IF;
|
||||||
|
RETURN NEW;
|
||||||
|
ELSIF TG_OP = 'DELETE' THEN
|
||||||
|
IF OLD.data->>'type' = 'Create' THEN
|
||||||
|
EXECUTE 'update counter_cache SET count = counter_cache.count - 1 where count > 0 and name = ''status_visibility_' || activity_visibility(OLD.actor, OLD.recipients, OLD.data) || ''';';
|
||||||
|
END IF;
|
||||||
|
RETURN OLD;
|
||||||
|
END IF;
|
||||||
|
END;
|
||||||
|
$$
|
||||||
|
LANGUAGE 'plpgsql';
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
|
||||||
|
"""
|
||||||
|
CREATE TRIGGER #{@trigger_name} BEFORE INSERT OR UPDATE of recipients, data OR DELETE ON activities
|
||||||
|
FOR EACH ROW
|
||||||
|
EXECUTE PROCEDURE #{@function_name}();
|
||||||
|
"""
|
||||||
|
|> execute()
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,11 @@
|
||||||
|
defmodule Pleroma.Repo.Migrations.RenameUserSettingsCol do
|
||||||
|
use Ecto.Migration
|
||||||
|
|
||||||
|
def up do
|
||||||
|
rename(table(:users), :settings, to: :mastofe_settings)
|
||||||
|
end
|
||||||
|
|
||||||
|
def down do
|
||||||
|
rename(table(:users), :mastofe_settings, to: :settings)
|
||||||
|
end
|
||||||
|
end
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -1 +1 @@
|
||||||
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.796ca6d4.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.b08eb412.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.0146039c.js></script></body></html>
|
<!DOCTYPE html><html><head><meta charset=utf-8><meta http-equiv=X-UA-Compatible content="IE=edge,chrome=1"><meta name=renderer content=webkit><meta name=viewport content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no"><title>Admin FE</title><link rel="shortcut icon" href=favicon.ico><link href=chunk-elementUI.1abbc9b8.css rel=stylesheet><link href=chunk-libs.686b5876.css rel=stylesheet><link href=app.01bdb34a.css rel=stylesheet></head><body><div id=app></div><script type=text/javascript src=static/js/runtime.0a70a9f5.js></script><script type=text/javascript src=static/js/chunk-elementUI.fba0efec.js></script><script type=text/javascript src=static/js/chunk-libs.b8c453ab.js></script><script type=text/javascript src=static/js/app.f220ac13.js></script></body></html>
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue