diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f6914638..e5f4d848a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ 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 @@ -15,6 +16,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - **Breaking:** Elixir >=1.8 is now required (was >= 1.7) - **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 @@ -26,6 +28,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - Store status data inside Flag activity - Deprecated (reorganized as `UserRelationship` entity) User fields with user AP IDs (`blocks`, `mutes`, `muted_reblogs`, `muted_notifications`, `subscribers`). - 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 @@ -54,6 +57,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - 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. @@ -97,6 +101,9 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). - 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.
### Fixed diff --git a/config/config.exs b/config/config.exs index 860780e82..a5b49a839 100644 --- a/config/config.exs +++ b/config/config.exs @@ -426,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, diff --git a/config/description.exs b/config/description.exs index f941349d5..5f3c58b08 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,13 @@ 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: :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 +790,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 +812,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 +821,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 +842,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 +850,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 +858,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 +874,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 +883,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 +891,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 +899,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 +920,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 +947,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 +972,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 +1026,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 +1048,11 @@ hideUserStats: false, scopeCopy: true, subjectLineBehavior: "email", - alwaysShowSubjectInput: true + alwaysShowSubjectInput: true, + logoMask: false, + logoMargin: ".1em", + stickers: false, + enableEmojiPicker: false } ], children: [ @@ -1074,7 +1080,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 +1088,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 +1101,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 +1141,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 +1216,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 +1232,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 +1295,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 +1370,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 +1391,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 +1414,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 +1500,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 +1521,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 +1846,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 +1896,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 +1910,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 +1960,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 +2077,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 +2121,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 +2138,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 +2157,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 +2191,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 +2205,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 +2231,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 +2253,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 +2278,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 +2305,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 +2362,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 +2407,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 +2441,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 +2514,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 +2533,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 +2547,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 +2558,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 +2581,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 +2595,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 +2648,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 +2748,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 +2795,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 +2967,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 +3000,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 2236870c7..030660b34 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}. Contains no information about the reacting users, for that use the `emoji_reactions_by` endpoint. ## Attachments diff --git a/docs/API/pleroma_api.md b/docs/API/pleroma_api.md index 9ca8a5af0..9f5cafe5a 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, "accounts": [{"id" => "xyz.."...}, {"id" => "zyx..."}]}, + {"emoji": "☕", "count": 1, "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/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/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/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/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..1ac67b618 100644 --- a/lib/pleroma/web/activity_pub/activity_pub.ex +++ b/lib/pleroma/web/activity_pub/activity_pub.ex @@ -728,7 +728,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 +745,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 +771,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/status_view.ex b/lib/pleroma/web/mastodon_api/views/status_view.ex index 64a97896a..e60ef709b 100644 --- a/lib/pleroma/web/mastodon_api/views/status_view.ex +++ b/lib/pleroma/web/mastodon_api/views/status_view.ex @@ -256,7 +256,7 @@ 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)} 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 1cd37a25f..5cd4806b1 100644 --- a/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex +++ b/lib/pleroma/web/nodeinfo/nodeinfo_controller.ex @@ -73,9 +73,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, @@ -108,11 +105,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/pleroma_api_controller.ex b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex index bb19836ae..cd1c0764f 100644 --- a/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex +++ b/lib/pleroma/web/pleroma_api/controllers/pleroma_api_controller.ex @@ -49,7 +49,12 @@ def emoji_reactions_by(%{assigns: %{user: user}} = conn, %{"id" => activity_id}) 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})} + + %{ + emoji: emoji, + count: length(users), + accounts: AccountView.render("index.json", %{users: users, for: user, as: :user}) + } 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/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 fd32624f0..8d803ccff 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 9eb06122e..a89b13466 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"}, @@ -70,7 +70,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"}, @@ -84,7 +84,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 539ff68a0..597b9e004 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/config.json b/priv/static/static/config.json index c82678699..9e5390f7c 100644 --- a/priv/static/static/config.json +++ b/priv/static/static/config.json @@ -1,5 +1,5 @@ { - "theme": "pleroma-dark", + "theme": "ihatebeingalive", "background": "/static/aurora_borealis.jpg", "logo": "/static/logo.png", "logoMask": true, diff --git a/priv/static/static/font/fontello.1579191725174.woff b/priv/static/static/font/fontello.1579191725174.woff deleted file mode 100644 index dec0aca59..000000000 Binary files a/priv/static/static/font/fontello.1579191725174.woff and /dev/null differ diff --git a/priv/static/static/font/fontello.1579191725174.woff2 b/priv/static/static/font/fontello.1579191725174.woff2 deleted file mode 100644 index 75dd7d17a..000000000 Binary files a/priv/static/static/font/fontello.1579191725174.woff2 and /dev/null differ diff --git a/priv/static/static/font/fontello.1579191725174.eot b/priv/static/static/font/fontello.1580295842879.eot similarity index 88% rename from priv/static/static/font/fontello.1579191725174.eot rename to priv/static/static/font/fontello.1580295842879.eot index 90f9bdb79..fe20cecef 100644 Binary files a/priv/static/static/font/fontello.1579191725174.eot and b/priv/static/static/font/fontello.1580295842879.eot differ diff --git a/priv/static/static/font/fontello.1579191725174.svg b/priv/static/static/font/fontello.1580295842879.svg similarity index 99% rename from priv/static/static/font/fontello.1579191725174.svg rename to priv/static/static/font/fontello.1580295842879.svg index 98111ac20..44beba9a2 100644 --- a/priv/static/static/font/fontello.1579191725174.svg +++ b/priv/static/static/font/fontello.1580295842879.svg @@ -74,6 +74,8 @@ + + diff --git a/priv/static/static/font/fontello.1579191725174.ttf b/priv/static/static/font/fontello.1580295842879.ttf similarity index 88% rename from priv/static/static/font/fontello.1579191725174.ttf rename to priv/static/static/font/fontello.1580295842879.ttf index 5cd2c1417..51ff62041 100644 Binary files a/priv/static/static/font/fontello.1579191725174.ttf and b/priv/static/static/font/fontello.1580295842879.ttf differ diff --git a/priv/static/static/font/fontello.1580295842879.woff b/priv/static/static/font/fontello.1580295842879.woff new file mode 100644 index 000000000..09de16e45 Binary files /dev/null and b/priv/static/static/font/fontello.1580295842879.woff differ diff --git a/priv/static/static/font/fontello.1580295842879.woff2 b/priv/static/static/font/fontello.1580295842879.woff2 new file mode 100644 index 000000000..c6de1240b Binary files /dev/null and b/priv/static/static/font/fontello.1580295842879.woff2 differ diff --git a/priv/static/static/font/lato-v15-latin-regular.eot b/priv/static/static/font/lato-v15-latin-regular.eot new file mode 100644 index 000000000..c64130696 Binary files /dev/null and b/priv/static/static/font/lato-v15-latin-regular.eot differ diff --git a/priv/static/static/font/lato-v15-latin-regular.svg b/priv/static/static/font/lato-v15-latin-regular.svg new file mode 100644 index 000000000..55b43fb86 --- /dev/null +++ b/priv/static/static/font/lato-v15-latin-regular.svg @@ -0,0 +1,435 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/priv/static/static/font/lato-v15-latin-regular.ttf b/priv/static/static/font/lato-v15-latin-regular.ttf new file mode 100644 index 000000000..3c2d417ea Binary files /dev/null and b/priv/static/static/font/lato-v15-latin-regular.ttf differ diff --git a/priv/static/static/font/lato-v15-latin-regular.woff b/priv/static/static/font/lato-v15-latin-regular.woff new file mode 100644 index 000000000..189a0feb5 Binary files /dev/null and b/priv/static/static/font/lato-v15-latin-regular.woff differ diff --git a/priv/static/static/font/lato-v15-latin-regular.woff2 b/priv/static/static/font/lato-v15-latin-regular.woff2 new file mode 100644 index 000000000..6904b6649 Binary files /dev/null and b/priv/static/static/font/lato-v15-latin-regular.woff2 differ diff --git a/priv/static/static/fontello.1579191725174.css b/priv/static/static/fontello.1580295842879.css similarity index 87% rename from priv/static/static/fontello.1579191725174.css rename to priv/static/static/fontello.1580295842879.css index 4d993747a..6f0596862 100644 --- a/priv/static/static/fontello.1579191725174.css +++ b/priv/static/static/fontello.1580295842879.css @@ -1,11 +1,11 @@ @font-face { font-family: "Icons"; - src: url("./font/fontello.1579191725174.eot"); - src: url("./font/fontello.1579191725174.eot") format("embedded-opentype"), - url("./font/fontello.1579191725174.woff2") format("woff2"), - url("./font/fontello.1579191725174.woff") format("woff"), - url("./font/fontello.1579191725174.ttf") format("truetype"), - url("./font/fontello.1579191725174.svg") format("svg"); + src: url("./font/fontello.1580295842879.eot"); + src: url("./font/fontello.1580295842879.eot") format("embedded-opentype"), + url("./font/fontello.1580295842879.woff2") format("woff2"), + url("./font/fontello.1580295842879.woff") format("woff"), + url("./font/fontello.1580295842879.ttf") format("truetype"), + url("./font/fontello.1580295842879.svg") format("svg"); font-weight: normal; font-style: normal; } @@ -132,3 +132,5 @@ .icon-home-2::before { content: "\e821"; } .icon-chat::before { content: "\e81e"; } .icon-login::before { content: "\e820"; } + +.icon-arrow-curved::before { content: "\e822"; } diff --git a/priv/static/static/fontello.json b/priv/static/static/fontello.json index c1ed33938..829241b55 100755 --- a/priv/static/static/fontello.json +++ b/priv/static/static/fontello.json @@ -333,6 +333,12 @@ "css": "login", "code": 59424, "src": "fontawesome" + }, + { + "uid": "f3ebd6751c15a280af5cc5f4a764187d", + "css": "arrow-curved", + "code": 59426, + "src": "iconic" } ] } \ No newline at end of file diff --git a/priv/static/static/js/2.7eaed4ad0d253fad6f55.js b/priv/static/static/js/2.59b096781ddca107175d.js similarity index 77% rename from priv/static/static/js/2.7eaed4ad0d253fad6f55.js rename to priv/static/static/js/2.59b096781ddca107175d.js index 5c8446f1a..f47e92efa 100644 Binary files a/priv/static/static/js/2.7eaed4ad0d253fad6f55.js and b/priv/static/static/js/2.59b096781ddca107175d.js differ diff --git a/priv/static/static/js/2.7eaed4ad0d253fad6f55.js.map b/priv/static/static/js/2.59b096781ddca107175d.js.map similarity index 98% rename from priv/static/static/js/2.7eaed4ad0d253fad6f55.js.map rename to priv/static/static/js/2.59b096781ddca107175d.js.map index c92dcc8fc..f13a48804 100644 Binary files a/priv/static/static/js/2.7eaed4ad0d253fad6f55.js.map and b/priv/static/static/js/2.59b096781ddca107175d.js.map differ diff --git a/priv/static/static/js/app.6fc3537f2985f0a2e06a.js b/priv/static/static/js/app.6fc3537f2985f0a2e06a.js deleted file mode 100644 index 8a9a3a142..000000000 Binary files a/priv/static/static/js/app.6fc3537f2985f0a2e06a.js and /dev/null differ diff --git a/priv/static/static/js/app.6fc3537f2985f0a2e06a.js.map b/priv/static/static/js/app.6fc3537f2985f0a2e06a.js.map deleted file mode 100644 index 9c1bfca59..000000000 Binary files a/priv/static/static/js/app.6fc3537f2985f0a2e06a.js.map and /dev/null differ diff --git a/priv/static/static/js/app.8457067449d0acdd01fc.js b/priv/static/static/js/app.8457067449d0acdd01fc.js new file mode 100644 index 000000000..7f431b2fe Binary files /dev/null and b/priv/static/static/js/app.8457067449d0acdd01fc.js differ diff --git a/priv/static/static/js/app.8457067449d0acdd01fc.js.map b/priv/static/static/js/app.8457067449d0acdd01fc.js.map new file mode 100644 index 000000000..2c6d9b231 Binary files /dev/null and b/priv/static/static/js/app.8457067449d0acdd01fc.js.map differ diff --git a/priv/static/static/js/vendors~app.ef669266eac4d086d74e.js b/priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js similarity index 75% rename from priv/static/static/js/vendors~app.ef669266eac4d086d74e.js rename to priv/static/static/js/vendors~app.9ab182239f3a2abee89f.js index 18bac2d14..0812cdba7 100644 Binary files a/priv/static/static/js/vendors~app.ef669266eac4d086d74e.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/static/js/vendors~app.ef669266eac4d086d74e.js.map b/priv/static/static/js/vendors~app.ef669266eac4d086d74e.js.map deleted file mode 100644 index 539456264..000000000 Binary files a/priv/static/static/js/vendors~app.ef669266eac4d086d74e.js.map and /dev/null differ diff --git a/priv/static/static/styles.json b/priv/static/static/styles.json index 4c564d125..f85c72f9d 100644 --- a/priv/static/static/styles.json +++ b/priv/static/static/styles.json @@ -1,6 +1,7 @@ { "pleroma-dark": [ "Pleroma Dark", "#121a24", "#182230", "#b9b9ba", "#d8a070", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "pleroma-light": [ "Pleroma Light", "#f2f4f6", "#dbe0e8", "#304055", "#f86f0f", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], + "pleroma-amoled": [ "Pleroma Dark AMOLED", "#000000", "#111111", "#b0b0b1", "#d8a070", "#aa0000", "#0fa00f", "#0095ff", "#d59500"], "classic-dark": [ "Classic Dark", "#161c20", "#282e32", "#b9b9b9", "#baaa9c", "#d31014", "#0fa00f", "#0095ff", "#ffa500" ], "bird": [ "Bird", "#f8fafd", "#e6ecf0", "#14171a", "#0084b8", "#e0245e", "#17bf63", "#1b95e0", "#fab81e"], "ir-black": [ "Ir Black", "#000000", "#242422", "#b5b3aa", "#ff6c60", "#FF6C60", "#A8FF60", "#96CBFE", "#FFFFB6" ], diff --git a/priv/static/static/terms-of-service.html b/priv/static/static/terms-of-service.html index 16ce636b6..e3823b032 100644 --- a/priv/static/static/terms-of-service.html +++ b/priv/static/static/terms-of-service.html @@ -1,6 +1,6 @@

