Merge branch 'develop' into issue/1790-updated-oban

This commit is contained in:
Mark Felder 2020-07-03 10:36:41 -05:00
commit 4695bdd81b
194 changed files with 3157 additions and 918 deletions

View file

@ -14,7 +14,7 @@
* 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):
* Operating system:
* PostgreSQL version (`postgres -V`):
* PostgreSQL version (`psql -V`):
### Bug description

View file

@ -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
- MFR policy to set global expiration for all local Create activities
- 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>
<summary>API Changes</summary>
- **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>
### Removed
@ -40,6 +54,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
<details>
<summary>API Changes</summary>
- Mastodon API: Add pleroma.parents_visible field to statuses.
- Mastodon API: Extended `/api/v1/instance`.
- Mastodon API: Support for `include_types` in `/api/v1/notifications`.
- 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):
- OTP: `./bin/pleroma_ctl 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

View file

@ -210,7 +210,6 @@
Pleroma.Web.ActivityPub.Publisher
],
allow_relay: true,
rewrite_policy: Pleroma.Web.ActivityPub.MRF.NoOpPolicy,
public: true,
quarantined_instances: [],
managed_config: true,
@ -221,8 +220,6 @@
"text/markdown",
"text/bbcode"
],
mrf_transparency: true,
mrf_transparency_exclusions: [],
autofollowed_nicknames: [],
max_pinned_statuses: 1,
attachment_links: false,
@ -437,6 +434,12 @@
],
unfurl_nsfw: false
config :pleroma, Pleroma.Web.Preload,
providers: [
Pleroma.Web.Preload.Providers.Instance,
Pleroma.Web.Preload.Providers.StatusNet
]
config :pleroma, :http_security,
enabled: true,
sts: false,
@ -692,6 +695,15 @@
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
# of this file so it overrides the configuration defined above.
import_config "#{Mix.env()}.exs"

View file

@ -40,12 +40,13 @@
key: :link_name,
type: :boolean,
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,
label: "Base URL",
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: [
"https://cdn-host.com"
]
@ -58,6 +59,7 @@
},
%{
key: :proxy_opts,
label: "Proxy Options",
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
suggestions: [
@ -85,6 +87,7 @@
},
%{
key: :http,
label: "HTTP",
type: :keyword,
description: "HTTP options",
children: [
@ -193,7 +196,9 @@
%{
key: :args,
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: [
"strip",
"auto-orient",
@ -479,6 +484,7 @@
%{
group: :pleroma,
key: :uri_schemes,
label: "URI Schemes",
type: :group,
description: "URI schemes related settings",
children: [
@ -651,17 +657,17 @@
key: :invites_enabled,
type: :boolean,
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,
type: :boolean,
description: "Require users to confirm their emails before signing in."
description: "Require users to confirm their emails before signing in"
},
%{
key: :federating,
type: :boolean,
description: "Enable federation with other instances."
description: "Enable federation with other instances"
},
%{
key: :federation_incoming_replies_max_depth,
@ -679,7 +685,7 @@
label: "Fed. reachability timeout days",
type: :integer,
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: [
7
]
@ -689,17 +695,6 @@
type: :boolean,
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,
type: :boolean,
@ -742,23 +737,6 @@
"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,
type: :boolean,
@ -829,6 +807,7 @@
},
%{
key: :safe_dm_mentions,
label: "Safe DM mentions",
type: :boolean,
description:
"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,
type: :boolean,
description: "Skip filtering out broken threads. Default: enabled"
description: "Skip filtering out broken threads. Default: enabled."
},
%{
key: :limit_to_local_content,
@ -932,6 +911,7 @@
children: [
%{
key: :totp,
label: "TOTP settings",
type: :keyword,
description: "TOTP settings",
suggestions: [digits: 6, period: 30],
@ -948,7 +928,7 @@
type: :integer,
suggestions: [30],
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,
type: :integer,
suggestions: [5],
description: "number of backup codes to generate."
description: "Number of backup codes to generate."
},
%{
key: :length,
@ -979,7 +959,7 @@
key: :instance_thumbnail,
type: :string,
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"]
}
]
@ -1002,6 +982,7 @@
group: :logger,
type: :group,
key: :ex_syslogger,
label: "ExSyslogger",
description: "ExSyslogger-related settings",
children: [
%{
@ -1020,7 +1001,7 @@
%{
key: :format,
type: :string,
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
suggestions: ["$metadata[$level] $message"]
},
%{
@ -1034,6 +1015,7 @@
group: :logger,
type: :group,
key: :console,
label: "Console Logger",
description: "Console logger settings",
children: [
%{
@ -1045,7 +1027,7 @@
%{
key: :format,
type: :string,
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".",
description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\"",
suggestions: ["$metadata[$level] $message"]
},
%{
@ -1058,6 +1040,7 @@
%{
group: :quack,
type: :group,
label: "Quack Logger",
description: "Quack-related settings",
children: [
%{
@ -1168,19 +1151,19 @@
key: :greentext,
label: "Greentext",
type: :boolean,
description: "Enables green text on lines prefixed with the > character."
description: "Enables green text on lines prefixed with the > character"
},
%{
key: :hideFilteredStatuses,
label: "Hide Filtered Statuses",
type: :boolean,
description: "Hides filtered statuses from timelines."
description: "Hides filtered statuses from timelines"
},
%{
key: :hideMutedPosts,
label: "Hide Muted Posts",
type: :boolean,
description: "Hides muted statuses from timelines."
description: "Hides muted statuses from timelines"
},
%{
key: :hidePostStats,
@ -1192,7 +1175,7 @@
key: :hideSitename,
label: "Hide Sitename",
type: :boolean,
description: "Hides instance name from PleromaFE banner."
description: "Hides instance name from PleromaFE banner"
},
%{
key: :hideUserStats,
@ -1237,14 +1220,14 @@
label: "NSFW Censor Image",
type: :string,
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"]
},
%{
key: :postContentType,
label: "Post Content Type",
type: {:dropdown, :atom},
description: "Default post formatting option.",
description: "Default post formatting option",
suggestions: ["text/plain", "text/html", "text/markdown", "text/bbcode"]
},
%{
@ -1273,14 +1256,14 @@
key: :sidebarRight,
label: "Sidebar on Right",
type: :boolean,
description: "Change alignment of sidebar and panels to the right."
description: "Change alignment of sidebar and panels to the right"
},
%{
key: :showFeaturesPanel,
label: "Show instance features panel",
type: :boolean,
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,
@ -1338,7 +1321,7 @@
key: :mascots,
type: {:keyword, :map},
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: [
pleroma_fox_tan: %{
url: "/images/pleroma-fox-tan-smol.png",
@ -1362,7 +1345,7 @@
%{
key: :default_user_avatar,
type: :string,
description: "URL of the default user avatar.",
description: "URL of the default user avatar",
suggestions: ["/images/avi.png"]
}
]
@ -1372,7 +1355,7 @@
key: :manifest,
type: :group,
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: [
%{
key: :icons,
@ -1408,10 +1391,49 @@
},
%{
group: :pleroma,
key: :mrf_simple,
label: "MRF simple",
key: :mrf,
tab: :mrf,
label: "MRF",
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: [
%{
key: :media_removal,
@ -1430,7 +1452,7 @@
key: :federated_timeline_removal,
type: {:list, :string},
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"]
},
%{
@ -1474,14 +1496,15 @@
%{
group: :pleroma,
key: :mrf_activity_expiration,
tab: :mrf,
label: "MRF Activity Expiration Policy",
type: :group,
description: "Adds expiration to all local Create Note activities",
description: "Adds automatic expiration to all local activities",
children: [
%{
key: :days,
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]
}
]
@ -1489,7 +1512,8 @@
%{
group: :pleroma,
key: :mrf_subchain,
label: "MRF subchain",
tab: :mrf,
label: "MRF Subchain",
type: :group,
description:
"This policy processes messages through an alternate pipeline when a given message matches certain criteria." <>
@ -1510,9 +1534,9 @@
%{
group: :pleroma,
key: :mrf_rejectnonpublic,
description:
"MRF RejectNonPublic settings. RejectNonPublic drops posts with non-public visibility settings.",
label: "MRF reject non public",
tab: :mrf,
description: "RejectNonPublic drops posts with non-public visibility settings.",
label: "MRF Reject Non Public",
type: :group,
children: [
%{
@ -1531,16 +1555,17 @@
%{
group: :pleroma,
key: :mrf_hellthread,
label: "MRF hellthread",
tab: :mrf,
label: "MRF Hellthread",
type: :group,
description: "Block messages with too much mentions",
description: "Block messages with excessive user mentions",
children: [
%{
key: :delist_threshold,
type: :integer,
description:
"Number of mentioned users after which the message gets delisted (the message can still be seen, " <>
" but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.",
"Number of mentioned users after which the message gets removed from timelines and" <>
"disables notifications. Set to 0 to disable.",
suggestions: [10]
},
%{
@ -1555,7 +1580,8 @@
%{
group: :pleroma,
key: :mrf_keyword,
label: "MRF keyword",
tab: :mrf,
label: "MRF Keyword",
type: :group,
description: "Reject or Word-Replace messages with a keyword or regex",
children: [
@ -1585,14 +1611,15 @@
%{
group: :pleroma,
key: :mrf_mention,
label: "MRF mention",
tab: :mrf,
label: "MRF Mention",
type: :group,
description: "Block messages which mention a user",
description: "Block messages which mention a specific user",
children: [
%{
key: :actors,
type: {:list, :string},
description: "A list of actors for which any post mentioning them will be dropped.",
description: "A list of actors for which any post mentioning them will be dropped",
suggestions: ["actor1", "actor2"]
}
]
@ -1600,7 +1627,8 @@
%{
group: :pleroma,
key: :mrf_vocabulary,
label: "MRF vocabulary",
tab: :mrf,
label: "MRF Vocabulary",
type: :group,
description: "Filter messages which belong to certain activity vocabularies",
children: [
@ -1608,14 +1636,14 @@
key: :accept,
type: {:list, :string},
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"]
},
%{
key: :reject,
type: {:list, :string},
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"]
}
]
@ -1645,6 +1673,7 @@
},
%{
key: :base_url,
label: "Base URL",
type: :string,
description:
"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,
label: "Proxy Options",
type: :keyword,
description: "Options for Pleroma.ReverseProxy",
suggestions: [
@ -1704,6 +1734,7 @@
},
%{
key: :http,
label: "HTTP",
type: :keyword,
description: "HTTP options",
children: [
@ -1799,6 +1830,7 @@
},
%{
key: :ip,
label: "IP",
type: :tuple,
description: "IP address to bind to",
suggestions: [{0, 0, 0, 0}]
@ -1812,7 +1844,7 @@
%{
key: :dstport,
type: :integer,
description: "Port advertised in urls (optional, defaults to port)",
description: "Port advertised in URLs (optional, defaults to port)",
suggestions: [9999]
}
]
@ -1820,6 +1852,7 @@
%{
group: :pleroma,
key: :activitypub,
label: "ActivityPub",
type: :group,
description: "ActivityPub-related settings",
children: [
@ -1842,7 +1875,7 @@
key: :note_replies_output_limit,
type: :integer,
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,
@ -1855,6 +1888,7 @@
%{
group: :pleroma,
key: :http_security,
label: "HTTP security",
type: :group,
description: "HTTP security settings",
children: [
@ -1893,7 +1927,7 @@
key: :report_uri,
label: "Report URI",
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"]
}
]
@ -1901,9 +1935,10 @@
%{
group: :web_push_encryption,
key: :vapid_details,
label: "Vapid Details",
type: :group,
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: [
%{
key: :subject,
@ -1970,6 +2005,7 @@
},
%{
group: :pleroma,
label: "Pleroma Admin Token",
type: :group,
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",
@ -1977,7 +2013,7 @@
%{
key: :admin_token,
type: :string,
description: "Token",
description: "Admin token",
suggestions: ["We recommend a secure random string or UUID"]
}
]
@ -2135,24 +2171,24 @@
key: :rich_media,
type: :group,
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: [
%{
key: :enabled,
type: :boolean,
description: "Enables RichMedia parsing of URLs."
description: "Enables RichMedia parsing of URLs"
},
%{
key: :ignore_hosts,
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"]
},
%{
key: :ignore_tld,
label: "Ignore TLD",
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"]
},
%{
@ -2180,31 +2216,32 @@
%{
group: :auto_linker,
key: :opts,
label: "Auto Linker",
type: :group,
description: "Configuration for the auto_linker library",
children: [
%{
key: :class,
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]
},
%{
key: :rel,
type: [:string, false],
description: "Override the rel attribute. Disable to clear",
description: "Override the rel attribute. Disable to clear.",
suggestions: ["ugc", "noopener noreferrer", false]
},
%{
key: :new_window,
type: :boolean,
description: "Link urls will open in new window/tab"
description: "Link URLs will open in new window/tab"
},
%{
key: :truncate,
type: [:integer, false],
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]
},
%{
@ -2215,7 +2252,7 @@
%{
key: :extra,
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,
label: "Pleroma Authenticator",
type: :group,
description: "Authenticator",
children: [
@ -2274,6 +2312,7 @@
%{
group: :pleroma,
key: :ldap,
label: "LDAP",
type: :group,
description:
"Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <>
@ -2360,6 +2399,7 @@
},
%{
key: :uid,
label: "UID",
type: :string,
description:
"LDAP attribute name to authenticate the user, e.g. when \"cn\", the filter will be \"cn=username,base\"",
@ -2375,11 +2415,12 @@
children: [
%{
key: :enforce_oauth_admin_scope_usage,
label: "Enforce OAuth admin scope usage",
type: :boolean,
description:
"OAuth admin scope requirement toggle. " <>
"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."
},
%{
@ -2391,6 +2432,7 @@
},
%{
key: :oauth_consumer_template,
label: "OAuth consumer template",
type: :string,
description:
"OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <>
@ -2399,6 +2441,7 @@
},
%{
key: :oauth_consumer_strategies,
label: "OAuth consumer strategies",
type: {:list, :string},
description:
"The list of enabled OAuth consumer strategies. By default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <>
@ -2527,7 +2570,7 @@
%{
key: :enabled,
type: :boolean,
description: "enables new users admin digest email when `true`",
description: "Enables new users admin digest email when `true`",
suggestions: [false]
}
]
@ -2535,6 +2578,7 @@
%{
group: :pleroma,
key: :oauth2,
label: "OAuth2",
type: :group,
description: "Configure OAuth 2 provider capabilities",
children: [
@ -2553,7 +2597,7 @@
%{
key: :clean_expired_tokens,
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,
label: "Relation ID action",
type: [:tuple, {:list, :tuple}],
description: "For actions on relation with a specific user (follow, unfollow)",
suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]]
@ -2650,6 +2695,7 @@
},
%{
key: :status_id_action,
label: "Status ID action",
type: [:tuple, {:list, :tuple}],
description:
"For fav / unfav or reblog / unreblog actions on the same status by the same user",
@ -2665,6 +2711,7 @@
},
%{
group: :esshd,
label: "ESSHD",
type: :group,
description:
"Before enabling this you must add :esshd to mix.exs as one of the extra_applications " <>
@ -2703,8 +2750,9 @@
},
%{
group: :mime,
label: "Mime Types",
type: :group,
description: "Mime types",
description: "Mime Types settings",
children: [
%{
key: :types,
@ -2763,6 +2811,7 @@
%{
group: :pleroma,
key: :http,
label: "HTTP",
type: :group,
description: "HTTP settings",
children: [
@ -2811,6 +2860,7 @@
%{
group: :pleroma,
key: :markup,
label: "Markup Settings",
type: :group,
children: [
%{
@ -2851,8 +2901,9 @@
},
%{
group: :pleroma,
tab: :mrf,
key: :mrf_normalize_markup,
label: "MRF normalize markup",
label: "MRF Normalize Markup",
description: "MRF NormalizeMarkup settings. Scrub configured hypertext markup.",
type: :group,
children: [
@ -2908,6 +2959,7 @@
},
%{
group: :cors_plug,
label: "CORS plug config",
type: :group,
children: [
%{
@ -2980,6 +3032,7 @@
%{
group: :pleroma,
key: :web_cache_ttl,
label: "Web cache TTL",
type: :group,
description:
"The expiration time for the web responses cache. Values should be in milliseconds or `nil` to disable expiration.",
@ -3002,9 +3055,10 @@
%{
group: :pleroma,
key: :static_fe,
label: "Static FE",
type: :group,
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: [
%{
key: :enabled,
@ -3022,18 +3076,18 @@
%{
key: :post_title,
type: :map,
description: "Configure title rendering.",
description: "Configure title rendering",
children: [
%{
key: :max_length,
type: :integer,
description: "Maximum number of characters before truncating title.",
description: "Maximum number of characters before truncating title",
suggestions: [100]
},
%{
key: :omission,
type: :string,
description: "Replacement which will be used after truncating string.",
description: "Replacement which will be used after truncating string",
suggestions: ["..."]
}
]
@ -3043,8 +3097,11 @@
%{
group: :pleroma,
key: :mrf_object_age,
label: "MRF Object Age",
tab: :mrf,
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: [
%{
key: :threshold,
@ -3057,7 +3114,7 @@
type: {:list, :atom},
description:
"A list of actions to apply to the post. `:delist` removes the post from public timelines; " <>
"`:strip_followers` removes followers from the ActivityPub recipient list, ensuring they won't be delivered to home timelines; " <>
"`:strip_followers` removes followers from the ActivityPub recipient list ensuring they won't be delivered to home timelines; " <>
"`:reject` rejects the message entirely",
suggestions: [:delist, :strip_followers, :reject]
}
@ -3085,13 +3142,13 @@
%{
key: :workers,
type: :integer,
description: "Number of workers to send notifications.",
description: "Number of workers to send notifications",
suggestions: [3]
},
%{
key: :overflow_workers,
type: :integer,
description: "Maximum number of workers created if pool is empty.",
description: "Maximum number of workers created if pool is empty",
suggestions: [2]
}
]

View file

@ -1118,6 +1118,10 @@ Loads json generated from `config/descriptions.exs`.
### 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:
```json

