diff --git a/CHANGELOG.md b/CHANGELOG.md index 0bc555878..713ae4361 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,13 +8,15 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking**: Removed 1.0+ deprecated configurations `Pleroma.Upload, :strip_exif` and `:instance, :dedupe_media` - **Breaking**: OStatus protocol support - **Breaking**: MDII uploader +- **Breaking**: Using third party engines for user recommendation ### Changed - **Breaking:** Pleroma won't start if it detects unapplied migrations -- **Breaking:** attachments are removed along with statuses when there are no other references to it - **Breaking:** Elixir >=1.8 is now required (was >= 1.7) +- **Breaking:** `Pleroma.Plugs.RemoteIp` and `:rate_limiter` enabled by default. Please ensure your reverse proxy forwards the real IP! - **Breaking:** attachment links (`config :pleroma, :instance, no_attachment_links` and `config :pleroma, Pleroma.Upload, link_name`) disabled by default - **Breaking:** OAuth: defaulted `[:auth, :enforce_oauth_admin_scope_usage]` setting to `true` which demands `admin` OAuth scope to perform admin actions (in addition to `is_admin` flag on User); make sure to use bundled or newer versions of AdminFE & PleromaFE to access admin / moderator features. +- **Breaking:** Dynamic configuration has been rearchitected. The `:pleroma, :instance, dynamic_configuration` setting has been replaced with `config :pleroma, configurable_from_database`. Please backup your configuration to a file and run the migration task to ensure consistency with the new schema. - Replaced [pleroma_job_queue](https://git.pleroma.social/pleroma/pleroma_job_queue) and `Pleroma.Web.Federator.RetryQueue` with [Oban](https://github.com/sorentwo/oban) (see [`docs/config.md`](docs/config.md) on migrating customized worker / retry settings) - Introduced [quantum](https://github.com/quantum-elixir/quantum-core) job scheduler - Enabled `:instance, extended_nickname_format` in the default config @@ -25,7 +27,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Deprecated `User.Info` embedded schema (fields moved to `User`) - Store status data inside Flag activity - Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`). +- Rate limiter is now disabled for localhost/socket (unless remoteip plug is enabled) - Logger: default log level changed from `warn` to `info`. +- Config mix task `migrate_to_db` truncates `config` table before migrating the config file.
API Changes @@ -49,11 +53,13 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Added - `:chat_limit` option to limit chat characters. +- `cleanup_attachments` option to 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. - Refreshing poll results for remote polls - Authentication: Added rate limit for password-authorized actions / login existence checks - Static Frontend: Add the ability to render user profiles and notices server-side without requiring JS app. - Mix task to re-count statuses for all users (`mix pleroma.count_statuses`) - Mix task to list all users (`mix pleroma.user list`) +- Mix task to send a test email (`mix pleroma.email test`) - Support for `X-Forwarded-For` and similar HTTP headers which used by reverse proxies to pass a real user IP address to the backend. Must not be enabled unless your instance is behind at least one reverse proxy (such as Nginx, Apache HTTPD or Varnish Cache). - MRF: New module which handles incoming posts based on their age. By default, all incoming posts that are older than 2 days will be unlisted and not shown to their followers. - User notification settings: Add `privacy_option` option. @@ -96,6 +102,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Mastodon API: Add support for `account_id` param to filter notifications by the account - Mastodon API: Add `emoji_reactions` property to Statuses - Mastodon API: Change emoji reaction reply format +- Notifications: Added `pleroma:emoji_reaction` notification type +- Mastodon API: Change emoji reaction reply format once more +- Configuration: `feed.logo` option for tag feed. +- Tag feed: `/tags/:tag.rss` - list public statuses by hashtag. +- Mastodon API: Add `reacted` property to `emoji_reactions`
### Fixed diff --git a/config/config.exs b/config/config.exs index 5f72df8a0..370828c1c 100644 --- a/config/config.exs +++ b/config/config.exs @@ -271,7 +271,8 @@ account_field_name_length: 512, account_field_value_length: 2048, external_user_synchronization: true, - extended_nickname_format: true + extended_nickname_format: true, + cleanup_attachments: false config :pleroma, :feed, post_title: %{ @@ -425,14 +426,6 @@ ], unfurl_nsfw: false -config :pleroma, :suggestions, - enabled: false, - third_party_engine: - "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}", - timeout: 300_000, - limit: 40, - web: "https://vinayaka.distsn.org" - config :pleroma, :http_security, enabled: true, sts: false, @@ -605,11 +598,21 @@ config :http_signatures, adapter: Pleroma.Signature -config :pleroma, :rate_limit, authentication: {60_000, 15} +config :pleroma, :rate_limit, + authentication: {60_000, 15}, + search: [{1000, 10}, {1000, 30}], + app_account_creation: {1_800_000, 25}, + relations_actions: {10_000, 10}, + relation_id_action: {60_000, 2}, + statuses_actions: {10_000, 15}, + status_id_action: {60_000, 3}, + password_reset: {1_800_000, 5}, + account_confirmation_resend: {8_640_000, 5}, + ap_routes: {60_000, 15} config :pleroma, Pleroma.ActivityExpiration, enabled: true -config :pleroma, Pleroma.Plugs.RemoteIp, enabled: false +config :pleroma, Pleroma.Plugs.RemoteIp, enabled: true config :pleroma, :static_fe, enabled: false diff --git a/config/description.exs b/config/description.exs index a0675ec30..909ae00d9 100644 --- a/config/description.exs +++ b/config/description.exs @@ -23,7 +23,7 @@ key: :uploader, type: :module, description: "Module which will be used for uploads", - suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.MDII, Pleroma.Uploaders.S3] + suggestions: [Pleroma.Uploaders.Local, Pleroma.Uploaders.S3] }, %{ key: :filters, @@ -39,7 +39,7 @@ 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, @@ -53,7 +53,7 @@ key: :proxy_remote, type: :boolean, description: - "If enabled, requests to media stored using a remote uploader will be proxied instead of being redirected." + "If enabled, requests to media stored using a remote uploader will be proxied instead of being redirected" }, %{ key: :proxy_opts, @@ -73,14 +73,14 @@ type: :boolean, description: "Redirects the client to the real remote URL if there's any HTTP errors. " <> - "Any error during body processing will not be redirected as the response is chunked" + "Any error during body processing will not be redirected as the response is chunked." }, %{ key: :max_body_length, type: :integer, description: - "limits the content length to be approximately the " <> - "specified length. It is validated with the `content-length` header and also verified when proxying" + "Limits the content length to be approximately the " <> + "specified length. It is validated with the `content-length` header and also verified when proxying." }, %{ key: :http, @@ -130,7 +130,7 @@ %{ key: :uploads, type: :string, - description: "Path where user uploads will be saved", + description: "Path where user's uploads will be saved", suggestions: [ "uploads" ] @@ -207,7 +207,7 @@ type: :string, description: "Text to replace filenames in links. If no setting, {random}.extension will be used. You can get the original" <> - " filename extension by using {extension}, for example custom-file-name.{extension}", + " filename extension by using {extension}, for example custom-file-name.{extension}.", suggestions: [ "custom-file-name.{extension}" ] @@ -515,6 +515,7 @@ }, %{ key: :email, + label: "Admin Email Address", type: :string, description: "Email used to reach an Administrator/Moderator of the instance", suggestions: [ @@ -523,8 +524,9 @@ }, %{ key: :notify_email, + label: "Sender Email Address", type: :string, - description: "Email used for notifications", + description: "Envelope FROM address for mail sent via Pleroma", suggestions: [ "notify@example.com" ] @@ -635,12 +637,12 @@ %{ key: :registrations_open, type: :boolean, - description: "Enable registrations for anyone, invitations can be enabled when false" + description: "Enable registrations for anyone, invitations can be enabled when `false`" }, %{ key: :invites_enabled, type: :boolean, - description: "Enable user invitations for admins (depends on registrations_open: false)" + description: "Enable user invitations for admins (depends on `registrations_open: false`)" }, %{ key: :account_activation_required, @@ -658,7 +660,7 @@ type: :integer, description: "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", + " 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.", suggestions: [ 100 ] @@ -668,7 +670,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 ] @@ -701,13 +703,13 @@ type: :boolean, description: "Makes the client API in authentificated mode-only except for user-profiles." <> - " Useful for disabling the Local Timeline and The Whole Known Network" + " Useful for disabling the Local Timeline and The Whole Known Network." }, %{ key: :quarantined_instances, type: {:list, :string}, description: - "List of ActivityPub instances where private(DMs, followers-only) activities will not be send", + "List of ActivityPub instances where private (DMs, followers-only) activities will not be send", suggestions: [ "quarantined.com", "*.quarantined.com" @@ -750,7 +752,7 @@ 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", + "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" ] @@ -759,13 +761,22 @@ key: :extended_nickname_format, type: :boolean, description: - "Set to true to use extended local nicknames format (allows underscores/dashes)." <> - " This will break federation with older software for theses nicknames" + "Set to `true` to use extended local nicknames format (allows underscores/dashes)." <> + " This will break federation with older software for theses nicknames." + }, + %{ + key: :cleanup_attachments, + type: :boolean, + description: """ + "Set to `true` to remove associated attachments when status is removed. + This will not affect duplicates and attachments without status. + Enabling this will increase load to database when deleting statuses on larger instances. + """ }, %{ key: :max_pinned_statuses, type: :integer, - description: "The maximum number of pinned statuses. 0 will disable the feature", + description: "The maximum number of pinned statuses. 0 will disable the feature.", suggestions: [ 0, 1, @@ -788,13 +799,13 @@ key: :no_attachment_links, type: :boolean, description: - "Set to true to disable automatically adding attachment link text to statuses" + "Set to `true` to disable automatically adding attachment link text to statuses" }, %{ key: :welcome_message, type: :string, description: - "A message that will be send to a newly registered users as a direct message", + "A message that will be sent to a newly registered users as a direct message", suggestions: [ "Hi, @username! Welcome on board!" ] @@ -810,7 +821,7 @@ %{ key: :max_report_comment_size, type: :integer, - description: "The maximum size of the report comment (Default: 1000)", + description: "The maximum size of the report comment. Default: 1000.", suggestions: [ 1_000 ] @@ -819,14 +830,14 @@ key: :safe_dm_mentions, type: :boolean, description: - "If set to true, only mentions at the beginning of a post will be used to address people in direct messages." <> - " This is to prevent accidental mentioning of people when talking about them (e.g. \"@friend hey i really don't like @enemy\")." <> - " Default: false" + "If set to `true`, only mentions at the beginning of a post will be used to address people in direct messages." <> + " This is to prevent accidental mentioning of people when talking about them (e.g. \"@admin please keep an eye on @bad_actor\")." <> + " Default: `false`" }, %{ key: :healthcheck, type: :boolean, - description: "If set to true, system data will be shown on /api/pleroma/healthcheck" + description: "If set to `true`, system data will be shown on /api/pleroma/healthcheck" }, %{ key: :remote_post_retention_days, @@ -840,7 +851,7 @@ %{ key: :user_bio_length, type: :integer, - description: "A user bio maximum length (default: 5000)", + description: "A user bio maximum length. Default: 5000.", suggestions: [ 5_000 ] @@ -848,7 +859,7 @@ %{ key: :user_name_length, type: :integer, - description: "A user name maximum length (default: 100)", + description: "A user name maximum length. Default: 100.", suggestions: [ 100 ] @@ -856,13 +867,13 @@ %{ key: :skip_thread_containment, type: :boolean, - description: "Skip filter out broken threads. The default is true" + description: "Skip filter out broken threads. Default: `true`" }, %{ key: :limit_to_local_content, type: [:atom, false], description: - "Limit unauthenticated users to search for local statutes and users only. The default is :unauthenticated ", + "Limit unauthenticated users to search for local statutes and users only. Default: `:unauthenticated`.", suggestions: [ :unauthenticated, :all, @@ -872,7 +883,7 @@ %{ key: :max_account_fields, type: :integer, - description: "The maximum number of custom fields in the user profile (default: 10)", + description: "The maximum number of custom fields in the user profile. Default: 10.", suggestions: [ 10 ] @@ -881,7 +892,7 @@ key: :max_remote_account_fields, type: :integer, description: - "The maximum number of custom fields in the remote user profile (default: 20)", + "The maximum number of custom fields in the remote user profile. Default: 20.", suggestions: [ 20 ] @@ -889,7 +900,7 @@ %{ key: :account_field_name_length, type: :integer, - description: "An account field name maximum length (default: 512)", + description: "An account field name maximum length. Default: 512.", suggestions: [ 512 ] @@ -897,7 +908,7 @@ %{ key: :account_field_value_length, type: :integer, - description: "An account field value maximum length (default: 2048)", + description: "An account field value maximum length. Default: 2048.", suggestions: [ 2048 ] @@ -918,7 +929,7 @@ key: :backends, type: [:atom, :tuple, :module], description: - "Where logs will be send, :console - send logs to stdout, {ExSyslogger, :ex_syslogger} - to syslog, Quack.Logger - to Slack.", + "Where logs will be sent, :console - send logs to stdout, { ExSyslogger, :ex_syslogger } - to syslog, Quack.Logger - to Slack.", suggestions: [:console, {ExSyslogger, :ex_syslogger}, Quack.Logger] } ] @@ -945,7 +956,7 @@ %{ key: :format, type: :string, - description: "It defaults to \"$date $time [$level] $levelpad$node $metadata $message\"", + description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".", suggestions: ["$metadata[$level] $message"] }, %{ @@ -970,7 +981,7 @@ %{ key: :format, type: :string, - description: "It defaults to \"$date $time [$level] $levelpad$node $metadata $message\"", + description: "Default: \"$date $time [$level] $levelpad$node $metadata $message\".", suggestions: ["$metadata[$level] $message"] }, %{ @@ -1024,7 +1035,7 @@ description: "This form can be used to configure a keyword list that keeps the configuration data for any " <> "kind of frontend. By default, settings for pleroma_fe and masto_fe are configured. If you want to " <> - "add your own configuration your settings need to be complete as they will override the defaults.", + "add your own configuration your settings all fields must be complete.", children: [ %{ key: :pleroma_fe, @@ -1046,7 +1057,11 @@ hideUserStats: false, scopeCopy: true, subjectLineBehavior: "email", - alwaysShowSubjectInput: true + alwaysShowSubjectInput: true, + logoMask: false, + logoMargin: ".1em", + stickers: false, + enableEmojiPicker: false } ], children: [ @@ -1074,7 +1089,7 @@ label: "Redirect root no login", type: :string, description: - "relative URL which indicates where to redirect when a user isn't logged in", + "Relative URL which indicates where to redirect when a user isn't logged in", suggestions: ["/main/all"] }, %{ @@ -1082,7 +1097,7 @@ label: "Redirect root login", type: :string, description: - "relative URL which indicates where to redirect when a user is logged in", + "Relative URL which indicates where to redirect when a user is logged in", suggestions: ["/main/friends"] }, %{ @@ -1095,34 +1110,34 @@ key: :scopeOptionsEnabled, label: "Scope options enabled", type: :boolean, - description: "Enable setting an notice visibility and subject/CW when posting" + description: "Enable setting a notice visibility and subject/CW when posting" }, %{ key: :formattingOptionsEnabled, label: "Formatting options enabled", type: :boolean, description: - "Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to :instance, allowed_post_formats" + "Enable setting a formatting different than plain-text (ie. HTML, Markdown) when posting, relates to `:instance`, `allowed_post_formats`" }, %{ key: :collapseMessageWithSubject, label: "Collapse message with subject", type: :boolean, description: - "When a message has a subject(aka Content Warning), collapse it by default" + "When a message has a subject (aka Content Warning), collapse it by default" }, %{ key: :hidePostStats, label: "Hide post stats", type: :boolean, - description: "Hide notices statistics(repeats, favorites, ...)" + description: "Hide notices statistics (repeats, favorites, ...)" }, %{ key: :hideUserStats, label: "Hide user stats", type: :boolean, description: - "Hide profile statistics(posts, posts per day, followers, followings, ...)" + "Hide profile statistics (posts, posts per day, followers, followings, ...)" }, %{ key: :scopeCopy, @@ -1135,16 +1150,46 @@ label: "Subject line behavior", type: :string, description: "Allows changing the default behaviour of subject lines in replies. - `email`: Copy and preprend re:, as in email, - `masto`: Copy verbatim, as in Mastodon, - `noop`: Don't copy the subjec", + `email`: copy and preprend re:, as in email, + `masto`: copy verbatim, as in Mastodon, + `noop`: don't copy the subject.", suggestions: ["email", "masto", "noop"] }, %{ key: :alwaysShowSubjectInput, label: "Always show subject input", type: :boolean, - description: "When set to false, auto-hide the subject field when it's empty" + description: "When set to `false`, auto-hide the subject field when it's empty" + }, + %{ + key: :logoMask, + label: "Logo mask", + type: :boolean, + description: + "By default it assumes logo used will be monochrome-with-alpha one, this is done to be compatible with both light and dark themes, " <> + "so that white logo designed with dark theme in mind won't be invisible over light theme, this is done via CSS3 Masking. " <> + "Basically - it will take alpha channel of the image and fill non-transparent areas of it with solid color. " <> + "If you really want colorful logo - it can be done by setting logoMask to false." + }, + %{ + key: :logoMargin, + label: "Logo margin", + type: :string, + description: + "Allows you to adjust vertical margins between logo boundary and navbar borders. " <> + "The idea is that to have logo's image without any extra margins and instead adjust them to your need in layout.", + suggestions: [".1em"] + }, + %{ + key: :stickers, + type: :boolean, + description: "Enables/disables stickers." + }, + %{ + key: :enableEmojiPicker, + label: "Emoji picker", + type: :boolean, + description: "Enables/disables emoji picker." } ] }, @@ -1180,7 +1225,7 @@ key: :mascots, type: {:keyword, :map}, description: - "Keyword of mascots, each element MUST contain both a 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", @@ -1196,7 +1241,7 @@ key: :default_mascot, type: :atom, description: - "This will be used as the default mascot on MastoFE (default: :pleroma_fox_tan)", + "This will be used as the default mascot on MastoFE. Default: `:pleroma_fox_tan`", suggestions: [ :pleroma_fox_tan ] @@ -1259,7 +1304,7 @@ key: :media_nsfw, label: "Media NSFW", type: {:list, :string}, - description: "List of instances to put medias as NSFW(sensitive) from", + description: "List of instances to put medias as NSFW (sensitive) from", suggestions: ["example.com", "*.example.com"] }, %{ @@ -1334,12 +1379,12 @@ key: :allow_followersonly, label: "Allow followers-only", type: :boolean, - description: "whether to allow followers-only posts" + description: "Whether to allow followers-only posts" }, %{ key: :allow_direct, type: :boolean, - description: "whether to allow direct messages" + description: "Whether to allow direct messages" } ] }, @@ -1355,14 +1400,14 @@ 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", + " but it will not show up in public timelines and mentioned users won't get notifications about it). Set to 0 to disable.", suggestions: [10] }, %{ key: :reject_threshold, type: :integer, description: - "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable", + "Number of mentioned users after which the messaged gets rejected. Set to 0 to disable.", suggestions: [20] } ] @@ -1378,14 +1423,14 @@ key: :reject, type: [:string, :regex], description: - "A list of patterns which result in message being rejected, each pattern can be a string or a regular expression", + "A list of patterns which result in message being rejected, each pattern can be a string or a regular expression.", suggestions: ["foo", ~r/foo/iu] }, %{ key: :federated_timeline_removal, type: [:string, :regex], description: - "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression", + "A list of patterns which result in message being removed from federated timelines (a.k.a unlisted), each pattern can be a string or a regular expression.", suggestions: ["foo", ~r/foo/iu] }, %{ @@ -1464,7 +1509,7 @@ key: :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", + "The base URL to access a user-uploaded file. Useful when you want to proxy the media files via another host/CDN fronts.", suggestions: ["https://example.com"] }, %{ @@ -1485,14 +1530,14 @@ type: :boolean, description: "Redirects the client to the real remote URL if there's any HTTP errors. " <> - "Any error during body processing will not be redirected as the response is chunked" + "Any error during body processing will not be redirected as the response is chunked." }, %{ key: :max_body_length, type: :integer, description: - "limits the content length to be approximately the " <> - "specified length. It is validated with the `content-length` header and also verified when proxying" + "Limits the content length to be approximately the " <> + "specified length. It is validated with the `content-length` header and also verified when proxying." }, %{ key: :http, @@ -1810,9 +1855,9 @@ key: :subject, type: :string, description: - "a mailto link for the administrative contact." <> + "A mailto link for the administrative contact." <> " It's best if this email is not a personal email address, but rather a group email so that if a person leaves an organization," <> - " is unavailable for an extended period, or otherwise can't respond, someone else on the list can", + " is unavailable for an extended period, or otherwise can't respond, someone else on the list can.", suggestions: ["Subject"] }, %{ @@ -1860,12 +1905,12 @@ type: :group, description: "Kocaptcha is a very simple captcha service with a single API endpoint, the source code is" <> - " here: https://github.com/koto-bank/kocaptcha. The default endpoint https://captcha.kotobank.ch is hosted by the developer", + " here: https://github.com/koto-bank/kocaptcha. The default endpoint (https://captcha.kotobank.ch) is hosted by the developer.", children: [ %{ key: :endpoint, type: :string, - description: "the kocaptcha endpoint to use", + description: "The kocaptcha endpoint to use", suggestions: ["https://captcha.kotobank.ch"] } ] @@ -1874,7 +1919,7 @@ group: :pleroma, 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", + "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", children: [ %{ key: :admin_token, @@ -1924,8 +1969,9 @@ }, %{ key: :verbose, - type: :boolean, - description: "Logs verbose mode" + type: [:atom, false], + description: "Logs verbose mode", + suggestions: [false, :error, :warn, :info, :debug] }, %{ key: :prune, @@ -2040,7 +2086,7 @@ key: :unfurl_nsfw, label: "Unfurl NSFW", type: :boolean, - description: "If set to true nsfw attachments will be shown in previews" + description: "If set to `true` NSFW attachments will be shown in previews" } ] }, @@ -2084,7 +2130,7 @@ key: :ttl_setters, label: "TTL setters", type: {:list, :module}, - description: "List of rich media ttl setters.", + description: "List of rich media TTL setters.", suggestions: [ Pleroma.Web.RichMedia.Parser.TTL.AwsSignedUrl ] @@ -2101,12 +2147,12 @@ key: :enabled, type: :boolean, description: - "if enabled, when a new user is federated with, fetch some of their latest posts" + "If enabled, when a new user is federated with, fetch some of their latest posts" }, %{ key: :pages, type: :integer, - description: "the amount of pages to fetch", + description: "The amount of pages to fetch", suggestions: [5] } ] @@ -2120,24 +2166,24 @@ %{ key: :class, type: [:string, false], - description: "specify the class to be added to the generated link. false to clear", + description: "Specify the class to be added to the generated link. `False` to clear", suggestions: ["auto-linker", false] }, %{ key: :rel, type: [:string, false], - description: "override the rel attribute. false to clear", + description: "Override the rel attribute. `False` to clear", suggestions: ["ugc", "noopener noreferrer", false] }, %{ key: :new_window, type: :boolean, - description: "set to false to remove target='_blank' attribute" + description: "Set to `false` to remove target='_blank' attribute" }, %{ key: :scheme, type: :boolean, - description: "Set to true to link urls with schema http://google.com" + description: "Set to `true` to link urls with schema http://google.com" }, %{ key: :truncate, @@ -2154,7 +2200,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.)" } ] }, @@ -2168,20 +2214,20 @@ key: :daily_user_limit, type: :integer, description: - "the number of scheduled activities a user is allowed to create in a single day (Default: 25)", + "The number of scheduled activities a user is allowed to create in a single day. Default: 25.", suggestions: [25] }, %{ key: :total_user_limit, type: :integer, description: - "the number of scheduled activities a user is allowed to create in total (Default: 300)", + "The number of scheduled activities a user is allowed to create in total. Default: 300.", suggestions: [300] }, %{ key: :enabled, type: :boolean, - description: "whether scheduled activities are sent to the job queue to be executed" + description: "Whether scheduled activities are sent to the job queue to be executed" } ] }, @@ -2194,7 +2240,7 @@ %{ key: :enabled, type: :boolean, - description: "whether expired activities will be sent to the job queue to be deleted" + description: "Whether expired activities will be sent to the job queue to be deleted" } ] }, @@ -2216,14 +2262,14 @@ type: :group, description: "Use LDAP for user authentication. When a user logs in to the Pleroma instance, the name and password" <> - " will be verified by trying to authenticate (bind) to an LDAP server." <> + " will be verified by trying to authenticate (bind) to a LDAP server." <> " If a user exists in the LDAP directory but there is no account with the same name yet on the" <> " Pleroma instance then a new Pleroma account will be created with the same name as the LDAP user name.", children: [ %{ key: :enabled, type: :boolean, - description: "enables LDAP authentication" + description: "Enables LDAP authentication" }, %{ key: :host, @@ -2241,13 +2287,13 @@ key: :ssl, label: "SSL", type: :boolean, - description: "true to use SSL, usually implies the port 636" + description: "`True` to use SSL, usually implies the port 636" }, %{ key: :sslopts, label: "SSL options", type: :keyword, - description: "additional SSL options", + description: "Additional SSL options", suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer], children: [ %{ @@ -2268,13 +2314,13 @@ key: :tls, label: "TLS", type: :boolean, - description: "true to start TLS, usually implies the port 389" + description: "`True` to start TLS, usually implies the port 389" }, %{ key: :tlsopts, label: "TLS options", type: :keyword, - description: "additional TLS options", + description: "Additional TLS options", suggestions: [cacertfile: "path/to/file/with/PEM/cacerts", verify: :verify_peer], children: [ %{ @@ -2325,23 +2371,23 @@ key: :auth_template, type: :string, description: - "authentication form template. By default it's show.html which corresponds to lib/pleroma/web/templates/o_auth/o_auth/show.html.ee", + "Authentication form template. By default it's `show.html` which corresponds to `lib/pleroma/web/templates/o_auth/o_auth/show.html.ee`.", suggestions: ["show.html"] }, %{ key: :oauth_consumer_template, type: :string, description: - "OAuth consumer mode authentication form template. By default it's consumer.html which corresponds to" <> - " lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex", + "OAuth consumer mode authentication form template. By default it's `consumer.html` which corresponds to" <> + " `lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex`.", suggestions: ["consumer.html"] }, %{ key: :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." <> - " Each entry in this space-delimited string should be of format or :" <> + "The list of enabled OAuth consumer strategies; by default it's set by OAUTH_CONSUMER_STRATEGIES environment variable." <> + " Each entry in this space-delimited string should be of format \"strategy\" or \"strategy:dependency\"" <> " (e.g. twitter or keycloak:ueberauth_keycloak_strategy in case dependency is named differently than ueberauth_).", suggestions: ["twitter", "keycloak:ueberauth_keycloak_strategy"] } @@ -2370,13 +2416,13 @@ %{ key: :active, type: :boolean, - description: "globally enable or disable digest emails" + description: "Globally enable or disable digest emails" }, %{ key: :schedule, type: :string, description: - "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\"", + "When to send digest email, in crontab format. \"0 0 0\" is the default, meaning \"once a week at midnight on Sunday morning\".", suggestions: ["0 0 * * 0"] }, %{ @@ -2404,7 +2450,7 @@ %{ key: :logo, type: :string, - description: "a path to a custom logo. Set it to nil to use the default Pleroma logo", + description: "A path to a custom logo. Set it to `nil` to use the default Pleroma logo.", suggestions: ["some/path/logo.png"] }, %{ @@ -2477,13 +2523,13 @@ %{ key: :clean_expired_tokens, type: :boolean, - description: "Enable a background job to clean expired oauth tokens. Defaults to false" + description: "Enable a background job to clean expired oauth tokens. Default: `false`." }, %{ key: :clean_expired_tokens_interval, type: :integer, description: - "Interval to run the job to clean expired tokens. Defaults to 86_400_000 (24 hours).", + "Interval to run the job to clean expired tokens. Default: 86_400_000 (24 hours).", suggestions: [86_400_000] } ] @@ -2496,7 +2542,7 @@ %{ key: :shortcode_globs, type: {:list, :string}, - description: "Location of custom emoji files. * can be used as a wildcard", + description: "Location of custom emoji files. * can be used as a wildcard.", suggestions: ["/emoji/custom/**/*.png"] }, %{ @@ -2510,8 +2556,8 @@ key: :groups, type: {:keyword, :string, {:list, :string}}, description: - "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the groupname" <> - " and the value the location or array of locations. * can be used as a wildcard", + "Emojis are ordered in groups (tags). This is an array of key-value pairs where the key is the group name" <> + " and the value is the location or array of locations. * can be used as a wildcard.", suggestions: [ Custom: ["/emoji/*.png", "/emoji/**/*.png"] ] @@ -2521,7 +2567,7 @@ type: :string, description: "Location of the JSON-manifest. This manifest contains information about the emoji-packs you can download." <> - " Currently only one manifest can be added (no arrays)", + " Currently only one manifest can be added (no arrays).", suggestions: ["https://git.pleroma.social/pleroma/emoji-index/raw/master/index.json"] }, %{ @@ -2544,7 +2590,7 @@ %{ key: :rum_enabled, type: :boolean, - description: "If RUM indexes should be used. Defaults to false" + description: "If RUM indexes should be used. Default: `false`" } ] }, @@ -2558,45 +2604,45 @@ %{ key: :search, type: [:tuple, {:list, :tuple}], - description: "for the search requests (account & status search etc.)", + description: "For the search requests (account & status search etc.)", suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] }, %{ key: :app_account_creation, type: [:tuple, {:list, :tuple}], - description: "for registering user accounts from the same IP address", + description: "For registering user accounts from the same IP address", suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] }, %{ key: :relations_actions, type: [:tuple, {:list, :tuple}], - description: "for actions on relations with all users (follow, unfollow)", + description: "For actions on relations with all users (follow, unfollow)", suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] }, %{ key: :relation_id_action, type: [:tuple, {:list, :tuple}], - description: "for actions on relation with a specific user (follow, unfollow)", + description: "For actions on relation with a specific user (follow, unfollow)", suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] }, %{ key: :statuses_actions, type: [:tuple, {:list, :tuple}], description: - "for create / delete / fav / unfav / reblog / unreblog actions on any statuses", + "For create / delete / fav / unfav / reblog / unreblog actions on any statuses", suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] }, %{ key: :status_id_action, type: [:tuple, {:list, :tuple}], description: - "for fav / unfav or reblog / unreblog actions on the same status by the same user", + "For fav / unfav or reblog / unreblog actions on the same status by the same user", suggestions: [{1000, 10}, [{10_000, 10}, {10_000, 50}]] }, %{ key: :authentication, type: [:tuple, {:list, :tuple}], - description: "for authentication create / password check / user existence check requests", + description: "For authentication create / password check / user existence check requests", suggestions: [{60_000, 15}] } ] @@ -2611,12 +2657,12 @@ %{ key: :enabled, type: :boolean, - description: "Enables ssh" + description: "Enables SSH" }, %{ key: :priv_dir, type: :string, - description: "Dir with ssh keys", + description: "Dir with SSH keys", suggestions: ["/some/path/ssh_keys"] }, %{ @@ -2711,43 +2757,6 @@ } ] }, - %{ - group: :pleroma, - key: :suggestions, - type: :group, - children: [ - %{ - key: :enabled, - type: :boolean, - description: "Enables suggestions" - }, - %{ - key: :third_party_engine, - type: :string, - description: "URL for third party engine", - suggestions: [ - "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}" - ] - }, - %{ - key: :timeout, - type: :integer, - description: "Request timeout to third party engine", - suggestions: [300_000] - }, - %{ - key: :limit, - type: :integer, - description: "Limit for suggestions", - suggestions: [40] - }, - %{ - key: :web, - type: :string, - suggestions: ["https://vinayaka.distsn.org"] - } - ] - }, %{ group: :prometheus, key: Pleroma.Web.Endpoint.MetricsExporter, @@ -2795,7 +2804,7 @@ key: :user_agent, type: [:string, :atom], description: - "What user agent to use. Must be a string or an atom `:default`. Default value is `:default`", + "What user agent to use. Must be a string or an atom `:default`. Default value is `:default`.", suggestions: ["Pleroma", :default] }, %{ @@ -2967,19 +2976,19 @@ %{ key: :enabled, type: :boolean, - description: "Enable/disable the plug. Defaults to `false`." + description: "Enable/disable the plug. Default: `false`." }, %{ key: :headers, type: {:list, :string}, description: - "A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`." + "A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Default: `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`." }, %{ key: :proxies, type: {:list, :string}, description: - "A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`." + "A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Default: `[]`." }, %{ key: :reserved, @@ -3000,14 +3009,13 @@ key: :activity_pub, type: :integer, description: - "activity pub routes (except question activities). Defaults to `nil` (no expiration).", - suggestions: [30_000] + "Activity pub routes (except question activities). Default: `nil` (no expiration).", + suggestions: [30_000, nil] }, %{ key: :activity_pub_question, type: :integer, - description: - "activity pub routes (question activities). Defaults to `30_000` (30 seconds).", + description: "Activity pub routes (question activities). Default: `30_000` (30 seconds).", suggestions: [30_000] } ] diff --git a/docs/API/differences_in_mastoapi_responses.md b/docs/API/differences_in_mastoapi_responses.md index 3f75a13f7..82d967e4d 100644 --- a/docs/API/differences_in_mastoapi_responses.md +++ b/docs/API/differences_in_mastoapi_responses.md @@ -29,7 +29,7 @@ Has these additional fields under the `pleroma` object: - `spoiler_text`: a map consisting of alternate representations of the `spoiler_text` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain` - `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 count tuples. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint. +- `emoji_reactions`: A list with emoji / reaction maps. The format is `{emoji: "β˜•", count: 1, reacted: true}`. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint. ## Attachments @@ -101,6 +101,14 @@ The `type` value is `move`. Has an additional field: - `target`: new account +### EmojiReaction Notification + +The `type` value is `pleroma:emoji_reaction`. Has these fields: + +- `emoji`: The used emoji +- `account`: The account of the user who reacted +- `status`: The status that was reacted on + ## GET `/api/v1/notifications` Accepts additional parameters: diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 9ca8a5af0..c7125c1cd 100644 --- a/docs/API/pleroma_api.md +++ b/docs/API/pleroma_api.md @@ -455,7 +455,7 @@ Emoji reactions work a lot like favourites do. They make it possible to react to * Example Response: ```json [ - ["πŸ˜€", [{"id" => "xyz.."...}, {"id" => "zyx..."}]], - ["β˜•", [{"id" => "abc..."}]] + {"emoji": "πŸ˜€", "count": 2, "reacted": true, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}, + {"emoji": "β˜•", "count": 1, "reacted": false, "accounts": [{"id" => "abc..."}]} ] ``` diff --git a/docs/administration/CLI_tasks/email.md b/docs/administration/CLI_tasks/email.md new file mode 100644 index 000000000..7b7a8457a --- /dev/null +++ b/docs/administration/CLI_tasks/email.md @@ -0,0 +1,24 @@ +# Managing emails + +{! backend/administration/CLI_tasks/general_cli_task_info.include !} + +## Send test email (instance email by default) + +```sh tab="OTP" + ./bin/pleroma_ctl email test [--to ] +``` + +```sh tab="From Source" +mix pleroma.email test [--to ] +``` + + +Example: + +```sh tab="OTP" +./bin/pleroma_ctl email test --to root@example.org +``` + +```sh tab="From Source" +mix pleroma.email test --to root@example.org +``` diff --git a/docs/configuration/cheatsheet.md b/docs/configuration/cheatsheet.md index 30d673eba..ed9049a8d 100644 --- a/docs/configuration/cheatsheet.md +++ b/docs/configuration/cheatsheet.md @@ -69,6 +69,7 @@ You shouldn't edit the base config directly to avoid breakages and merge conflic * `account_field_name_length`: An account field name maximum length (default: `512`). * `account_field_value_length`: An account field value maximum length (default: `2048`). * `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. ## Federation ### MRF policies @@ -308,16 +309,15 @@ This will make Pleroma listen on `127.0.0.1` port `8080` and generate urls start Available options: * `enabled` - Enable/disable the plug. Defaults to `false`. -* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `~w[forwarded x-forwarded-for x-client-ip x-real-ip]`. +* `headers` - A list of strings naming the `req_headers` to use when deriving the `remote_ip`. Order does not matter. Defaults to `["x-forwarded-for"]`. * `proxies` - A list of strings in [CIDR](https://en.wikipedia.org/wiki/CIDR) notation specifying the IPs of known proxies. Defaults to `[]`. * `reserved` - Defaults to [localhost](https://en.wikipedia.org/wiki/Localhost) and [private network](https://en.wikipedia.org/wiki/Private_network). ### :rate_limit -This is an advanced feature and disabled by default. - -If your instance is behind a reverse proxy you must enable and configure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip). +!!! note + If your instance is behind a reverse proxy ensure [`Pleroma.Plugs.RemoteIp`](#pleroma-plugs-remoteip) is enabled (it is enabled by default). A keyword list of rate limiters where a key is a limiter name and value is the limiter configuration. The basic configuration is a tuple where: @@ -326,14 +326,31 @@ A keyword list of rate limiters where a key is a limiter name and value is the l It is also possible to have different limits for unauthenticated and authenticated users: the keyword value must be a list of two tuples where the first one is a config for unauthenticated users and the second one is for authenticated. +For example: + +```elixir +config :pleroma, :rate_limit, + authentication: {60_000, 15}, + search: [{1000, 10}, {1000, 30}] +``` + +Means that: + +1. In 60 seconds, 15 authentication attempts can be performed from the same IP address. +2. In 1 second, 10 search requests can be performed from the same IP adress by unauthenticated users, while authenticated users can perform 30 search requests per second. + Supported rate limiters: -* `:search` for the search requests (account & status search etc.) -* `:app_account_creation` for registering user accounts from the same IP address -* `:relations_actions` for actions on relations with all users (follow, unfollow) -* `:relation_id_action` for actions on relation with a specific user (follow, unfollow) -* `:statuses_actions` for create / delete / fav / unfav / reblog / unreblog actions on any statuses -* `:status_id_action` for fav / unfav or reblog / unreblog actions on the same status by the same user +* `:search` - Account/Status search. +* `:app_account_creation` - Account registration from the API. +* `:relations_actions` - Following/Unfollowing in general. +* `:relation_id_action` - Following/Unfollowing for a specific user. +* `:statuses_actions` - Status actions such as: (un)repeating, (un)favouriting, creating, deleting. +* `:status_id_action` - (un)Repeating/(un)Favouriting a particular status. +* `:authentication` - Authentication actions, i.e getting an OAuth token. +* `:password_reset` - Requesting password reset emails. +* `:account_confirmation_resend` - Requesting resending account confirmation emails. +* `:ap_routes` - Requesting statuses via ActivityPub. ### :web_cache_ttl diff --git a/docs/configuration/howto_user_recomendation.md b/docs/configuration/howto_user_recomendation.md deleted file mode 100644 index c4d749d0c..000000000 --- a/docs/configuration/howto_user_recomendation.md +++ /dev/null @@ -1,31 +0,0 @@ -# How to activate user recommendation (Who to follow panel) -![who-to-follow-panel-small](/uploads/9de1b1300436c32461d272945f1bc23e/who-to-follow-panel-small.png) - -To show the *who to follow* panel, edit `config/prod.secret.exs` in the Pleroma backend. Following code activates the *who to follow* panel: - -```elixir -config :pleroma, :suggestions, - enabled: true, - third_party_engine: - "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-match-suggestions-api.cgi?{{host}}+{{user}}", - timeout: 300_000, - limit: 40, - web: "https://vinayaka.distsn.org" - -``` - -`config/config.exs` already includes this code, but `enabled:` is `false`. - -`/api/v1/suggestions` is also provided when *who to follow* panel is enabled. - -For advanced customization, following code shows the newcomers of the fediverse at the *who to follow* panel: - -```elixir -config :pleroma, :suggestions, - enabled: true, - third_party_engine: - "http://vinayaka.distsn.org/cgi-bin/vinayaka-user-new-suggestions-api.cgi?{{host}}+{{user}}", - timeout: 60_000, - limit: 40, - web: "https://vinayaka.distsn.org/user-new.html" -``` diff --git a/docs/installation/openbsd_en.md b/docs/installation/openbsd_en.md index 45602bd75..e8c5d844c 100644 --- a/docs/installation/openbsd_en.md +++ b/docs/installation/openbsd_en.md @@ -1,6 +1,6 @@ # Installing on OpenBSD -This guide describes the installation and configuration of pleroma (and the required software to run it) on a single OpenBSD 6.4 server. +This guide describes the installation and configuration of pleroma (and the required software to run it) on a single OpenBSD 6.6 server. For any additional information regarding commands and configuration files mentioned here, check the man pages [online](https://man.openbsd.org/) or directly on your server with the man command. @@ -40,7 +40,12 @@ Enter a shell as the \_pleroma user. As root, run `su _pleroma -;cd`. Then clone #### PostgreSQL Start a shell as the \_postgresql user (as root run `su _postgresql -` then run the `initdb` command to initialize postgresql: -If you wish to not use the default location for postgresql's data (/var/postgresql/data), add the following switch at the end of the command: `-D ` and modify the `datadir` variable in the /etc/rc.d/postgresql script. +You will need to specify pgdata directory to the default (/var/postgresql/data) with the `-D ` and set the user to postgres with the `-U ` flag. This can be done as follows: + +``` +initdb -D /var/postgresql/data -U postgres +``` +If you are not using the default directory, you will have to update the `datadir` variable in the /etc/rc.d/postgresql script. When this is done, enable postgresql so that it starts on boot and start it. As root, run: ``` @@ -81,7 +86,6 @@ server "default" { } types { - include "/usr/share/misc/mime.types" } ``` Do not forget to change ** to your server's address(es). If httpd should only listen on one protocol family, comment one of the two first *listen* options. @@ -103,7 +107,7 @@ Insert the following configuration in /etc/acme-client.conf: authority letsencrypt- { #agreement url "https://letsencrypt.org/documents/LE-SA-v1.2-November-15-2017.pdf" - api url "https://acme-v01.api.letsencrypt.org/directory" + api url "https://acme-v02.api.letsencrypt.org/directory" account key "/etc/acme/letsencrypt-privkey-.pem" } @@ -222,7 +226,7 @@ Then follow the main installation guide: * run `mix deps.get` * run `mix pleroma.instance gen` and enter your instance's information when asked * copy config/generated\_config.exs to config/prod.secret.exs. The default values should be sufficient but you should edit it and check that everything seems OK. - * exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/config/setup_db.psql` to setup the database. + * exit your current shell back to a root one and run `psql -U postgres -f /home/_pleroma/pleroma/config/setup_db.psql` to setup the database. * return to a \_pleroma shell into pleroma's installation directory (`su _pleroma -;cd ~/pleroma`) and run `MIX_ENV=prod mix ecto.migrate` As \_pleroma in /home/\_pleroma/pleroma, you can now run `LC_ALL=en_US.UTF-8 MIX_ENV=prod mix phx.server` to start your instance. @@ -230,3 +234,11 @@ In another SSH session/tmux window, check that it is working properly by running ##### Starting pleroma at boot An rc script to automatically start pleroma at boot hasn't been written yet, it can be run in a tmux session (tmux is in base). + + +#### Create administrative user + +If your instance is up and running, you can create your first user with administrative rights with the following command as the \_pleroma user. +``` +LC_ALL=en_US.UTF-8 MIX_ENV=prod mix pleroma.user new --admin +``` diff --git a/docs/introduction.md b/docs/introduction.md index 045dc7c05..823e14f53 100644 --- a/docs/introduction.md +++ b/docs/introduction.md @@ -3,53 +3,63 @@ Pleroma is a federated social networking platform, compatible with GNU social, Mastodon and other OStatus and ActivityPub implementations. It is free software licensed under the AGPLv3. It actually consists of two components: a backend, named simply Pleroma, and a user-facing frontend, named Pleroma-FE. It also includes the Mastodon frontend, if that's your thing. It's part of what we call the fediverse, a federated network of instances which speak common protocols and can communicate with each other. -One account on a instance is enough to talk to the entire fediverse! +One account on an instance is enough to talk to the entire fediverse! ## How can I use it? -Pleroma instances are already widely deployed, a list can be found here: -http://distsn.org/pleroma-instances.html +Pleroma instances are already widely deployed, a list can be found at . Information on all existing fediverse instances can be found at . If you don't feel like joining an existing instance, but instead prefer to deploy your own instance, that's easy too! -Installation instructions can be found here: -[main Pleroma wiki](/) +Installation instructions can be found in the installation section of these docs. ## I got an account, now what? -Great! Now you can explore the fediverse! -- Open the login page for your Pleroma instance (for ex. https://pleroma.soykaf.com) and login with your username and password. -(If you don't have one yet, click on Register) :slightly_smiling_face: +Great! Now you can explore the fediverse! Open the login page for your Pleroma instance (e.g. ) and login with your username and password. (If you don't have an account yet, click on Register) At this point you will have two columns in front of you. ### Left column -- first block: here you can see your avatar, your nickname a bio, and statistics (Statuses, Following, Followers). -Under that you have a text form which allows you to post new statuses. The icon on the left is for uploading media files and attach them to your post. The number under the text form is a character counter, every instance can have a different character limit (the default is 5000). -If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. :slight_smile: + +- first block: here you can see your avatar, your nickname and statistics (Statuses, Following, Followers). Clicking your profile pic will open your profile. +Under that you have a text form which allows you to post new statuses. The number on the bottom of the text form is a character counter, every instance can have a different character limit (the default is 5000). +If you want to mention someone, type @ + name of the person. A drop-down menu will help you in finding the right person. +Under the text form there are also several visibility options and there is the option to use rich text. +Under that the icon on the left is for uploading media files and attach them to your post. There is also an emoji-picker and an option to post a poll. To post your status, simply press Submit. +On the top right you will also see a wrench icon. This opens your personal settings. - second block: Here you can switch between the different timelines: - - Timeline: all the people that you follow - - Mentions: all the statutes where you are mentioned - - Public Timeline: all the statutes from the local instance - - The Whole Known Network: everything, local and remote! - -- third block: this is the Chat block, where you communicate with people on the same instance in realtime. It is local-only, for now, but we're planning to make it extendable to the entire fediverse! :sweat_smile: - + - Timeline: all the people that you follow + - Interactions: here you can switch between different timelines where there was interaction with your account. There is Mentions, Repeats and Favorites, and New follows + - Direct Messages: these are the Direct Messages sent to you + - Public Timeline: all the statutes from the local instance + - The Whole Known Network: all public posts the instance knows about, both local and remote! + - About: This isn't a Timeline but shows relevant info about the instance. You can find a list of the moderators and admins, Terms of Service, MRF policies and enabled features. +- Optional third block: This is the Instance panel that can be activated, but is deactivated by default. It's fully customisable and by default has links to the pleroma-fe and Mastodon-fe. - fourth block: This is the Notifications block, here you will get notified whenever somebody mentions you, follows you, repeats or favorites one of your statuses. ### Right column -This is where the interesting stuff happens! :slight_smile: +This is where the interesting stuff happens! Depending on the timeline you will see different statuses, but each status has a standard structure: -- Icon + name + link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). -- A + button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime! -- A binocular icon allows you to open the status on the instance where it's originating from. -- The text of the status, including mentions. If you click on a mention, it will automatically open the profile page of that person. -- Four buttons (left to right): Reply, Repeat, Favorite, Delete. -## Mastodon interface -If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! :smile: -Just add a "/web" after your instance url (for ex. https://pleroma.soycaf.com/web) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! :fireworks: -For more information on the Mastodon interface, please look here: -https://github.com/tootsuite/documentation/blob/master/Using-Mastodon/User-guide.md +- Profile pic, name and link to profile. An optional left-arrow if it's a reply to another status (hovering will reveal the replied-to status). Clicking on the profile pic will uncollapse the user's profile. +- A `+` button on the right allows you to Expand/Collapse an entire discussion thread. It also updates in realtime! +- An arrow icon allows you to open the status on the instance where it's originating from. +- The text of the status, including mentions and attachements. If you click on a mention, it will automatically open the profile page of that person. +- Three buttons (left to right): Reply, Repeat, Favorite. There is also a forth button, this is a dropdown menu for simple moderation like muting the conversation or, if you have moderation rights, delete the status from the server. + +### Top right + +- The magnifier icon opens the search screen where you can search for statuses, people and hashtags. It's also possible to import statusses from remote servers by pasting the url to the post in the search field. +- The gear icon gives you general settings +- If you have admin rights, you'll see an icon that opens the admin interface +- The last icon is to log out + +### Bottom right +On the bottom right you have a chatbox. Here you can communicate with people on the same instance in realtime. It is local-only, for now, but there are plans to make it extendable to the entire fediverse! + +### Mastodon interface +If the Pleroma interface isn't your thing, or you're just trying something new but you want to keep using the familiar Mastodon interface, we got that too! +Just add a "/web" after your instance url (e.g. ) and you'll end on the Mastodon web interface, but with a Pleroma backend! MAGIC! +The Mastodon interface is from the Glitch-soc fork. For more information on the Mastodon interface you can check the [Mastodon](https://docs.joinmastodon.org/) and [Glitch-soc](https://glitch-soc.github.io/docs/) documentation. Remember, what you see is only the frontend part of Mastodon, the backend is still Pleroma. diff --git a/lib/mix/tasks/pleroma/config.ex b/lib/mix/tasks/pleroma/config.ex index 861832451..3e76d2c97 100644 --- a/lib/mix/tasks/pleroma/config.ex +++ b/lib/mix/tasks/pleroma/config.ex @@ -52,6 +52,9 @@ def migrate_to_db(file_path \\ nil) do defp do_migrate_to_db(config_file) do if File.exists?(config_file) do + Ecto.Adapters.SQL.query!(Repo, "TRUNCATE config;") + Ecto.Adapters.SQL.query!(Repo, "ALTER SEQUENCE config_id_seq RESTART;") + custom_config = config_file |> read_file() diff --git a/lib/mix/tasks/pleroma/email.ex b/lib/mix/tasks/pleroma/email.ex new file mode 100644 index 000000000..2c3801429 --- /dev/null +++ b/lib/mix/tasks/pleroma/email.ex @@ -0,0 +1,25 @@ +defmodule Mix.Tasks.Pleroma.Email do + use Mix.Task + + @shortdoc "Simple Email test" + @moduledoc File.read!("docs/administration/CLI_tasks/email.md") + + def run(["test" | args]) do + Mix.Pleroma.start_pleroma() + + {options, [], []} = + OptionParser.parse( + args, + strict: [ + to: :string + ] + ) + + email = Pleroma.Emails.AdminEmail.test_email(options[:to]) + {:ok, _} = Pleroma.Emails.Mailer.deliver(email) + + Mix.shell().info( + "Test email has been sent to #{inspect(email.to)} from #{inspect(email.from)}" + ) + end +end diff --git a/lib/mix/tasks/pleroma/emoji.ex b/lib/mix/tasks/pleroma/emoji.ex index 35669af27..24d999707 100644 --- a/lib/mix/tasks/pleroma/emoji.ex +++ b/lib/mix/tasks/pleroma/emoji.ex @@ -9,6 +9,7 @@ defmodule Mix.Tasks.Pleroma.Emoji do @moduledoc File.read!("docs/administration/CLI_tasks/emoji.md") def run(["ls-packs" | args]) do + Mix.Pleroma.start_pleroma() Application.ensure_all_started(:hackney) {options, [], []} = parse_global_opts(args) @@ -35,6 +36,7 @@ def run(["ls-packs" | args]) do end def run(["get-packs" | args]) do + Mix.Pleroma.start_pleroma() Application.ensure_all_started(:hackney) {options, pack_names, []} = parse_global_opts(args) diff --git a/lib/mix/tasks/pleroma/robotstxt.ex b/lib/mix/tasks/pleroma/robotstxt.ex index 2128e1cd6..e99dd8502 100644 --- a/lib/mix/tasks/pleroma/robotstxt.ex +++ b/lib/mix/tasks/pleroma/robotstxt.ex @@ -18,6 +18,7 @@ defmodule Mix.Tasks.Pleroma.RobotsTxt do """ def run(["disallow_all"]) do + Mix.Pleroma.start_pleroma() static_dir = Pleroma.Config.get([:instance, :static_dir], "instance/static/") if !File.exists?(static_dir) do diff --git a/lib/pleroma/activity.ex b/lib/pleroma/activity.ex index b7be7a800..c9294a716 100644 --- a/lib/pleroma/activity.ex +++ b/lib/pleroma/activity.ex @@ -30,7 +30,8 @@ defmodule Pleroma.Activity do "Follow" => "follow", "Announce" => "reblog", "Like" => "favourite", - "Move" => "move" + "Move" => "move", + "EmojiReaction" => "pleroma:emoji_reaction" } @mastodon_to_ap_notification_types for {k, v} <- @mastodon_notification_types, diff --git a/lib/pleroma/application.ex b/lib/pleroma/application.ex index e17068876..2c8889ce5 100644 --- a/lib/pleroma/application.ex +++ b/lib/pleroma/application.ex @@ -33,6 +33,7 @@ def user_agent do def start(_type, _args) do Pleroma.HTML.compile_scrubbers() Pleroma.Config.DeprecationWarnings.warn() + Pleroma.Plugs.HTTPSecurityPlug.warn_if_disabled() Pleroma.Repo.check_migrations_applied!() setup_instrumenters() load_custom_modules() diff --git a/lib/pleroma/config/config_db.ex b/lib/pleroma/config/config_db.ex index 91a1aa0cc..119251bee 100644 --- a/lib/pleroma/config/config_db.ex +++ b/lib/pleroma/config/config_db.ex @@ -236,15 +236,7 @@ def from_binary_with_convert(binary) do end @spec from_string(String.t()) :: atom() | no_return() - def from_string(":" <> entity), do: String.to_existing_atom(entity) - - def from_string(entity) when is_binary(entity) do - if is_module_name?(entity) do - String.to_existing_atom("Elixir.#{entity}") - else - entity - end - end + def from_string(string), do: do_transform_string(string) @spec convert(any()) :: any() def convert(entity), do: do_convert(entity) @@ -416,7 +408,7 @@ defp do_transform_string(value) do @spec is_module_name?(String.t()) :: boolean() def is_module_name?(string) do - Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth)\./, string) or + Regex.match?(~r/^(Pleroma|Phoenix|Tesla|Quack|Ueberauth|Swoosh)\./, string) or string in ["Oban", "Ueberauth", "ExSyslogger"] end end diff --git a/lib/pleroma/emails/admin_email.ex b/lib/pleroma/emails/admin_email.ex index b15e4041b..5f23345f7 100644 --- a/lib/pleroma/emails/admin_email.ex +++ b/lib/pleroma/emails/admin_email.ex @@ -7,6 +7,7 @@ defmodule Pleroma.Emails.AdminEmail do import Swoosh.Email + alias Pleroma.Config alias Pleroma.Web.Router.Helpers defp instance_config, do: Pleroma.Config.get(:instance) @@ -17,7 +18,20 @@ defp instance_notify_email do end defp user_url(user) do - Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id) + Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, user.id) + end + + def test_email(mail_to \\ nil) do + html_body = """ +