Terms of Service

-

It's mainly "be nice".

+

It's mainly "be nice"

  1. diff --git a/priv/static/sw-pleroma.js b/priv/static/sw-pleroma.js index 5d299d592..bc5d1b06a 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/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/object_test.exs b/test/object_test.exs index 9b4e6f0bf..c6b2bc399 100644 --- a/test/object_test.exs +++ b/test/object_test.exs @@ -177,6 +177,39 @@ test "with objects that have legacy data.url attribute" do 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/") + + 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(attachment.id) == nil + + assert {:ok, []} == File.ls("#{uploads_dir}/#{path}") + end end describe "normalizer" do 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/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/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/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/status_view_test.exs b/test/web/mastodon_api/views/status_view_test.exs index 069bb8eac..25777b011 100644 --- a/test/web/mastodon_api/views/status_view_test.exs +++ b/test/web/mastodon_api/views/status_view_test.exs @@ -36,7 +36,10 @@ 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}, + %{emoji: "🍵", count: 1} + ] 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/pleroma_api_controller_test.exs b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs index a79ecd05b..3978c2ec5 100644 --- a/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs +++ b/test/web/pleroma_api/controllers/pleroma_api_controller_test.exs @@ -71,7 +71,7 @@ test "GET /api/v1/pleroma/statuses/:id/emoji_reactions_by", %{conn: conn} do |> get("/api/v1/pleroma/statuses/#{activity.id}/emoji_reactions_by") |> json_response(200) - [["🎅", [represented_user]]] = result + [%{"emoji" => "🎅", "count" => 1, "accounts" => [represented_user]}] = result assert represented_user["id"] == other_user.id end 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