View file

@ -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
- `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.
- `parent_visible`: If the parent of this post is visible to the user or not.
## 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:
- `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
- `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_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
- `hide_favorites`: boolean, true when the user has hiding favorites enabled
- `hide_followers`: boolean, true when the user has follower 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
@ -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
- `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.
- `notification_settings`: object, can be absent. See `/api/pleroma/notification_settings` for the parameters/keys returned.
### 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
- `pleroma.metadata.features`: A list of supported features
- `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
## Markers
@ -234,3 +240,43 @@ Has these additional fields under the `pleroma` object:
## 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.
## 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

View file

@ -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_reachability_timeout_days`: Timeout (in days) of each external federation target being unreachable prior to pausing federating to it.
* `allow_relay`: Enable Pleromas 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`: Doesnt modify activities (default).
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesnt 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.
* `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``.
* `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
older software for theses nicknames.
* `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.
* `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`: Doesnt modify activities (default).
* `Pleroma.Web.ActivityPub.MRF.DropPolicy`: Drops all activities. It generally doesnt 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
### MRF policies
!!! 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
* `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.
* `timelines` - public and federated timelines
* `local` - public timeline
* `timelines`: public and federated timelines
* `local`: public timeline
* `federated`
* `profiles` - user profiles
* `profiles`: user profiles
* `local`
* `remote`
* `activities` - statuses
* `activities`: statuses
* `local`
* `remote`

View file

@ -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:
```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:
@ -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`:
```elixir
config :pleroma, :instance,
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
config :pleroma, :mrf,
policies: [Pleroma.Web.ActivityPub.MRF.SimplePolicy]
config :pleroma, :mrf_simple,
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
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":
@ -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:
```elixir
config :pleroma, :instance,
rewrite_policy: [
config :pleroma, :mrf,
policies: [
Pleroma.Web.ActivityPub.MRF.SimplePolicy,
Pleroma.Web.ActivityPub.MRF.RewritePolicy
]

View file

@ -33,6 +33,6 @@ as soon as the post is received by your instance.
Add to your `prod.secret.exs`:
```
config :pleroma, :instance,
rewrite_policy: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
config :pleroma, :mrf,
policies: [Pleroma.Web.ActivityPub.MRF.MediaProxyWarmingPolicy]
```

View file

@ -17,30 +17,53 @@ defmodule Mix.Tasks.Pleroma.RefreshCounterCache do
def run([]) do
Mix.Pleroma.start_pleroma()
["public", "unlisted", "private", "direct"]
|> Enum.each(fn visibility ->
count = status_visibility_count_query(visibility)
name = "status_visibility_#{visibility}"
CounterCache.set(name, count)
Mix.Pleroma.shell_info("Set #{name} to #{count}")
instances =
Activity
|> distinct([a], true)
|> select([a], fragment("split_part(?, '/', 3)", a.actor))
|> Repo.all()
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)
Mix.Pleroma.shell_info("Done")
end
defp status_visibility_count_query(visibility) do
defp instance_counters(instance) do
counters = %{"public" => 0, "unlisted" => 0, "private" => 0, "direct" => 0}
Activity
|> where(
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|> where([a], fragment("split_part(?, '/', 3) = ?", a.actor, ^instance))
|> select(
[a],
{fragment(
"activity_visibility(?, ?, ?)",
a.actor,
a.recipients,
a.data
), count(a.id)}
)
|> group_by(
[a],
fragment(
"activity_visibility(?, ?, ?) = ?",
"activity_visibility(?, ?, ?)",
a.actor,
a.recipients,
a.data,
^visibility
a.data
)
)
|> where([a], fragment("(? ->> 'type'::text) = 'Create'", a.data))
|> Repo.aggregate(:count, :id, timeout: :timer.minutes(30))
|> Repo.all(timeout: :timer.minutes(30))
|> Enum.reduce(counters, fn {visibility, count}, acc ->
Map.put(acc, visibility, count)
end)
end
end

View file

@ -167,7 +167,9 @@ defp only_full_update?(%ConfigDB{group: group, key: key}) do
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
search_opts = Map.delete(params, :subkeys)

View file

@ -3,9 +3,23 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Config.DeprecationWarnings do
alias Pleroma.Config
require Logger
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
if Config.get([:mrf_hellthread, :threshold]) do
Logger.warn("""
@ -39,5 +53,35 @@ def mrf_user_allowlist do
def warn do
check_hellthread_threshold()
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

View file

@ -10,32 +10,70 @@ defmodule Pleroma.CounterCache do
import Ecto.Query
schema "counter_cache" do
field(:name, :string)
field(:count, :integer)
field(:instance, :string)
field(:public, :integer)
field(:unlisted, :integer)
field(:private, :integer)
field(:direct, :integer)
end
def changeset(struct, params) do
struct
|> cast(params, [:name, :count])
|> validate_required([:name])
|> unique_constraint(:name)
|> cast(params, [:instance, :public, :unlisted, :private, :direct])
|> validate_required([:instance])
|> unique_constraint(:instance)
end
def get_as_map(names) when is_list(names) do
def get_by_instance(instance) do
CounterCache
|> where([cc], cc.name in ^names)
|> Repo.all()
|> Enum.group_by(& &1.name, & &1.count)
|> Map.new(fn {k, v} -> {k, hd(v)} end)
|> select([c], %{
"public" => c.public,
"unlisted" => c.unlisted,
"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
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{}
|> changeset(%{"name" => name, "count" => count})
|> changeset(params)
|> Repo.insert(
on_conflict: [set: [count: count]],
on_conflict: [
set: [
public: params["public"],
private: params["private"],
unlisted: params["unlisted"],
direct: params["direct"]
]
],
returning: true,
conflict_target: :name
conflict_target: :instance
)
end
end

View file

@ -45,6 +45,7 @@ def show(opts) do
shortcodes =
pack.files
|> Map.keys()
|> Enum.sort()
|> paginate(opts[:page], opts[:page_size])
pack = Map.put(pack, :files, Map.take(pack.files, shortcodes))

View file

@ -124,6 +124,7 @@ def get_follow_requests(%User{id: id}) do
|> join(:inner, [r], f in assoc(r, :follower))
|> where([r], r.state == ^:follow_pending)
|> where([r], r.following_id == ^id)
|> where([r, f], f.deactivated != true)
|> select([r, f], f)
|> Repo.all()
end

View file

@ -109,7 +109,7 @@ def extract_first_external_url(object, content) do
result =
content
|> 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")
|> Enum.at(0)

View file

@ -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

View file

@ -16,6 +16,7 @@ defmodule Pleroma.HTTP do
require Logger
@type t :: __MODULE__
@type method() :: :get | :post | :put | :delete | :head
@doc """
Performs GET request.
@ -28,6 +29,9 @@ def get(url, headers \\ [], options \\ [])
def get(nil, _, _), do: nil
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 """
Performs POST request.
@ -42,7 +46,7 @@ def post(url, body, headers \\ [], options \\ []),
Builds and performs http request.
# Arguments:
`method` - :get, :post, :put, :delete
`method` - :get, :post, :put, :delete, :head
`url` - full url
`body` - request body
`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}`
"""
@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()}
def request(method, url, body, headers, options) when is_binary(url) do
uri = URI.parse(url)

View file

@ -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

View file

@ -3,7 +3,6 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.MigrationHelper.NotificationBackfill do
alias Pleroma.Notification
alias Pleroma.Object
alias Pleroma.Repo
alias Pleroma.User
@ -25,18 +24,27 @@ def fill_in_notification_types do
|> type_from_activity()
notification
|> Notification.changeset(%{type: type})
|> Ecto.Changeset.change(%{type: type})
|> Repo.update()
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.
defp type_from_activity(%{data: %{"type" => type}} = activity) do
case type do
"Follow" ->
accepted_function = fn activity ->
with %User{} = follower <- User.get_by_ap_id(activity.data["actor"]),
%User{} = followed <- User.get_by_ap_id(activity.data["object"]) do
with %User{} = follower <- get_by_ap_id(activity.data["actor"]),
%User{} = followed <- get_by_ap_id(activity.data["object"]) do
Pleroma.FollowingRelationship.following?(follower, followed)
end
end

View file

@ -367,6 +367,7 @@ defp do_create_notifications(%Activity{} = activity, options) do
do_send = do_send && user in enabled_receivers
create_notification(activity, user, do_send)
end)
|> Enum.reject(&is_nil/1)
{:ok, notifications}
end

View file

@ -83,8 +83,8 @@ def fetch_object_from_id(id, options \\ []) do
{:transmogrifier, {:error, {:reject, nil}}} ->
{:reject, nil}
{:transmogrifier, _} ->
{:error, "Transmogrifier failure."}
{:transmogrifier, _} = e ->
{:error, e}
{:object, data, nil} ->
reinject_object(%Object{}, data)

View file

@ -97,20 +97,11 @@ def calculate_stat_data do
}
end
def get_status_visibility_count do
counter_cache =
CounterCache.get_as_map([
"status_visibility_public",
"status_visibility_private",
"status_visibility_unlisted",
"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
}
def get_status_visibility_count(instance \\ nil) do
if is_nil(instance) do
CounterCache.get_sum()
else
CounterCache.get_by_instance(instance)
end
end
end

View file

@ -115,7 +115,7 @@ defmodule Pleroma.User do
field(:is_moderator, :boolean, default: false)
field(:is_admin, :boolean, default: false)
field(:show_role, :boolean, default: true)
field(:settings, :map, default: nil)
field(:mastofe_settings, :map, default: nil)
field(:uri, ObjectValidators.Uri, default: nil)
field(:hide_followers_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)
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, _} = Participation.mark_all_as_read(blocker, blocked)
@ -1527,8 +1528,7 @@ def perform(:blocks_import, %User{} = blocker, blocked_identifiers)
blocked_identifiers,
fn blocked_identifier ->
with {:ok, %User{} = blocked} <- get_or_fetch(blocked_identifier),
{:ok, _user_block} <- block(blocker, blocked),
{:ok, _} <- ActivityPub.block(blocker, blocked) do
{:ok, _block} <- CommonAPI.block(blocker, blocked) do
blocked
else
err ->
@ -2118,8 +2118,8 @@ def mascot_update(user, url) do
def mastodon_settings_update(user, settings) do
user
|> cast(%{settings: settings}, [:settings])
|> validate_required([:settings])
|> cast(%{mastofe_settings: settings}, [:mastofe_settings])
|> validate_required([:mastofe_settings])
|> update_and_set_cache()
end

View file

@ -52,6 +52,7 @@ defp search_query(query_string, for_user, following) do
|> base_query(following)
|> filter_blocked_user(for_user)
|> filter_invisible_users()
|> filter_internal_users()
|> filter_blocked_domains(for_user)
|> fts_search(query_string)
|> trigram_rank(query_string)
@ -109,6 +110,10 @@ defp filter_invisible_users(query) do
from(q in query, where: q.invisible == false)
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
query
|> join(:left, [u], b in Pleroma.UserRelationship,

View file

@ -321,28 +321,6 @@ defp accept_or_reject(type, %{to: to, actor: actor, object: object} = params) do
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()) ::
{:ok, Activity.t()} | {:error, any()}
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
@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()}
def flag(
%{
@ -1420,6 +1371,16 @@ def fetch_and_prepare_user_from_ap_id(ap_id) do
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
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.update_and_set_cache()
else
maybe_handle_clashing_nickname(data[:nickname])
data
|> User.remote_user_changeset()
|> Repo.insert()

View file

@ -123,6 +123,33 @@ def like(actor, object) do
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()}
def announce(actor, object, options \\ []) do
public? = Keyword.get(options, :public, false)

View file

@ -16,7 +16,7 @@ def filter(policies, %{} = object) do
def filter(%{} = object), do: get_policies() |> filter(object)
def get_policies do
Pleroma.Config.get([:instance, :rewrite_policy], []) |> get_policies()
Pleroma.Config.get([:mrf, :policies], []) |> get_policies()
end
defp get_policies(policy) when is_atom(policy), do: [policy]
@ -51,7 +51,7 @@ def describe(policies) do
get_policies()
|> 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 =
%{

View file

@ -27,11 +27,14 @@ defp contains_links?(_), do: false
@impl true
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)},
{:old_user, true} <- {:old_user, old_user?(u)} do
{:ok, message}
else
{:ok, %User{local: true}} ->
{:ok, message}
{:contains_links, false} ->
{:ok, message}

View file

@ -3,21 +3,23 @@
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Web.ActivityPub.MRF.SimplePolicy do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
@moduledoc "Filter activities depending on their origin instance"
@behaviour Pleroma.Web.ActivityPub.MRF
alias Pleroma.Config
alias Pleroma.User
alias Pleroma.Web.ActivityPub.MRF
require Pleroma.Constants
defp check_accept(%{host: actor_host} = _actor_info, object) do
accepts =
Pleroma.Config.get([:mrf_simple, :accept])
Config.get([:mrf_simple, :accept])
|> MRF.subdomains_regex()
cond do
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}
true -> {:reject, nil}
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
rejects =
Pleroma.Config.get([:mrf_simple, :reject])
Config.get([:mrf_simple, :reject])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(rejects, actor_host) do
@ -41,7 +43,7 @@ defp check_media_removal(
)
when length(child_attachment) > 0 do
media_removal =
Pleroma.Config.get([:mrf_simple, :media_removal])
Config.get([:mrf_simple, :media_removal])
|> MRF.subdomains_regex()
object =
@ -65,7 +67,7 @@ defp check_media_nsfw(
} = object
) do
media_nsfw =
Pleroma.Config.get([:mrf_simple, :media_nsfw])
Config.get([:mrf_simple, :media_nsfw])
|> MRF.subdomains_regex()
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
timeline_removal =
Pleroma.Config.get([:mrf_simple, :federated_timeline_removal])
Config.get([:mrf_simple, :federated_timeline_removal])
|> MRF.subdomains_regex()
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
report_removal =
Pleroma.Config.get([:mrf_simple, :report_removal])
Config.get([:mrf_simple, :report_removal])
|> MRF.subdomains_regex()
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
avatar_removal =
Pleroma.Config.get([:mrf_simple, :avatar_removal])
Config.get([:mrf_simple, :avatar_removal])
|> MRF.subdomains_regex()
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
banner_removal =
Pleroma.Config.get([:mrf_simple, :banner_removal])
Config.get([:mrf_simple, :banner_removal])
|> MRF.subdomains_regex()
if MRF.subdomain_match?(banner_removal, actor_host) do
@ -197,10 +199,10 @@ def filter(object), do: {:ok, object}
@impl true
def describe do
exclusions = Pleroma.Config.get([:instance, :mrf_transparency_exclusions])
exclusions = Config.get([:mrf, :transparency_exclusions])
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.into(%{})

View file

@ -13,16 +13,47 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidator do
alias Pleroma.Object
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.AnnounceValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.BlockValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.ChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.CreateChatMessageValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.DeleteValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.EmojiReactValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.LikeValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UndoValidator
alias Pleroma.Web.ActivityPub.ObjectValidators.UpdateValidator
@spec validate(map(), keyword()) :: {:ok, map(), keyword()} | {:error, any()}
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
with {:ok, object} <-
object

View file

@ -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

View file

@ -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

View file

@ -6,6 +6,7 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
collection, and so on.
"""
alias Pleroma.Activity
alias Pleroma.Activity.Ir.Topics
alias Pleroma.Chat
alias Pleroma.Chat.MessageReference
alias Pleroma.Notification
@ -20,6 +21,41 @@ defmodule Pleroma.Web.ActivityPub.SideEffects do
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:
# - Add like to object
# - Set up notification
@ -62,7 +98,10 @@ def handle(%{data: %{"type" => "Announce"}} = object, meta) do
if !User.is_internal_user?(user) do
Notification.create_notifications(object)
ActivityPub.stream_out(object)
object
|> Topics.get_activity_topics()
|> Streamer.stream(object)
end
{:ok, object, meta}

View file

@ -446,12 +446,9 @@ def handle_incoming(
when objtype in ["Article", "Event", "Note", "Video", "Page", "Question", "Answer", "Audio"] do
actor = Containment.get_actor(data)
data =
Map.put(data, "actor", actor)
|> fix_addressing
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)
params = %{
@ -673,7 +670,7 @@ def handle_incoming(
end
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),
{:ok, activity, _meta} <-
Pipeline.common_pipeline(data, local: false) do
@ -684,35 +681,13 @@ def handle_incoming(%{"type" => type} = data, _options)
end
def handle_incoming(
%{"type" => "Update", "object" => %{"type" => object_type} = object, "actor" => actor_id} =
data,
%{"type" => type} = data,
_options
)
when object_type in [
"Person",
"Application",
"Service",
"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
when type in ~w{Update Block} do
with {:ok, %User{}} <- ObjectValidator.fetch_actor(data),
{:ok, activity, _} <- Pipeline.common_pipeline(data, local: false) do
{:ok, activity}
end
end
@ -788,21 +763,6 @@ def handle_incoming(
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(
%{
"type" => "Move",

View file

@ -47,6 +47,10 @@ def is_list?(_), do: false
@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?(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
user.ap_id in activity.data["to"] ||
list_ap_id
@ -54,8 +58,6 @@ def visible_for_user?(%{data: %{"listMessage" => list_ap_id}} = activity, %User{
|> Pleroma.List.member?(user)
end
def visible_for_user?(%{data: %{"listMessage" => _}}, nil), do: false
def visible_for_user?(%{local: local} = activity, nil) do
cfg_key =
if local,

View file

@ -643,10 +643,10 @@ def resend_confirmation_email(%{assigns: %{user: admin}} = conn, %{"nicknames" =
json(conn, "")
end
def stats(conn, _) do
count = Stats.get_status_visibility_count()
def stats(conn, params) do
counters = Stats.get_status_visibility_count(params["instance"])
json(conn, %{"status_visibility" => count})
json(conn, %{"status_visibility" => counters})
end
defp page_params(params) do

View file

@ -40,7 +40,7 @@ def call(%{private: %{open_api_spex: private_data}} = conn, %{
|> List.first()
_ ->
nil
"application/json"
end
private_data = Map.put(private_data, :operation_id, operation_id)

View file

@ -84,7 +84,7 @@ def delete_operation do
operationId: "StatusController.delete",
parameters: [id_param()],
responses: %{
200 => empty_object_response(),
200 => status_response(),
403 => Operation.response("Forbidden", "application/json", ApiError),
404 => Operation.response("Not Found", "application/json", ApiError)
}

View file

@ -40,20 +40,53 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
pleroma: %Schema{
type: :object,
properties: %{
allow_following_move: %Schema{type: :boolean},
background_image: %Schema{type: :string, nullable: true},
allow_following_move: %Schema{
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},
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_followers_count: %Schema{type: :boolean},
hide_followers: %Schema{type: :boolean},
hide_follows_count: %Schema{type: :boolean},
hide_follows: %Schema{type: :boolean},
is_admin: %Schema{type: :boolean},
is_moderator: %Schema{type: :boolean},
hide_followers_count: %Schema{
type: :boolean,
description: "whether the user has follower stat hiding enabled"
},
hide_followers: %Schema{
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},
tags: %Schema{type: :array, items: %Schema{type: :string}},
unread_conversation_count: %Schema{type: :integer},
tags: %Schema{
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{
type: :object,
properties: %{
@ -66,7 +99,9 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Account do
},
relationship: AccountRelationship,
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,
properties: %{
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,
sensitive: %Schema{type: :boolean},
pleroma: %Schema{
type: :object,
properties: %{
actor_type: ActorType,
discoverable: %Schema{type: :boolean},
no_rich_text: %Schema{type: :boolean},
show_role: %Schema{type: :boolean}
discoverable: %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"
}
}
}
}

View file

@ -62,6 +62,11 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
}
},
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{
type: :string,
format: "date-time",
@ -184,6 +189,10 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
thread_muted: %Schema{
type: :boolean,
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"
}
}
},

View file

@ -186,6 +186,7 @@ defp object(draft) do
draft.poll
)
|> Map.put("emoji", emoji)
|> Map.put("source", draft.status)
%__MODULE__{draft | object: object}
end

View file

@ -25,6 +25,13 @@ defmodule Pleroma.Web.CommonAPI do
require Pleroma.Constants
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
with maybe_attachment <- opts[:media_id] && Object.get_by_id(opts[:media_id]),
:ok <- validate_chat_content_length(content, !!maybe_attachment),

View file

@ -9,6 +9,7 @@ defmodule Fallback.RedirectController do
alias Pleroma.User
alias Pleroma.Web.Metadata
alias Pleroma.Web.Preload
def api_not_implemented(conn, _params) do
conn
@ -16,16 +17,7 @@ def api_not_implemented(conn, _params) do
|> json(%{error: "Not implemented"})
end
def redirector(conn, _params, code \\ 200)
# 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
def redirector(conn, _params, code \\ 200) do
conn
|> put_resp_content_type("text/html")
|> 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
{:ok, index_content} = File.read(index_file_path())
tags =
try do
Metadata.build_tags(params)
rescue
e ->
Logger.error(
"Metadata rendering for #{conn.request_path} failed.\n" <>
Exception.format(:error, e, __STACKTRACE__)
)
tags = build_tags(conn, params)
preloads = preload_data(conn, params)
""
end
response = String.replace(index_content, "<!--server-generated-meta-->", tags)
response =
index_content
|> String.replace("<!--server-generated-meta-->", tags <> preloads)
conn
|> put_resp_content_type("text/html")
|> send_resp(200, response)
end
def index_file_path do
Pleroma.Plugs.InstanceStatic.file_path("index.html")
def redirector_with_preload(conn, %{"path" => ["pleroma", "admin"]}) do
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
def registration_page(conn, params) do
@ -76,4 +73,36 @@ def empty(conn, _params) do
|> put_status(204)
|> text("")
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

View file

@ -20,6 +20,8 @@ defmodule Pleroma.Web.MastodonAPI.AccountController do
alias Pleroma.Plugs.RateLimiter
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ActivityPub
alias Pleroma.Web.ActivityPub.Builder
alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.MastodonAPI.ListView
alias Pleroma.Web.MastodonAPI.MastodonAPI
@ -182,34 +184,39 @@ def update_credentials(%{assigns: %{user: user}, body_params: params} = conn, _p
end)
|> Maps.put_if_present(:actor_type, params[:actor_type])
changeset = User.update_changeset(user, user_params)
with {:ok, user} <- User.update_and_set_cache(changeset) do
user
|> build_update_activity_params()
|> ActivityPub.update()
render(conn, "show.json", user: user, for: user, with_pleroma_settings: true)
# What happens here:
#
# We want to update the user through the pipeline, but the ActivityPub
# update information is not quite enough for this, because this also
# contains local settings that don't federate and don't even appear
# in the Update activity.
#
# 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
_e -> render_error(conn, :forbidden, "Invalid request")
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
if Enum.all?(fields, &is_tuple/1) do
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"
def block(%{assigns: %{user: blocker, account: blocked}} = conn, _params) do
with {:ok, _user_block} <- User.block(blocker, blocked),
{:ok, _activity} <- ActivityPub.block(blocker, blocked) do
with {:ok, _activity} <- CommonAPI.block(blocker, blocked) do
render(conn, "relationship.json", user: blocker, target: blocked)
else
{:error, message} -> json_response(conn, :forbidden, %{error: message})

View file

@ -44,6 +44,7 @@ def search2(conn, params), do: do_search(:v2, conn, params)
def search(conn, params), do: do_search(:v1, conn, params)
defp do_search(version, %{assigns: %{user: user}} = conn, %{q: query} = params) do
query = String.trim(query)
options = search_options(params, user)
timeout = Keyword.get(Repo.config(), :timeout, 15_000)
default_values = %{"statuses" => [], "accounts" => [], "hashtags" => []}

View file

@ -200,11 +200,18 @@ def show(%{assigns: %{user: user}} = conn, %{id: id}) do
@doc "DELETE /api/v1/statuses/:id"
def delete(%{assigns: %{user: user}} = conn, %{id: id}) do
with {:ok, %Activity{}} <- CommonAPI.delete(id, user) do
json(conn, %{})
with %Activity{} = activity <- Activity.get_by_id_with_object(id),
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
{:error, :not_found} = e -> e
_e -> render_error(conn, :forbidden, "Can't delete this post")
_e -> {:error, :not_found}
end
end

View file

@ -36,8 +36,10 @@ def render("show.json", _) do
background_image: Keyword.get(instance, :background_image),
pleroma: %{
metadata: %{
account_activation_required: Keyword.get(instance, :account_activation_required),
features: features(),
federation: federation()
federation: federation(),
fields_limits: fields_limits()
},
vapid_public_key: Keyword.get(Pleroma.Web.Push.vapid_config(), :public_key)
}
@ -78,7 +80,7 @@ def features do
def federation do
quarantined = Config.get([:instance, :quarantined_instances], [])
if Config.get([:instance, :mrf_transparency]) do
if Config.get([:mrf, :transparency]) do
{:ok, data} = MRF.describe()
data
@ -88,4 +90,13 @@ def federation do
end
|> Map.put(:enabled, Config.get([:instance, :federating]))
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

View file

@ -21,7 +21,7 @@ defmodule Pleroma.Web.MastodonAPI.StatusView do
alias Pleroma.Web.MastodonAPI.StatusView
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.
defp get_replied_to_activities([]), do: %{}
@ -333,6 +333,7 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
reblog: nil,
card: card,
content: content_html,
text: opts[:with_source] && object.data["source"],
created_at: created_at,
reblogs_count: announcement_count,
replies_count: object.data["repliesCount"] || 0,
@ -364,7 +365,8 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
expires_at: expires_at,
direct_conversation_id: direct_conversation_id,
thread_muted: thread_muted?,
emoji_reactions: emoji_reactions
emoji_reactions: emoji_reactions,
parent_visible: visible_for_user?(reply_to, opts[:for])
}
}
end

View file

@ -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

View file

@ -5,12 +5,8 @@
defmodule Pleroma.Web.Nodeinfo.NodeinfoController do
use Pleroma.Web, :controller
alias Pleroma.Config
alias Pleroma.Stats
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.Federator.Publisher
alias Pleroma.Web.MastodonAPI.InstanceView
alias Pleroma.Web.Nodeinfo.Nodeinfo
def schemas(conn, _params) do
response = %{
@ -29,102 +25,20 @@ def schemas(conn, _params) do
json(conn, response)
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
# and https://github.com/jhass/nodeinfo/blob/master/schemas/2.1/schema.json
def nodeinfo(conn, %{"version" => "2.0"}) do
conn
|> put_resp_header(
"content-type",
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
)
|> json(raw_nodeinfo())
end
def nodeinfo(conn, %{"version" => version}) do
case Nodeinfo.get_nodeinfo(version) do
{:error, :missing} ->
render_error(conn, :not_found, "Nodeinfo schema version not handled")
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")
node_info ->
conn
|> put_resp_header(
"content-type",
"application/json; profile=http://nodeinfo.diaspora.software/ns/schema/2.0#; charset=utf-8"
)
|> json(node_info)
end
end
end

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -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

View file

@ -726,7 +726,7 @@ defmodule Pleroma.Web.Router do
get("/registration/:token", RedirectController, :registration_page)
get("/:maybe_nickname_or_id", RedirectController, :redirector_with_meta)
get("/api*path", RedirectController, :api_not_implemented)
get("/*path", RedirectController, :redirector)
get("/*path", RedirectController, :redirector_with_preload)
options("/*path", RedirectController, :empty)
end

View file

@ -116,6 +116,7 @@ def filtered_by_user?(%User{} = user, %Activity{} = item) do
true <-
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" && parent.data["actor"] == user.ap_id),
true <- Enum.all?([blocked_ap_ids, muted_ap_ids], &(parent.data["actor"] not in &1)),
true <- MapSet.disjoint?(recipients, recipient_blocks),
%{host: item_host} <- URI.parse(item.actor),

View file

@ -15,6 +15,7 @@ defmodule Pleroma.Web.TwitterAPI.UtilController do
alias Pleroma.User
alias Pleroma.Web
alias Pleroma.Web.CommonAPI
alias Pleroma.Web.TwitterAPI.UtilView
alias Pleroma.Web.WebFinger
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
instance = Pleroma.Config.get(: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>
"""
response = UtilView.status_net_config(instance)
conn
|> put_resp_content_type("application/xml")

View file

@ -5,4 +5,18 @@
defmodule Pleroma.Web.TwitterAPI.UtilView do
use Pleroma.Web, :view
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

View file

@ -86,7 +86,7 @@ def initial_state(token, user, custom_emojis) do
"video\/mp4"
]
},
settings: user.settings || @default_settings,
settings: user.mastofe_settings || @default_settings,
push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},
custom_emojis: render(CustomEmojiView, "index.json", custom_emojis: custom_emojis),

View file

@ -117,7 +117,7 @@ defp oauth_deps do
defp deps do
[
{:phoenix, "~> 1.4.8"},
{:tzdata, "~> 0.5.21"},
{:tzdata, "~> 1.0.3"},
{:plug_cowboy, "~> 2.0"},
{:phoenix_pubsub, "~> 1.1"},
{:phoenix_ecto, "~> 4.0"},
@ -159,7 +159,10 @@ defp deps do
{:cors_plug, "~> 1.5"},
{:ex_doc, "~> 0.21", only: :dev, runtime: false},
{: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"},
{:gen_smtp, "~> 0.13"},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test},

View file

@ -104,13 +104,13 @@
"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"},
"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"},
"telemetry": {:hex, :telemetry, "0.4.2", "2808c992455e08d6177322f14d3bdb6b625fbcfd233a73505870d8738a2f4599", [:rebar3], [], "hexpm", "2d1419bd9dda6a206d7b5852179511722e2b18812310d304620c7bd92a13fcef"},
"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"},
"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"},
"unicode_util_compat": {:hex, :unicode_util_compat, "0.5.0", "8516502659002cec19e244ebd90d312183064be95025a319a6c7e89f4bccd65b", [:rebar3], [], "hexpm", "d48d002e15f5cc105a696cf2f1bbb3fc72b4b770a184d8420c8db20da2674b38"},
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},

View file

@ -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

View file

@ -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

View file

@ -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.

View file

@ -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.

Some files were not shown because too many files have changed in this diff Show more