Instance Test Email

+

A test email was requested. Hello. :)

+ """ + + new() + |> to(mail_to || Config.get([:instance, :email])) + |> from({instance_name(), instance_notify_email()}) + |> subject("Instance Test Email") + |> html_body(html_body) end def report(to, reporter, account, statuses, comment) do diff --git a/lib/pleroma/notification.ex b/lib/pleroma/notification.ex index 8f3e46af9..d04a65a1e 100644 --- a/lib/pleroma/notification.ex +++ b/lib/pleroma/notification.ex @@ -294,7 +294,7 @@ def create_notifications(%Activity{data: %{"to" => _, "type" => "Create"}} = act end def create_notifications(%Activity{data: %{"type" => type}} = activity) - when type in ["Like", "Announce", "Follow", "Move"] do + when type in ["Like", "Announce", "Follow", "Move", "EmojiReaction"] do notifications = activity |> get_notified_from_activity() @@ -322,7 +322,7 @@ def create_notification(%Activity{} = activity, %User{} = user) do def get_notified_from_activity(activity, local_only \\ true) def get_notified_from_activity(%Activity{data: %{"type" => type}} = activity, local_only) - when type in ["Create", "Like", "Announce", "Follow", "Move"] do + when type in ["Create", "Like", "Announce", "Follow", "Move", "EmojiReaction"] do [] |> Utils.maybe_notify_to_recipients(activity) |> Utils.maybe_notify_mentioned_recipients(activity) diff --git a/lib/pleroma/object.ex b/lib/pleroma/object.ex index 38e372f6d..52556bf31 100644 --- a/lib/pleroma/object.ex +++ b/lib/pleroma/object.ex @@ -184,11 +184,14 @@ def delete(%Object{data: %{"id" => id}} = object) do with {:ok, _obj} = swap_object_with_tombstone(object), deleted_activity = Activity.delete_all_by_object_ap_id(id), {:ok, true} <- Cachex.del(:object_cache, "object:#{id}"), - {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path), - {:ok, _} <- - Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{ - "object" => object - }) do + {:ok, _} <- Cachex.del(:web_resp_cache, URI.parse(id).path) do + with true <- Pleroma.Config.get([:instance, :cleanup_attachments]) do + {:ok, _} = + Pleroma.Workers.AttachmentsCleanupWorker.enqueue("cleanup_attachments", %{ + "object" => object + }) + end + {:ok, object, deleted_activity} end end diff --git a/lib/pleroma/plugs/http_security_plug.ex b/lib/pleroma/plugs/http_security_plug.ex index a7cc22831..b04273979 100644 --- a/lib/pleroma/plugs/http_security_plug.ex +++ b/lib/pleroma/plugs/http_security_plug.ex @@ -6,6 +6,8 @@ defmodule Pleroma.Plugs.HTTPSecurityPlug do alias Pleroma.Config import Plug.Conn + require Logger + def init(opts), do: opts def call(conn, _options) do @@ -90,6 +92,51 @@ defp csp_string do |> Enum.join("; ") end + def warn_if_disabled do + unless Config.get([:http_security, :enabled]) do + Logger.warn(" + .i;;;;i. + iYcviii;vXY: + .YXi .i1c. + .YC. . in7. + .vc. ...... ;1c. + i7, .. .;1; + i7, .. ... .Y1i + ,7v .6MMM@; .YX, + .7;. ..IMMMMMM1 :t7. + .;Y. ;$MMMMMM9. :tc. + vY. .. .nMMM@MMU. ;1v. + i7i ... .#MM@M@C. .....:71i + it: .... $MMM@9;.,i;;;i,;tti + :t7. ..... 0MMMWv.,iii:::,,;St. + .nC. ..... IMMMQ..,::::::,.,czX. + .ct: ....... .ZMMMI..,:::::::,,:76Y. + c2: ......,i..Y$M@t..:::::::,,..inZY + vov ......:ii..c$MBc..,,,,,,,,,,..iI9i + i9Y ......iii:..7@MA,..,,,,,,,,,....;AA: + iIS. ......:ii::..;@MI....,............;Ez. + .I9. ......:i::::...8M1..................C0z. + .z9; ......:i::::,.. .i:...................zWX. + vbv ......,i::::,,. ................. :AQY + c6Y. .,...,::::,,..:t0@@QY. ................ :8bi + :6S. ..,,...,:::,,,..EMMMMMMI. ............... .;bZ, + :6o, .,,,,..:::,,,..i#MMMMMM#v................. YW2. + .n8i ..,,,,,,,::,,,,.. tMMMMM@C:.................. .1Wn + 7Uc. .:::,,,,,::,,,,.. i1t;,..................... .UEi + 7C...::::::::::::,,,,.. .................... vSi. + ;1;...,,::::::,......... .................. Yz: + v97,......... .voC. + izAotX7777777777777777777777777777777777777777Y7n92: + .;CoIIIIIUAA666666699999ZZZZZZZZZZZZZZZZZZZZ6ov. + +HTTP Security is disabled. Please re-enable it to prevent users from attacking +your instance and your users via malicious posts: + + config :pleroma, :http_security, enabled: true + ") + end + end + defp maybe_send_sts_header(conn, true) do max_age_sts = Config.get([:http_security, :sts_max_age]) max_age_ct = Config.get([:http_security, :ct_max_age]) diff --git a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex index d720508c8..7fb92489c 100644 --- a/lib/pleroma/plugs/rate_limiter/rate_limiter.ex +++ b/lib/pleroma/plugs/rate_limiter/rate_limiter.ex @@ -67,6 +67,8 @@ defmodule Pleroma.Plugs.RateLimiter do alias Pleroma.Plugs.RateLimiter.LimiterSupervisor alias Pleroma.User + require Logger + def init(opts) do limiter_name = Keyword.get(opts, :name) @@ -89,18 +91,39 @@ def init(opts) do def call(conn, nil), do: conn def call(conn, settings) do - settings - |> incorporate_conn_info(conn) - |> check_rate() - |> case do - {:ok, _count} -> + case disabled?() do + true -> + if Pleroma.Config.get(:env) == :prod, + do: Logger.warn("Rate limiter is disabled for localhost/socket") + conn - {:error, _count} -> - render_throttled_error(conn) + false -> + settings + |> incorporate_conn_info(conn) + |> check_rate() + |> case do + {:ok, _count} -> + conn + + {:error, _count} -> + render_throttled_error(conn) + end end end + def disabled? do + localhost_or_socket = + Pleroma.Config.get([Pleroma.Web.Endpoint, :http, :ip]) + |> Tuple.to_list() + |> Enum.join(".") + |> String.match?(~r/^local|^127.0.0.1/) + + remote_ip_disabled = not Pleroma.Config.get([Pleroma.Plugs.RemoteIp, :enabled]) + + localhost_or_socket and remote_ip_disabled + end + def inspect_bucket(conn, name_root, settings) do settings = settings diff --git a/lib/pleroma/plugs/remote_ip.ex b/lib/pleroma/plugs/remote_ip.ex index fdedc27ee..1cd5af48a 100644 --- a/lib/pleroma/plugs/remote_ip.ex +++ b/lib/pleroma/plugs/remote_ip.ex @@ -10,10 +10,7 @@ defmodule Pleroma.Plugs.RemoteIp do @behaviour Plug @headers ~w[ - forwarded x-forwarded-for - x-client-ip - x-real-ip ] # https://en.wikipedia.org/wiki/Localhost diff --git a/lib/pleroma/plugs/user_enabled_plug.ex b/lib/pleroma/plugs/user_enabled_plug.ex index 8d102ee5b..7b304eebc 100644 --- a/lib/pleroma/plugs/user_enabled_plug.ex +++ b/lib/pleroma/plugs/user_enabled_plug.ex @@ -11,11 +11,9 @@ def init(options) do end def call(%{assigns: %{user: %User{} = user}} = conn, _) do - if User.auth_active?(user) do - conn - else - conn - |> assign(:user, nil) + case User.account_status(user) do + :active -> conn + _ -> assign(conn, :user, nil) end end diff --git a/lib/pleroma/user.ex b/lib/pleroma/user.ex index 430f04ae9..3c86cdb38 100644 --- a/lib/pleroma/user.ex +++ b/lib/pleroma/user.ex @@ -12,6 +12,7 @@ defmodule Pleroma.User do alias Comeonin.Pbkdf2 alias Ecto.Multi alias Pleroma.Activity + alias Pleroma.Config alias Pleroma.Conversation.Participation alias Pleroma.Delivery alias Pleroma.FollowingRelationship @@ -35,7 +36,7 @@ defmodule Pleroma.User do require Logger @type t :: %__MODULE__{} - + @type account_status :: :active | :deactivated | :password_reset_pending | :confirmation_pending @primary_key {:id, FlakeId.Ecto.CompatType, autogenerate: true} # credo:disable-for-next-line Credo.Check.Readability.MaxLineLength @@ -216,14 +217,21 @@ def unquote(:"#{outgoing_relation_target}_ap_ids")(user, restrict_deactivated? \ end end - @doc "Returns if the user should be allowed to authenticate" - def auth_active?(%User{deactivated: true}), do: false + @doc "Returns status account" + @spec account_status(User.t()) :: account_status() + def account_status(%User{deactivated: true}), do: :deactivated + def account_status(%User{password_reset_pending: true}), do: :password_reset_pending - def auth_active?(%User{confirmation_pending: true}), - do: !Pleroma.Config.get([:instance, :account_activation_required]) + def account_status(%User{confirmation_pending: true}) do + case Config.get([:instance, :account_activation_required]) do + true -> :confirmation_pending + _ -> :active + end + end - def auth_active?(%User{}), do: true + def account_status(%User{}), do: :active + @spec visible_for?(User.t(), User.t() | nil) :: boolean() def visible_for?(user, for_user \\ nil) def visible_for?(%User{invisible: true}, _), do: false @@ -231,15 +239,17 @@ def visible_for?(%User{invisible: true}, _), do: false def visible_for?(%User{id: user_id}, %User{id: for_id}) when user_id == for_id, do: true def visible_for?(%User{} = user, for_user) do - auth_active?(user) || superuser?(for_user) + account_status(user) == :active || superuser?(for_user) end def visible_for?(_, _), do: false + @spec superuser?(User.t()) :: boolean() def superuser?(%User{local: true, is_admin: true}), do: true def superuser?(%User{local: true, is_moderator: true}), do: true def superuser?(_), do: false + @spec invisible?(User.t()) :: boolean() def invisible?(%User{invisible: true}), do: true def invisible?(_), do: false @@ -1502,7 +1512,7 @@ def insert_or_update_user(data) do data |> Map.put(:name, blank?(data[:name]) || data[:nickname]) |> remote_user_creation() - |> Repo.insert(on_conflict: :replace_all_except_primary_key, conflict_target: :nickname) + |> Repo.insert(on_conflict: {:replace_all_except, [:id]}, conflict_target: :nickname) |> set_cache() end diff --git a/lib/pleroma/user_relationship.ex b/lib/pleroma/user_relationship.ex index 24c724549..3149e10e9 100644 --- a/lib/pleroma/user_relationship.ex +++ b/lib/pleroma/user_relationship.ex @@ -58,7 +58,7 @@ def create(relationship_type, %User{} = source, %User{} = target) do target_id: target.id }) |> Repo.insert( - on_conflict: :replace_all_except_primary_key, + on_conflict: {:replace_all_except, [:id]}, conflict_target: [:source_id, :relationship_type, :target_id] ) end diff --git a/lib/pleroma/web/activity_pub/activity_pub.ex b/lib/pleroma/web/activity_pub/activity_pub.ex index 2e9d56ee5..5c436941a 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -325,12 +325,14 @@ def update(%{to: to, cc: cc, actor: actor, object: object} = params) do def react_with_emoji(user, object, emoji, options \\ []) do with local <- Keyword.get(options, :local, true), activity_id <- Keyword.get(options, :activity_id, nil), - Pleroma.Emoji.is_unicode_emoji?(emoji), + true <- Pleroma.Emoji.is_unicode_emoji?(emoji), reaction_data <- make_emoji_reaction_data(user, object, emoji, activity_id), {:ok, activity} <- insert(reaction_data, local), {:ok, object} <- add_emoji_reaction_to_object(activity, object), :ok <- maybe_federate(activity) do {:ok, activity, object} + else + e -> {:error, e} end end @@ -345,6 +347,8 @@ def unreact_with_emoji(user, reaction_id, options \\ []) do {:ok, object} <- remove_emoji_reaction_from_object(reaction_activity, object), :ok <- maybe_federate(activity) do {:ok, activity, object} + else + e -> {:error, e} end end @@ -728,7 +732,6 @@ def fetch_user_abstract_activities(user, reading_user, params \\ %{}) do params |> Map.put("user", reading_user) |> Map.put("actor_id", user.ap_id) - |> Map.put("whole_db", true) recipients = user_activities_recipients(%{ @@ -746,7 +749,6 @@ def fetch_user_activities(user, reading_user, params \\ %{}) do |> Map.put("type", ["Create", "Announce"]) |> Map.put("user", reading_user) |> Map.put("actor_id", user.ap_id) - |> Map.put("whole_db", true) |> Map.put("pinned_activity_ids", user.pinned_activities) params = @@ -773,7 +775,6 @@ def fetch_instance_activities(params) do params |> Map.put("type", ["Create", "Announce"]) |> Map.put("instance", params["instance"]) - |> Map.put("whole_db", true) fetch_activities([Pleroma.Constants.as_public()], params, :offset) |> Enum.reverse() diff --git a/lib/pleroma/web/activity_pub/utils.ex b/lib/pleroma/web/activity_pub/utils.ex index 4def431f1..4f7fdaf38 100644 --- a/lib/pleroma/web/activity_pub/utils.ex +++ b/lib/pleroma/web/activity_pub/utils.ex @@ -337,7 +337,7 @@ def add_emoji_reaction_to_object( %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || [] + reactions = get_cached_emoji_reactions(object) new_reactions = case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do @@ -365,7 +365,7 @@ def remove_emoji_reaction_from_object( %Activity{data: %{"content" => emoji, "actor" => actor}}, object ) do - reactions = object.data["reactions"] || [] + reactions = get_cached_emoji_reactions(object) new_reactions = case Enum.find_index(reactions, fn [candidate, _] -> emoji == candidate end) do @@ -385,6 +385,14 @@ def remove_emoji_reaction_from_object( update_element_in_object("reaction", new_reactions, object, count) end + def get_cached_emoji_reactions(object) do + if is_list(object.data["reactions"]) do + object.data["reactions"] + else + [] + end + end + @spec add_like_to_object(Activity.t(), Object.t()) :: {:ok, Object.t()} | {:error, Ecto.Changeset.t()} def add_like_to_object(%Activity{data: %{"actor" => actor}}, object) do diff --git a/lib/pleroma/web/controller_helper.ex b/lib/pleroma/web/controller_helper.ex index 9a4e322c9..e3d7a465b 100644 --- a/lib/pleroma/web/controller_helper.ex +++ b/lib/pleroma/web/controller_helper.ex @@ -76,8 +76,7 @@ def assign_account_by_id(%{params: %{"id" => id}} = conn, _) do end end - def try_render(conn, target, params) - when is_binary(target) do + def try_render(conn, target, params) when is_binary(target) do case render(conn, target, params) do nil -> render_error(conn, :not_implemented, "Can't display this activity") res -> res @@ -87,4 +86,8 @@ def try_render(conn, target, params) def try_render(conn, _, _) do render_error(conn, :not_implemented, "Can't display this activity") end + + @spec put_in_if_exist(map(), atom() | String.t(), any) :: map() + def put_in_if_exist(map, _key, nil), do: map + def put_in_if_exist(map, key, value), do: put_in(map, key, value) end diff --git a/lib/pleroma/web/feed/feed_view.ex b/lib/pleroma/web/feed/feed_view.ex index bb1332fd3..334802e0a 100644 --- a/lib/pleroma/web/feed/feed_view.ex +++ b/lib/pleroma/web/feed/feed_view.ex @@ -13,21 +13,53 @@ defmodule Pleroma.Web.Feed.FeedView do require Pleroma.Constants - def prepare_activity(activity) do + @spec pub_date(String.t() | DateTime.t()) :: String.t() + def pub_date(date) when is_binary(date) do + date + |> Timex.parse!("{ISO:Extended}") + |> pub_date + end + + def pub_date(%DateTime{} = date), do: Timex.format!(date, "{RFC822}") + + def prepare_activity(activity, opts \\ []) do object = activity_object(activity) + actor = + if opts[:actor] do + Pleroma.User.get_cached_by_ap_id(activity.actor) + end + %{ activity: activity, data: Map.get(object, :data), - object: object + object: object, + actor: actor } end + def most_recent_update(activities) do + with %{updated_at: updated_at} <- List.first(activities) do + NaiveDateTime.to_iso8601(updated_at) + end + end + def most_recent_update(activities, user) do (List.first(activities) || user).updated_at |> NaiveDateTime.to_iso8601() end + def feed_logo do + case Pleroma.Config.get([:feed, :logo]) do + nil -> + "#{Pleroma.Web.base_url()}/static/logo.png" + + logo -> + "#{Pleroma.Web.base_url()}#{logo}" + end + |> MediaProxy.url() + end + def logo(user) do user |> User.avatar_url() @@ -40,6 +72,8 @@ def activity_object(activity), do: Object.normalize(activity) def activity_title(%{data: %{"content" => content}}, opts \\ %{}) do content + |> Pleroma.Web.Metadata.Utils.scrub_html() + |> Pleroma.Emoji.Formatter.demojify() |> Formatter.truncate(opts[:max_length], opts[:omission]) |> escape() end @@ -50,6 +84,8 @@ def activity_content(%{data: %{"content" => content}}) do |> escape() end + def activity_content(_), do: "" + def activity_context(activity), do: activity.data["context"] def attachment_href(attachment) do diff --git a/lib/pleroma/web/feed/tag_controller.ex b/lib/pleroma/web/feed/tag_controller.ex new file mode 100644 index 000000000..9accd0872 --- /dev/null +++ b/lib/pleroma/web/feed/tag_controller.ex @@ -0,0 +1,41 @@ +# Pleroma: A lightweight social networking server +# Copyright Β© 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.TagController do + use Pleroma.Web, :controller + + alias Pleroma.Config + alias Pleroma.Web.ActivityPub.ActivityPub + alias Pleroma.Web.Feed.FeedView + + import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3] + + def feed(conn, %{"tag" => raw_tag} = params) do + {format, tag} = parse_tag(raw_tag) + + activities = + %{"type" => ["Create"], "tag" => tag} + |> put_in_if_exist("max_id", params["max_id"]) + |> ActivityPub.fetch_public_activities() + + conn + |> put_resp_content_type("application/atom+xml") + |> put_view(FeedView) + |> render("tag.#{format}", + activities: activities, + tag: tag, + feed_config: Config.get([:feed]) + ) + end + + @spec parse_tag(binary() | any()) :: {format :: String.t(), tag :: String.t()} + defp parse_tag(raw_tag) when is_binary(raw_tag) do + case Enum.reverse(String.split(raw_tag, ".")) do + [format | tag] when format in ["atom", "rss"] -> {format, Enum.join(tag, ".")} + _ -> {"rss", raw_tag} + end + end + + defp parse_tag(raw_tag), do: {"rss", raw_tag} +end diff --git a/lib/pleroma/web/feed/feed_controller.ex b/lib/pleroma/web/feed/user_controller.ex similarity index 84% rename from lib/pleroma/web/feed/feed_controller.ex rename to lib/pleroma/web/feed/user_controller.ex index d0e23007d..f5096834b 100644 --- a/lib/pleroma/web/feed/feed_controller.ex +++ b/lib/pleroma/web/feed/user_controller.ex @@ -2,13 +2,16 @@ # Copyright Β© 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.Feed.FeedController do +defmodule Pleroma.Web.Feed.UserController do use Pleroma.Web, :controller alias Fallback.RedirectController alias Pleroma.User alias Pleroma.Web.ActivityPub.ActivityPub alias Pleroma.Web.ActivityPub.ActivityPubController + alias Pleroma.Web.Feed.FeedView + + import Pleroma.Web.ControllerHelper, only: [put_in_if_exist: 3] plug(Pleroma.Plugs.SetFormatPlug when action in [:feed_redirect]) @@ -27,7 +30,7 @@ def feed_redirect(%{assigns: %{format: format}} = conn, _params) def feed_redirect(conn, %{"nickname" => nickname}) do with {_, %User{} = user} <- {:fetch_user, User.get_cached_by_nickname(nickname)} do - redirect(conn, external: "#{feed_url(conn, :feed, user.nickname)}.atom") + redirect(conn, external: "#{user_feed_url(conn, :feed, user.nickname)}.atom") end end @@ -36,15 +39,15 @@ def feed(conn, %{"nickname" => nickname} = params) do activities = %{ "type" => ["Create"], - "whole_db" => true, "actor_id" => user.ap_id } - |> Map.merge(Map.take(params, ["max_id"])) + |> put_in_if_exist("max_id", params["max_id"]) |> ActivityPub.fetch_public_activities() conn |> put_resp_content_type("application/atom+xml") - |> render("feed.xml", + |> put_view(FeedView) + |> render("user.xml", user: user, activities: activities, feed_config: Pleroma.Config.get([:feed]) diff --git a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex index fe71c36af..b9cc8f104 100644 --- a/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex +++ b/lib/pleroma/web/mastodon_api/controllers/suggestion_controller.ex @@ -7,62 +7,8 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionController do require Logger - alias Pleroma.Config - alias Pleroma.Plugs.OAuthScopesPlug - alias Pleroma.User - alias Pleroma.Web.MediaProxy - - action_fallback(Pleroma.Web.MastodonAPI.FallbackController) - - plug(OAuthScopesPlug, %{scopes: ["read"]} when action == :index) - - plug(Pleroma.Plugs.EnsurePublicOrAuthenticatedPlug) - @doc "GET /api/v1/suggestions" - def index(%{assigns: %{user: user}} = conn, _) do - if Config.get([:suggestions, :enabled], false) do - with {:ok, data} <- fetch_suggestions(user) do - limit = Config.get([:suggestions, :limit], 23) - - data = - data - |> Enum.slice(0, limit) - |> Enum.map(fn x -> - x - |> Map.put("id", fetch_suggestion_id(x)) - |> Map.put("avatar", MediaProxy.url(x["avatar"])) - |> Map.put("avatar_static", MediaProxy.url(x["avatar_static"])) - end) - - json(conn, data) - end - else - json(conn, []) - end - end - - defp fetch_suggestions(user) do - api = Config.get([:suggestions, :third_party_engine], "") - timeout = Config.get([:suggestions, :timeout], 5000) - host = Config.get([Pleroma.Web.Endpoint, :url, :host]) - - url = - api - |> String.replace("{{host}}", host) - |> String.replace("{{user}}", user.nickname) - - with {:ok, %{status: 200, body: body}} <- - Pleroma.HTTP.get(url, [], adapter: [recv_timeout: timeout, pool: :default]) do - Jason.decode(body) - else - e -> Logger.error("Could not retrieve suggestions at fetch #{url}, #{inspect(e)}") - end - end - - defp fetch_suggestion_id(attrs) do - case User.get_or_fetch(attrs["acct"]) do - {:ok, %User{id: id}} -> id - _ -> 0 - end + def index(conn, _) do + json(conn, []) end end diff --git a/lib/pleroma/web/mastodon_api/views/app_view.ex b/lib/pleroma/web/mastodon_api/views/app_view.ex index f52b693a6..beba89edb 100644 --- a/lib/pleroma/web/mastodon_api/views/app_view.ex +++ b/lib/pleroma/web/mastodon_api/views/app_view.ex @@ -7,10 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.AppView do alias Pleroma.Web.OAuth.App - @vapid_key :web_push_encryption - |> Application.get_env(:vapid_details, []) - |> Keyword.get(:public_key) - def render("show.json", %{app: %App{} = app}) do %{ id: app.id |> to_string, @@ -32,8 +28,10 @@ def render("short.json", %{app: %App{website: webiste, client_name: name}}) do end defp with_vapid_key(data) do - if @vapid_key do - Map.put(data, "vapid_key", @vapid_key) + vapid_key = Application.get_env(:web_push_encryption, :vapid_details, [])[:public_key] + + if vapid_key do + Map.put(data, "vapid_key", vapid_key) else data end diff --git a/lib/pleroma/web/mastodon_api/views/notification_view.ex b/lib/pleroma/web/mastodon_api/views/notification_view.ex index ddd7f5318..360ec10f0 100644 --- a/lib/pleroma/web/mastodon_api/views/notification_view.ex +++ b/lib/pleroma/web/mastodon_api/views/notification_view.ex @@ -37,18 +37,37 @@ def render("show.json", %{ } case mastodon_type do - "mention" -> put_status(response, activity, user) - "favourite" -> put_status(response, parent_activity, user) - "reblog" -> put_status(response, parent_activity, user) - "move" -> put_target(response, activity, user) - "follow" -> response - _ -> nil + "mention" -> + put_status(response, activity, user) + + "favourite" -> + put_status(response, parent_activity, user) + + "reblog" -> + put_status(response, parent_activity, user) + + "move" -> + put_target(response, activity, user) + + "follow" -> + response + + "pleroma:emoji_reaction" -> + put_status(response, parent_activity, user) |> put_emoji(activity) + + _ -> + nil end else _ -> nil end end + defp put_emoji(response, activity) do + response + |> Map.put(:emoji, activity.data["content"]) + end + defp put_status(response, activity, user) do Map.put(response, :status, StatusView.render("show.json", %{activity: activity, for: user})) end diff --git a/lib/pleroma/web/mastodon_api/views/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 64a97896a..5df29d93f 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -256,7 +256,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity} emoji_reactions = with %{data: %{"reactions" => emoji_reactions}} <- object do Enum.map(emoji_reactions, fn [emoji, users] -> - [emoji, length(users)] + %{ + emoji: emoji, + count: length(users), + reacted: !!(opts[:for] && opts[:for].ap_id in users) + } end) else _ -> [] diff --git a/lib/pleroma/web/metadata/feed.ex b/lib/pleroma/web/metadata/feed.ex index 8043e6c54..ee48913a7 100644 --- a/lib/pleroma/web/metadata/feed.ex +++ b/lib/pleroma/web/metadata/feed.ex @@ -16,7 +16,7 @@ def build_tags(%{user: user}) do [ rel: "alternate", type: "application/atom+xml", - href: Helpers.feed_path(Endpoint, :feed, user.nickname) <> ".atom" + href: Helpers.user_feed_path(Endpoint, :feed, user.nickname) <> ".atom" ], []} ] end diff --git a/lib/pleroma/web/metadata/utils.ex b/lib/pleroma/web/metadata/utils.ex index 589d11901..000bd9f66 100644 --- a/lib/pleroma/web/metadata/utils.ex +++ b/lib/pleroma/web/metadata/utils.ex @@ -21,15 +21,22 @@ def scrub_html_and_truncate(%{data: %{"content" => content}} = object) do def scrub_html_and_truncate(content, max_length \\ 200) when is_binary(content) do content - # html content comes from DB already encoded, decode first and scrub after - |> HtmlEntities.decode() - |> String.replace(~r//, " ") - |> HTML.strip_tags() + |> scrub_html |> Emoji.Formatter.demojify() |> HtmlEntities.decode() |> Formatter.truncate(max_length) end + def scrub_html(content) when is_binary(content) do + content + # html content comes from DB already encoded, decode first and scrub after + |> HtmlEntities.decode() + |> String.replace(~r//, " ") + |> HTML.strip_tags() + end + + def scrub_html(content), do: content + def attachment_url(url) do MediaProxy.url(url) end diff --git a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex index abcf46034..03c35cc2a 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -69,9 +69,6 @@ def raw_nodeinfo do if Config.get([:chat, :enabled]) do "chat" end, - if Config.get([:suggestions, :enabled]) do - "suggestions" - end, if Config.get([:instance, :allow_relay]) do "relay" end, @@ -104,11 +101,7 @@ def raw_nodeinfo do nodeDescription: Config.get([:instance, :description]), private: !Config.get([:instance, :public], true), suggestions: %{ - enabled: Config.get([:suggestions, :enabled], false), - thirdPartyEngine: Config.get([:suggestions, :third_party_engine], ""), - timeout: Config.get([:suggestions, :timeout], 5000), - limit: Config.get([:suggestions, :limit], 23), - web: Config.get([:suggestions, :web], "") + enabled: false }, staffAccounts: staff_accounts, federation: federation_response, diff --git a/lib/pleroma/web/oauth/oauth_controller.ex b/lib/pleroma/web/oauth/oauth_controller.ex index 5292aedf2..528f08574 100644 --- a/lib/pleroma/web/oauth/oauth_controller.ex +++ b/lib/pleroma/web/oauth/oauth_controller.ex @@ -167,17 +167,37 @@ defp handle_create_authorization_error( defp handle_create_authorization_error( %Plug.Conn{} = conn, - {:auth_active, false}, + {:account_status, :confirmation_pending}, %{"authorization" => _} = params ) do - # Per https://github.com/tootsuite/mastodon/blob/ - # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 conn |> put_flash(:error, dgettext("errors", "Your login is missing a confirmed e-mail address")) |> put_status(:forbidden) |> authorize(params) end + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :password_reset_pending}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Password reset is required")) + |> put_status(:forbidden) + |> authorize(params) + end + + defp handle_create_authorization_error( + %Plug.Conn{} = conn, + {:account_status, :deactivated}, + %{"authorization" => _} = params + ) do + conn + |> put_flash(:error, dgettext("errors", "Your account is currently disabled")) + |> put_status(:forbidden) + |> authorize(params) + end + defp handle_create_authorization_error(%Plug.Conn{} = conn, error, %{"authorization" => _}) do Authenticator.handle_error(conn, error) end @@ -218,46 +238,14 @@ def token_exchange( ) do with {:ok, %User{} = user} <- Authenticator.get_user(conn), {:ok, app} <- Token.Utils.fetch_app(conn), - {:auth_active, true} <- {:auth_active, User.auth_active?(user)}, - {:user_active, true} <- {:user_active, !user.deactivated}, - {:password_reset_pending, false} <- - {:password_reset_pending, user.password_reset_pending}, + {:account_status, :active} <- {:account_status, User.account_status(user)}, {:ok, scopes} <- validate_scopes(app, params), {:ok, auth} <- Authorization.create_authorization(app, user, scopes), {:ok, token} <- Token.exchange_token(app, auth) do json(conn, Token.Response.build(user, token)) else - {:auth_active, false} -> - # Per https://github.com/tootsuite/mastodon/blob/ - # 51e154f5e87968d6bb115e053689767ab33e80cd/app/controllers/api/base_controller.rb#L76 - render_error( - conn, - :forbidden, - "Your login is missing a confirmed e-mail address", - %{}, - "missing_confirmed_email" - ) - - {:user_active, false} -> - render_error( - conn, - :forbidden, - "Your account is currently disabled", - %{}, - "account_is_disabled" - ) - - {:password_reset_pending, true} -> - render_error( - conn, - :forbidden, - "Password reset is required", - %{}, - "password_reset_required" - ) - - _error -> - render_invalid_credentials_error(conn) + error -> + handle_token_exchange_error(conn, error) end end @@ -286,6 +274,43 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "client_credentials"} # Bad request def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params) + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :deactivated}) do + render_error( + conn, + :forbidden, + "Your account is currently disabled", + %{}, + "account_is_disabled" + ) + end + + defp handle_token_exchange_error( + %Plug.Conn{} = conn, + {:account_status, :password_reset_pending} + ) do + render_error( + conn, + :forbidden, + "Password reset is required", + %{}, + "password_reset_required" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, {:account_status, :confirmation_pending}) do + render_error( + conn, + :forbidden, + "Your login is missing a confirmed e-mail address", + %{}, + "missing_confirmed_email" + ) + end + + defp handle_token_exchange_error(%Plug.Conn{} = conn, _error) do + render_invalid_credentials_error(conn) + end + def token_revoke(%Plug.Conn{} = conn, %{"token" => _token} = params) do with {:ok, app} <- Token.Utils.fetch_app(conn), {:ok, _token} <- RevokeToken.revoke(app, params) do @@ -472,7 +497,7 @@ defp do_create_authorization( %App{} = app <- Repo.get_by(App, client_id: client_id), true <- redirect_uri in String.split(app.redirect_uris), {:ok, scopes} <- validate_scopes(app, auth_attrs), - {:auth_active, true} <- {:auth_active, User.auth_active?(user)} do + {:account_status, :active} <- {:account_status, User.account_status(user)} do Authorization.create_authorization(app, user, scopes) end end diff --git a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex index 0bbf84fd3..a2f6d2287 100644 --- a/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex @@ -573,11 +573,14 @@ def update_file(conn, %{"action" => action}) do assumed to be emojis and stored in the new `pack.json` file. """ def import_from_fs(conn, _params) do - with {:ok, results} <- File.ls(emoji_dir_path()) do + emoji_path = emoji_dir_path() + + with {:ok, %{access: :read_write}} <- File.stat(emoji_path), + {:ok, results} <- File.ls(emoji_path) do imported_pack_names = results |> Enum.filter(fn file -> - dir_path = Path.join(emoji_dir_path(), file) + dir_path = Path.join(emoji_path, file) # Find the directories that do NOT have pack.json File.dir?(dir_path) and not File.exists?(Path.join(dir_path, "pack.json")) end) @@ -585,6 +588,11 @@ def import_from_fs(conn, _params) do json(conn, imported_pack_names) else + {:ok, %{access: _}} -> + conn + |> put_status(:internal_server_error) + |> json(%{error: "Error: emoji pack directory must be writable"}) + {:error, _} -> conn |> put_status(:internal_server_error) diff --git a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index bb19836ae..d76e39795 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -47,9 +47,17 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) Object.normalize(activity) do reactions = emoji_reactions - |> Enum.map(fn [emoji, users] -> - users = Enum.map(users, &User.get_cached_by_ap_id/1) - {emoji, AccountView.render("index.json", %{users: users, for: user, as: :user})} + |> Enum.map(fn [emoji, user_ap_ids] -> + users = + Enum.map(user_ap_ids, &User.get_cached_by_ap_id/1) + |> Enum.filter(& &1) + + %{ + emoji: emoji, + count: length(users), + accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}), + reacted: !!(user && user.ap_id in user_ap_ids) + } end) conn diff --git a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex index 913975616..fae3c462e 100644 --- a/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex +++ b/lib/pleroma/web/rich_media/parsers/meta_tags_parser.ex @@ -48,6 +48,6 @@ defp maybe_put_title(meta, html) when meta != %{} do defp maybe_put_title(meta, _), do: meta defp get_page_title(html) do - Floki.find(html, "title") |> Floki.text() + Floki.find(html, "html head title") |> List.first() |> Floki.text() end end diff --git a/lib/pleroma/web/router.ex b/lib/pleroma/web/router.ex index ef6e5a565..b5c1d85c7 100644 --- a/lib/pleroma/web/router.ex +++ b/lib/pleroma/web/router.ex @@ -527,8 +527,10 @@ defmodule Pleroma.Web.Router do get("/notice/:id", OStatus.OStatusController, :notice) get("/notice/:id/embed_player", OStatus.OStatusController, :notice_player) - get("/users/:nickname/feed", Feed.FeedController, :feed) - get("/users/:nickname", Feed.FeedController, :feed_redirect) + get("/users/:nickname/feed", Feed.UserController, :feed, as: :user_feed) + get("/users/:nickname", Feed.UserController, :feed_redirect, as: :user_feed) + + get("/tags/:tag", Feed.TagController, :feed, as: :tag_feed) end scope "/", Pleroma.Web do diff --git a/lib/pleroma/web/streamer/worker.ex b/lib/pleroma/web/streamer/worker.ex index a1b445f2f..5392c1ec3 100644 --- a/lib/pleroma/web/streamer/worker.ex +++ b/lib/pleroma/web/streamer/worker.ex @@ -138,7 +138,8 @@ defp should_send?(%User{} = user, %Activity{} = item) do with parent <- Object.normalize(item) || item, true <- - Enum.all?([blocked_ap_ids, muted_ap_ids, reblog_muted_ap_ids], &(item.actor not in &1)), + Enum.all?([blocked_ap_ids, muted_ap_ids], &(item.actor not in &1)), + true <- item.data["type"] != "Announce" || item.actor not in reblog_muted_ap_ids, true <- 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), diff --git a/lib/pleroma/web/templates/feed/feed/_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_activity.xml.eex index 514eacaed..ac8a75009 100644 --- a/lib/pleroma/web/templates/feed/feed/_activity.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/_activity.xml.eex @@ -9,7 +9,7 @@ <%= activity_context(@activity) %> - + <%= if @data["summary"] do %> <%= @data["summary"] %> diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex new file mode 100644 index 000000000..da4fa6d6c --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.atom.eex @@ -0,0 +1,51 @@ + + http://activitystrea.ms/schema/1.0/note + http://activitystrea.ms/schema/1.0/post + + <%= render @view_module, "_tag_author.atom", assigns %> + + <%= @data["id"] %> + <%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %> + <%= activity_content(@object) %> + + <%= if @activity.local do %> + + + <% else %> + + <% end %> + + <%= @data["published"] %> + <%= @data["published"] %> + + + <%= activity_context(@activity) %> + + + + <%= if @data["summary"] do %> + <%= @data["summary"] %> + <% end %> + + <%= for id <- @activity.recipients do %> + <%= if id == Pleroma.Constants.as_public() do %> + + <% else %> + <%= unless Regex.match?(~r/^#{Pleroma.Web.base_url()}.+followers$/, id) do %> + + <% end %> + <% end %> + <% end %> + + <%= for tag <- @data["tag"] || [] do %> + + <% end %> + + <%= for {emoji, file} <- @data["emoji"] || %{} do %> + + <% end %> + diff --git a/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex new file mode 100644 index 000000000..295574df1 --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/_tag_activity.xml.eex @@ -0,0 +1,15 @@ + + <%= activity_title(@object, Keyword.get(@feed_config, :post_title, %{})) %> + + + <%= activity_context(@activity) %> + <%= activity_context(@activity) %> + <%= pub_date(@data["published"]) %> + + <%= activity_content(@object) %> + <%= for attachment <- @data["attachment"] || [] do %> + + <% end %> + + + diff --git a/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex new file mode 100644 index 000000000..997c4936e --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/_tag_author.atom.eex @@ -0,0 +1,18 @@ + + http://activitystrea.ms/schema/1.0/person + <%= @actor.ap_id %> + <%= @actor.ap_id %> + <%= @actor.nickname %> + <%= escape(@actor.bio) %> + + <%= if User.banner_url(@actor) do %> + + <% end %> + <%= if @actor.local do %> + true + <% end %> + + <%= @actor.nickname %> + <%= @actor.name %> + <%= escape(@actor.bio) %> + diff --git a/lib/pleroma/web/templates/feed/feed/tag.atom.eex b/lib/pleroma/web/templates/feed/feed/tag.atom.eex new file mode 100644 index 000000000..a288539ed --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/tag.atom.eex @@ -0,0 +1,22 @@ + + + + + <%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %> + #<%= @tag %> + + These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse. + <%= feed_logo() %> + <%= most_recent_update(@activities) %> + + <%= for activity <- @activities do %> + <%= render @view_module, "_tag_activity.atom", Map.merge(assigns, prepare_activity(activity, actor: true)) %> + <% end %> + diff --git a/lib/pleroma/web/templates/feed/feed/tag.rss.eex b/lib/pleroma/web/templates/feed/feed/tag.rss.eex new file mode 100644 index 000000000..eeda01a04 --- /dev/null +++ b/lib/pleroma/web/templates/feed/feed/tag.rss.eex @@ -0,0 +1,15 @@ + + + + + + #<%= @tag %> + These are public toots tagged with #<%= @tag %>. You can interact with them if you have an account anywhere in the fediverse. + <%= '#{tag_feed_url(@conn, :feed, @tag)}.rss' %> + <%= feed_logo() %> + 2b90d9 + <%= for activity <- @activities do %> + <%= render @view_module, "_tag_activity.xml", Map.merge(assigns, prepare_activity(activity)) %> + <% end %> + + diff --git a/lib/pleroma/web/templates/feed/feed/feed.xml.eex b/lib/pleroma/web/templates/feed/feed/user.xml.eex similarity index 67% rename from lib/pleroma/web/templates/feed/feed/feed.xml.eex rename to lib/pleroma/web/templates/feed/feed/user.xml.eex index 5ae36d345..d274c08ae 100644 --- a/lib/pleroma/web/templates/feed/feed/feed.xml.eex +++ b/lib/pleroma/web/templates/feed/feed/user.xml.eex @@ -6,16 +6,16 @@ xmlns:poco="http://portablecontacts.net/spec/1.0" xmlns:ostatus="http://ostatus.org/schema/1.0"> - <%= feed_url(@conn, :feed, @user.nickname) <> ".atom" %> + <%= user_feed_url(@conn, :feed, @user.nickname) <> ".atom" %> <%= @user.nickname <> "'s timeline" %> <%= most_recent_update(@activities, @user) %> <%= logo(@user) %> - + <%= render @view_module, "_author.xml", assigns %> <%= if last_activity(@activities) do %> - + <% end %> <%= for activity <- @activities do %> diff --git a/lib/pleroma/workers/attachments_cleanup_worker.ex b/lib/pleroma/workers/attachments_cleanup_worker.ex index 3f421db40..2cbc6b64d 100644 --- a/lib/pleroma/workers/attachments_cleanup_worker.ex +++ b/lib/pleroma/workers/attachments_cleanup_worker.ex @@ -12,7 +12,10 @@ defmodule Pleroma.Workers.AttachmentsCleanupWorker do @impl Oban.Worker def perform( - %{"object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}}}, + %{ + "op" => "cleanup_attachments", + "object" => %{"data" => %{"attachment" => [_ | _] = attachments, "actor" => actor}} + }, _job ) do hrefs = @@ -37,7 +40,7 @@ def perform( ) # The query above can be time consumptive on large instances until we # refactor how uploads are stored - |> Repo.all(timout: :infinity) + |> Repo.all(timeout: :infinity) # we should delete 1 object for any given attachment, but don't delete # files if there are more than 1 object for it |> Enum.reduce(%{}, fn %{ @@ -70,7 +73,11 @@ def perform( _ -> "" end - base_url = Pleroma.Config.get([__MODULE__, :base_url], Pleroma.Web.base_url()) + base_url = + String.trim_trailing( + Pleroma.Config.get([Pleroma.Upload, :base_url], Pleroma.Web.base_url()), + "/" + ) file_path = String.trim_leading(href, "#{base_url}/#{prefix}") @@ -84,5 +91,5 @@ def perform( |> Repo.delete_all() end - def perform(%{"object" => _object}, _job), do: :ok + def perform(%{"op" => "cleanup_attachments", "object" => _object}, _job), do: :ok end diff --git a/mix.exs b/mix.exs index 0aa7c862f..ea6b29f57 100644 --- a/mix.exs +++ b/mix.exs @@ -101,7 +101,7 @@ defp deps do {:phoenix_pubsub, "~> 1.1"}, {:phoenix_ecto, "~> 4.0"}, {:ecto_enum, "~> 1.4"}, - {:ecto_sql, "~> 3.2"}, + {:ecto_sql, "~> 3.3.2"}, {:postgrex, ">= 0.13.5"}, {:oban, "~> 0.12.0"}, {:quantum, "~> 2.3"}, diff --git a/mix.lock b/mix.lock index c1fe223c0..17b8900e0 100644 --- a/mix.lock +++ b/mix.lock @@ -20,13 +20,13 @@ "crontab": {:hex, :crontab, "1.1.8", "2ce0e74777dfcadb28a1debbea707e58b879e6aa0ffbf9c9bb540887bce43617", [:mix], [{:ecto, "~> 1.0 or ~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: true]}], "hexpm"}, "crypt": {:git, "https://github.com/msantos/crypt", "1f2b58927ab57e72910191a7ebaeff984382a1d3", [ref: "1f2b58927ab57e72910191a7ebaeff984382a1d3"]}, "custom_base": {:hex, :custom_base, "0.2.1", "4a832a42ea0552299d81652aa0b1f775d462175293e99dfbe4d7dbaab785a706", [:mix], [], "hexpm"}, - "db_connection": {:hex, :db_connection, "2.1.1", "a51e8a2ee54ef2ae6ec41a668c85787ed40cb8944928c191280fe34c15b76ae5", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, - "decimal": {:hex, :decimal, "1.8.0", "ca462e0d885f09a1c5a342dbd7c1dcf27ea63548c65a65e67334f4b61803822e", [:mix], [], "hexpm"}, + "db_connection": {:hex, :db_connection, "2.2.0", "e923e88887cd60f9891fd324ac5e0290954511d090553c415fbf54be4c57ee63", [:mix], [{:connection, "~> 1.0.2", [hex: :connection, repo: "hexpm", optional: false]}], "hexpm"}, + "decimal": {:hex, :decimal, "1.8.1", "a4ef3f5f3428bdbc0d35374029ffcf4ede8533536fa79896dd450168d9acdf3c", [:mix], [], "hexpm"}, "deep_merge": {:hex, :deep_merge, "1.0.0", "b4aa1a0d1acac393bdf38b2291af38cb1d4a52806cf7a4906f718e1feb5ee961", [:mix], [], "hexpm"}, "earmark": {:hex, :earmark, "1.4.2", "3aa0bd23bc4c61cf2f1e5d752d1bb470560a6f8539974f767a38923bb20e1d7f", [:mix], [], "hexpm"}, - "ecto": {:hex, :ecto, "3.2.5", "76c864b77948a479e18e69cc1d0f0f4ee7cced1148ffe6a093ff91eba644f0b5", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "ecto": {:hex, :ecto, "3.3.1", "82ab74298065bf0c64ca299f6c6785e68ea5d6b980883ee80b044499df35aba1", [:mix], [{:decimal, "~> 1.6", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "ecto_enum": {:hex, :ecto_enum, "1.4.0", "d14b00e04b974afc69c251632d1e49594d899067ee2b376277efd8233027aec8", [:mix], [{:ecto, ">= 3.0.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:ecto_sql, "> 3.0.0", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:mariaex, ">= 0.0.0", [hex: :mariaex, repo: "hexpm", optional: true]}, {:postgrex, ">= 0.0.0", [hex: :postgrex, repo: "hexpm", optional: true]}], "hexpm"}, - "ecto_sql": {:hex, :ecto_sql, "3.2.2", "d10845bc147b9f61ef485cbf0973c0a337237199bd9bd30dd9542db00aadc26b", [:mix], [{:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.2.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.2.0 or ~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "ecto_sql": {:hex, :ecto_sql, "3.3.2", "92804e0de69bb63e621273c3492252cb08a29475c05d40eeb6f41ad2d483cfd3", [:mix], [{:db_connection, "~> 2.2", [hex: :db_connection, repo: "hexpm", optional: false]}, {:ecto, "~> 3.3", [hex: :ecto, repo: "hexpm", optional: false]}, {:myxql, "~> 0.3.0", [hex: :myxql, repo: "hexpm", optional: true]}, {:postgrex, "~> 0.15.0", [hex: :postgrex, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "esshd": {:hex, :esshd, "0.1.0", "6f93a2062adb43637edad0ea7357db2702a4b80dd9683482fe00f5134e97f4c1", [:mix], [], "hexpm"}, "eternal": {:hex, :eternal, "1.2.1", "d5b6b2499ba876c57be2581b5b999ee9bdf861c647401066d3eeed111d096bc4", [:mix], [], "hexpm"}, "ex2ms": {:hex, :ex2ms, "1.5.0", "19e27f9212be9a96093fed8cdfbef0a2b56c21237196d26760f11dfcfae58e97", [:mix], [], "hexpm"}, @@ -69,7 +69,7 @@ "myhtmlex": {:git, "https://git.pleroma.social/pleroma/myhtmlex.git", "ad0097e2f61d4953bfef20fb6abddf23b87111e6", [ref: "ad0097e2f61d4953bfef20fb6abddf23b87111e6", submodules: true]}, "nimble_parsec": {:hex, :nimble_parsec, "0.5.1", "c90796ecee0289dbb5ad16d3ad06f957b0cd1199769641c961cfe0b97db190e0", [:mix], [], "hexpm"}, "nodex": {:git, "https://git.pleroma.social/pleroma/nodex", "cb6730f943cfc6aad674c92161be23a8411f15d1", [ref: "cb6730f943cfc6aad674c92161be23a8411f15d1"]}, - "oban": {:hex, :oban, "0.12.0", "5477d5ab4a5a201c0b6c89764040ebfc5d2c71c488a36f378016ce5990838f0f", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, + "oban": {:hex, :oban, "0.12.1", "695e9490c6e0edfca616d80639528e448bd29b3bff7b7dd10a56c79b00a5d7fb", [:mix], [{:ecto_sql, "~> 3.1", [hex: :ecto_sql, repo: "hexpm", optional: false]}, {:jason, "~> 1.1", [hex: :jason, repo: "hexpm", optional: false]}, {:postgrex, "~> 0.14", [hex: :postgrex, repo: "hexpm", optional: false]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, "parse_trans": {:hex, :parse_trans, "3.3.0", "09765507a3c7590a784615cfd421d101aec25098d50b89d7aa1d66646bc571c1", [:rebar3], [], "hexpm"}, "pbkdf2_elixir": {:hex, :pbkdf2_elixir, "0.12.4", "8dd29ed783f2e12195d7e0a4640effc0a7c37e6537da491f1db01839eee6d053", [:mix], [], "hexpm"}, "phoenix": {:hex, :phoenix, "1.4.10", "619e4a545505f562cd294df52294372d012823f4fd9d34a6657a8b242898c255", [:mix], [{:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}, {:phoenix_pubsub, "~> 1.1", [hex: :phoenix_pubsub, repo: "hexpm", optional: false]}, {:plug, "~> 1.8.1 or ~> 1.9", [hex: :plug, repo: "hexpm", optional: false]}, {:plug_cowboy, "~> 1.0 or ~> 2.0", [hex: :plug_cowboy, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm"}, @@ -83,7 +83,7 @@ "plug_static_index_html": {:hex, :plug_static_index_html, "1.0.0", "840123d4d3975585133485ea86af73cb2600afd7f2a976f9f5fd8b3808e636a0", [:mix], [{:plug, "~> 1.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm"}, "poison": {:hex, :poison, "3.1.0", "d9eb636610e096f86f25d9a46f35a9facac35609a7591b3be3326e99a0484665", [:mix], [], "hexpm"}, "poolboy": {:hex, :poolboy, "1.5.2", "392b007a1693a64540cead79830443abf5762f5d30cf50bc95cb2c1aaafa006b", [:rebar3], [], "hexpm"}, - "postgrex": {:hex, :postgrex, "0.15.1", "23ce3417de70f4c0e9e7419ad85bdabcc6860a6925fe2c6f3b1b5b1e8e47bf2f", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, + "postgrex": {:hex, :postgrex, "0.15.3", "5806baa8a19a68c4d07c7a624ccdb9b57e89cbc573f1b98099e3741214746ae4", [:mix], [{:connection, "~> 1.0", [hex: :connection, repo: "hexpm", optional: false]}, {:db_connection, "~> 2.1", [hex: :db_connection, repo: "hexpm", optional: false]}, {:decimal, "~> 1.5", [hex: :decimal, repo: "hexpm", optional: false]}, {:jason, "~> 1.0", [hex: :jason, repo: "hexpm", optional: true]}], "hexpm"}, "prometheus": {:hex, :prometheus, "4.4.1", "1e96073b3ed7788053768fea779cbc896ddc3bdd9ba60687f2ad50b252ac87d6", [:mix, :rebar3], [], "hexpm"}, "prometheus_ecto": {:hex, :prometheus_ecto, "1.4.3", "3dd4da1812b8e0dbee81ea58bb3b62ed7588f2eae0c9e97e434c46807ff82311", [:mix], [{:ecto, "~> 2.0 or ~> 3.0", [hex: :ecto, repo: "hexpm", optional: false]}, {:prometheus_ex, "~> 1.1 or ~> 2.0 or ~> 3.0", [hex: :prometheus_ex, repo: "hexpm", optional: false]}], "hexpm"}, "prometheus_ex": {:hex, :prometheus_ex, "3.0.5", "fa58cfd983487fc5ead331e9a3e0aa622c67232b3ec71710ced122c4c453a02f", [:mix], [{:prometheus, "~> 4.0", [hex: :prometheus, repo: "hexpm", optional: false]}], "hexpm"}, diff --git a/priv/gettext/fr/LC_MESSAGES/errors.po b/priv/gettext/fr/LC_MESSAGES/errors.po new file mode 100644 index 000000000..678b32289 --- /dev/null +++ b/priv/gettext/fr/LC_MESSAGES/errors.po @@ -0,0 +1,459 @@ +## `msgid`s in this file come from POT (.pot) files. +## +## Do not add, change, or remove `msgid`s manually here as +## they're tied to the ones in the corresponding POT file +## (with the same domain). +## +## Use `mix gettext.extract --merge` or `mix gettext.merge` +## to merge POT files into PO files. +msgid "" +msgstr "" +"Language: fr\n" +"Plural-Forms: nplurals=2; plural=(n > 1);\n" + +msgid "can't be blank" +msgstr "ne peut Γͺtre vide" + +## grammatical gender… +msgid "has already been taken" +msgstr "a dΓ©jΓ  Γ©tΓ© pris" + +msgid "is invalid" +msgstr "est invalide" + +msgid "has invalid format" +msgstr "a un format invalide" + +msgid "has an invalid entry" +msgstr "a une entrΓ©e invalide" + +## grammatical gender… +msgid "is reserved" +msgstr "est rΓ©servΓ©" + +msgid "does not match confirmation" +msgstr "ne correspondent pas" + +msgid "is still associated with this entry" +msgstr "" + +msgid "are still associated with this entry" +msgstr "" + +msgid "should be %{count} character(s)" +msgid_plural "should be %{count} character(s)" +msgstr[0] "devrait avoir %{count} charactΓ¨re" +msgstr[1] "devrait avoir %{count} charactΓ¨res" + +msgid "should have %{count} item(s)" +msgid_plural "should have %{count} item(s)" +msgstr[0] "devrait avoir %{count} objet" +msgstr[1] "devrait avoir %{count} objets" + +msgid "should be at least %{count} character(s)" +msgid_plural "should be at least %{count} character(s)" +msgstr[0] "devrait avoir au moins %{count} charactΓ¨re" +msgstr[1] "devrait avoir au moins %{count} charactΓ¨res" + +msgid "should have at least %{count} item(s)" +msgid_plural "should have at least %{count} item(s)" +msgstr[0] "devrait avoir au moins %{count} objet" +msgstr[1] "devrait avoir au moins %{count} objets" + +msgid "should be at most %{count} character(s)" +msgid_plural "should be at most %{count} character(s)" +msgstr[0] "devrait avoir au plus %{count} charactΓ¨re" +msgstr[1] "devrait avoir au plus %{count} charactΓ¨res" + +msgid "should have at most %{count} item(s)" +msgid_plural "should have at most %{count} item(s)" +msgstr[0] "devrait avoir au plus %{count} objet" +msgstr[1] "devrait avoir au plus %{count} objets" + +msgid "must be less than %{number}" +msgstr "doit Γͺtre infΓ©rieur Γ  %{number}" + +msgid "must be greater than %{number}" +msgstr "doit Γͺtre supΓ©rieur Γ  %{number}" + +msgid "must be less than or equal to %{number}" +msgstr "doit Γͺtre infΓ©rieur ou Γ©gal Γ  %{number}" + +msgid "must be greater than or equal to %{number}" +msgstr "doit Γͺtre supΓ©rieur ou Γ©gal Γ  %{number}" + +msgid "must be equal to %{number}" +msgstr "doit Γ©gal Γ  %{number}" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:381 +msgid "Account not found" +msgstr "Compte non trouvΓ©" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:153 +msgid "Already voted" +msgstr "A dΓ©jΓ  votΓ©" + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:263 +msgid "Bad request" +msgstr "RequΓͺte Invalide" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:254 +msgid "Can't delete object" +msgstr "Ne peut supprimer cet objet" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:569 +msgid "Can't delete this post" +msgstr "Ne peut supprimer ce message" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1731 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1737 +msgid "Can't display this activity" +msgstr "Ne peut afficher cette activitΓ©e" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:195 +msgid "Can't find user" +msgstr "Compte non trouvΓ©" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1148 +msgid "Can't get favorites" +msgstr "Favoris non trouvables" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:263 +msgid "Can't like object" +msgstr "Ne peut aimer cet objet" + +#, elixir-format +#: lib/pleroma/web/common_api/utils.ex:518 +msgid "Cannot post an empty status without attachments" +msgstr "Ne peut envoyer un status vide sans attachements" + +#, elixir-format +#: lib/pleroma/web/common_api/utils.ex:461 +msgid "Comment must be up to %{max_size} characters" +msgstr "Le commentaire ne doit faire plus de %{max_size} charactΓ¨res" + +#, elixir-format +#: lib/pleroma/web/admin_api/config.ex:63 +msgid "Config with params %{params} not found" +msgstr "Configuration avec les paramΓ¨tres %{params} non trouvΓ©e" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:78 +msgid "Could not delete" +msgstr "Γ‰chec de la suppression" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:110 +msgid "Could not favorite" +msgstr "Γ‰chec de mise en favoris" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:310 +msgid "Could not pin" +msgstr "Γ‰chec de l'Γ©pinglage" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:89 +msgid "Could not repeat" +msgstr "Γ‰chec de crΓ©ation la rΓ©pΓ©tition" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:120 +msgid "Could not unfavorite" +msgstr "Γ‰chec de suppression des favoris" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:327 +msgid "Could not unpin" +msgstr "Γ‰chec du dΓ©pinglage" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:99 +msgid "Could not unrepeat" +msgstr "Γ‰chec de suppression de la rΓ©pΓ©tition" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:392 +msgid "Could not update state" +msgstr "Γ‰chec de la mise Γ  jour du status" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1271 +msgid "Error." +msgstr "Erreur." + +#, elixir-format +#: lib/pleroma/captcha/kocaptcha.ex:36 +msgid "Invalid CAPTCHA" +msgstr "CAPTCHA invalide" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1700 +#: lib/pleroma/web/oauth/oauth_controller.ex:465 +msgid "Invalid credentials" +msgstr "ParamΓ¨tres d'authentification invalides" + +#, elixir-format +#: lib/pleroma/plugs/ensure_authenticated_plug.ex:20 +msgid "Invalid credentials." +msgstr "ParamΓ¨tres d'authentification invalides." + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:154 +msgid "Invalid indices" +msgstr "Indices invalides" + +#, elixir-format +#: lib/pleroma/web/admin_api/admin_api_controller.ex:411 +msgid "Invalid parameters" +msgstr "ParamΓ¨tres invalides" + +#, elixir-format +#: lib/pleroma/web/common_api/utils.ex:377 +msgid "Invalid password." +msgstr "Mot de passe invalide." + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:163 +msgid "Invalid request" +msgstr "RequΓͺte invalide" + +#, elixir-format +#: lib/pleroma/captcha/kocaptcha.ex:16 +msgid "Kocaptcha service unavailable" +msgstr "Service Kocaptcha non disponible" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1696 +msgid "Missing parameters" +msgstr "ParamΓ¨tres manquants" + +#, elixir-format +#: lib/pleroma/web/common_api/utils.ex:496 +msgid "No such conversation" +msgstr "Conversation inconnue" + +#, elixir-format +#: lib/pleroma/web/admin_api/admin_api_controller.ex:163 +#: lib/pleroma/web/admin_api/admin_api_controller.ex:206 +msgid "No such permission_group" +msgstr "Groupe de permission inconnu" + +#, elixir-format +#: lib/pleroma/plugs/uploaded_media.ex:69 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:311 +#: lib/pleroma/web/admin_api/admin_api_controller.ex:399 +#: lib/pleroma/web/mastodon_api/subscription_controller.ex:63 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:248 +msgid "Not found" +msgstr "Non TrouvΓ©" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:152 +msgid "Poll's author can't vote" +msgstr "L'auteurΒ·rice d'un sondage ne peut voter" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:443 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:444 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:473 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:476 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1180 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1564 +msgid "Record not found" +msgstr "Enregistrement non trouvΓ©" + +#, elixir-format +#: lib/pleroma/web/admin_api/admin_api_controller.ex:417 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1570 +#: lib/pleroma/web/mastodon_api/subscription_controller.ex:69 +#: lib/pleroma/web/ostatus/ostatus_controller.ex:252 +msgid "Something went wrong" +msgstr "Erreur inconnue" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:253 +msgid "The message visibility must be direct" +msgstr "La visibilitΓ©e du message doit Γͺtre « directΒ Β»" + +#, elixir-format +#: lib/pleroma/web/common_api/utils.ex:521 +msgid "The status is over the character limit" +msgstr "Le status est au-delΓ  de la limite de charactΓ¨res" + +#, elixir-format +#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:27 +msgid "This resource requires authentication." +msgstr "Cette resource nΓ©cessite une authentification." + +#, elixir-format +#: lib/pleroma/plugs/rate_limiter.ex:89 +msgid "Throttled" +msgstr "LimitΓ©" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:155 +msgid "Too many choices" +msgstr "Trop de choix" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:268 +msgid "Unhandled activity type" +msgstr "Type d'activitΓ©e non-gΓ©rΓ©e" + +#, elixir-format +#: lib/pleroma/plugs/user_is_admin_plug.ex:20 +msgid "User is not admin." +msgstr "Le compte n'est pas admin." + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:380 +msgid "Valid `account_id` required" +msgstr "Un `account_id` valide est requis" + +#, elixir-format +#: lib/pleroma/web/admin_api/admin_api_controller.ex:185 +msgid "You can't revoke your own admin status." +msgstr "Vous ne pouvez rΓ©voquer votre propre status d'admin." + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:216 +msgid "Your account is currently disabled" +msgstr "Votre compte est actuellement dΓ©sactivΓ©" + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:158 +#: lib/pleroma/web/oauth/oauth_controller.ex:213 +msgid "Your login is missing a confirmed e-mail address" +msgstr "Une confirmation de l'addresse de couriel est requise pour l'authentification" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:221 +msgid "can't read inbox of %{nickname} as %{as_nickname}" +msgstr "Ne peut lire la boite de rΓ©ception de %{nickname} en tant que %{as_nickname}" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:297 +msgid "can't update outbox of %{nickname} as %{as_nickname}" +msgstr "Ne peut poster dans la boite d'Γ©mission de %{nickname} en tant que %{as_nickname}" + +#, elixir-format +#: lib/pleroma/web/common_api/common_api.ex:335 +msgid "conversation is already muted" +msgstr "la conversation est dΓ©jΓ  baillonΓ©e" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:192 +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:317 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1196 +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:1247 +msgid "error" +msgstr "erreur" + +#, elixir-format +#: lib/pleroma/web/mastodon_api/mastodon_api_controller.ex:789 +msgid "mascots can only be images" +msgstr "les mascottes ne peuvent Γͺtre que des images" + +#, elixir-format +#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:34 +msgid "not found" +msgstr "non trouvΓ©" + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:298 +msgid "Bad OAuth request." +msgstr "RequΓͺte OAuth invalide." + +#, elixir-format +#: lib/pleroma/captcha/captcha.ex:92 +msgid "CAPTCHA already used" +msgstr "CAPTCHA dΓ©jΓ  utilisΓ©" + +#, elixir-format +#: lib/pleroma/captcha/captcha.ex:89 +msgid "CAPTCHA expired" +msgstr "CAPTCHA expirΓ©" + +#, elixir-format +#: lib/pleroma/plugs/uploaded_media.ex:50 +msgid "Failed" +msgstr "Γ‰chec" + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:314 +msgid "Failed to authenticate: %{message}." +msgstr "Γ‰chec de l'authentification: %{message}" + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:345 +msgid "Failed to set up user account." +msgstr "Γ‰chec de crΓ©ation de votre compte." + +#, elixir-format +#: lib/pleroma/plugs/oauth_scopes_plug.ex:37 +msgid "Insufficient permissions: %{permissions}." +msgstr "Permissions insuffisantes: %{permissions}." + +#, elixir-format +#: lib/pleroma/plugs/uploaded_media.ex:89 +msgid "Internal Error" +msgstr "Erreur interne" + +#, elixir-format +#: lib/pleroma/web/oauth/fallback_controller.ex:22 +#: lib/pleroma/web/oauth/fallback_controller.ex:29 +msgid "Invalid Username/Password" +msgstr "Nom d'utilisateur/mot de passe invalide" + +#, elixir-format +#: lib/pleroma/captcha/captcha.ex:107 +msgid "Invalid answer data" +msgstr "RΓ©ponse invalide" + +#, elixir-format +#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:204 +msgid "Nodeinfo schema version not handled" +msgstr "Version du schΓ©ma nodeinfo non gΓ©rΓ©" + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:145 +msgid "This action is outside the authorized scopes" +msgstr "Cette action est en dehors des authorisations" # "scopes" ? + +#, elixir-format +#: lib/pleroma/web/oauth/fallback_controller.ex:14 +msgid "Unknown error, please check the details and try again." +msgstr "Erreur inconnue, veuillez vΓ©rifier les dΓ©tails et rΓ©essayer." + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:93 +#: lib/pleroma/web/oauth/oauth_controller.ex:131 +msgid "Unlisted redirect_uri." +msgstr "redirect_uri non listΓ©." + +#, elixir-format +#: lib/pleroma/web/oauth/oauth_controller.ex:294 +msgid "Unsupported OAuth provider: %{provider}." +msgstr "Fournisseur OAuth non supporté : %{provider}." + +#, elixir-format +#: lib/pleroma/uploaders/uploader.ex:71 +msgid "Uploader callback timeout" +msgstr "" +## msgstr "Attente Γ©coulΓ©e" + +#, elixir-format +#: lib/pleroma/web/uploader_controller.ex:11 +#: lib/pleroma/web/uploader_controller.ex:23 +msgid "bad request" +msgstr "requΓͺte invalide" diff --git a/priv/static/index.html b/priv/static/index.html index b0aadb1a1..2fc0d5349 100644 --- a/priv/static/index.html +++ b/priv/static/index.html @@ -1 +1 @@ -Pleroma
\ No newline at end of file +Pleroma
\ No newline at end of file diff --git a/priv/static/static/font/fontello.1579102213354.woff2 b/priv/static/static/font/fontello.1579102213354.woff2 deleted file mode 100644 index 9c354e7f6..000000000 Binary files a/priv/static/static/font/fontello.1579102213354.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1579102213354.eot b/priv/static/static/font/fontello.1580232989700.eot similarity index 99% rename from priv/static/static/font/fontello.1579102213354.eot rename to priv/static/static/font/fontello.1580232989700.eot index 160cfa9f6..6be901301 100644 Binary files a/priv/static/static/font/fontello.1579102213354.eot and b/priv/static/static/font/fontello.1580232989700.eot differ diff --git a/priv/static/static/font/fontello.1579102213354.svg b/priv/static/static/font/fontello.1580232989700.svg similarity index 100% rename from priv/static/static/font/fontello.1579102213354.svg rename to priv/static/static/font/fontello.1580232989700.svg diff --git a/priv/static/static/font/fontello.1579102213354.ttf b/priv/static/static/font/fontello.1580232989700.ttf similarity index 99% rename from priv/static/static/font/fontello.1579102213354.ttf rename to priv/static/static/font/fontello.1580232989700.ttf index 44753f8c1..51d3f1e08 100644 Binary files a/priv/static/static/font/fontello.1579102213354.ttf and b/priv/static/static/font/fontello.1580232989700.ttf differ diff --git a/priv/static/static/font/fontello.1579102213354.woff b/priv/static/static/font/fontello.1580232989700.woff similarity index 98% rename from priv/static/static/font/fontello.1579102213354.woff rename to priv/static/static/font/fontello.1580232989700.woff index 23351a090..c70e7fb7e 100644 Binary files a/priv/static/static/font/fontello.1579102213354.woff and b/priv/static/static/font/fontello.1580232989700.woff differ diff --git a/priv/static/static/font/fontello.1580232989700.woff2 b/priv/static/static/font/fontello.1580232989700.woff2 new file mode 100644 index 000000000..73acac54f Binary files /dev/null and b/priv/static/static/font/fontello.1580232989700.woff2 differ diff --git a/priv/static/static/fontello.1580232989700.css b/priv/static/static/fontello.1580232989700.css new file mode 100644 index 000000000..a9cbcb04d Binary files /dev/null and b/priv/static/static/fontello.1580232989700.css differ diff --git a/priv/static/static/js/2.8896ea39a0ea8016391a.js b/priv/static/static/js/2.59b096781ddca107175d.js similarity index 79% rename from priv/static/static/js/2.8896ea39a0ea8016391a.js rename to priv/static/static/js/2.59b096781ddca107175d.js index ece883546..f47e92efa 100644 Binary files a/priv/static/static/js/2.8896ea39a0ea8016391a.js and b/priv/static/static/js/2.59b096781ddca107175d.js differ diff --git a/priv/static/static/js/2.8896ea39a0ea8016391a.js.map b/priv/static/static/js/2.59b096781ddca107175d.js.map similarity index 99% rename from priv/static/static/js/2.8896ea39a0ea8016391a.js.map rename to priv/static/static/js/2.59b096781ddca107175d.js.map index 4a5dc5be7..f13a48804 100644 Binary files a/priv/static/static/js/2.8896ea39a0ea8016391a.js.map and b/priv/static/static/js/2.59b096781ddca107175d.js.map differ diff --git a/priv/static/static/js/2.c96b30ae9f2d3f46f0ad.js.map b/priv/static/static/js/2.c96b30ae9f2d3f46f0ad.js.map deleted file mode 100644 index 25e514a5b..000000000 Binary files a/priv/static/static/js/2.c96b30ae9f2d3f46f0ad.js.map and /dev/null differ diff --git a/priv/static/static/js/app.9cfed8f3d06c299128ea.js b/priv/static/static/js/app.9cfed8f3d06c299128ea.js new file mode 100644 index 000000000..d373c2a07 Binary files /dev/null and b/priv/static/static/js/app.9cfed8f3d06c299128ea.js differ diff --git a/priv/static/static/js/app.9cfed8f3d06c299128ea.js.map b/priv/static/static/js/app.9cfed8f3d06c299128ea.js.map new file mode 100644 index 000000000..a7a943e15 Binary files /dev/null and b/priv/static/static/js/app.9cfed8f3d06c299128ea.js.map differ diff --git a/priv/static/static/js/app.a43640742dacfb13b6b0.js b/priv/static/static/js/app.a43640742dacfb13b6b0.js deleted file mode 100644 index 82265996f..000000000 Binary files a/priv/static/static/js/app.a43640742dacfb13b6b0.js and /dev/null differ diff --git a/priv/static/static/js/app.a43640742dacfb13b6b0.js.map b/priv/static/static/js/app.a43640742dacfb13b6b0.js.map deleted file mode 100644 index b30f1ac4c..000000000 Binary files a/priv/static/static/js/app.a43640742dacfb13b6b0.js.map and /dev/null differ diff --git a/priv/static/static/js/app.a9b3f4c3e79baf3fa8b7.js.map b/priv/static/static/js/app.a9b3f4c3e79baf3fa8b7.js.map deleted file mode 100644 index 7c369185e..000000000 Binary files a/priv/static/static/js/app.a9b3f4c3e79baf3fa8b7.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js.map b/priv/static/static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js.map deleted file mode 100644 index 2e88b3ce2..000000000 Binary files a/priv/static/static/js/vendors~app.3f1ed7a4fdfc37ee27a7.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js.map b/priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js.map deleted file mode 100644 index 98d62c3b1..000000000 Binary files a/priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js.map and /dev/null differ diff --git a/priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js b/priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js similarity index 80% rename from priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js rename to priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js index 0b8705ae8..0812cdba7 100644 Binary files a/priv/static/static/js/vendors~app.86bc6d5e06d2e17976c5.js and b/priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js differ diff --git a/priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js.map b/priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js.map new file mode 100644 index 000000000..f551dfa51 Binary files /dev/null and b/priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js.map differ diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index ae01a067e..64bde2024 100644 Binary files a/priv/static/sw-pleroma.js and b/priv/static/sw-pleroma.js differ diff --git a/priv/static/sw.js b/priv/static/sw.js index 334bf79ac..5605bb05e 100644 Binary files a/priv/static/sw.js and b/priv/static/sw.js differ diff --git a/test/config/config_db_test.exs b/test/config/config_db_test.exs index 61a0b1d5d..812709fd8 100644 --- a/test/config/config_db_test.exs +++ b/test/config/config_db_test.exs @@ -307,6 +307,15 @@ test "Quack.Logger module" do assert ConfigDB.from_binary(binary) == Quack.Logger end + test "Swoosh.Adapters modules" do + binary = ConfigDB.transform("Swoosh.Adapters.SMTP") + assert binary == :erlang.term_to_binary(Swoosh.Adapters.SMTP) + assert ConfigDB.from_binary(binary) == Swoosh.Adapters.SMTP + binary = ConfigDB.transform("Swoosh.Adapters.AmazonSES") + assert binary == :erlang.term_to_binary(Swoosh.Adapters.AmazonSES) + assert ConfigDB.from_binary(binary) == Swoosh.Adapters.AmazonSES + end + test "sigil" do binary = ConfigDB.transform("~r[comp[lL][aA][iI][nN]er]") assert binary == :erlang.term_to_binary(~r/comp[lL][aA][iI][nN]er/) diff --git a/test/config/transfer_task_test.exs b/test/config/transfer_task_test.exs index b9072e0fc..53e8703fd 100644 --- a/test/config/transfer_task_test.exs +++ b/test/config/transfer_task_test.exs @@ -105,17 +105,4 @@ test "transfer config values with full subkey update" do Application.put_env(:pleroma, :assets, assets) end) end - - test "non existing atom" do - ConfigDB.create(%{ - group: ":pleroma", - key: ":undefined_atom_key", - value: [live: 2, com: 3] - }) - - assert ExUnit.CaptureLog.capture_log(fn -> - TransferTask.start_link([]) - end) =~ - "updating env causes error, group: \":pleroma\" key: \":undefined_atom_key\" value: [live: 2, com: 3] error: %ArgumentError{message: \"argument error\"}" - end end diff --git a/test/emails/admin_email_test.exs b/test/emails/admin_email_test.exs index ad89f9213..383cc3459 100644 --- a/test/emails/admin_email_test.exs +++ b/test/emails/admin_email_test.exs @@ -19,8 +19,8 @@ test "build report email" do AdminEmail.report(to_user, reporter, account, [%{name: "Test", id: "12"}], "Test comment") status_url = Helpers.o_status_url(Pleroma.Web.Endpoint, :notice, "12") - reporter_url = Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id) - account_url = Helpers.feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id) + reporter_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, reporter.id) + account_url = Helpers.user_feed_url(Pleroma.Web.Endpoint, :feed_redirect, account.id) assert res.to == [{to_user.name, to_user.email}] assert res.from == {config[:name], config[:notify_email]} diff --git a/test/fixtures/emoji-reaction-no-emoji.json b/test/fixtures/emoji-reaction-no-emoji.json new file mode 100644 index 000000000..fff77b29b --- /dev/null +++ b/test/fixtures/emoji-reaction-no-emoji.json @@ -0,0 +1,30 @@ +{ + "type": "EmojiReaction", + "signature": { + "type": "RsaSignature2017", + "signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==", + "creator": "http://mastodon.example.org/users/admin#main-key", + "created": "2018-02-17T18:57:49Z" + }, + "object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454", + "content": "~", + "nickname": "lain", + "id": "http://mastodon.example.org/users/admin#reactions/2", + "actor": "http://mastodon.example.org/users/admin", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "toot": "http://joinmastodon.org/ns#", + "sensitive": "as:sensitive", + "ostatus": "http://ostatus.org#", + "movedTo": "as:movedTo", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "atomUri": "ostatus:atomUri", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji" + } + ] +} diff --git a/test/fixtures/emoji-reaction-too-long.json b/test/fixtures/emoji-reaction-too-long.json new file mode 100644 index 000000000..31830d90c --- /dev/null +++ b/test/fixtures/emoji-reaction-too-long.json @@ -0,0 +1,30 @@ +{ + "type": "EmojiReaction", + "signature": { + "type": "RsaSignature2017", + "signatureValue": "fdxMfQSMwbC6wP6sh6neS/vM5879K67yQkHTbiT5Npr5wAac0y6+o3Ij+41tN3rL6wfuGTosSBTHOtta6R4GCOOhCaCSLMZKypnp1VltCzLDoyrZELnYQIC8gpUXVmIycZbREk22qWUe/w7DAFaKK4UscBlHDzeDVcA0K3Se5Sluqi9/Zh+ldAnEzj/rSEPDjrtvf5wGNf3fHxbKSRKFt90JvKK6hS+vxKUhlRFDf6/SMETw+EhwJSNW4d10yMUakqUWsFv4Acq5LW7l+HpYMvlYY1FZhNde1+uonnCyuQDyvzkff8zwtEJmAXC4RivO/VVLa17SmqheJZfI8oluVg==", + "creator": "http://mastodon.example.org/users/admin#main-key", + "created": "2018-02-17T18:57:49Z" + }, + "object": "http://localtesting.pleroma.lol/objects/eb92579d-3417-42a8-8652-2492c2d4f454", + "content": "πŸ‘ŒπŸ‘Œ", + "nickname": "lain", + "id": "http://mastodon.example.org/users/admin#reactions/2", + "actor": "http://mastodon.example.org/users/admin", + "@context": [ + "https://www.w3.org/ns/activitystreams", + "https://w3id.org/security/v1", + { + "toot": "http://joinmastodon.org/ns#", + "sensitive": "as:sensitive", + "ostatus": "http://ostatus.org#", + "movedTo": "as:movedTo", + "manuallyApprovesFollowers": "as:manuallyApprovesFollowers", + "inReplyToAtomUri": "ostatus:inReplyToAtomUri", + "conversation": "ostatus:conversation", + "atomUri": "ostatus:atomUri", + "Hashtag": "as:Hashtag", + "Emoji": "toot:Emoji" + } + ] +} diff --git a/test/fixtures/margaret-corbin-grave-west-point.html b/test/fixtures/margaret-corbin-grave-west-point.html new file mode 100644 index 000000000..f6d387cc8 --- /dev/null +++ b/test/fixtures/margaret-corbin-grave-west-point.html @@ -0,0 +1,2895 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + +
+
+
+
+
+

The Missing Grave of Margaret Corbin, Revolutionary War Veteran

+

+ She’s the only woman veteran honored with a monument at West Point. But where was she buried? +

+
+ + +
+
+
+
+ +
+
+
+ In 1926, the Daughters of the American Revolution joined forces with the U.S. Military Academy to exhume the supposed burial site of Margaret Corbin. +
+ In 1926, the Daughters of the American Revolution joined forces with the U.S. Military Academy to exhume + the supposed burial site of Margaret Corbin. Daughters of the American + Revolution +
+
+
+
+ +
+
+

In 2016, five days after + Thanksgiving, Margaret Corbin’s grave was dug up for the second time since her death in + 1800. It began by accident. Contractors were working on a retaining wall near the West Point Cemetery, + at the U.S. Military Academy, when a hydraulic excavator got too close and chewed through the grave.

+

As soon as they noticed bones spilling from the soil, they alerted the + military police. The plot was quickly cordoned off, her monument was wrapped in tarp, and rumors started + to spread about Corbin’s resting place—that is, if it even was her resting place. + When forensic archaeologists arrived at the scene, they were perplexed: The bones seemed oddly large. +

+

The monument to Margaret Corbin is West Point’s only monument to a + woman veteran, and it greets visitors near the main gate, just feet from a neoclassical chapel. It faces + Washington Road, where the Academy’s top brass live, and depicts Corbin in a long dress, operating + a cannon as her long hair and cape fly in the wind. She wears a powder horn and holds a rammer to load + cannonballs; the rest of the rather cramped cemetery sprawls out behind her. The monument portrays the + moments before Corbin became a prisoner of war.

+
On the West Point monument, Corbin wears a long dress and a powder horn, and she operates a cannon while her long hair flies in the wind. +
On the West Point monument, Corbin wears a long + dress and a powder horn, and she operates a cannon while her long hair flies in the wind. Science History Images / Alamy Stock Photo
+
+

The story goes that Corbin joined her husband, John, to fight in the + American Revolution. At the time, many women followed their husbands to war, where they were commonly + known as “camp followers.” Typically, they foraged for food, cooked, and did laundry. Before + Martha Washington was the United States’ first first lady, she was also a camp follower. In fact, + she and Margaret were with the same company—though the two experienced different lives, since + George was a general, and John manned a cannon.

+

At the Battle of Fort Washington, on November 16, 1776, in what is now + Washington Heights, the British and Hessians advanced far enough to make the Continental Army’s + position untenable. George Washington retreated with his forces to White Plains; John Corbin was shot + dead at his cannon. But Margaret was there to jump into John’s position and help fire the cannon. + During the battle, her jaw and shoulder were seriously injured, and grapeshot tore off part of her + breast. Despite the Continental Army’s efforts, the fort was soon surrendered, and Corbin was + captured along with approximately 2,837 soldiers.

+
A watercolor by Thomas Davies depicting the attack on Fort Washington by the British and Hessian Brigades. Margaret Corbin was taken prisoner after fighting in the battle. +
A watercolor by Thomas Davies depicting the attack + on Fort Washington by the British and Hessian Brigades. Margaret Corbin was taken prisoner after + fighting in the battle. Alamy Stock Photo
+
+

The British may have been unsure what to do with an injured woman, because + she was released fairly soon after the battle. The ordeal was one of many traumas in her life: According + to records collected by the historian Stella Bailey, + Margaret was only five years old when her father was killed in a conflict with Native Americans in + Pennsylvania, where they lived. Her mother was kidnapped, and Margaret and her brother moved in with an + uncle. They never saw her again.

+

After Corbin’s return, she joined the Corps of Invalids, a group of + wounded soldiers that were still able to contribute to the war effort. They were stationed at West + Point, New York, where Corbin became known as a cantankerous woman who had a tough time making a home + for herself in the neighboring village of Highland Falls. She moved between various local families who + tried to care for her. Having witnessed her husband’s death and sustaining wounds, she was + probably in constant mental and physical pain.

+
+

When Margaret Corbin + died in 1800, she was buried in a pauper’s cemetery in Highland Falls, just three miles + from West Point. But in 1926, the national society of women known as the Daughters of the American + Revolution saw to it that Corbin would earn her vaunted cemetery plot. The society, which is made up of + women who can trace their lineage to participants in the American Revolution, was celebrating the + sesquicentennial of American independence, and saw Corbin as the consummate symbol of both their + organization and the Revolution. A year-long effort convinced the U.S. Military Academy to help them + exhume and transport the remains to the prestigious cemetery, to be reburied with a military funeral. +

+
This sign directs visitors to the United States Military Academy to the purported site of Margaret Corbin's grave. +
This sign directs visitors to the United States + Military Academy to the purported site of Margaret Corbin’s grave. Ahodges7 / + CC BY-SA 3.0
+
+

Exhumation was not a simple task: By the time the DAR began their campaign + to move Corbin, the location of her exact burial was known only by word-of-mouth, passed down through + generations. In collaboration with West Point, the Daughters found the great-grandson of the man who + supposedly dug Corbin’s original grave, a steamboat captain by the name of Farout. Her burial site + was apparently marked by the stump of a cedar tree; during the exhumation process, the gravedigger + accidentally drove the shovel through the skull. Still, the Army Surgeon reported injuries to the + skeleton that were consistent with grapeshot. The remains were given a new, flag-draped casket and + delivered to West Point by horse-drawn hearse.

+

Every year since then, the Daughters have gathered at Corbin’s + monument for Margaret Corbin Day. On the first Tuesday of May, the Daughters fill the chapel, share + Corbin’s story, sing hymns, and stand at the grave while soldiers perform a 21-gun salute.

+
A horse-drawn hearse carried a flag-draped casket that was said to contain Corbin’s remains. +
A horse-drawn hearse carried a flag-draped casket + that was said to contain Corbin’s remains. Daughters of the + American Revolution
+
+

After the U.S. Military Academy unintentionally reopened the grave beneath + Corbin’s monument in 2016, they decided to conduct an emergency forensic archaeological + excavation. They enlisted the help of Elizabeth A. DiGangi, an anthropology professor at Binghamton + University, and Michael K. Trimble, an archaeologist for the Army Corps of Engineers. Almost + immediately, the pair noticed that the size of the bones didn’t match Corbin’s description. + Corbin was reportedly a stout woman. “One of the first bones I saw when I was on site was the + humerus, or upper arm bone,” DiGangi says. “It was very large, which is not what you would + expect with an arm bone from a woman.”

+

DiGangi took the remains to her laboratory at Binghamton University to do a + full analysis. Some worried that other remains were mixed up with Corbin’s. (In the past, West + Point has discovered unknown remains when they’ve broken new ground for construction.) Ultimately, + DiGangi’s analysis revealed something even more shocking: The remains in Corbin’s grave + actually came from an adult male. DiGangi determined that it was a large man, who could’ve been + anywhere from five-foot-seven to six and a half feet tall. The remains of Margaret Corbin were not in + Margaret Corbin’s grave.

+
In 1926, the remains from Highland Park were reinterred at West Point, and sat at the foot of the Margaret Corbin monument until 2016. +
In 1926, the remains from Highland Park were + reinterred at West Point, and sat at the foot of the Margaret Corbin monument until 2016. Daughters of the American Revolution
+
+

Once the archaeological excavation teams completed + their reports, the Army National Cemeteries contacted the Daughters of the American Revolution. They + wanted a meeting at DAR headquarters in Washington, DC.

+

Jennifer Minus, the head of the New York chapter of the DAR, was among + those present at the meeting. Minus, a graduate of West Point and a former member of the Corbin Forum, a + club for cadet women, knew her Corbin history better than most. She asked how it could’ve been a + man in the grave, if in 1926 the Army surgeon said that grapeshot injuries were present. In her report, + DiGangi explains that what the surgeon considered a grapeshot injury was, in fact, post-mortem damage to + the remains.

+

So where is Margaret Corbin? Since the attempted reburial of Corbin’s + remains, in 1926, her original gravesite in Highland Falls has been lost to time. Sometime in the 1970s, + the town dropped a sewage plant where many believe it was once located. Yet Minus remains optimistic + that Corbin’s remains will one day be found.

+
At left, another monument that pays homage to Margaret Corbin, near the site where she took over her husband's cannon; at right, a view of her monument at West Point. +
At left, another monument that pays homage to + Margaret Corbin, near the site where she took over her husband’s cannon; at right, a view of her + monument at West Point. Beyond + My Ken / CC BY-SA 4.0; Ahodges7 / CC BY-SA 3.0
+
+

As upsetting as it was to learn that her remains were missing, the + Daughters also tried to see the discovery as an opportunity to spread Margaret’s story. It’s + as though they picked up right where the 1926 DAR members left off. Minus formed an unofficial Margaret + Corbin Task Force, drawing on the strengths of DAR members: One was a genealogist, and another was a + Navy veteran who had worked on locating the remains of American soldiers overseas.

+
+

On April 30, 2019, + Minus combed the woods of Highland Falls, looking for the original gravesite. They tried to + match up the old photographs with newer ones, but this proved difficult, because most of the trees in + the photographs were saplings at the time. They looked for flat areas that would have been suitable for + burials: It was common at the time to bury people in elevated areas, to avoid rising water tables that + could push the caskets back up to the surface.

+

The next day, the Daughters gathered again around Corbin’s monument, + dressed in large hats and sashes. The ground looked as though it had never been disturbed. A casual + viewer would’ve never known that Corbin wasn’t under their feet.

+
In 2018, the Margaret Corbin monument was rededicated with a wreath laying.
+ +
In 2018, the Margaret Corbin monument was + rededicated with a wreath laying. + Daughters of the American Revolution
+
+

It was an important day for the Daughters, but especially for Minus, who + joined the DAR in part because of Corbin. Once, before she graduated from West Point, she told her + grandparents about a lunch honoring Margaret Corbin. Her grandmother told her that her heritage made her + eligible to join the Daughters of the American Revolution, and a few years later, when she returned from + a post in Germany, her grandma prepared the necessary papers.

+

Minus is hopeful that they’ll find Corbin near the river, not far + from the grave that the Daughters dug up in 1926. “When they started digging, they found bones. + So, they didn’t make, like, 10 different holes over a field. They got it on their first attempt + and found bones. What I’m hoping is that they just had to do a 180, and she would’ve been + five feet over.”

+

The man they found in Corbin’s grave has since come back to the West + Point cemetery, to be reinterred with the other unidentified remains found in the area. No one yet knows + who the man could be. Some theorize it’s Corbin’s second husband—but there’s no + proof that she remarried. Others believe it was a Native American. It’s possible that the unknown + man might be dug up a third time, should the proper clues demand his participation. Corbin’s + original gravesite did not turn up in 2018, but the search + continues.

+

On Corbin Day in 2019, after the 21-gun salute, the Daughters hold another + luncheon. This time, the Margaret Corbin Task Force has something special on display: a machine that + looks like a souped-up lawn mower. Many Daughters file into the room and ask what it is. “Just + wait,” Minus answers. Then Lieutenant Colonel Mindy Kimball, an environmental science professor at + West Point, holds a demonstration. It’s a ground-penetrating radar machine, which shoots + electromagnetic waves into the ground and sends information back up to the antennae, to identify + underground disturbances that could reveal human remains.

+
The monument to Margaret Corbin is West Point’s only monument to a woman veteran. +
The monument to Margaret Corbin is West + Point’s only monument to a woman veteran. Ahodges7 + / CC BY-SA 3.0
+
+

Whether or not Corbin is ever located, just sharing her story helps to + immortalize her. Minus is fascinated by the many identities that Corbin came to inhabit. + “She’s an army spouse, and then an army widow, and then she was a soldier, and then she was + a wounded soldier, and then she was a prisoner of war, and then she was a veteran,” she says. + Corbin was also the first woman to receive a military pension from the government, and is mentioned by + name in the Congressional Record. “I really think of her as that building block for women in the + military.”

+

Stella Bailey, the town historian of Highland Falls, has been researching + Margaret Corbin for decades. She’s pored over old maps, trying to pinpoint exactly where Corbin + might have been buried in 1800. She even gets emails from people who think they might be related to + Corbin.

+

Sitting in her office, overlooking Main Street in Highland + Falls, Bailey sighs. “We know she was real. West Point’s records acknowledge her + existence,” she says. But she can list discrepancies in Corbin’s story. Some say her husband + was shot in the head; some say he was shot in the heart. Others say Corbin dressed as a man to fight in + the war. Sometimes she wonders whether she will ever find answers. Perhaps these conflicting stories are + just a part of Corbin’s mystique. “The more I research, the less I know,” Bailey says. +

+

You can join the conversation about this and other stories in + the Atlas Obscura Community Forums.

+
+ + +
+
+
+
+ + +
+
+ + +
+
+
+ + + + +
+ + + + + +
+ +
+ + + + + + + +
+
+ + + +
+
+ + + + + + + + + + + + \ No newline at end of file diff --git a/test/fixtures/nypd-facial-recognition-children-teenagers4.html b/test/fixtures/nypd-facial-recognition-children-teenagers4.html new file mode 100644 index 000000000..9f15cc42e --- /dev/null +++ b/test/fixtures/nypd-facial-recognition-children-teenagers4.html @@ -0,0 +1,228 @@ + + + + She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times + She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times + + + + + + + + + + + + + + + + + + + + + +

Advertisement

She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database.

With little oversight, the N.Y.P.D. has been using powerful surveillance technology on photos of children and teenagers.

Image
CreditCreditSarah Blesener for The New York Times

[What you need to know to start the day: Get New York Today in your inbox.]

The New York Police Department has been loading thousands of arrest photos of children and teenagers into a facial recognition database despite evidence the technology has a higher risk of false matches in younger faces.

For about four years, internal records show, the department has used the technology to compare crime scene images with its collection of juvenile mug shots, the photos that are taken at an arrest. Most of the photos are of teenagers, largely 13 to 16 years old, but children as young as 11 have been included.

Elected officials and civil rights groups said the disclosure that the city was deploying a powerful surveillance tool on adolescents β€” whose privacy seems sacrosanct and whose status is protected in the criminal justice system β€” was a striking example of the Police Department’s ability to adopt advancing technology with little public scrutiny.

Several members of the City Council as well as a range of civil liberties groups said they were unaware of the policy until they were contacted by The New York Times.

Police Department officials defended the decision, saying it was just the latest evolution of a longstanding policing technique: using arrest photos to identify suspects.

β€œI don’t think this is any secret decision that’s made behind closed doors,” the city’s chief of detectives, Dermot F. Shea, said in an interview. β€œThis is just process, and making sure we’re doing everything to fight crime.”

Other cities have begun to debate whether law enforcement should use facial recognition, which relies on an algorithm to quickly pore through images and suggest matches. In May, San Francisco blocked city agencies, including the police, from using the tool amid unease about potential government abuse. Detroit is facing public resistance to a technology that has been shown to have lower accuracy with people with darker skin.

In New York, the state Education Department recently told the Lockport, N.Y., school district to delay a plan to use facial recognition on students, citing privacy concerns.

β€œAt the end of the day, it should be banned β€” no young people,” said Councilman Donovan Richards, a Queens Democrat who heads the Public Safety Committee, which oversees the Police Department.

The department said its legal bureau had approved using facial recognition on juveniles. The algorithm may suggest a lead, but detectives would not make an arrest based solely on that, Chief Shea said.

Image
CreditChang W. Lee/The New York Times

Still, facial recognition has not been widely tested on children. Most algorithms are taught to β€œthink” based on adult faces, and there is growing evidence that they do not work as well on children.

The National Institute of Standards and Technology, which is part of the Commerce Department and evaluates facial recognition algorithms for accuracy, recently found the vast majority of more than 100 facial recognition algorithms had a higher rate of mistaken matches among children. The error rate was most pronounced in young children but was also seen in those aged 10 to 16.

Aging poses another problem: The appearance of children and adolescents can change drastically as bones stretch and shift, altering the underlying facial structure.

β€œI would use extreme caution in using those algorithms,” said Karl Ricanek Jr., a computer science professor and co-founder of the Face Aging Group at the University of North Carolina-Wilmington.

Technology that can match an image of a younger teenager to a recent arrest photo may be less effective at finding the same person even one or two years later, he said.

β€œThe systems do not have the capacity to understand the dynamic changes that occur to a child’s face,” Dr. Ricanek said.

Idemia and DataWorks Plus, the two companies that provide facial recognition software to the Police Department, did not respond to requests for comment.

The New York Police Department can take arrest photos of minors as young as 11 who are charged with a felony, depending on the severity of the charge.

And in many cases, the department keeps the photos for years, making facial recognition comparisons to what may have effectively become outdated images. There are photos of 5,500 individuals in the juvenile database, 4,100 of whom are no longer 16 or under, the department said. Teenagers 17 and older are considered adults in the criminal justice system.

Police officials declined to provide statistics on how often their facial recognition systems provide false matches, or to explain how they evaluate the system’s effectiveness.

β€œWe are comfortable with this technology because it has proved to be a valuable investigative method,” Chief Shea said. Facial recognition has helped lead to thousands of arrests of both adults and juveniles, the department has said.

Mayor Bill de Blasio had been aware the department was using the technology on minors, said Freddi Goldstein, a spokeswoman for the mayor.

She said the Police Department followed β€œstrict guidelines” in applying the technology and City Hall monitored the agency’s compliance with the policies.

The Times learned details of the department’s use of facial recognition by reviewing documents posted online earlier this year by Clare Garvie, a senior associate at the Center on Privacy and Technology at Georgetown Law. Ms. Garvie received the documents as part of an open records lawsuit.

It could not be determined whether other large police departments used facial recognition with juveniles because very few have written policies governing the use of the technology, Ms. Garvie said.

New York detectives rely on a vast network of surveillance cameras throughout the city to provide images of people believed to have committed a crime. Since 2011, the department has had a dedicated unit of officers who use facial recognition to compare those images against millions of photos, usually mug shots. The software proposes matches, which have led to thousands of arrests, the department said.

By 2013, top police officials were meeting to discuss including juveniles in the program, the documents reviewed by The Times showed.

The documents showed that the juvenile database had been integrated into the system by 2015.

β€œWe have these photos. It makes sense,” Chief Shea said in the interview.

State law requires that arrest photos be destroyed if the case is resolved in the juvenile’s favor, or if the child is found to have committed only a misdemeanor, rather than a felony. The photos also must be destroyed if a person reaches age 21 without a criminal record.

When children are charged with crimes, the court system usually takes some steps to prevent their acts from defining them in later years. Children who are 16 and under, for instance, are generally sent to Family Court, where records are not public.

Yet including their photos in a facial recognition database runs the risk that an imperfect algorithm identifies them as possible suspects in later crimes, civil rights advocates said. A mistaken match could lead investigators to focus on the wrong person from the outset, they said.

β€œIt’s very disturbing to know that no matter what I’m doing at that moment, someone might be scanning my picture to try to find someone who committed a crime,” said Bailey, a 17-year-old Brooklyn girl who had admitted guilt in Family Court to a group attack that happened when she was 14. She said she was present at the attack but did not participate.

Bailey, who asked that she be identified only by her last name because she did not want her juvenile arrest to be public, has not been arrested again and is now a student at John Jay College of Criminal Justice.

Recent studies indicate that people of color, as well as children and women, have a greater risk of misidentification than their counterparts, said Joy Buolamwini, the founder of the Algorithmic Justice League and graduate researcher at the M.I.T. Media Lab, who has examined how human biases are built into artificial intelligence.

The racial disparities in the juvenile justice system are stark: In New York, black and Latino juveniles were charged with crimes at far higher rates than whites in 2017, the most recent year for which numbers were available. Black juveniles outnumbered white juveniles more than 15 to 1.

β€œIf the facial recognition algorithm has a negative bias toward a black population, that will get magnified more toward children,” Dr. Ricanek said, adding that in terms of diminished accuracy, β€œyou’re now putting yourself in unknown territory.”

Joseph Goldstein writes about policing and the criminal justice system. He has been a reporter at The Times since 2011, and is based in New York. He also worked for a year in the Kabul bureau, reporting on Afghanistan. @JoeKGoldstein

Ali Watkins is a reporter on the Metro Desk, covering courts and social services. Previously, she covered national security in Washington for The Times, BuzzFeed and McClatchy Newspapers. @AliWatkins

A version of this article appears in print on , Section A, Page 1 of the New York edition with the headline: In New York, Police Computers Scan Faces, Some as Young as 11. Order Reprints | Today’s Paper | Subscribe

Advertisement

+ + + + + + + + + + +
+ +
+ + + + diff --git a/test/formatter_test.exs b/test/formatter_test.exs index 087bdbcc2..37f8bb800 100644 --- a/test/formatter_test.exs +++ b/test/formatter_test.exs @@ -119,7 +119,20 @@ test "turning urls into links" do end end - describe "add_user_links" do + describe "Formatter.linkify" do + test "correctly finds mentions that contain the domain name" do + _user = insert(:user, %{nickname: "lain"}) + _remote_user = insert(:user, %{nickname: "lain@lain.com", local: false}) + + text = "hey @lain@lain.com what's up" + + {_text, mentions, []} = Formatter.linkify(text) + [{username, user}] = mentions + + assert username == "@lain@lain.com" + assert user.nickname == "lain@lain.com" + end + test "gives a replacement for user links, using local nicknames in user links text" do text = "@gsimg According to @archa_eme_, that is @daggsy. Also hello @archaeme@archae.me" gsimg = insert(:user, %{nickname: "gsimg"}) diff --git a/test/notification_test.exs b/test/notification_test.exs index 9a1c2f2b5..04bf5b41a 100644 --- a/test/notification_test.exs +++ b/test/notification_test.exs @@ -15,6 +15,18 @@ defmodule Pleroma.NotificationTest do alias Pleroma.Web.Streamer describe "create_notifications" do + test "creates a notification for an emoji reaction" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "yeah"}) + {:ok, activity, _object} = CommonAPI.react_with_emoji(activity.id, other_user, "β˜•") + + {:ok, [notification]} = Notification.create_notifications(activity) + + assert notification.user_id == user.id + end + test "notifies someone when they are directly addressed" do user = insert(:user) other_user = insert(:user) diff --git a/test/object_test.exs b/test/object_test.exs index 9b4e6f0bf..5690bedec 100644 --- a/test/object_test.exs +++ b/test/object_test.exs @@ -76,8 +76,9 @@ test "ensures cache is cleared for the object" do describe "delete attachments" do clear_config([Pleroma.Upload]) - test "in subdirectories" do + test "Disabled via config" do Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + Pleroma.Config.put([:instance, :cleanup_attachments], false) file = %Plug.Upload{ content_type: "image/jpg", @@ -103,6 +104,41 @@ test "in subdirectories" do ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker)) + assert Object.get_by_id(note.id).data["deleted"] + refute Object.get_by_id(attachment.id) == nil + + assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + end + + test "in subdirectories" do + Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + Pleroma.Config.put([:instance, :cleanup_attachments], true) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + user = insert(:user) + + {:ok, %Object{} = attachment} = + Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) + + %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + + uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) + + path = href |> Path.dirname() |> Path.basename() + + assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + + Object.delete(note) + + ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker)) + + assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") @@ -111,6 +147,7 @@ test "in subdirectories" do test "with dedupe enabled" do Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) Pleroma.Config.put([Pleroma.Upload, :filters], [Pleroma.Upload.Filter.Dedupe]) + Pleroma.Config.put([:instance, :cleanup_attachments], true) uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) @@ -139,6 +176,7 @@ test "with dedupe enabled" do ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker)) + assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil assert {:ok, files} = File.ls(uploads_dir) refute filename in files @@ -146,6 +184,7 @@ test "with dedupe enabled" do test "with objects that have legacy data.url attribute" do Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + Pleroma.Config.put([:instance, :cleanup_attachments], true) file = %Plug.Upload{ content_type: "image/jpg", @@ -173,6 +212,42 @@ test "with objects that have legacy data.url attribute" do ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker)) + assert Object.get_by_id(note.id).data["deleted"] + assert Object.get_by_id(attachment.id) == nil + + assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") + end + + test "With custom base_url" do + Pleroma.Config.put([Pleroma.Upload, :uploader], Pleroma.Uploaders.Local) + Pleroma.Config.put([Pleroma.Upload, :base_url], "https://sub.domain.tld/dir/") + Pleroma.Config.put([:instance, :cleanup_attachments], true) + + file = %Plug.Upload{ + content_type: "image/jpg", + path: Path.absname("test/fixtures/image.jpg"), + filename: "an_image.jpg" + } + + user = insert(:user) + + {:ok, %Object{} = attachment} = + Pleroma.Web.ActivityPub.ActivityPub.upload(file, actor: user.ap_id) + + %{data: %{"attachment" => [%{"url" => [%{"href" => href}]}]}} = + note = insert(:note, %{user: user, data: %{"attachment" => [attachment.data]}}) + + uploads_dir = Pleroma.Config.get!([Pleroma.Uploaders.Local, :uploads]) + + path = href |> Path.dirname() |> Path.basename() + + assert {:ok, ["an_image.jpg"]} == File.ls("#{uploads_dir}/#{path}") + + Object.delete(note) + + ObanHelpers.perform(all_enqueued(worker: Pleroma.Workers.AttachmentsCleanupWorker)) + + assert Object.get_by_id(note.id).data["deleted"] assert Object.get_by_id(attachment.id) == nil assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") diff --git a/test/plugs/rate_limiter_test.exs b/test/plugs/rate_limiter_test.exs index 78f1ea9e4..06ffa7b70 100644 --- a/test/plugs/rate_limiter_test.exs +++ b/test/plugs/rate_limiter_test.exs @@ -16,6 +16,7 @@ defmodule Pleroma.Plugs.RateLimiterTest do test "config is required for plug to work" do limiter_name = :test_init Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) assert %{limits: {1, 1}, name: :test_init, opts: [name: :test_init]} == RateLimiter.init(name: limiter_name) @@ -23,11 +24,39 @@ test "config is required for plug to work" do assert nil == RateLimiter.init(name: :foo) end + test "it is disabled for localhost" do + limiter_name = :test_init + Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {127, 0, 0, 1}) + Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) + + assert RateLimiter.disabled?() == true + end + + test "it is disabled for socket" do + limiter_name = :test_init + Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) + Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], false) + + assert RateLimiter.disabled?() == true + end + + test "it is enabled for socket when remote ip is enabled" do + limiter_name = :test_init + Pleroma.Config.put([:rate_limit, limiter_name], {1, 1}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {:local, "/path/to/pleroma.sock"}) + Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) + + assert RateLimiter.disabled?() == false + end + test "it restricts based on config values" do limiter_name = :test_opts scale = 80 limit = 5 + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Pleroma.Config.put([:rate_limit, limiter_name], {scale, limit}) opts = RateLimiter.init(name: limiter_name) @@ -61,6 +90,7 @@ test "`bucket_name` option overrides default bucket name" do limiter_name = :test_bucket_name Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) base_bucket_name = "#{limiter_name}:group1" opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name) @@ -75,6 +105,7 @@ test "`bucket_name` option overrides default bucket name" do test "`params` option allows different queries to be tracked independently" do limiter_name = :test_params Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) opts = RateLimiter.init(name: limiter_name, params: ["id"]) @@ -90,6 +121,7 @@ test "`params` option allows different queries to be tracked independently" do test "it supports combination of options modifying bucket name" do limiter_name = :test_options_combo Pleroma.Config.put([:rate_limit, limiter_name], {1000, 5}) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) base_bucket_name = "#{limiter_name}:group1" opts = RateLimiter.init(name: limiter_name, bucket_name: base_bucket_name, params: ["id"]) @@ -109,6 +141,7 @@ test "it supports combination of options modifying bucket name" do test "are restricted based on remote IP" do limiter_name = :test_unauthenticated Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 5}, {1, 10}]) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) opts = RateLimiter.init(name: limiter_name) @@ -147,6 +180,7 @@ test "can have limits seperate from unauthenticated connections" do scale = 50 limit = 5 + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) Pleroma.Config.put([:rate_limit, limiter_name], [{1000, 1}, {scale, limit}]) opts = RateLimiter.init(name: limiter_name) @@ -169,6 +203,7 @@ test "can have limits seperate from unauthenticated connections" do test "diffrerent users are counted independently" do limiter_name = :test_authenticated Pleroma.Config.put([:rate_limit, limiter_name], [{1, 10}, {1000, 5}]) + Pleroma.Config.put([Pleroma.Web.Endpoint, :http, :ip], {8, 8, 8, 8}) opts = RateLimiter.init(name: limiter_name) diff --git a/test/runtime_test.exs b/test/runtime_test.exs index f7b6f23d4..6bde608ae 100644 --- a/test/runtime_test.exs +++ b/test/runtime_test.exs @@ -6,6 +6,6 @@ defmodule Pleroma.RuntimeTest do use ExUnit.Case, async: true test "it loads custom runtime modules" do - assert Code.ensure_compiled?(RuntimeModule) + assert {:module, RuntimeModule} == Code.ensure_compiled(RuntimeModule) end end diff --git a/test/support/http_request_mock.ex b/test/support/http_request_mock.ex index f43de700d..ba3341327 100644 --- a/test/support/http_request_mock.ex +++ b/test/support/http_request_mock.ex @@ -19,7 +19,7 @@ def request( else error -> with {:error, message} <- error do - Logger.warn(message) + Logger.warn(to_string(message)) end {_, _r} = error diff --git a/test/tasks/config_test.exs b/test/tasks/config_test.exs index 2e56e6cfe..d79d34276 100644 --- a/test/tasks/config_test.exs +++ b/test/tasks/config_test.exs @@ -25,30 +25,50 @@ defmodule Mix.Tasks.Pleroma.ConfigTest do end test "error if file with custom settings doesn't exist" do - Mix.Tasks.Pleroma.Config.run(["migrate_to_db"]) + Mix.Tasks.Pleroma.Config.migrate_to_db("config/not_existance_config_file.exs") assert_receive {:mix_shell, :info, [ - "To migrate settings, you must define custom settings in config/test.secret.exs." + "To migrate settings, you must define custom settings in config/not_existance_config_file.exs." ]}, 15 end - test "settings are migrated to db" do - initial = Application.get_env(:quack, :level) - on_exit(fn -> Application.put_env(:quack, :level, initial) end) - assert Repo.all(ConfigDB) == [] + describe "migrate_to_db/1" do + setup do + initial = Application.get_env(:quack, :level) + on_exit(fn -> Application.put_env(:quack, :level, initial) end) + end - Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") + test "settings are migrated to db" do + assert Repo.all(ConfigDB) == [] - config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"}) - config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"}) - config3 = ConfigDB.get_by_params(%{group: ":quack", key: ":level"}) - refute ConfigDB.get_by_params(%{group: ":pleroma", key: "Pleroma.Repo"}) + Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") - assert ConfigDB.from_binary(config1.value) == [key: "value", key2: [Repo]] - assert ConfigDB.from_binary(config2.value) == [key: "value2", key2: ["Activity"]] - assert ConfigDB.from_binary(config3.value) == :info + config1 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"}) + config2 = ConfigDB.get_by_params(%{group: ":pleroma", key: ":second_setting"}) + config3 = ConfigDB.get_by_params(%{group: ":quack", key: ":level"}) + refute ConfigDB.get_by_params(%{group: ":pleroma", key: "Pleroma.Repo"}) + + assert ConfigDB.from_binary(config1.value) == [key: "value", key2: [Repo]] + assert ConfigDB.from_binary(config2.value) == [key: "value2", key2: ["Activity"]] + assert ConfigDB.from_binary(config3.value) == :info + end + + test "config table is truncated before migration" do + ConfigDB.create(%{ + group: ":pleroma", + key: ":first_setting", + value: [key: "value", key2: ["Activity"]] + }) + + assert Repo.aggregate(ConfigDB, :count, :id) == 1 + + Mix.Tasks.Pleroma.Config.migrate_to_db("test/fixtures/config/temp.secret.exs") + + config = ConfigDB.get_by_params(%{group: ":pleroma", key: ":first_setting"}) + assert ConfigDB.from_binary(config.value) == [key: "value", key2: [Repo]] + end end describe "with deletion temp file" do diff --git a/test/tasks/email_test.exs b/test/tasks/email_test.exs new file mode 100644 index 000000000..944c07064 --- /dev/null +++ b/test/tasks/email_test.exs @@ -0,0 +1,52 @@ +defmodule Mix.Tasks.Pleroma.EmailTest do + use Pleroma.DataCase + + import Swoosh.TestAssertions + + alias Pleroma.Config + alias Pleroma.Tests.ObanHelpers + + setup_all do + Mix.shell(Mix.Shell.Process) + + on_exit(fn -> + Mix.shell(Mix.Shell.IO) + end) + + :ok + end + + describe "pleroma.email test" do + test "Sends test email with no given address" do + mail_to = Config.get([:instance, :email]) + + :ok = Mix.Tasks.Pleroma.Email.run(["test"]) + + ObanHelpers.perform_all() + + assert_receive {:mix_shell, :info, [message]} + assert message =~ "Test email has been sent" + + assert_email_sent( + to: mail_to, + html_body: ~r/a test email was requested./i + ) + end + + test "Sends test email with given address" do + mail_to = "hewwo@example.com" + + :ok = Mix.Tasks.Pleroma.Email.run(["test", "--to", mail_to]) + + ObanHelpers.perform_all() + + assert_receive {:mix_shell, :info, [message]} + assert message =~ "Test email has been sent" + + assert_email_sent( + to: mail_to, + html_body: ~r/a test email was requested./i + ) + end + end +end diff --git a/test/user_test.exs b/test/user_test.exs index 9da1e02a9..1b5e63bd4 100644 --- a/test/user_test.exs +++ b/test/user_test.exs @@ -585,7 +585,7 @@ test "returns an ap_id for a user" do user = insert(:user) assert User.ap_id(user) == - Pleroma.Web.Router.Helpers.feed_url( + Pleroma.Web.Router.Helpers.user_feed_url( Pleroma.Web.Endpoint, :feed_redirect, user.nickname @@ -596,7 +596,7 @@ test "returns an ap_followers link for a user" do user = insert(:user) assert User.ap_followers(user) == - Pleroma.Web.Router.Helpers.feed_url( + Pleroma.Web.Router.Helpers.user_feed_url( Pleroma.Web.Endpoint, :feed_redirect, user.nickname @@ -1286,23 +1286,35 @@ test "User.delete() plugs any possible zombie objects" do end end - test "auth_active?/1 works correctly" do - Pleroma.Config.put([:instance, :account_activation_required], true) + describe "account_status/1" do + clear_config([:instance, :account_activation_required]) - local_user = insert(:user, local: true, confirmation_pending: true) - confirmed_user = insert(:user, local: true, confirmation_pending: false) - remote_user = insert(:user, local: false) + test "return confirmation_pending for unconfirm user" do + Pleroma.Config.put([:instance, :account_activation_required], true) + user = insert(:user, confirmation_pending: true) + assert User.account_status(user) == :confirmation_pending + end - refute User.auth_active?(local_user) - assert User.auth_active?(confirmed_user) - assert User.auth_active?(remote_user) + test "return active for confirmed user" do + Pleroma.Config.put([:instance, :account_activation_required], true) + user = insert(:user, confirmation_pending: false) + assert User.account_status(user) == :active + end - # also shows unactive for deactivated users + test "return active for remote user" do + user = insert(:user, local: false) + assert User.account_status(user) == :active + end - deactivated_but_confirmed = - insert(:user, local: true, confirmation_pending: false, deactivated: true) + test "returns :password_reset_pending for user with reset password" do + user = insert(:user, password_reset_pending: true) + assert User.account_status(user) == :password_reset_pending + end - refute User.auth_active?(deactivated_but_confirmed) + test "returns :deactivated for deactivated user" do + user = insert(:user, local: true, confirmation_pending: false, deactivated: true) + assert User.account_status(user) == :deactivated + end end describe "superuser?/1" do diff --git a/test/web/activity_pub/transmogrifier_test.exs b/test/web/activity_pub/transmogrifier_test.exs index 0fefb60da..194b314a3 100644 --- a/test/web/activity_pub/transmogrifier_test.exs +++ b/test/web/activity_pub/transmogrifier_test.exs @@ -397,6 +397,25 @@ test "it works for incoming emoji reactions" do assert data["content"] == "πŸ‘Œ" end + test "it reject invalid emoji reactions" do + user = insert(:user) + {:ok, activity} = CommonAPI.post(user, %{"status" => "hello"}) + + data = + File.read!("test/fixtures/emoji-reaction-too-long.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + assert :error = Transmogrifier.handle_incoming(data) + + data = + File.read!("test/fixtures/emoji-reaction-no-emoji.json") + |> Poison.decode!() + |> Map.put("object", activity.data["object"]) + + assert :error = Transmogrifier.handle_incoming(data) + end + test "it works for incoming emoji reaction undos" do user = insert(:user) diff --git a/test/web/activity_pub/utils_test.exs b/test/web/activity_pub/utils_test.exs index 586eb1d2f..211fa6c95 100644 --- a/test/web/activity_pub/utils_test.exs +++ b/test/web/activity_pub/utils_test.exs @@ -636,4 +636,17 @@ test "removes actor from announcements" do assert updated_object.data["announcement_count"] == 1 end end + + describe "get_cached_emoji_reactions/1" do + test "returns the data or an emtpy list" do + object = insert(:note) + assert Utils.get_cached_emoji_reactions(object) == [] + + object = insert(:note, data: %{"reactions" => [["x", ["lain"]]]}) + assert Utils.get_cached_emoji_reactions(object) == [["x", ["lain"]]] + + object = insert(:note, data: %{"reactions" => %{}}) + assert Utils.get_cached_emoji_reactions(object) == [] + end + end end diff --git a/test/web/common_api/common_api_test.exs b/test/web/common_api/common_api_test.exs index f8963e42e..8fa0c6faa 100644 --- a/test/web/common_api/common_api_test.exs +++ b/test/web/common_api/common_api_test.exs @@ -238,7 +238,9 @@ test "reacting to a status with an emoji" do assert reaction.data["actor"] == user.ap_id assert reaction.data["content"] == "πŸ‘" - # TODO: test error case. + {:ok, activity} = CommonAPI.post(other_user, %{"status" => "cofe"}) + + {:error, _} = CommonAPI.react_with_emoji(activity.id, user, ".") end test "unreacting to a status with an emoji" do diff --git a/test/web/feed/tag_controller_test.exs b/test/web/feed/tag_controller_test.exs new file mode 100644 index 000000000..2aa1b9587 --- /dev/null +++ b/test/web/feed/tag_controller_test.exs @@ -0,0 +1,154 @@ +# Pleroma: A lightweight social networking server +# Copyright Β© 2017-2019 Pleroma Authors +# SPDX-License-Identifier: AGPL-3.0-only + +defmodule Pleroma.Web.Feed.TagControllerTest do + use Pleroma.Web.ConnCase + + import Pleroma.Factory + import SweetXml + + alias Pleroma.Web.Feed.FeedView + + clear_config([:feed]) + + test "gets a feed (ATOM)", %{conn: conn} do + Pleroma.Config.put( + [:feed, :post_title], + %{max_length: 25, omission: "..."} + ) + + user = insert(:user) + {:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) + + object = Pleroma.Object.normalize(activity1) + + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ]) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + {:ok, _activity2} = + Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) + + {:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"}) + + response = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(tag_feed_path(conn, :feed, "pleromaart.atom")) + |> response(200) + + xml = parse(response) + + assert xpath(xml, ~x"//feed/title/text()") == '#pleromaart' + + assert xpath(xml, ~x"//feed/entry/title/text()"l) == [ + '42 This is :moominmamm...', + 'yeah #PleromaArt' + ] + + assert xpath(xml, ~x"//feed/entry/author/name/text()"ls) == [user.nickname, user.nickname] + assert xpath(xml, ~x"//feed/entry/author/id/text()"ls) == [user.ap_id, user.ap_id] + end + + test "gets a feed (RSS)", %{conn: conn} do + Pleroma.Config.put( + [:feed, :post_title], + %{max_length: 25, omission: "..."} + ) + + user = insert(:user) + {:ok, activity1} = Pleroma.Web.CommonAPI.post(user, %{"status" => "yeah #PleromaArt"}) + + object = Pleroma.Object.normalize(activity1) + + object_data = + Map.put(object.data, "attachment", [ + %{ + "url" => [ + %{ + "href" => + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4", + "mediaType" => "video/mp4", + "type" => "Link" + } + ] + } + ]) + + object + |> Ecto.Changeset.change(data: object_data) + |> Pleroma.Repo.update() + + {:ok, activity2} = + Pleroma.Web.CommonAPI.post(user, %{"status" => "42 This is :moominmamma #PleromaArt"}) + + {:ok, _activity3} = Pleroma.Web.CommonAPI.post(user, %{"status" => "This is :moominmamma"}) + + response = + conn + |> put_req_header("content-type", "application/rss+xml") + |> get(tag_feed_path(conn, :feed, "pleromaart.rss")) + |> response(200) + + xml = parse(response) + assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart' + + assert xpath(xml, ~x"//channel/description/text()"s) == + "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse." + + assert xpath(xml, ~x"//channel/link/text()") == + '#{Pleroma.Web.base_url()}/tags/pleromaart.rss' + + assert xpath(xml, ~x"//channel/webfeeds:logo/text()") == + '#{Pleroma.Web.base_url()}/static/logo.png' + + assert xpath(xml, ~x"//channel/item/title/text()"l) == [ + '42 This is :moominmamm...', + 'yeah #PleromaArt' + ] + + assert xpath(xml, ~x"//channel/item/pubDate/text()"sl) == [ + FeedView.pub_date(activity1.data["published"]), + FeedView.pub_date(activity2.data["published"]) + ] + + assert xpath(xml, ~x"//channel/item/enclosure/@url"sl) == [ + "https://peertube.moe/static/webseed/df5f464b-be8d-46fb-ad81-2d4c2d1630e3-480.mp4" + ] + + obj1 = Pleroma.Object.normalize(activity1) + obj2 = Pleroma.Object.normalize(activity2) + + assert xpath(xml, ~x"//channel/item/description/text()"sl) == [ + HtmlEntities.decode(FeedView.activity_content(obj2)), + HtmlEntities.decode(FeedView.activity_content(obj1)) + ] + + response = + conn + |> put_req_header("content-type", "application/atom+xml") + |> get(tag_feed_path(conn, :feed, "pleromaart")) + |> response(200) + + xml = parse(response) + assert xpath(xml, ~x"//channel/title/text()") == '#pleromaart' + + assert xpath(xml, ~x"//channel/description/text()"s) == + "These are public toots tagged with #pleromaart. You can interact with them if you have an account anywhere in the fediverse." + end +end diff --git a/test/web/feed/feed_controller_test.exs b/test/web/feed/user_controller_test.exs similarity index 97% rename from test/web/feed/feed_controller_test.exs rename to test/web/feed/user_controller_test.exs index 6f61acf43..41cc9e07e 100644 --- a/test/web/feed/feed_controller_test.exs +++ b/test/web/feed/user_controller_test.exs @@ -2,7 +2,7 @@ # Copyright Β© 2017-2019 Pleroma Authors # SPDX-License-Identifier: AGPL-3.0-only -defmodule Pleroma.Web.Feed.FeedControllerTest do +defmodule Pleroma.Web.Feed.UserControllerTest do use Pleroma.Web.ConnCase import Pleroma.Factory @@ -49,7 +49,7 @@ test "gets a feed", %{conn: conn} do resp = conn |> put_req_header("content-type", "application/atom+xml") - |> get("/users/#{user.nickname}/feed.atom") + |> get(user_feed_path(conn, :feed, user.nickname)) |> response(200) activity_titles = @@ -65,7 +65,7 @@ test "returns 404 for a missing feed", %{conn: conn} do conn = conn |> put_req_header("content-type", "application/atom+xml") - |> get("/users/nonexisting/feed.atom") + |> get(user_feed_path(conn, :feed, "nonexisting")) assert response(conn, 404) end @@ -91,7 +91,7 @@ test "undefined format. it returns error when user not found", %{conn: conn} do response = conn |> put_req_header("accept", "application/xml") - |> get("/users/jimm") + |> get(user_feed_path(conn, :feed, "jimm")) |> response(404) assert response == ~S({"error":"Not found"}) diff --git a/test/web/mastodon_api/controllers/account_controller_test.exs b/test/web/mastodon_api/controllers/account_controller_test.exs index 0d4860a42..ec1e18002 100644 --- a/test/web/mastodon_api/controllers/account_controller_test.exs +++ b/test/web/mastodon_api/controllers/account_controller_test.exs @@ -668,6 +668,7 @@ test "returns error when user already registred", %{conn: conn, valid_params: va end test "rate limit", %{conn: conn} do + Pleroma.Config.put([Pleroma.Plugs.RemoteIp, :enabled], true) app_token = insert(:oauth_token, user: nil) conn = diff --git a/test/web/mastodon_api/controllers/suggestion_controller_test.exs b/test/web/mastodon_api/controllers/suggestion_controller_test.exs index c4118a576..0319d3475 100644 --- a/test/web/mastodon_api/controllers/suggestion_controller_test.exs +++ b/test/web/mastodon_api/controllers/suggestion_controller_test.exs @@ -7,7 +7,6 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do alias Pleroma.Config - import ExUnit.CaptureLog import Pleroma.Factory import Tesla.Mock @@ -36,11 +35,7 @@ defmodule Pleroma.Web.MastodonAPI.SuggestionControllerTest do [other_user: other_user] end - clear_config(:suggestions) - - test "returns empty result when suggestions disabled", %{conn: conn} do - Config.put([:suggestions, :enabled], false) - + test "returns empty result", %{conn: conn} do res = conn |> get("/api/v1/suggestions") @@ -48,43 +43,4 @@ test "returns empty result when suggestions disabled", %{conn: conn} do assert res == [] end - - test "returns error", %{conn: conn} do - Config.put([:suggestions, :enabled], true) - Config.put([:suggestions, :third_party_engine], "http://test500?{{host}}&{{user}}") - - assert capture_log(fn -> - res = - conn - |> get("/api/v1/suggestions") - |> json_response(500) - - assert res == "Something went wrong" - end) =~ "Could not retrieve suggestions" - end - - test "returns suggestions", %{conn: conn, other_user: other_user} do - Config.put([:suggestions, :enabled], true) - Config.put([:suggestions, :third_party_engine], "http://test200?{{host}}&{{user}}") - - res = - conn - |> get("/api/v1/suggestions") - |> json_response(200) - - assert res == [ - %{ - "acct" => "yj455", - "avatar" => "https://social.heldscal.la/avatar/201.jpeg", - "avatar_static" => "https://social.heldscal.la/avatar/s/201.jpeg", - "id" => 0 - }, - %{ - "acct" => other_user.ap_id, - "avatar" => "https://social.heldscal.la/avatar/202.jpeg", - "avatar_static" => "https://social.heldscal.la/avatar/s/202.jpeg", - "id" => other_user.id - } - ] - end end diff --git a/test/web/mastodon_api/views/notification_view_test.exs b/test/web/mastodon_api/views/notification_view_test.exs index ba1721e06..1fe83cb2c 100644 --- a/test/web/mastodon_api/views/notification_view_test.exs +++ b/test/web/mastodon_api/views/notification_view_test.exs @@ -134,4 +134,31 @@ test "Move notification" do assert [expected] == NotificationView.render("index.json", %{notifications: [notification], for: follower}) end + + test "EmojiReaction notification" do + user = insert(:user) + other_user = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) + {:ok, _activity, _} = CommonAPI.react_with_emoji(activity.id, other_user, "β˜•") + + activity = Repo.get(Activity, activity.id) + + [notification] = Notification.for_user(user) + + assert notification + + expected = %{ + id: to_string(notification.id), + pleroma: %{is_seen: false}, + type: "pleroma:emoji_reaction", + emoji: "β˜•", + account: AccountView.render("show.json", %{user: other_user, for: user}), + status: StatusView.render("show.json", %{activity: activity, for: user}), + created_at: Utils.to_masto_date(notification.inserted_at) + } + + assert expected == + NotificationView.render("show.json", %{notification: notification, for: user}) + end end diff --git a/test/web/mastodon_api/views/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 069bb8eac..fc110417c 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -36,7 +36,17 @@ test "has an emoji reaction list" do activity = Repo.get(Activity, activity.id) status = StatusView.render("show.json", activity: activity) - assert status[:pleroma][:emoji_reactions] == [["β˜•", 2], ["🍡", 1]] + assert status[:pleroma][:emoji_reactions] == [ + %{emoji: "β˜•", count: 2, reacted: false}, + %{emoji: "🍡", count: 1, reacted: false} + ] + + status = StatusView.render("show.json", activity: activity, for: user) + + assert status[:pleroma][:emoji_reactions] == [ + %{emoji: "β˜•", count: 2, reacted: true}, + %{emoji: "🍡", count: 1, reacted: false} + ] end test "loads and returns the direct conversation id when given the `with_direct_conversation_id` option" do diff --git a/test/web/oauth/oauth_controller_test.exs b/test/web/oauth/oauth_controller_test.exs index 59f4674eb..adeff8e25 100644 --- a/test/web/oauth/oauth_controller_test.exs +++ b/test/web/oauth/oauth_controller_test.exs @@ -819,7 +819,7 @@ test "rejects token exchange for valid credentials belonging to unconfirmed user |> User.confirmation_changeset(need_confirmation: true) |> User.update_and_set_cache() - refute Pleroma.User.auth_active?(user) + refute Pleroma.User.account_status(user) == :active app = insert(:oauth_app) @@ -849,7 +849,7 @@ test "rejects token exchange for valid credentials belonging to deactivated user app = insert(:oauth_app) - conn = + resp = build_conn() |> post("/oauth/token", %{ "grant_type" => "password", @@ -858,10 +858,12 @@ test "rejects token exchange for valid credentials belonging to deactivated user "client_id" => app.client_id, "client_secret" => app.client_secret }) + |> json_response(403) - assert resp = json_response(conn, 403) - assert %{"error" => _} = resp - refute Map.has_key?(resp, "access_token") + assert resp == %{ + "error" => "Your account is currently disabled", + "identifier" => "account_is_disabled" + } end test "rejects token exchange for user with password_reset_pending set to true" do @@ -875,7 +877,7 @@ test "rejects token exchange for user with password_reset_pending set to true" d app = insert(:oauth_app, scopes: ["read", "write"]) - conn = + resp = build_conn() |> post("/oauth/token", %{ "grant_type" => "password", @@ -884,12 +886,41 @@ test "rejects token exchange for user with password_reset_pending set to true" d "client_id" => app.client_id, "client_secret" => app.client_secret }) + |> json_response(403) - assert resp = json_response(conn, 403) + assert resp == %{ + "error" => "Password reset is required", + "identifier" => "password_reset_required" + } + end - assert resp["error"] == "Password reset is required" - assert resp["identifier"] == "password_reset_required" - refute Map.has_key?(resp, "access_token") + test "rejects token exchange for user with confirmation_pending set to true" do + Pleroma.Config.put([:instance, :account_activation_required], true) + password = "testpassword" + + user = + insert(:user, + password_hash: Comeonin.Pbkdf2.hashpwsalt(password), + confirmation_pending: true + ) + + app = insert(:oauth_app, scopes: ["read", "write"]) + + resp = + build_conn() + |> post("/oauth/token", %{ + "grant_type" => "password", + "username" => user.nickname, + "password" => password, + "client_id" => app.client_id, + "client_secret" => app.client_secret + }) + |> json_response(403) + + assert resp == %{ + "error" => "Your login is missing a confirmed e-mail address", + "identifier" => "missing_confirmed_email" + } end test "rejects an invalid authorization code" do diff --git a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs index 8e76f2f3d..6f1ea78ec 100644 --- a/test/web/pleroma_api/controllers/emoji_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/emoji_api_controller_test.exs @@ -6,7 +6,6 @@ defmodule Pleroma.Web.PleromaAPI.EmojiAPIControllerTest do use Pleroma.Web.ConnCase import Tesla.Mock - import Pleroma.Factory @emoji_dir_path Path.join( diff --git a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index a79ecd05b..be5007de5 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -25,9 +25,14 @@ test "POST /api/v1/pleroma/statuses/:id/react_with_emoji", %{conn: conn} do |> assign(:user, other_user) |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["write:statuses"])) |> post("/api/v1/pleroma/statuses/#{activity.id}/react_with_emoji", %{"emoji" => "β˜•"}) + |> json_response(200) - assert %{"id" => id} = json_response(result, 200) + assert %{"id" => id} = result assert to_string(activity.id) == id + + assert result["pleroma"]["emoji_reactions"] == [ + %{"emoji" => "β˜•", "count" => 1, "reacted" => true} + ] end test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do @@ -54,6 +59,7 @@ test "POST /api/v1/pleroma/statuses/:id/unreact_with_emoji", %{conn: conn} do test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do user = insert(:user) other_user = insert(:user) + doomed_user = insert(:user) {:ok, activity} = CommonAPI.post(user, %{"status" => "#cofe"}) @@ -65,14 +71,29 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do assert result == [] {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, other_user, "πŸŽ…") + {:ok, _, _} = CommonAPI.react_with_emoji(activity.id, doomed_user, "πŸŽ…") + + User.perform(:delete, doomed_user) result = conn |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") |> json_response(200) - [["πŸŽ…", [represented_user]]] = result + [%{"emoji" => "πŸŽ…", "count" => 1, "accounts" => [represented_user], "reacted" => false}] = + result + assert represented_user["id"] == other_user.id + + result = + conn + |> assign(:user, other_user) + |> assign(:token, insert(:oauth_token, user: other_user, scopes: ["read:statuses"])) + |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") + |> json_response(200) + + assert [%{"emoji" => "πŸŽ…", "count" => 1, "accounts" => [_represented_user], "reacted" => true}] = + result end test "/api/v1/pleroma/conversations/:id" do diff --git a/test/web/rich_media/parsers/twitter_card_test.exs b/test/web/rich_media/parsers/twitter_card_test.exs index f8e1c9b40..751ca614c 100644 --- a/test/web/rich_media/parsers/twitter_card_test.exs +++ b/test/web/rich_media/parsers/twitter_card_test.exs @@ -66,4 +66,38 @@ test "parses twitter card with name & property attributes" do "https://www.nytimes.com/2019/08/01/nyregion/nypd-facial-recognition-children-teenagers.html" }} end + + test "respect only first title tag on the page" do + image_path = + "https://assets.atlasobscura.com/media/W1siZiIsInVwbG9hZHMvYXNzZXRzLzkwYzgyMzI4LThlMDUtNGRiNS05MDg3LTUzMGUxZTM5N2RmMmVkOTM5ZDM4MGM4OTIx" <> + "YTQ5MF9EQVIgZXhodW1hdGlvbiBvZiBNYXJnYXJldCBDb3JiaW4gZ3JhdmUgMTkyNi5qcGciXSxbInAiLCJjb252ZXJ0IiwiIl0sWyJwIiwiY29udmVydCIsIi1xdWFsaXR5IDgxIC1hdXRvLW9" <> + "yaWVudCJdLFsicCIsInRodW1iIiwiNjAweD4iXV0/DAR%20exhumation%20of%20Margaret%20Corbin%20grave%201926.jpg" + + html = File.read!("test/fixtures/margaret-corbin-grave-west-point.html") + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + site: "@atlasobscura", + title: + "The Missing Grave of Margaret Corbin, Revolutionary War Veteran - Atlas Obscura", + card: "summary_large_image", + image: image_path + }} + end + + test "takes first founded title in html head if there is html markup error" do + html = File.read!("test/fixtures/nypd-facial-recognition-children-teenagers4.html") + + assert TwitterCard.parse(html, %{}) == + {:ok, + %{ + site: nil, + title: + "She Was Arrested at 14. Then Her Photo Went to a Facial Recognition Database. - The New York Times", + "app:id:googleplay": "com.nytimes.android", + "app:name:googleplay": "NYTimes", + "app:url:googleplay": "nytimes://reader/id/100000006583622" + }} + end end diff --git a/test/web/streamer/streamer_test.exs b/test/web/streamer/streamer_test.exs index 7166d6f0b..2a7550551 100644 --- a/test/web/streamer/streamer_test.exs +++ b/test/web/streamer/streamer_test.exs @@ -65,6 +65,9 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl blocked = insert(:user) {:ok, _user_relationship} = User.block(user, blocked) + {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) + {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( @@ -72,9 +75,6 @@ test "it doesn't send notify to the 'user:notification' stream when a user is bl %{transport_pid: task.pid, assigns: %{user: user}} ) - {:ok, activity} = CommonAPI.post(user, %{"status" => ":("}) - {:ok, notif, _} = CommonAPI.favorite(activity.id, blocked) - Streamer.stream("user:notification", notif) Task.await(task) end @@ -83,6 +83,11 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is user: user } do user2 = insert(:user) + + {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + {:ok, activity} = CommonAPI.add_mute(user, activity) + {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( @@ -90,9 +95,6 @@ test "it doesn't send notify to the 'user:notification' stream when a thread is %{transport_pid: task.pid, assigns: %{user: user}} ) - {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) - {:ok, activity} = CommonAPI.add_mute(user, activity) - {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) Streamer.stream("user:notification", notif) Task.await(task) end @@ -101,6 +103,11 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is user: user } do user2 = insert(:user, %{ap_id: "https://hecking-lewd-place.com/user/meanie"}) + + {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") + {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) + {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) + task = Task.async(fn -> refute_receive {:text, _}, @streamer_timeout end) Streamer.add_socket( @@ -108,10 +115,6 @@ test "it doesn't send notify to the 'user:notification' stream' when a domain is %{transport_pid: task.pid, assigns: %{user: user}} ) - {:ok, user} = User.block_domain(user, "hecking-lewd-place.com") - {:ok, activity} = CommonAPI.post(user, %{"status" => "super hot take"}) - {:ok, notif, _} = CommonAPI.favorite(activity.id, user2) - Streamer.stream("user:notification", notif) Task.await(task) end @@ -267,6 +270,8 @@ test "it doesn't send messages involving blocked users" do blocked_user = insert(:user) {:ok, _user_relationship} = User.block(user, blocked_user) + {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) + task = Task.async(fn -> refute_receive {:text, _}, 1_000 @@ -277,8 +282,6 @@ test "it doesn't send messages involving blocked users" do user: user } - {:ok, activity} = CommonAPI.post(blocked_user, %{"status" => "Test"}) - topics = %{ "public" => [fake_socket] } @@ -335,6 +338,12 @@ test "it doesn't send unwanted DMs to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) + {:ok, activity} = + CommonAPI.post(user_b, %{ + "status" => "@#{user_c.nickname} Test", + "visibility" => "direct" + }) + task = Task.async(fn -> refute_receive {:text, _}, 1_000 @@ -345,12 +354,6 @@ test "it doesn't send unwanted DMs to list" do user: user_a } - {:ok, activity} = - CommonAPI.post(user_b, %{ - "status" => "@#{user_c.nickname} Test", - "visibility" => "direct" - }) - topics = %{ "list:#{list.id}" => [fake_socket] } @@ -367,6 +370,12 @@ test "it doesn't send unwanted private posts to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) + {:ok, activity} = + CommonAPI.post(user_b, %{ + "status" => "Test", + "visibility" => "private" + }) + task = Task.async(fn -> refute_receive {:text, _}, 1_000 @@ -377,12 +386,6 @@ test "it doesn't send unwanted private posts to list" do user: user_a } - {:ok, activity} = - CommonAPI.post(user_b, %{ - "status" => "Test", - "visibility" => "private" - }) - topics = %{ "list:#{list.id}" => [fake_socket] } @@ -401,6 +404,12 @@ test "it sends wanted private posts to list" do {:ok, list} = List.create("Test", user_a) {:ok, list} = List.follow(list, user_b) + {:ok, activity} = + CommonAPI.post(user_b, %{ + "status" => "Test", + "visibility" => "private" + }) + task = Task.async(fn -> assert_receive {:text, _}, 1_000 @@ -411,12 +420,6 @@ test "it sends wanted private posts to list" do user: user_a } - {:ok, activity} = - CommonAPI.post(user_b, %{ - "status" => "Test", - "visibility" => "private" - }) - Streamer.add_socket( "list:#{list.id}", fake_socket @@ -433,6 +436,9 @@ test "it doesn't send muted reblogs" do user3 = insert(:user) CommonAPI.hide_reblogs(user1, user2) + {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) + {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) + task = Task.async(fn -> refute_receive {:text, _}, 1_000 @@ -443,9 +449,6 @@ test "it doesn't send muted reblogs" do user: user1 } - {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) - {:ok, announce_activity, _} = CommonAPI.repeat(create_activity.id, user2) - topics = %{ "public" => [fake_socket] } @@ -455,6 +458,34 @@ test "it doesn't send muted reblogs" do Task.await(task) end + test "it does send non-reblog notification for reblog-muted actors" do + user1 = insert(:user) + user2 = insert(:user) + user3 = insert(:user) + CommonAPI.hide_reblogs(user1, user2) + + {:ok, create_activity} = CommonAPI.post(user3, %{"status" => "I'm kawen"}) + {:ok, favorite_activity, _} = CommonAPI.favorite(create_activity.id, user2) + + task = + Task.async(fn -> + assert_receive {:text, _}, 1_000 + end) + + fake_socket = %StreamerSocket{ + transport_pid: task.pid, + user: user1 + } + + topics = %{ + "public" => [fake_socket] + } + + Worker.push_to_socket(topics, "public", favorite_activity) + + Task.await(task) + end + test "it doesn't send posts from muted threads" do user = insert(:user) user2 = insert(:user)