Compare commits

...

58 commits

Author SHA1 Message Date
b865b3450f Merge branch 'develop' into fedi-absturztau-be 2022-08-28 10:25:07 +02:00
722e56b308 add changelog entry 2022-08-27 19:12:15 +01:00
Tusooa Zhu
95e4018c1a Disconnect streaming sessions when token is revoked
Use Websockex to replace websocket_client

Test that server will disconnect websocket upon token revocation

Lint

Execute session disconnect in background

Refactor streamer test

allow multi-streams

rebase websocket change
2022-08-27 19:07:48 +01:00
772c209914 GTS: cherry-picks and collection usage (#186)
https://git.pleroma.social/pleroma/pleroma/-/merge_requests/3725?commit_id=61254111e59f02118cad15de49d1e0704c07030e

what is this, a yoink of a yoink? good times

Co-authored-by: Hélène <pleroma-dev@helene.moe>
Co-authored-by: FloatingGhost <hannah@coffee-and-dreams.uk>
Reviewed-on: AkkomaGang/akkoma#186
2022-08-27 18:05:48 +00:00
f32e288711 Merge pull request 'Add ability to obfuscate domains in MRF transparency' (#185) from obfuscate-domain-blocks into develop
Reviewed-on: AkkomaGang/akkoma#185
2022-08-27 11:11:56 +00:00
85137f591f Add ability to obfuscate domains in MRF transparency 2022-08-27 11:57:57 +01:00
f11a6eb8dd Merge pull request 'Update min elixir version in mix.exs to 1.12' (#184) from norm/akkoma:mix-exs-elixir into develop
Reviewed-on: AkkomaGang/akkoma#184
2022-08-25 19:39:56 +00:00
db7ad08d1e Update min elixir version in mix.exs to 1.12
The install docs already mention 1.12 as the minimum supported version, so this should also be reflected in `mix.exs`.
2022-08-25 18:30:19 +00:00
e4f2251e0f Add support for setting language in instance metadata (#183)
Reviewed-on: AkkomaGang/akkoma#183
2022-08-25 16:11:21 +00:00
618cf7ff7f reuse valid oauth tokens (#182)
Reviewed-on: AkkomaGang/akkoma#182
2022-08-25 14:37:51 +00:00
017b50550b add changelog entry 2022-08-24 15:38:02 +01:00
92ba2802fb generate-keys-at-registration-time (#181)
Reviewed-on: AkkomaGang/akkoma#181
2022-08-24 14:36:33 +00:00
fd7f4874ba allow new mfm classes 2022-08-24 10:06:48 +01:00
c40b45e675 Merge pull request 'add maintained language tags' (#180) from add-language-tags into develop
Reviewed-on: AkkomaGang/akkoma#180
2022-08-23 15:11:14 +00:00
9b6feb6657 add language tags 2022-08-23 16:10:19 +01:00
3cf8c1eb31 use public temple dep 2022-08-23 12:13:35 +01:00
152c43ac9e update mfm_parser 2022-08-23 12:09:01 +01:00
8d7b63a766 Revert "Fix oauth2 (for real) (#179)"
This reverts commit aa681d7e15.
2022-08-21 17:52:02 +01:00
aa681d7e15 Fix oauth2 (for real) (#179)
Reviewed-on: AkkomaGang/akkoma#179
2022-08-21 16:24:37 +00:00
b0130bfa7b Revert "oauth2 fixes (#177)"
This reverts commit 429e2ac832.
2022-08-21 16:22:15 +01:00
d72f9e39d9 add visibility check on quote (#178)
Reviewed-on: AkkomaGang/akkoma#178
2022-08-21 15:17:01 +00:00
429e2ac832 oauth2 fixes (#177)
Reviewed-on: AkkomaGang/akkoma#177
2022-08-21 14:46:52 +00:00
f8dffa6126 Merge pull request 'Update OTP OpenRC service' (#174) from norm/akkoma:otp-openrc into develop
Reviewed-on: AkkomaGang/akkoma#174
2022-08-19 09:25:12 +00:00
ffbf8304e0 Update OTP OpenRC service
This makes the paths match that of the OTP install guide on OpenRC distros.
2022-08-18 23:13:09 +00:00
59b886e86e Merge pull request 'Update OTP systemd service' (#173) from kawen/akkoma:develop into develop
Reviewed-on: AkkomaGang/akkoma#173
2022-08-18 10:51:46 +00:00
22333f13e8 Update OTP systemd service 2022-08-18 12:43:20 +02:00
a8f8ecce31 add changelog entry 2022-08-18 04:23:07 +01:00
e9f1897cfd parser MFM server-side (#172)
Reviewed-on: AkkomaGang/akkoma#172
2022-08-18 03:14:48 +00:00
aaf78e2b52 only put linked mfm in source (#171)
Reviewed-on: AkkomaGang/akkoma#171
2022-08-17 09:35:11 +00:00
11ec9daa5b API compatibility with fedibird, frontend config (#163)
Reviewed-on: AkkomaGang/akkoma#163
2022-08-17 00:22:59 +00:00
89ffc01c23 only return create objects for ES search (#165)
Reviewed-on: AkkomaGang/akkoma#165
2022-08-16 23:24:19 +00:00
61641957cb fix compatibility with meilisearch (#164)
Reviewed-on: AkkomaGang/akkoma#164
2022-08-16 22:56:49 +00:00
37a1001b97 add finch outbound proxy support (#158)
Reviewed-on: AkkomaGang/akkoma#158
2022-08-14 23:13:49 +00:00
5796d81d98 Merge branch 'stable' into develop 2022-08-12 16:06:30 +01:00
7544939c83 Merge pull request 'stable tag' (#159) from stable-tag into develop
Reviewed-on: AkkomaGang/akkoma#159
2022-08-12 15:04:05 +00:00
5192e21e53 bump version to 3.1.0 2022-08-12 16:00:40 +01:00
19ccdc8762 mix format 2022-08-11 19:21:50 +01:00
967c325b0d fix tests 2022-08-11 19:21:43 +01:00
d3b9cfb03f use :discard instead of cancel 2022-08-11 19:17:50 +01:00
ceeeefc707 we don't need to purge emoji.txt now 2022-08-11 19:06:49 +01:00
366889f97c remove default emoji file 2022-08-11 19:05:41 +01:00
74dbea4cf8 add masto-fe docs 2022-08-11 17:43:34 +01:00
Weblate
8bca9a7dbe Update translation files
Updated by "Squash Git commits" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-static-pages/
Translation: Pleroma fe/Akkoma Backend (Static pages)
2022-08-11 09:29:28 +00:00
Weblate
fcb5e4a48d Translated using Weblate (Dutch)
Currently translated at 100.0% (83 of 83 strings)

Added translation using Weblate (Dutch)

Co-authored-by: Fristi <fristi@subcon.town>
Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-static-pages/nl/
Translation: Pleroma fe/Akkoma Backend (Static pages)
2022-08-11 09:29:28 +00:00
Weblate
b1e2f3f646 Update translation files
Updated by "Squash Git commits" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-static-pages/
Translation: Pleroma fe/Akkoma Backend (Static pages)
2022-08-11 09:29:28 +00:00
Weblate
2f074a6966 Translated using Weblate (Catalan)
Currently translated at 2.9% (30 of 1002 strings)

Update translation files

Updated by "Squash Git commits" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Co-authored-by: sola <spla@mastodont.cat>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-config-descriptions/ca/
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-static-pages/
Translation: Pleroma fe/Akkoma Backend (Config Descriptions)
Translation: Pleroma fe/Akkoma Backend (Static pages)
2022-08-11 09:29:28 +00:00
Weblate
fd35a66312 Update translation files
Updated by "Squash Git commits" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-static-pages/
Translation: Pleroma fe/Akkoma Backend (Static pages)
2022-08-11 09:29:28 +00:00
Weblate
5022ecd766 Update translation files
Updated by "Squash Git commits" hook in Weblate.

Co-authored-by: Weblate <noreply@weblate.org>
Translate-URL: http://translate.akkoma.dev/projects/akkoma/akkoma-backend-static-pages/
Translation: Pleroma fe/Akkoma Backend (Static pages)
2022-08-11 09:29:28 +00:00
d16eff1c0f describe color keys
fixes #126
2022-08-11 10:28:59 +01:00
55179d4214 set soapbox-fe v2 by default
fixes #157
2022-08-11 10:25:03 +01:00
e5a2548521 remove warning that breaks update 2022-08-09 12:57:39 +01:00
1245141779 treat rejections in MRF as a reject in federator (#155)
Reviewed-on: AkkomaGang/akkoma#155
2022-08-08 15:47:57 +00:00
5d23df84c9 Mix format 2022-08-07 20:49:56 +01:00
b3e4d81362 StatusView: implement pleroma.context field
This field replaces the now deprecated conversation_id field, and now
exposes the ActivityPub object `context` directly via the MastoAPI
instead of relying on StatusNet-era data concepts.
2022-08-07 20:48:08 +01:00
b9bb093600 StatusView: clear MSB on calculated conversation_id
This field seems to be a left-over from the StatusNet era.
If your application uses `pleroma.conversation_id`: this field is
deprecated.

It is currently stubbed instead by doing a CRC32 of the context, and
clearing the MSB to avoid overflow exceptions with signed integers on
the different clients using this field (Java/Kotlin code, mostly; see
Husky and probably other mobile clients.)

This should be removed in a future version of Pleroma. Pleroma-FE
currently depends on this field, as well.
2022-08-07 20:47:59 +01:00
d0b7d37cd8 bump version for release 2022-07-23 20:01:48 +01:00
6ff6f12fec bugfix/follow-state (#104)
Reviewed-on: AkkomaGang/akkoma#104
2022-07-23 20:01:48 +01:00
a4a7f4cad1 Merge pull request '2022.07 stable' (#79) from develop into stable
Reviewed-on: AkkomaGang/akkoma#79
2022-07-15 15:54:49 +00:00
86 changed files with 17048 additions and 2024 deletions

View file

@ -93,7 +93,6 @@ pipeline:
MIX_ENV: prod MIX_ENV: prod
DEBIAN_FRONTEND: noninteractive DEBIAN_FRONTEND: noninteractive
commands: commands:
- rm config/emoji.txt
- apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget - apt-get update && apt-get install -y cmake libmagic-dev rclone zip imagemagick libmagic-dev git build-essential g++ wget
- *clean - *clean
- echo "import Config" > config/prod.secret.exs - echo "import Config" > config/prod.secret.exs

View file

@ -6,6 +6,28 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
## [Unreleased] ## [Unreleased]
### Added
- support for fedibird-fe, and non-breaking API parity for it to function
- support for setting instance languages in metadata
- support for reusing oauth tokens, and not requiring new authorizations
- the ability to obfuscate domains in your MRF descriptions
### Changed
- MFM parsing is now done on the backend by a modified version of ilja's parser -> https://akkoma.dev/AkkomaGang/mfm-parser
### Fixed
- Compatibility with latest meilisearch
- Resolution of nested mix tasks (i.e search.meilisearch) in OTP releases
- Elasticsearch returning likes and repeats, displaying as posts
- Ensure key generation happens at registration-time to prevent potential race-conditions
- Ensured websockets get closed on logout
- Allowed GoToSocial-style `?query_string` signatures
### Removed
- Non-finch HTTP adapters. `:tesla, :adapter` is now highly recommended to be set to the default.
## 2022.08
### Added ### Added
- extended runtime module support, see config cheatsheet - extended runtime module support, see config cheatsheet
- quote posting; quotes are limited to public posts - quote posting; quotes are limited to public posts

View file

@ -2,6 +2,8 @@
*a smallish microblogging platform, aka the cooler pleroma* *a smallish microblogging platform, aka the cooler pleroma*
![English OK](https://img.shields.io/badge/English-OK-blueviolet) ![日本語OK](https://img.shields.io/badge/%E6%97%A5%E6%9C%AC%E8%AA%9E-OK-blueviolet)
## About ## About
This is a fork of Pleroma, which is a microblogging server software that can federate (= exchange messages with) other servers that support ActivityPub. What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Akkoma will federate with all servers that implement ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed. This is a fork of Pleroma, which is a microblogging server software that can federate (= exchange messages with) other servers that support ActivityPub. What that means is that you can host a server for yourself or your friends and stay in control of your online identity, but still exchange messages with people on larger servers. Akkoma will federate with all servers that implement ActivityPub, like Friendica, GNU Social, Hubzilla, Mastodon, Misskey, Peertube, and Pixelfed.

View file

@ -197,6 +197,7 @@
avatar_upload_limit: 2_000_000, avatar_upload_limit: 2_000_000,
background_upload_limit: 4_000_000, background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000, banner_upload_limit: 4_000_000,
languages: ["en"],
poll_limits: %{ poll_limits: %{
max_options: 20, max_options: 20,
max_option_chars: 200, max_option_chars: 200,
@ -734,6 +735,14 @@
"build_dir" => "distribution", "build_dir" => "distribution",
"ref" => "akkoma" "ref" => "akkoma"
}, },
"fedibird-fe" => %{
"name" => "fedibird-fe",
"git" => "https://akkoma.dev/AkkomaGang/fedibird-fe",
"build_url" =>
"https://akkoma-updates.s3-website.fr-par.scw.cloud/frontend/${ref}/fedibird-fe.zip",
"build_dir" => "distribution",
"ref" => "akkoma"
},
"admin-fe" => %{ "admin-fe" => %{
"name" => "admin-fe", "name" => "admin-fe",
"git" => "https://akkoma.dev/AkkomaGang/admin-fe", "git" => "https://akkoma.dev/AkkomaGang/admin-fe",
@ -746,7 +755,7 @@
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe", "git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
"build_url" => "build_url" =>
"https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production", "https://gitlab.com/soapbox-pub/soapbox-fe/-/jobs/artifacts/${ref}/download?job=build-production",
"ref" => "v1.0.0", "ref" => "v2.0.0",
"build_dir" => "static" "build_dir" => "static"
}, },
# For developers - enables a swagger frontend to view the openapi spec # For developers - enables a swagger frontend to view the openapi spec
@ -785,7 +794,8 @@
config :pleroma, :mrf, config :pleroma, :mrf,
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy], policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
transparency: true, transparency: true,
transparency_exclusions: [] transparency_exclusions: [],
transparency_obfuscate_domains: []
config :ex_aws, http_client: Pleroma.HTTP.ExAws config :ex_aws, http_client: Pleroma.HTTP.ExAws

View file

@ -509,6 +509,16 @@
"Pleroma" "Pleroma"
] ]
}, },
%{
key: :languages,
type: {:list, :string},
description: "Languages the instance uses",
suggestions: [
"en",
"ja",
"fr"
]
},
%{ %{
key: :email, key: :email,
label: "Admin Email Address", label: "Admin Email Address",
@ -1169,7 +1179,6 @@
hideFilteredStatuses: false, hideFilteredStatuses: false,
hideMutedPosts: false, hideMutedPosts: false,
hidePostStats: false, hidePostStats: false,
hideSitename: false,
hideUserStats: false, hideUserStats: false,
loginMethod: "password", loginMethod: "password",
logo: "/static/logo.svg", logo: "/static/logo.svg",
@ -1235,12 +1244,6 @@
type: :boolean, type: :boolean,
description: "Hide notices statistics (repeats, favorites, ...)" description: "Hide notices statistics (repeats, favorites, ...)"
}, },
%{
key: :hideSitename,
label: "Hide Sitename",
type: :boolean,
description: "Hides instance name from PleromaFE banner"
},
%{ %{
key: :hideUserStats, key: :hideUserStats,
label: "Hide user stats", label: "Hide user stats",
@ -1350,6 +1353,42 @@
type: :string, type: :string,
description: "Which theme to use. Available themes are defined in styles.json", description: "Which theme to use. Available themes are defined in styles.json",
suggestions: ["pleroma-dark"] suggestions: ["pleroma-dark"]
},
%{
key: :showPanelNavShortcuts,
label: "Show timeline panel nav shortcuts",
type: :boolean,
description: "Whether to put timeline nav tabs on the top of the panel"
},
%{
key: :showNavShortcuts,
label: "Show navbar shortcuts",
type: :boolean,
description: "Whether to put extra navigation options on the navbar"
},
%{
key: :showWiderShortcuts,
label: "Increase navbar shortcut spacing",
type: :boolean,
description: "Whether to add extra space between navbar icons"
},
%{
key: :hideSiteFavicon,
label: "Hide site favicon",
type: :boolean,
description: "Whether to hide the instance favicon from the navbar"
},
%{
key: :hideSiteName,
label: "Hide site name",
type: :boolean,
description: "Whether to hide the site name from the navbar"
},
%{
key: :renderMisskeyMarkdown,
label: "Render misskey markdown",
type: :boolean,
description: "Whether to render Misskey-flavoured markdown"
} }
] ]
}, },
@ -1442,13 +1481,14 @@
%{ %{
key: :theme_color, key: :theme_color,
type: :string, type: :string,
description: "Describe the theme color of the app", description: "Describe the theme color of the app - this is only used for mastodon-fe",
suggestions: ["#282c37", "mediumpurple"] suggestions: ["#282c37", "mediumpurple"]
}, },
%{ %{
key: :background_color, key: :background_color,
type: :string, type: :string,
description: "Describe the background color of the app", description:
"Describe the background color of the app - this is only used for mastodon-fe",
suggestions: ["#191b22", "aliceblue"] suggestions: ["#191b22", "aliceblue"]
} }
] ]
@ -2597,9 +2637,10 @@
%{ %{
key: :proxy_url, key: :proxy_url,
label: "Proxy URL", label: "Proxy URL",
type: [:string, :tuple], type: :string,
description: "Proxy URL", description:
suggestions: ["localhost:9020", {:socks5, :localhost, 3090}] "Proxy URL - of the format http://host:port. Advise setting in .exs instead of admin-fe due to this being set at boot-time.",
suggestions: ["http://localhost:3128"]
}, },
%{ %{
key: :user_agent, key: :user_agent,

View file

@ -1,4 +0,0 @@
firefox, /emoji/Firefox.gif, Gif,Fun
blank, /emoji/blank.png, Fun
dinosaur, /emoji/dino walking.gif, Gif
100a, /emoji/100a.png, Fun

View file

@ -120,6 +120,7 @@ To add configuration to your config file, you can copy it from the base config.
* `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)). * `Pleroma.Web.ActivityPub.MRF.KeywordPolicy`: Rejects or removes from the federated timeline or replaces keywords. (See [`:mrf_keyword`](#mrf_keyword)).
* `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo). * `transparency`: Make the content of your Message Rewrite Facility settings public (via nodeinfo).
* `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value. * `transparency_exclusions`: Exclude specific instance names from MRF transparency. The use of the exclusions feature will be disclosed in nodeinfo as a boolean value.
* `transparency_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me`
## Federation ## Federation
### MRF policies ### MRF policies
@ -283,14 +284,19 @@ config :pleroma, :frontends,
"name" => "swagger-ui", "name" => "swagger-ui",
"ref" => "stable", "ref" => "stable",
"enabled" => true "enabled" => true
},
mastodon: %{
"name" => "mastodon-fe",
"ref" => "akkoma"
} }
``` ```
* `:primary` - The frontend that will be served at `/` * `:primary` - The frontend that will be served at `/`
* `:admin` - The frontend that will be served at `/pleroma/admin` * `:admin` - The frontend that will be served at `/pleroma/admin`
* `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default. * `:swagger` - Config for developers to act as an API reference to be served at `/akkoma/swaggerui/` (trailing slash _needed_). Disabled by default.
* `:mastodon` - The mastodon-fe configuration. This shouldn't need to be changed. This is served at `/web` when installed.
### :static_fe ### :static\_fe
Render profiles and posts using server-generated HTML that is viewable without using JavaScript. Render profiles and posts using server-generated HTML that is viewable without using JavaScript.
@ -516,7 +522,7 @@ Available caches:
### :http ### :http
* `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`) * `proxy_url`: an upstream proxy to fetch posts and/or media with, (default: `nil`); for example `http://127.0.0.1:3192`. Does not support SOCKS5 proxy, only http(s).
* `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`) * `send_user_agent`: should we include a user agent with HTTP requests? (default: `true`)
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default` * `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
* `adapter`: array of adapter options * `adapter`: array of adapter options

View file

@ -19,6 +19,10 @@ config :pleroma, :frontends,
admin: %{ admin: %{
"name" => "admin-fe", "name" => "admin-fe",
"ref" => "stable" "ref" => "stable"
},
mastodon: %{
"name" => "mastodon-fe",
"ref" => "akkoma"
} }
``` ```
@ -26,12 +30,18 @@ This would serve the frontend from the the folder at `$instance_static/frontends
Refer to [the frontend CLI task](../../administration/CLI_tasks/frontend) for how to install the frontend's files Refer to [the frontend CLI task](../../administration/CLI_tasks/frontend) for how to install the frontend's files
If you wish masto-fe to also be enabled, you will also need to run the install task for `mastodon-fe`. Not doing this will lead to the frontend not working.
If you choose not to install a frontend for whatever reason, it is recommended that you enable [`:static_fe`](#static_fe) to allow remote users to click "view remote source". Don't bother with this if you've got no unauthenticated access though. If you choose not to install a frontend for whatever reason, it is recommended that you enable [`:static_fe`](#static_fe) to allow remote users to click "view remote source". Don't bother with this if you've got no unauthenticated access though.
You can also replace the default "no frontend" page by placing an `index.html` file under your `instance/static/` directory. You can also replace the default "no frontend" page by placing an `index.html` file under your `instance/static/` directory.
## Mastodon-FE
Akkoma supports both [glitchsoc](https://github.com/glitch-soc/mastodon)'s more "vanilla" mastodon frontend,
as well as [fedibird](https://github.com/fedibird/mastodon)'s extended frontend which has near-feature-parity with akkoma (with quoting and reactions).
To enable either one, you must run the `frontend.install` task for either `mastodon-fe` or `fedibird-fe` (both `--ref akkoma`), then make sure
`:pleroma, :frontends, :mastodon` references the one you want.
## Swagger (openAPI) documentation viewer ## Swagger (openAPI) documentation viewer
If you're a developer and you'd like a human-readable rendering of the If you're a developer and you'd like a human-readable rendering of the

View file

@ -23,7 +23,15 @@ def start_pleroma do
Pleroma.Config.Oban.warn() Pleroma.Config.Oban.warn()
Pleroma.Application.limiters_setup() Pleroma.Application.limiters_setup()
Application.put_env(:phoenix, :serve_endpoints, false, persistent: true) Application.put_env(:phoenix, :serve_endpoints, false, persistent: true)
Finch.start_link(name: MyFinch)
proxy_url = Pleroma.Config.get([:http, :proxy_url])
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
finch_config =
[:http, :adapter]
|> Pleroma.Config.get([])
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|> Keyword.put(:name, MyFinch)
unless System.get_env("DEBUG") do unless System.get_env("DEBUG") do
Logger.remove_backend(:console) Logger.remove_backend(:console)
@ -45,6 +53,7 @@ def start_pleroma do
Pleroma.Emoji, Pleroma.Emoji,
{Pleroma.Config.TransferTask, false}, {Pleroma.Config.TransferTask, false},
Pleroma.Web.Endpoint, Pleroma.Web.Endpoint,
{Finch, finch_config},
{Oban, oban_config}, {Oban, oban_config},
{Majic.Pool, {Majic.Pool,
[name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]} [name: Pleroma.MajicPool, pool_size: Pleroma.Config.get([:majic_pool, :size], 2)]}

View file

@ -9,7 +9,7 @@ defmodule Mix.Tasks.Pleroma.Search.Meilisearch do
import Ecto.Query import Ecto.Query
import Pleroma.Search.Meilisearch, import Pleroma.Search.Meilisearch,
only: [meili_post: 2, meili_put: 2, meili_get: 1, meili_delete!: 1] only: [meili_put: 2, meili_get: 1, meili_delete!: 1]
def run(["index"]) do def run(["index"]) do
start_pleroma() start_pleroma()
@ -27,7 +27,7 @@ def run(["index"]) do
end end
{:ok, _} = {:ok, _} =
meili_post( meili_put(
"/indexes/objects/settings/ranking-rules", "/indexes/objects/settings/ranking-rules",
[ [
"published:desc", "published:desc",
@ -41,7 +41,7 @@ def run(["index"]) do
) )
{:ok, _} = {:ok, _} =
meili_post( meili_put(
"/indexes/objects/settings/searchable-attributes", "/indexes/objects/settings/searchable-attributes",
[ [
"content" "content"
@ -91,7 +91,7 @@ def run(["index"]) do
) )
with {:ok, res} <- result do with {:ok, res} <- result do
if not Map.has_key?(res, "uid") do if not Map.has_key?(res, "indexUid") do
IO.puts("\nFailed to index: #{inspect(result)}") IO.puts("\nFailed to index: #{inspect(result)}")
end end
else else

View file

@ -258,6 +258,25 @@ def run(["untag", nickname | tags]) do
end end
end end
def run(["refetch_public_keys"]) do
start_pleroma()
Pleroma.User.Query.build(%{
external: true,
is_active: true
})
|> refetch_public_keys()
end
def run(["refetch_public_keys" | rest]) do
start_pleroma()
Pleroma.User.Query.build(%{
ap_id: rest
})
|> refetch_public_keys()
end
def run(["invite" | rest]) do def run(["invite" | rest]) do
{options, [], []} = {options, [], []} =
OptionParser.parse(rest, OptionParser.parse(rest,
@ -519,6 +538,26 @@ def run(["fix_follow_state", local_user, remote_user]) do
end end
end end
defp refetch_public_keys(query) do
query
|> Pleroma.Repo.chunk_stream(50, :batches)
|> Stream.each(fn users ->
users
|> Enum.each(fn user ->
IO.puts("Re-Resolving: #{user.ap_id}")
with {:ok, user} <- Pleroma.User.fetch_by_ap_id(user.ap_id),
changeset <- Pleroma.User.update_changeset(user),
{:ok, _user} <- Pleroma.User.update_and_set_cache(changeset) do
:ok
else
error -> IO.puts("Could not resolve: #{user.ap_id}, #{inspect(error)}")
end
end)
end)
|> Stream.run()
end
defp set_moderator(user, value) do defp set_moderator(user, value) do
{:ok, user} = {:ok, user} =
user user

View file

@ -63,7 +63,8 @@ def start(_type, _args) do
Pleroma.Repo, Pleroma.Repo,
Config.TransferTask, Config.TransferTask,
Pleroma.Emoji, Pleroma.Emoji,
Pleroma.Web.Plugs.RateLimiter.Supervisor Pleroma.Web.Plugs.RateLimiter.Supervisor,
{Task.Supervisor, name: Pleroma.TaskSupervisor}
] ++ ] ++
cachex_children() ++ cachex_children() ++
http_children() ++ http_children() ++
@ -248,9 +249,13 @@ def limiters_setup do
end end
defp http_children do defp http_children do
proxy_url = Config.get([:http, :proxy_url])
proxy = Pleroma.HTTP.AdapterHelper.format_proxy(proxy_url)
config = config =
[:http, :adapter] [:http, :adapter]
|> Config.get([]) |> Config.get([])
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy_pool(proxy)
|> Keyword.put(:name, MyFinch) |> Keyword.put(:name, MyFinch)
[{Finch, config}] [{Finch, config}]

View file

@ -11,10 +11,7 @@ defmodule Akkoma.Collections.Fetcher do
alias Pleroma.Config alias Pleroma.Config
require Logger require Logger
def fetch_collection_by_ap_id(ap_id) when is_binary(ap_id) do @spec fetch_collection(String.t() | map()) :: {:ok, [Pleroma.Object.t()]} | {:error, any()}
fetch_collection(ap_id)
end
def fetch_collection(ap_id) when is_binary(ap_id) do def fetch_collection(ap_id) when is_binary(ap_id) do
with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(ap_id) do
{:ok, objects_from_collection(page)} {:ok, objects_from_collection(page)}
@ -26,7 +23,7 @@ def fetch_collection(ap_id) when is_binary(ap_id) do
end end
def fetch_collection(%{"type" => type} = page) def fetch_collection(%{"type" => type} = page)
when type in ["Collection", "OrderedCollection"] do when type in ["Collection", "OrderedCollection", "CollectionPage", "OrderedCollectionPage"] do
{:ok, objects_from_collection(page)} {:ok, objects_from_collection(page)}
end end
@ -38,12 +35,13 @@ defp items_in_page(%{"type" => type, "items" => items})
when is_list(items) and type in ["Collection", "CollectionPage"], when is_list(items) and type in ["Collection", "CollectionPage"],
do: items do: items
defp objects_from_collection(%{"type" => "OrderedCollection", "orderedItems" => items}) defp objects_from_collection(%{"type" => type, "orderedItems" => items} = page)
when is_list(items), when is_list(items) and type in ["OrderedCollection", "OrderedCollectionPage"],
do: items do: maybe_next_page(page, items)
defp objects_from_collection(%{"type" => "Collection", "items" => items}) when is_list(items), defp objects_from_collection(%{"type" => type, "items" => items} = page)
do: items when is_list(items) and type in ["Collection", "CollectionPage"],
do: maybe_next_page(page, items)
defp objects_from_collection(%{"type" => type, "first" => first}) defp objects_from_collection(%{"type" => type, "first" => first})
when is_binary(first) and type in ["Collection", "OrderedCollection"] do when is_binary(first) and type in ["Collection", "OrderedCollection"] do
@ -55,11 +53,13 @@ defp objects_from_collection(%{"type" => type, "first" => %{"id" => id}})
fetch_page_items(id) fetch_page_items(id)
end end
defp objects_from_collection(_page), do: []
defp fetch_page_items(id, items \\ []) do defp fetch_page_items(id, items \\ []) do
if Enum.count(items) >= Config.get([:activitypub, :max_collection_objects]) do if Enum.count(items) >= Config.get([:activitypub, :max_collection_objects]) do
items items
else else
{:ok, page} = Fetcher.fetch_and_contain_remote_object_from_id(id) with {:ok, page} <- Fetcher.fetch_and_contain_remote_object_from_id(id) do
objects = items_in_page(page) objects = items_in_page(page)
if Enum.count(objects) > 0 do if Enum.count(objects) > 0 do
@ -67,6 +67,14 @@ defp fetch_page_items(id, items \\ []) do
else else
items items
end end
else
{:error, "Object has been deleted"} ->
items
{:error, error} ->
Logger.error("Could not fetch page #{id} - #{inspect(error)}")
{:error, error}
end
end end
end end

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Helpers.AuthHelper do
import Plug.Conn import Plug.Conn
@oauth_token_session_key :oauth_token @oauth_token_session_key :oauth_token
@oauth_user_session_key :oauth_user
@doc """ @doc """
Skips OAuth permissions (scopes) checks, assigns nil `:token`. Skips OAuth permissions (scopes) checks, assigns nil `:token`.
@ -43,4 +44,16 @@ def put_session_token(%Conn{} = conn, token) when is_binary(token) do
def delete_session_token(%Conn{} = conn) do def delete_session_token(%Conn{} = conn) do
delete_session(conn, @oauth_token_session_key) delete_session(conn, @oauth_token_session_key)
end end
def put_session_user(%Conn{} = conn, user) do
put_session(conn, @oauth_user_session_key, user)
end
def delete_session_user(%Conn{} = conn) do
delete_session(conn, @oauth_user_session_key)
end
def get_session_user(%Conn{} = conn) do
get_session(conn, @oauth_user_session_key)
end
end end

View file

@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
@moduledoc """ @moduledoc """
Configure Tesla.Client with default and customized adapter options. Configure Tesla.Client with default and customized adapter options.
""" """
@defaults [name: MyFinch, connect_timeout: 5_000, recv_timeout: 5_000] @defaults [name: MyFinch, pool_timeout: 5_000, receive_timeout: 5_000]
@type proxy_type() :: :socks4 | :socks5 @type proxy_type() :: :socks4 | :socks5
@type host() :: charlist() | :inet.ip_address() @type host() :: charlist() | :inet.ip_address()
@ -25,15 +25,58 @@ def format_proxy(nil), do: nil
def format_proxy(proxy_url) do def format_proxy(proxy_url) do
case parse_proxy(proxy_url) do case parse_proxy(proxy_url) do
{:ok, host, port} -> {host, port} {:ok, host, port} -> {:http, host, port, []}
{:ok, type, host, port} -> {type, host, port} {:ok, type, host, port} -> {type, host, port, []}
_ -> nil _ -> nil
end end
end end
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword() @spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
def maybe_add_proxy(opts, nil), do: opts def maybe_add_proxy(opts, nil), do: opts
def maybe_add_proxy(opts, proxy), do: Keyword.put_new(opts, :proxy, proxy)
def maybe_add_proxy(opts, proxy) do
Keyword.put(opts, :proxy, proxy)
end
def maybe_add_proxy_pool(opts, nil), do: opts
def maybe_add_proxy_pool(opts, proxy) do
Logger.info("Using HTTP Proxy: #{inspect(proxy)}")
opts
|> maybe_add_pools()
|> maybe_add_default_pool()
|> maybe_add_conn_opts()
|> put_in([:pools, :default, :conn_opts, :proxy], proxy)
end
defp maybe_add_pools(opts) do
if Keyword.has_key?(opts, :pools) do
opts
else
Keyword.put(opts, :pools, %{})
end
end
defp maybe_add_default_pool(opts) do
pools = Keyword.get(opts, :pools)
if Map.has_key?(pools, :default) do
opts
else
put_in(opts, [:pools, :default], [])
end
end
defp maybe_add_conn_opts(opts) do
conn_opts = get_in(opts, [:pools, :default, :conn_opts])
unless is_nil(conn_opts) do
opts
else
put_in(opts, [:pools, :default, :conn_opts], [])
end
end
@doc """ @doc """
Merge default connection & adapter options with received ones. Merge default connection & adapter options with received ones.
@ -46,36 +89,31 @@ def options(%URI{} = uri, opts \\ []) do
|> AdapterHelper.Default.options(uri) |> AdapterHelper.Default.options(uri)
end end
defp proxy_type("http"), do: {:ok, :http}
defp proxy_type("https"), do: {:ok, :https}
defp proxy_type(_), do: {:error, :unknown}
@spec parse_proxy(String.t() | tuple() | nil) :: @spec parse_proxy(String.t() | tuple() | nil) ::
{:ok, host(), pos_integer()} {:ok, host(), pos_integer()}
| {:ok, proxy_type(), host(), pos_integer()} | {:ok, proxy_type(), host(), pos_integer()}
| {:error, atom()} | {:error, atom()}
| nil | nil
def parse_proxy(nil), do: nil def parse_proxy(nil), do: nil
def parse_proxy(proxy) when is_binary(proxy) do def parse_proxy(proxy) when is_binary(proxy) do
with [host, port] <- String.split(proxy, ":"), with %URI{} = uri <- URI.parse(proxy),
{port, ""} <- Integer.parse(port) do {:ok, type} <- proxy_type(uri.scheme) do
{:ok, parse_host(host), port} {:ok, type, uri.host, uri.port}
else else
{_, _} -> e ->
Logger.warn("Parsing port failed #{inspect(proxy)}") Logger.warn("Parsing proxy failed #{inspect(proxy)}, #{inspect(e)}")
{:error, :invalid_proxy_port}
:error ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
_ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
{:error, :invalid_proxy} {:error, :invalid_proxy}
end end
end end
def parse_proxy(proxy) when is_tuple(proxy) do def parse_proxy(proxy) when is_tuple(proxy) do
with {type, host, port} <- proxy do with {type, host, port} <- proxy do
{:ok, type, parse_host(host), port} {:ok, type, host, port}
else else
_ -> _ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}") Logger.warn("Parsing proxy failed #{inspect(proxy)}")

View file

@ -9,7 +9,7 @@ defmodule Pleroma.HTTP.AdapterHelper.Default do
@spec options(keyword(), URI.t()) :: keyword() @spec options(keyword(), URI.t()) :: keyword()
def options(opts, _uri) do def options(opts, _uri) do
proxy = Pleroma.Config.get([:http, :proxy_url], nil) proxy = Pleroma.Config.get([:http, :proxy_url])
AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy)) AdapterHelper.maybe_add_proxy(opts, AdapterHelper.format_proxy(proxy))
end end

View file

@ -1,82 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.Gun do
@behaviour Pleroma.HTTP.AdapterHelper
alias Pleroma.Config
alias Pleroma.HTTP.AdapterHelper
require Logger
@defaults [
retry: 1,
retry_timeout: 1_000
]
@type pool() :: :federation | :upload | :media | :default
@spec options(keyword(), URI.t()) :: keyword()
def options(incoming_opts \\ [], %URI{} = uri) do
proxy =
[:http, :proxy_url]
|> Config.get()
|> AdapterHelper.format_proxy()
config_opts = Config.get([:http, :adapter], [])
@defaults
|> Keyword.merge(config_opts)
|> add_scheme_opts(uri)
|> AdapterHelper.maybe_add_proxy(proxy)
|> Keyword.merge(incoming_opts)
|> put_timeout()
end
defp add_scheme_opts(opts, %{scheme: "http"}), do: opts
defp add_scheme_opts(opts, %{scheme: "https"}) do
Keyword.put(opts, :certificates_verification, true)
end
defp put_timeout(opts) do
{recv_timeout, opts} = Keyword.pop(opts, :recv_timeout, pool_timeout(opts[:pool]))
# this is the timeout to receive a message from Gun
# `:timeout` key is used in Tesla
Keyword.put(opts, :timeout, recv_timeout)
end
@spec pool_timeout(pool()) :: non_neg_integer()
def pool_timeout(pool) do
default = Config.get([:pools, :default, :recv_timeout], 5_000)
Config.get([:pools, pool, :recv_timeout], default)
end
def limiter_setup do
prefix = Pleroma.Gun.ConnectionPool
wait = Config.get([:connections_pool, :connection_acquisition_wait])
retries = Config.get([:connections_pool, :connection_acquisition_retries])
:pools
|> Config.get([])
|> Enum.each(fn {name, opts} ->
max_running = Keyword.get(opts, :size, 50)
max_waiting = Keyword.get(opts, :max_waiting, 10)
result =
ConcurrentLimiter.new(:"#{prefix}.#{name}", max_running, max_waiting,
wait: wait,
max_retries: retries
)
case result do
:ok -> :ok
{:error, :existing} -> :ok
end
end)
:ok
end
end

View file

@ -1,40 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.Hackney do
@behaviour Pleroma.HTTP.AdapterHelper
@defaults [
follow_redirect: true,
force_redirect: true
]
@spec options(keyword(), URI.t()) :: keyword()
def options(connection_opts \\ [], %URI{} = uri) do
proxy = Pleroma.Config.get([:http, :proxy_url])
config_opts = Pleroma.Config.get([:http, :adapter], [])
@defaults
|> Keyword.merge(config_opts)
|> Keyword.merge(connection_opts)
|> add_scheme_opts(uri)
|> maybe_add_with_body()
|> Pleroma.HTTP.AdapterHelper.maybe_add_proxy(proxy)
end
defp add_scheme_opts(opts, %URI{scheme: "https"}) do
Keyword.put(opts, :ssl_options, versions: [:"tlsv1.3", :"tlsv1.2", :"tlsv1.1", :tlsv1])
end
defp add_scheme_opts(opts, _), do: opts
defp maybe_add_with_body(opts) do
if opts[:max_body] do
Keyword.put(opts, :with_body, true)
else
opts
end
end
end

View file

@ -25,7 +25,7 @@ defp mix_task(task, args) do
module = Module.split(module) module = Module.split(module)
match?(["Mix", "Tasks", "Pleroma" | _], module) and match?(["Mix", "Tasks", "Pleroma" | _], module) and
String.downcase(List.last(module)) == task task_match?(module, task)
end) end)
if module do if module do
@ -35,6 +35,13 @@ defp mix_task(task, args) do
end end
end end
defp task_match?(["Mix", "Tasks", "Pleroma" | module_path], task) do
module_path
|> Enum.join(".")
|> String.downcase()
|> String.equivalent?(String.downcase(task))
end
def migrate(args) do def migrate(args) do
Mix.Tasks.Pleroma.Ecto.Migrate.run(args) Mix.Tasks.Pleroma.Ecto.Migrate.run(args)
end end

View file

@ -23,7 +23,7 @@ def es_query(:activity, query, offset, limit) do
timeout: "5s", timeout: "5s",
sort: [ sort: [
"_score", "_score",
%{_timestamp: %{order: "desc", format: "basic_date_time"}} %{"_timestamp" => %{order: "desc", format: "basic_date_time"}}
], ],
query: %{ query: %{
bool: %{ bool: %{
@ -62,8 +62,12 @@ def search(user, query, options) do
Task.async(fn -> Task.async(fn ->
q = es_query(:activity, parsed_query, offset, limit) q = es_query(:activity, parsed_query, offset, limit)
Pleroma.Search.Elasticsearch.Store.search(:activities, q) :activities
|> Enum.filter(fn x -> Visibility.visible_for_user?(x, user) end) |> Pleroma.Search.Elasticsearch.Store.search(q)
|> Enum.filter(fn x ->
x.data["type"] == "Create" && x.object.data["type"] == "Note" &&
Visibility.visible_for_user?(x, user)
end)
end) end)
activity_results = Task.await(activity_task) activity_results = Task.await(activity_task)

View file

@ -42,7 +42,6 @@ def search(:activities, q) do
results results
|> Enum.map(fn result -> result["_id"] end) |> Enum.map(fn result -> result["_id"] end)
|> Pleroma.Activity.all_by_ids_with_object() |> Pleroma.Activity.all_by_ids_with_object()
|> Enum.sort(&(&1.inserted_at >= &2.inserted_at))
else else
e -> e ->
Logger.error(e) Logger.error(e)

View file

@ -681,6 +681,7 @@ def register_changeset_ldap(struct, params = %{password: password})
|> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames])) |> validate_exclusion(:nickname, Config.get([User, :restricted_nicknames]))
|> validate_format(:nickname, local_nickname_regex()) |> validate_format(:nickname, local_nickname_regex())
|> put_ap_id() |> put_ap_id()
|> put_keys()
|> unique_constraint(:ap_id) |> unique_constraint(:ap_id)
|> put_following_and_follower_and_featured_address() |> put_following_and_follower_and_featured_address()
end end
@ -740,6 +741,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_length(:registration_reason, max: reason_limit) |> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external]) |> maybe_validate_required_email(opts[:external])
|> put_password_hash |> put_password_hash
|> put_keys()
|> put_ap_id() |> put_ap_id()
|> unique_constraint(:ap_id) |> unique_constraint(:ap_id)
|> put_following_and_follower_and_featured_address() |> put_following_and_follower_and_featured_address()
@ -755,6 +757,11 @@ def maybe_validate_required_email(changeset, _) do
end end
end end
def put_keys(changeset) do
{:ok, pem} = Keys.generate_rsa_pem()
put_change(changeset, :keys, pem)
end
def put_ap_id(changeset) do def put_ap_id(changeset) do
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)}) ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
put_change(changeset, :ap_id, ap_id) put_change(changeset, :ap_id, ap_id)

View file

@ -41,6 +41,16 @@ defmodule Pleroma.Web.ActivityPub.MRF do
suggestions: [ suggestions: [
"exclusion.com" "exclusion.com"
] ]
},
%{
key: :transparency_obfuscate_domains,
label: "MRF domain obfuscation",
type: {:list, :string},
description:
"Obfuscate domains in MRF transparency. This is useful if the domain you're blocking contains words you don't want displayed, but still want to disclose the MRF settings.",
suggestions: [
"badword.com"
]
} }
] ]
} }

View file

@ -256,10 +256,35 @@ def filter(object) when is_binary(object) do
def filter(object), do: {:ok, object} def filter(object), do: {:ok, object}
defp obfuscate(string) when is_binary(string) do
string
|> to_charlist()
|> Enum.with_index()
|> Enum.map(fn
{?., _index} ->
?.
{char, index} ->
if 3 <= index && index < String.length(string) - 3, do: ?*, else: char
end)
|> to_string()
end
defp maybe_obfuscate(host, obfuscations) do
if MRF.subdomain_match?(obfuscations, host) do
obfuscate(host)
else
host
end
end
@impl true @impl true
def describe do def describe do
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples() exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
obfuscations =
Config.get([:mrf, :transparency_obfuscate_domains], []) |> MRF.subdomains_regex()
mrf_simple_excluded = mrf_simple_excluded =
Config.get(:mrf_simple) Config.get(:mrf_simple)
|> Enum.map(fn {rule, instances} -> |> Enum.map(fn {rule, instances} ->
@ -269,7 +294,7 @@ def describe do
mrf_simple = mrf_simple =
mrf_simple_excluded mrf_simple_excluded
|> Enum.map(fn {rule, instances} -> |> Enum.map(fn {rule, instances} ->
{rule, Enum.map(instances, fn {host, _} -> host end)} {rule, Enum.map(instances, fn {host, _} -> maybe_obfuscate(host, obfuscations) end)}
end) end)
|> Map.new() |> Map.new()
@ -286,7 +311,9 @@ def describe do
|> Enum.map(fn {rule, instances} -> |> Enum.map(fn {rule, instances} ->
instances = instances =
instances instances
|> Enum.map(fn {host, reason} -> {host, %{"reason" => reason}} end) |> Enum.map(fn {host, reason} ->
{maybe_obfuscate(host, obfuscations), %{"reason" => reason}}
end)
|> Map.new() |> Map.new()
{rule, instances} {rule, instances}

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
use Ecto.Schema use Ecto.Schema
alias Pleroma.User alias Pleroma.User
alias Pleroma.EctoType.ActivityPub.ObjectValidators alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object.Fetcher
alias Pleroma.Web.CommonAPI.Utils alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
@ -58,19 +57,10 @@ defp fix_tag(%{"tag" => tag} = data) when is_list(tag), do: data
defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag]) defp fix_tag(%{"tag" => tag} = data) when is_map(tag), do: Map.put(data, "tag", [tag])
defp fix_tag(data), do: Map.drop(data, ["tag"]) defp fix_tag(data), do: Map.drop(data, ["tag"])
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = data) defp fix_replies(%{"replies" => replies} = data) when is_list(replies), do: data
when is_list(replies),
do: Map.put(data, "replies", replies)
defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
do: Map.put(data, "replies", replies)
defp fix_replies(%{"replies" => replies} = data) when is_bitstring(replies),
do: Map.drop(data, ["replies"])
defp fix_replies(%{"replies" => %{"first" => first}} = data) do defp fix_replies(%{"replies" => %{"first" => first}} = data) do
with {:ok, %{"orderedItems" => replies}} <- with {:ok, replies} <- Akkoma.Collections.Fetcher.fetch_collection(first) do
Fetcher.fetch_and_contain_remote_object_from_id(first) do
Map.put(data, "replies", replies) Map.put(data, "replies", replies)
else else
{:error, _} -> {:error, _} ->
@ -79,7 +69,10 @@ defp fix_replies(%{"replies" => %{"first" => first}} = data) do
end end
end end
defp fix_replies(data), do: data defp fix_replies(%{"replies" => %{"items" => replies}} = data) when is_list(replies),
do: Map.put(data, "replies", replies)
defp fix_replies(data), do: Map.delete(data, "replies")
defp remote_mention_resolver( defp remote_mention_resolver(
%{"id" => ap_id, "tag" => tags}, %{"id" => ap_id, "tag" => tags},
@ -108,6 +101,8 @@ defp remote_mention_resolver(
end end
# https://github.com/misskey-dev/misskey/pull/8787 # https://github.com/misskey-dev/misskey/pull/8787
# Misskey has an awful tendency to drop all custom formatting when it sends remotely
# So this basically reprocesses their MFM source
defp fix_misskey_content( defp fix_misskey_content(
%{"source" => %{"mediaType" => "text/x.misskeymarkdown", "content" => content}} = object %{"source" => %{"mediaType" => "text/x.misskeymarkdown", "content" => content}} = object
) )

View file

@ -7,8 +7,8 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes do
alias Pleroma.Object alias Pleroma.Object
alias Pleroma.Object.Containment alias Pleroma.Object.Containment
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
require Pleroma.Constants
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
{:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback) {:ok, data} = ObjectValidators.Recipients.cast(message[field] || field_fallback)
@ -32,7 +32,7 @@ def fix_object_defaults(data) do
|> cast_and_filter_recipients("cc", follower_collection) |> cast_and_filter_recipients("cc", follower_collection)
|> cast_and_filter_recipients("bto", follower_collection) |> cast_and_filter_recipients("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection) |> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection) |> fix_implicit_addressing(follower_collection)
end end
def fix_activity_addressing(activity) do def fix_activity_addressing(activity) do
@ -43,7 +43,7 @@ def fix_activity_addressing(activity) do
|> cast_and_filter_recipients("cc", follower_collection) |> cast_and_filter_recipients("cc", follower_collection)
|> cast_and_filter_recipients("bto", follower_collection) |> cast_and_filter_recipients("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection) |> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection) |> fix_implicit_addressing(follower_collection)
end end
def fix_actor(data) do def fix_actor(data) do
@ -73,4 +73,27 @@ def fix_object_action_recipients(data, %Object{data: %{"actor" => actor}}) do
Map.put(data, "to", to) Map.put(data, "to", to)
end end
# if as:Public is addressed, then make sure the followers collection is also addressed
# so that the activities will be delivered to local users.
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
recipients = to ++ cc
if followers_collection not in recipients do
cond do
Pleroma.Constants.as_public() in cc ->
to = to ++ [followers_collection]
Map.put(object, "to", to)
Pleroma.Constants.as_public() in to ->
cc = cc ++ [followers_collection]
Map.put(object, "cc", cc)
true ->
object
end
else
object
end
end
end end

View file

@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.Transmogrifier
import Ecto.Changeset import Ecto.Changeset
@ -67,7 +66,7 @@ defp fix_addressing(data, object) do
|> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"]) |> CommonFixes.cast_and_filter_recipients("cc", follower_collection, object["cc"])
|> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"]) |> CommonFixes.cast_and_filter_recipients("bto", follower_collection, object["bto"])
|> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"]) |> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
|> Transmogrifier.fix_implicit_addressing(follower_collection) |> CommonFixes.fix_implicit_addressing(follower_collection)
end end
def fix(data, meta) do def fix(data, meta) do

View file

@ -19,6 +19,7 @@ defmodule Pleroma.Web.ActivityPub.Transmogrifier do
alias Pleroma.Web.ActivityPub.Pipeline alias Pleroma.Web.ActivityPub.Pipeline
alias Pleroma.Web.ActivityPub.Utils alias Pleroma.Web.ActivityPub.Utils
alias Pleroma.Web.ActivityPub.Visibility alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.Federator alias Pleroma.Web.Federator
alias Pleroma.Workers.TransmogrifierWorker alias Pleroma.Workers.TransmogrifierWorker
@ -95,29 +96,6 @@ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collect
|> Map.put("cc", final_cc) |> Map.put("cc", final_cc)
end end
# if as:Public is addressed, then make sure the followers collection is also addressed
# so that the activities will be delivered to local users.
def fix_implicit_addressing(%{"to" => to, "cc" => cc} = object, followers_collection) do
recipients = to ++ cc
if followers_collection not in recipients do
cond do
Pleroma.Constants.as_public() in cc ->
to = to ++ [followers_collection]
Map.put(object, "to", to)
Pleroma.Constants.as_public() in to ->
cc = cc ++ [followers_collection]
Map.put(object, "cc", cc)
true ->
object
end
else
object
end
end
def fix_addressing(object) do def fix_addressing(object) do
{:ok, %User{follower_address: follower_collection}} = {:ok, %User{follower_address: follower_collection}} =
object object
@ -130,7 +108,7 @@ def fix_addressing(object) do
|> fix_addressing_list("bto") |> fix_addressing_list("bto")
|> fix_addressing_list("bcc") |> fix_addressing_list("bcc")
|> fix_explicit_addressing(follower_collection) |> fix_explicit_addressing(follower_collection)
|> fix_implicit_addressing(follower_collection) |> CommonFixes.fix_implicit_addressing(follower_collection)
end end
def fix_actor(%{"attributedTo" => actor} = object) do def fix_actor(%{"attributedTo" => actor} = object) do

View file

@ -152,9 +152,15 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
description: description:
"A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`" "A map consisting of alternate representations of the `content` property with the key being it's mimetype. Currently the only alternate representation supported is `text/plain`"
}, },
context: %Schema{
type: :string,
description: "The thread identifier the status is associated with"
},
conversation_id: %Schema{ conversation_id: %Schema{
type: :integer, type: :integer,
description: "The ID of the AP context the status is associated with (if any)" deprecated: true,
description:
"The ID of the AP context the status is associated with (if any); deprecated, please use `context` instead"
}, },
direct_conversation_id: %Schema{ direct_conversation_id: %Schema{
type: :integer, type: :integer,
@ -356,6 +362,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"pinned" => false, "pinned" => false,
"pleroma" => %{ "pleroma" => %{
"content" => %{"text/plain" => "foobar"}, "content" => %{"text/plain" => "foobar"},
"context" => "http://localhost:4001/objects/8b4c0c80-6a37-4d2a-b1b9-05a19e3875aa",
"conversation_id" => 345_972, "conversation_id" => 345_972,
"direct_conversation_id" => nil, "direct_conversation_id" => nil,
"emoji_reactions" => [], "emoji_reactions" => [],

View file

@ -285,11 +285,11 @@ def format_input(text, "text/html", options) do
def format_input(text, "text/x.misskeymarkdown", options) do def format_input(text, "text/x.misskeymarkdown", options) do
text text
|> Formatter.markdown_to_html()
|> MfmParser.Parser.parse()
|> MfmParser.Encoder.to_html()
|> Formatter.linkify(options) |> Formatter.linkify(options)
|> Formatter.html_escape("text/x.misskeymarkdown") |> Formatter.html_escape("text/html")
|> (fn {text, mentions, tags} ->
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
end).()
end end
def format_input(text, "text/markdown", options) do def format_input(text, "text/markdown", options) do

View file

@ -27,9 +27,21 @@ defmodule Pleroma.Web.MastoFEController do
def index(conn, _params) do def index(conn, _params) do
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn, with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
{:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do {:ok, %{id: ^token_app_id}} <- AuthController.local_mastofe_app() do
flavour =
[:frontends, :mastodon]
|> Pleroma.Config.get()
|> Map.get("name", "mastodon-fe")
index =
if flavour == "fedibird-fe" do
"fedibird.index.html"
else
"glitchsoc.index.html"
end
conn conn
|> put_layout(false) |> put_layout(false)
|> render("index.html", |> render(index,
token: token.token, token: token.token,
user: user, user: user,
custom_emojis: Pleroma.Emoji.get_all() custom_emojis: Pleroma.Emoji.get_all()

View file

@ -27,7 +27,8 @@ defmodule Pleroma.Web.MastodonAPI.AuthController do
def login(conn, %{"code" => auth_token} = params) do def login(conn, %{"code" => auth_token} = params) do
with {:ok, app} <- local_mastofe_app(), with {:ok, app} <- local_mastofe_app(),
{:ok, auth} <- Authorization.get_by_token(app, auth_token), {:ok, auth} <- Authorization.get_by_token(app, auth_token),
{:ok, oauth_token} <- Token.exchange_token(app, auth) do %User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, oauth_token} <- Token.get_or_exchange_token(auth, app, user) do
redirect_to = redirect_to =
conn conn
|> local_mastodon_post_login_path() |> local_mastodon_post_login_path()

View file

@ -26,7 +26,7 @@ def render("show.json", _) do
thumbnail: thumbnail:
URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail)) URI.merge(Pleroma.Web.Endpoint.url(), Keyword.get(instance, :instance_thumbnail))
|> to_string, |> to_string,
languages: ["en"], languages: Keyword.get(instance, :languages, ["en"]),
registrations: Keyword.get(instance, :registrations_open), registrations: Keyword.get(instance, :registrations_open),
approval_required: Keyword.get(instance, :account_approval_required), approval_required: Keyword.get(instance, :account_approval_required),
# Extra (not present in Mastodon): # Extra (not present in Mastodon):

View file

@ -57,8 +57,19 @@ defp get_replied_to_activities(activities) do
end) end)
end end
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context), # DEPRECATED This field seems to be a left-over from the StatusNet era.
do: :erlang.crc32(context) # If your application uses `pleroma.conversation_id`: this field is deprecated.
# It is currently stubbed instead by doing a CRC32 of the context, and
# clearing the MSB to avoid overflow exceptions with signed integers on the
# different clients using this field (Java/Kotlin code, mostly; see Husky.)
# This should be removed in a future version of Pleroma. Pleroma-FE currently
# depends on this field, as well.
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context) do
use Bitwise
:erlang.crc32(context)
|> band(bnot(0x8000_0000))
end
defp get_context_id(_), do: nil defp get_context_id(_), do: nil
@ -364,9 +375,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
emojis: build_emojis(object.data["emoji"]), emojis: build_emojis(object.data["emoji"]),
quote_id: if(quote, do: quote.id, else: nil), quote_id: if(quote, do: quote.id, else: nil),
quote: maybe_render_quote(quote, opts), quote: maybe_render_quote(quote, opts),
emoji_reactions: emoji_reactions,
pleroma: %{ pleroma: %{
local: activity.local, local: activity.local,
conversation_id: get_context_id(activity), conversation_id: get_context_id(activity),
context: object.data["context"],
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname, in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext}, content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary}, spoiler_text: %{"text/plain" => summary},
@ -577,7 +590,8 @@ defp build_emoji_map(emoji, users, url, current_user) do
name: emoji, name: emoji,
count: length(users), count: length(users),
url: MediaProxy.url(url), url: MediaProxy.url(url),
me: !!(current_user && current_user.ap_id in users) me: !!(current_user && current_user.ap_id in users),
account_ids: Enum.map(users, fn user -> User.get_cached_by_ap_id(user).id end)
} }
end end
@ -609,15 +623,19 @@ defp build_image_url(_, _), do: nil
defp maybe_render_quote(nil, _), do: nil defp maybe_render_quote(nil, _), do: nil
defp maybe_render_quote(quote, opts) do defp maybe_render_quote(quote, opts) do
if opts[:do_not_recurse] || !visible_for_user?(quote, opts[:for]) do with %User{} = quoted_user <- User.get_cached_by_ap_id(quote.actor),
nil false <- Map.get(opts, :do_not_recurse, false),
else true <- visible_for_user?(quote, opts[:for]),
false <- User.blocks?(opts[:for], quoted_user),
false <- User.mutes?(opts[:for], quoted_user) do
opts = opts =
opts opts
|> Map.put(:activity, quote) |> Map.put(:activity, quote)
|> Map.put(:do_not_recurse, true) |> Map.put(:do_not_recurse, true)
render("show.json", opts) render("show.json", opts)
else
_ -> nil
end end
end end
end end

View file

@ -32,8 +32,15 @@ def init(%{qs: qs} = req, state) do
req req
end end
{:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil}, {:cowboy_websocket, req,
%{idle_timeout: @timeout}} %{
user: user,
topic: topic,
count: 0,
timer: nil,
subscriptions: [],
oauth_token: oauth_token
}, %{idle_timeout: @timeout}}
else else
{:error, :bad_topic} -> {:error, :bad_topic} ->
Logger.debug("#{__MODULE__} bad topic #{inspect(req)}") Logger.debug("#{__MODULE__} bad topic #{inspect(req)}")
@ -52,7 +59,7 @@ def websocket_init(state) do
"#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}" "#{__MODULE__} accepted websocket connection for user #{(state.user || %{id: "anonymous"}).id}, topic #{state.topic}"
) )
Streamer.add_socket(state.topic, state.user) Streamer.add_socket(state.topic, state.oauth_token)
{:ok, %{state | timer: timer()}} {:ok, %{state | timer: timer()}}
end end
@ -65,21 +72,50 @@ def websocket_handle(:pong, state) do
# We only receive pings for now # We only receive pings for now
def websocket_handle(:ping, state), do: {:ok, state} def websocket_handle(:ping, state), do: {:ok, state}
def websocket_handle({:text, "ping"}, state) do def websocket_handle({:text, ping}, state) when ping in ~w[ping PING] do
if state.timer, do: Process.cancel_timer(state.timer) if state.timer, do: Process.cancel_timer(state.timer)
{:reply, {:text, "pong"}, %{state | timer: timer()}} {:reply, {:text, "pong"}, %{state | timer: timer()}}
end end
def websocket_handle({:text, text}, state) do
with {:ok, json} <- Jason.decode(text) do
websocket_handle({:json, json}, state)
else
_ ->
Logger.error("#{__MODULE__} received text frame: #{text}")
{:ok, state}
end
end
def websocket_handle(
{:json, %{"type" => "subscribe", "stream" => stream_name}},
%{user: user, oauth_token: token} = state
) do
with {:ok, topic} <- Streamer.get_topic(stream_name, user, token, %{}) do
new_subscriptions =
[topic | Map.get(state, :subscriptions, [])]
|> Enum.uniq()
{:ok, _topic} = Streamer.add_socket(topic, user)
{:ok, Map.put(state, :subscriptions, new_subscriptions)}
else
_ ->
Logger.error("#{__MODULE__} received invalid topic: #{stream_name}")
{:ok, state}
end
end
def websocket_handle(frame, state) do def websocket_handle(frame, state) do
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}") Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
{:ok, state} {:ok, state}
end end
def websocket_info({:render_with_user, view, template, item}, state) do def websocket_info({:render_with_user, view, template, item, topic}, state) do
user = %User{} = User.get_cached_by_ap_id(state.user.ap_id) user = %User{} = User.get_cached_by_ap_id(state.user.ap_id)
unless Streamer.filtered_by_user?(user, item) do unless Streamer.filtered_by_user?(user, item) do
websocket_info({:text, view.render(template, item, user)}, %{state | user: user}) websocket_info({:text, view.render(template, item, user, topic)}, %{state | user: user})
else else
{:ok, state} {:ok, state}
end end
@ -103,6 +139,10 @@ def websocket_info(:tick, state) do
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate} {:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
end end
def websocket_info(:close, state) do
{:stop, state}
end
# State can be `[]` only in case we terminate before switching to websocket, # State can be `[]` only in case we terminate before switching to websocket,
# we already log errors for these cases in `init/1`, so just do nothing here # we already log errors for these cases in `init/1`, so just do nothing here
def terminate(_reason, _req, []), do: :ok def terminate(_reason, _req, []), do: :ok

View file

@ -94,4 +94,9 @@ def get_by_token(%App{id: app_id} = _app, token) do
from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token) from(t in __MODULE__, where: t.app_id == ^app_id and t.token == ^token)
|> Repo.find_resource() |> Repo.find_resource()
end end
def get_preeexisting_by_app_and_user(%App{id: app_id} = _app, %User{id: user_id} = _user) do
from(t in __MODULE__, where: t.app_id == ^app_id and t.user_id == ^user_id, limit: 1)
|> Repo.find_resource()
end
end end

View file

@ -59,18 +59,39 @@ def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" =>
# after user already authorized to MastodonFE. # after user already authorized to MastodonFE.
# So we have to check client and token. # So we have to check client and token.
def authorize( def authorize(
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn, %Plug.Conn{assigns: %{token: %Token{} = token, user: %User{} = user}} = conn,
%{"client_id" => client_id} = params %{"client_id" => client_id} = params
) do ) do
with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app), with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
^client_id <- t.app.client_id do ^client_id <- t.app.client_id do
handle_existing_authorization(conn, params) handle_existing_authorization(conn, params)
else
_ ->
maybe_reuse_token(conn, params, user.id)
end
end
def authorize(%Plug.Conn{} = conn, params) do
# if we have a user in the session, attempt to authenticate as them
# otherwise show the login form
maybe_reuse_token(conn, params, AuthHelper.get_session_user(conn))
end
defp maybe_reuse_token(conn, params, user_id) when is_binary(user_id) do
with %User{} = user <- User.get_cached_by_id(user_id),
%App{} = app <- Repo.get_by(App, client_id: params["client_id"]),
{:ok, %Token{} = token} <- Token.get_preeexisting_by_app_and_user(app, user),
{:ok, %Authorization{} = auth} <-
Authorization.get_preeexisting_by_app_and_user(app, user) do
conn
|> assign(:token, token)
|> after_create_authorization(auth, %{"authorization" => params})
else else
_ -> do_authorize(conn, params) _ -> do_authorize(conn, params)
end end
end end
def authorize(%Plug.Conn{} = conn, params), do: do_authorize(conn, params) defp maybe_reuse_token(conn, params, _user), do: do_authorize(conn, params)
defp do_authorize(%Plug.Conn{} = conn, params) do defp do_authorize(%Plug.Conn{} = conn, params) do
app = Repo.get_by(App, client_id: params["client_id"]) app = Repo.get_by(App, client_id: params["client_id"])
@ -148,7 +169,9 @@ def create_authorization(%Plug.Conn{assigns: %{user: %User{} = user}} = conn, pa
def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do def create_authorization(%Plug.Conn{} = conn, %{"authorization" => _} = params, opts) do
with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]), with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
{:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do {:mfa_required, _, _, false} <- {:mfa_required, user, auth, MFA.require?(user)} do
after_create_authorization(conn, auth, params) conn
|> AuthHelper.put_session_user(user.id)
|> after_create_authorization(auth, params)
else else
error -> error ->
handle_create_authorization_error(conn, error, params) handle_create_authorization_error(conn, error, params)
@ -269,7 +292,7 @@ def token_exchange(%Plug.Conn{} = conn, %{"grant_type" => "authorization_code"}
fixed_token = Token.Utils.fix_padding(params["code"]), fixed_token = Token.Utils.fix_padding(params["code"]),
{:ok, auth} <- Authorization.get_by_token(app, fixed_token), {:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%User{} = user <- User.get_cached_by_id(auth.user_id), %User{} = user <- User.get_cached_by_id(auth.user_id),
{:ok, token} <- Token.exchange_token(app, auth) do {:ok, token} <- Token.get_or_exchange_token(auth, app, user) do
after_token_exchange(conn, %{user: user, token: token}) after_token_exchange(conn, %{user: user, token: token})
else else
error -> error ->
@ -321,6 +344,7 @@ def token_exchange(%Plug.Conn{} = conn, params), do: bad_request(conn, params)
def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do def after_token_exchange(%Plug.Conn{} = conn, %{token: token} = view_params) do
conn conn
|> AuthHelper.put_session_token(token.token) |> AuthHelper.put_session_token(token.token)
|> AuthHelper.put_session_user(token.user_id)
|> json(OAuthView.render("token.json", view_params)) |> json(OAuthView.render("token.json", view_params))
end end

View file

@ -70,6 +70,16 @@ def exchange_token(app, auth) do
end end
end end
def get_preeexisting_by_app_and_user(app, user) do
Query.get_by_app(app.id)
|> Query.get_by_user(user.id)
|> Query.get_unexpired()
|> Query.preload([:user])
|> Query.sort_by_inserted_at()
|> Query.limit(1)
|> Repo.find_resource()
end
defp put_token(changeset) do defp put_token(changeset) do
changeset changeset
|> change(%{token: Token.Utils.generate_token()}) |> change(%{token: Token.Utils.generate_token()})
@ -86,6 +96,14 @@ defp put_refresh_token(changeset, attrs) do
|> unique_constraint(:refresh_token) |> unique_constraint(:refresh_token)
end end
def get_or_exchange_token(%Authorization{} = auth, %App{} = app, %User{} = user) do
if auth.used do
get_preeexisting_by_app_and_user(app, user)
else
exchange_token(app, auth)
end
end
defp put_valid_until(changeset, attrs) do defp put_valid_until(changeset, attrs) do
valid_until = valid_until =
Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan())) Map.get(attrs, :valid_until, NaiveDateTime.add(NaiveDateTime.utc_now(), lifespan()))

View file

@ -38,6 +38,19 @@ def get_by_user(query \\ Token, user_id) do
from(q in query, where: q.user_id == ^user_id) from(q in query, where: q.user_id == ^user_id)
end end
def get_unexpired(query) do
now = NaiveDateTime.utc_now()
from(q in query, where: q.valid_until > ^now)
end
def limit(query, limit) do
from(q in query, limit: ^limit)
end
def sort_by_inserted_at(query) do
from(q in query, order_by: [desc: :updated_at])
end
@spec preload(query, any) :: query @spec preload(query, any) :: query
def preload(query \\ Token, assoc_preload \\ []) def preload(query \\ Token, assoc_preload \\ [])

View file

@ -21,6 +21,18 @@ def revoke(%App{} = app, %{"token" => token} = _attrs) do
@doc "Revokes access token" @doc "Revokes access token"
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()} @spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
def revoke(%Token{} = token) do def revoke(%Token{} = token) do
Repo.delete(token) with {:ok, token} <- Repo.delete(token) do
Task.Supervisor.start_child(
Pleroma.TaskSupervisor,
Pleroma.Web.Streamer,
:close_streams_by_oauth_token,
[token],
restart: :transient
)
{:ok, token}
else
result -> result
end
end end
end end

View file

@ -74,6 +74,8 @@ defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
defp filter(reactions, _), do: reactions defp filter(reactions, _), do: reactions
def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do def create(%{assigns: %{user: user}} = conn, %{id: activity_id, emoji: emoji}) do
emoji = Pleroma.Emoji.maybe_quote(emoji)
with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do with {:ok, _activity} <- CommonAPI.react_with_emoji(activity_id, user, emoji) do
activity = Activity.get_by_id(activity_id) activity = Activity.get_by_id(activity_id)

View file

@ -27,11 +27,11 @@ def call(conn, _opts) do
end end
end end
def route_aliases(%{path_info: ["objects", id]} = conn) do def route_aliases(%{path_info: ["objects", id], query_string: query_string}) do
ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id) ap_id = Router.Helpers.o_status_url(Pleroma.Web.Endpoint, :object, id)
with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do with %Activity{} = activity <- Activity.get_by_object_ap_id_with_object(ap_id) do
["/notice/#{activity.id}"] ["/notice/#{activity.id}", "/notice/#{activity.id}?#{query_string}"]
else else
_ -> [] _ -> []
end end
@ -64,7 +64,9 @@ defp maybe_assign_valid_signature(conn) do
if has_signature_header?(conn) do if has_signature_header?(conn) do
# set (request-target) header to the appropriate value # set (request-target) header to the appropriate value
# we also replace the digest header with the one we computed # we also replace the digest header with the one we computed
possible_paths = route_aliases(conn) ++ [conn.request_path] possible_paths =
route_aliases(conn) ++ [conn.request_path, conn.request_path <> "?#{conn.query_string}"]
assign_valid_signature_on_route_aliases(conn, possible_paths) assign_valid_signature_on_route_aliases(conn, possible_paths)
else else
Logger.debug("No signature header!") Logger.debug("No signature header!")

View file

@ -457,6 +457,11 @@ defmodule Pleroma.Web.Router do
get("/federation_status", InstancesController, :show) get("/federation_status", InstancesController, :show)
end end
scope "/api/v1", Pleroma.Web.PleromaAPI do
pipe_through(:authenticated_api)
put("/statuses/:id/emoji_reactions/:emoji", EmojiReactionController, :create)
end
scope "/api/v1", Pleroma.Web.MastodonAPI do scope "/api/v1", Pleroma.Web.MastodonAPI do
pipe_through(:authenticated_api) pipe_through(:authenticated_api)

View file

@ -36,7 +36,7 @@ def registry, do: @registry
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized} {:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do def get_topic_and_add_socket(stream, user, oauth_token, params \\ %{}) do
with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do with {:ok, topic} <- get_topic(stream, user, oauth_token, params) do
add_socket(topic, user) add_socket(topic, oauth_token)
end end
end end
@ -114,15 +114,20 @@ def get_topic("list", _user, _oauth_token, _params) do
{:error, :unauthorized} {:error, :unauthorized}
end end
# mastodon multi-topic WS
def get_topic(nil, _user, _oauth_token, _params) do
{:ok, :multi}
end
def get_topic(_stream, _user, _oauth_token, _params) do def get_topic(_stream, _user, _oauth_token, _params) do
{:error, :bad_topic} {:error, :bad_topic}
end end
@doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic." @doc "Registers the process for streaming. Use `get_topic/3` to get the full authorized topic."
def add_socket(topic, user) do def add_socket(topic, oauth_token) do
if should_env_send?() do if should_env_send?() do
auth? = if user, do: true oauth_token_id = if oauth_token, do: oauth_token.id, else: false
Registry.register(@registry, topic, auth?) Registry.register(@registry, topic, oauth_token_id)
end end
{:ok, topic} {:ok, topic}
@ -186,8 +191,8 @@ defp do_stream("direct", item) do
end end
defp do_stream("follow_relationship", item) do defp do_stream("follow_relationship", item) do
text = StreamerView.render("follow_relationships_update.json", item)
user_topic = "user:#{item.follower.id}" user_topic = "user:#{item.follower.id}"
text = StreamerView.render("follow_relationships_update.json", item, user_topic)
Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n") Logger.debug("Trying to push follow relationship update to #{user_topic}\n\n")
@ -235,7 +240,7 @@ defp do_stream(topic, %Notification{} = item)
when topic in ["user", "user:notification"] do when topic in ["user", "user:notification"] do
Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list -> Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
Enum.each(list, fn {pid, _auth} -> Enum.each(list, fn {pid, _auth} ->
send(pid, {:render_with_user, StreamerView, "notification.json", item}) send(pid, {:render_with_user, StreamerView, "notification.json", item, topic})
end) end)
end) end)
end end
@ -259,7 +264,7 @@ defp do_stream(topic, item) do
end end
defp push_to_socket(topic, %Participation{} = participation) do defp push_to_socket(topic, %Participation{} = participation) do
rendered = StreamerView.render("conversation.json", participation) rendered = StreamerView.render("conversation.json", participation, topic)
Registry.dispatch(@registry, topic, fn list -> Registry.dispatch(@registry, topic, fn list ->
Enum.each(list, fn {pid, _} -> Enum.each(list, fn {pid, _} ->
@ -283,12 +288,12 @@ defp push_to_socket(topic, %Activity{
defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop defp push_to_socket(_topic, %Activity{data: %{"type" => "Delete"}}), do: :noop
defp push_to_socket(topic, item) do defp push_to_socket(topic, item) do
anon_render = StreamerView.render("update.json", item) anon_render = StreamerView.render("update.json", item, topic)
Registry.dispatch(@registry, topic, fn list -> Registry.dispatch(@registry, topic, fn list ->
Enum.each(list, fn {pid, auth?} -> Enum.each(list, fn {pid, auth?} ->
if auth? do if auth? do
send(pid, {:render_with_user, StreamerView, "update.json", item}) send(pid, {:render_with_user, StreamerView, "update.json", item, topic})
else else
send(pid, {:text, anon_render}) send(pid, {:text, anon_render})
end end
@ -306,6 +311,22 @@ defp thread_containment(activity, user) do
end end
end end
def close_streams_by_oauth_token(oauth_token) do
if should_env_send?() do
Registry.select(
@registry,
[
{
{:"$1", :"$2", :"$3"},
[{:==, :"$3", oauth_token.id}],
[:"$2"]
}
]
)
|> Enum.each(fn pid -> send(pid, :close) end)
end
end
# In test environement, only return true if the registry is started. # In test environement, only return true if the registry is started.
# In benchmark environment, returns false. # In benchmark environment, returns false.
# In any other environment, always returns true. # In any other environment, always returns true.

View file

@ -0,0 +1,34 @@
<!DOCTYPE html>
<html lang='en'>
<head>
<meta charset='utf-8'>
<meta content='width=device-width, initial-scale=1' name='viewport'>
<title>
<%= Config.get([:instance, :name]) %>
</title>
<link rel="icon" type="image/png" href="/favicon.png"/>
<link rel="manifest" type="applicaton/manifest+json" href="<%= Routes.masto_fe_path(Pleroma.Web.Endpoint, :manifest) %>" />
<meta name="theme-color" content="<%= Config.get([:manifest, :theme_color]) %>" />
<script id='initial-state' type='application/json'><%= initial_state(@token, @user, @custom_emojis) %></script>
<script crossorigin='anonymous' src="/packs/js/common.js"></script>
<script crossorigin='anonymous' src="/packs/js/locale_en.js"></script>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/getting_started.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/compose.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/home_timeline.js'>
<link rel='preload' as='script' crossorigin='anonymous' href='/packs/js/features/notifications.js'>
<script crossorigin='anonymous' src="/packs/js/application.js"></script>
<link rel="stylesheet" media="all" href="/packs/css/common.css" />
<link rel="stylesheet" media="all" href="/packs/css/default.css" />
</head>
<body class='app-body no-reduce-motion system-font'>
<div class='app-holder' data-props='{&quot;locale&quot;:&quot;en&quot;}' id='mastodon'>
</div>
</body>
</html>

View file

@ -14,6 +14,7 @@ def initial_state(token, user, custom_emojis) do
%{ %{
meta: %{ meta: %{
title: Config.get([:instance, :name]),
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(), streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
access_token: token, access_token: token,
locale: "en", locale: "en",
@ -27,7 +28,11 @@ def initial_state(token, user, custom_emojis) do
display_sensitive_media: false, display_sensitive_media: false,
reduce_motion: false, reduce_motion: false,
max_toot_chars: limit, max_toot_chars: limit,
mascot: User.get_mascot(user)["url"] mascot: User.get_mascot(user)["url"],
show_quote_button: true,
enable_reaction: true,
compact_reaction: false,
advanced_layout: true
}, },
poll_limits: Config.get([:instance, :poll_limits]), poll_limits: Config.get([:instance, :poll_limits]),
rights: %{ rights: %{
@ -56,6 +61,7 @@ def initial_state(token, user, custom_emojis) do
"video\/mp4" "video\/mp4"
] ]
}, },
lists: [],
settings: user.mastofe_settings || %{}, settings: user.mastofe_settings || %{},
push_subscription: nil, push_subscription: nil,
accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)}, accounts: %{user.id => render(AccountView, "show.json", user: user, for: user)},

View file

@ -11,8 +11,9 @@ defmodule Pleroma.Web.StreamerView do
alias Pleroma.User alias Pleroma.User
alias Pleroma.Web.MastodonAPI.NotificationView alias Pleroma.Web.MastodonAPI.NotificationView
def render("update.json", %Activity{} = activity, %User{} = user) do def render("update.json", %Activity{} = activity, %User{} = user, topic) do
%{ %{
stream: [topic],
event: "update", event: "update",
payload: payload:
Pleroma.Web.MastodonAPI.StatusView.render( Pleroma.Web.MastodonAPI.StatusView.render(
@ -25,8 +26,9 @@ def render("update.json", %Activity{} = activity, %User{} = user) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("notification.json", %Notification{} = notify, %User{} = user) do def render("notification.json", %Notification{} = notify, %User{} = user, topic) do
%{ %{
stream: [topic],
event: "notification", event: "notification",
payload: payload:
NotificationView.render( NotificationView.render(
@ -38,8 +40,9 @@ def render("notification.json", %Notification{} = notify, %User{} = user) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("update.json", %Activity{} = activity) do def render("update.json", %Activity{} = activity, topic) do
%{ %{
stream: [topic],
event: "update", event: "update",
payload: payload:
Pleroma.Web.MastodonAPI.StatusView.render( Pleroma.Web.MastodonAPI.StatusView.render(
@ -51,8 +54,9 @@ def render("update.json", %Activity{} = activity) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("follow_relationships_update.json", item) do def render("follow_relationships_update.json", item, topic) do
%{ %{
stream: [topic],
event: "pleroma:follow_relationships_update", event: "pleroma:follow_relationships_update",
payload: payload:
%{ %{
@ -73,8 +77,9 @@ def render("follow_relationships_update.json", item) do
|> Jason.encode!() |> Jason.encode!()
end end
def render("conversation.json", %Participation{} = participation) do def render("conversation.json", %Participation{} = participation, topic) do
%{ %{
stream: [topic],
event: "conversation", event: "conversation",
payload: payload:
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{ Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{

View file

@ -9,6 +9,12 @@ defmodule Pleroma.Workers.ReceiverWorker do
@impl Oban.Worker @impl Oban.Worker
def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do def perform(%Job{args: %{"op" => "incoming_ap_doc", "params" => params}}) do
Federator.perform(:incoming_ap_doc, params) with {:ok, res} <- Federator.perform(:incoming_ap_doc, params) do
{:ok, res}
else
{:error, :origin_containment_failed} -> {:discard, :origin_containment_failed}
{:error, {:reject, reason}} -> {:discard, reason}
e -> e
end
end end
end end

11
mix.exs
View file

@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do
def project do def project do
[ [
app: :pleroma, app: :pleroma,
version: version("3.0.1"), version: version("3.1.0"),
elixir: "~> 1.9", elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()), elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(), compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()], elixirc_options: [warnings_as_errors: warnings_as_errors()],
@ -129,7 +129,7 @@ defp deps do
override: true}, override: true},
{:bcrypt_elixir, "~> 2.2"}, {:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"}, {:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"}, {:fast_sanitize, "~> 0.2.3"},
{:html_entities, "~> 0.5", override: true}, {:html_entities, "~> 0.5", override: true},
{:phoenix_html, "~> 3.1", override: true}, {:phoenix_html, "~> 3.1", override: true},
{:calendar, "~> 1.0"}, {:calendar, "~> 1.0"},
@ -191,6 +191,9 @@ defp deps do
{:ecto_psql_extras, "~> 0.6"}, {:ecto_psql_extras, "~> 0.6"},
{:elasticsearch, {:elasticsearch,
git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"}, git: "https://akkoma.dev/AkkomaGang/elasticsearch-elixir.git", ref: "main"},
{:mfm_parser,
git: "https://akkoma.dev/AkkomaGang/mfm-parser.git",
ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"},
# indirect dependency version override # indirect dependency version override
{:plug, "~> 1.10.4", override: true}, {:plug, "~> 1.10.4", override: true},
@ -203,7 +206,7 @@ defp deps do
# temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed # temporary downgrade for excoveralls, hackney until hackney max_connections bug will be fixed
{:excoveralls, "0.12.3", only: :test}, {:excoveralls, "0.12.3", only: :test},
{:mox, "~> 1.0", only: :test}, {:mox, "~> 1.0", only: :test},
{:websocket_client, git: "https://github.com/jeremyong/websocket_client.git", only: :test} {:websockex, "~> 0.4.3", only: :test}
] ++ oauth_deps() ] ++ oauth_deps()
end end

View file

@ -67,6 +67,7 @@
"makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"}, "makeup_erlang": {:hex, :makeup_erlang, "0.1.1", "3fcb7f09eb9d98dc4d208f49cc955a34218fc41ff6b84df7c75b3e6e533cc65f", [:mix], [{:makeup, "~> 1.0", [hex: :makeup, repo: "hexpm", optional: false]}], "hexpm", "174d0809e98a4ef0b3309256cbf97101c6ec01c4ab0b23e926a9e17df2077cbb"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"}, "meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"}, "metrics": {:hex, :metrics, "1.0.1", "25f094dea2cda98213cecc3aeff09e940299d950904393b2a29d191c346a8486", [:rebar3], [], "hexpm", "69b09adddc4f74a40716ae54d140f93beb0fb8978d8636eaded0c31b6f099f16"},
"mfm_parser": {:git, "https://akkoma.dev/AkkomaGang/mfm-parser.git", "912fba81152d4d572e457fd5427f9875b2bc3dbe", [ref: "912fba81152d4d572e457fd5427f9875b2bc3dbe"]},
"mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"}, "mime": {:hex, :mime, "1.6.0", "dabde576a497cef4bbdd60aceee8160e02a6c89250d6c0b29e56c0dfb00db3d2", [:mix], [], "hexpm", "31a1a8613f8321143dde1dafc36006a17d28d02bdfecb9e95a880fa7aabd19a7"},
"mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"}, "mimerl": {:hex, :mimerl, "1.2.0", "67e2d3f571088d5cfd3e550c383094b47159f3eee8ffa08e64106cdf5e981be3", [:rebar3], [], "hexpm", "f278585650aa581986264638ebf698f8bb19df297f66ad91b18910dfc6e19323"},
"mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"}, "mint": {:hex, :mint, "1.4.2", "50330223429a6e1260b2ca5415f69b0ab086141bc76dc2fbf34d7c389a6675b2", [:mix], [{:castore, "~> 0.1.0", [hex: :castore, repo: "hexpm", optional: true]}, {:hpax, "~> 0.1.1", [hex: :hpax, repo: "hexpm", optional: false]}], "hexpm", "ce75a5bbcc59b4d7d8d70f8b2fc284b1751ffb35c7b6a6302b5192f8ab4ddd80"},
@ -109,6 +110,7 @@
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"}, "table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
"telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"}, "telemetry": {:hex, :telemetry, "0.4.3", "a06428a514bdbc63293cd9a6263aad00ddeb66f608163bdec7c8995784080818", [:rebar3], [], "hexpm", "eb72b8365ffda5bed68a620d1da88525e326cb82a75ee61354fc24b844768041"},
"telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"}, "telemetry_metrics": {:hex, :telemetry_metrics, "0.6.1", "315d9163a1d4660aedc3fee73f33f1d355dcc76c5c3ab3d59e76e3edf80eef1f", [:mix], [{:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: false]}], "hexpm", "7be9e0871c41732c233be71e4be11b96e56177bf15dde64a8ac9ce72ac9834c6"},
"temple": {:git, "https://akkoma.dev/AkkomaGang/temple.git", "066a699ade472d8fa42a9d730b29a61af9bc8b59", [ref: "066a699ade472d8fa42a9d730b29a61af9bc8b59"]},
"tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"}, "tesla": {:hex, :tesla, "1.4.4", "bb89aa0c9745190930366f6a2ac612cdf2d0e4d7fff449861baa7875afd797b2", [:mix], [{:castore, "~> 0.1", [hex: :castore, repo: "hexpm", optional: true]}, {:exjsx, ">= 3.0.0", [hex: :exjsx, repo: "hexpm", optional: true]}, {:finch, "~> 0.3", [hex: :finch, repo: "hexpm", optional: true]}, {:fuse, "~> 2.4", [hex: :fuse, repo: "hexpm", optional: true]}, {:gun, "~> 1.3", [hex: :gun, repo: "hexpm", optional: true]}, {:hackney, "~> 1.6", [hex: :hackney, repo: "hexpm", optional: true]}, {:ibrowse, "4.4.0", [hex: :ibrowse, repo: "hexpm", optional: true]}, {:jason, ">= 1.0.0", [hex: :jason, repo: "hexpm", optional: true]}, {:mime, "~> 1.0 or ~> 2.0", [hex: :mime, repo: "hexpm", optional: false]}, {:mint, "~> 1.0", [hex: :mint, repo: "hexpm", optional: true]}, {:poison, ">= 1.0.0", [hex: :poison, repo: "hexpm", optional: true]}, {:telemetry, "~> 0.4 or ~> 1.0", [hex: :telemetry, repo: "hexpm", optional: true]}], "hexpm", "d5503a49f9dec1b287567ea8712d085947e247cb11b06bc54adb05bfde466457"},
"timex": {:hex, :timex, "3.7.8", "0e6e8bf7c0aba95f1e13204889b2446e7a5297b1c8e408f15ab58b2c8dc85f81", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8f3b8edc5faab5205d69e5255a1d64a83b190bab7f16baa78aefcb897cf81435"}, "timex": {:hex, :timex, "3.7.8", "0e6e8bf7c0aba95f1e13204889b2446e7a5297b1c8e408f15ab58b2c8dc85f81", [:mix], [{:combine, "~> 0.10", [hex: :combine, repo: "hexpm", optional: false]}, {:gettext, "~> 0.10", [hex: :gettext, repo: "hexpm", optional: false]}, {:tzdata, "~> 1.1", [hex: :tzdata, repo: "hexpm", optional: false]}], "hexpm", "8f3b8edc5faab5205d69e5255a1d64a83b190bab7f16baa78aefcb897cf81435"},
"trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"}, "trailing_format_plug": {:hex, :trailing_format_plug, "0.0.7", "64b877f912cf7273bed03379936df39894149e35137ac9509117e59866e10e45", [:mix], [{:plug, "> 0.12.0", [hex: :plug, repo: "hexpm", optional: false]}], "hexpm", "bd4fde4c15f3e993a999e019d64347489b91b7a9096af68b2bdadd192afa693f"},
@ -118,5 +120,5 @@
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"}, "unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
"vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"}, "vex": {:hex, :vex, "0.9.0", "613ea5eb3055662e7178b83e25b2df0975f68c3d8bb67c1645f0573e1a78d606", [:mix], [], "hexpm", "c69fff44d5c8aa3f1faee71bba1dcab05dd36364c5a629df8bb11751240c857f"},
"web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"}, "web_push_encryption": {:hex, :web_push_encryption, "0.3.1", "76d0e7375142dfee67391e7690e89f92578889cbcf2879377900b5620ee4708d", [:mix], [{:httpoison, "~> 1.0", [hex: :httpoison, repo: "hexpm", optional: false]}, {:jose, "~> 1.11.1", [hex: :jose, repo: "hexpm", optional: false]}], "hexpm", "4f82b2e57622fb9337559058e8797cb0df7e7c9790793bdc4e40bc895f70e2a2"},
"websocket_client": {:git, "https://github.com/jeremyong/websocket_client.git", "9a6f65d05ebf2725d62fb19262b21f1805a59fbf", []}, "websockex": {:hex, :websockex, "0.4.3", "92b7905769c79c6480c02daacaca2ddd49de936d912976a4d3c923723b647bf0", [:mix], [], "hexpm", "95f2e7072b85a3a4cc385602d42115b73ce0b74a9121d0d6dbbf557645ac53e4"},
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,163 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-06 22:24+0000\n"
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
"Last-Translator: Automatically generated\n"
"Language-Team: none\n"
"Language: es\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"X-Generator: Translate Toolkit 3.7.1\n"
## This file is a PO Template file.
##
## `msgid`s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run `mix gettext.extract` to bring this file up to
## date. Leave `msgstr`s empty as changing them here as no
## effect: edit them in PO (`.po`) files instead.
msgid "eperm"
msgstr ""
msgid "eacces"
msgstr ""
msgid "eagain"
msgstr ""
msgid "ebadf"
msgstr ""
msgid "ebadmsg"
msgstr ""
msgid "ebusy"
msgstr ""
msgid "edeadlk"
msgstr ""
msgid "edeadlock"
msgstr ""
msgid "edquot"
msgstr ""
msgid "eexist"
msgstr ""
msgid "efault"
msgstr ""
msgid "efbig"
msgstr ""
msgid "eftype"
msgstr ""
msgid "eintr"
msgstr ""
msgid "einval"
msgstr ""
msgid "eio"
msgstr ""
msgid "eisdir"
msgstr ""
msgid "eloop"
msgstr ""
msgid "emfile"
msgstr ""
msgid "emlink"
msgstr ""
msgid "emultihop"
msgstr ""
msgid "enametoolong"
msgstr ""
msgid "enfile"
msgstr ""
msgid "enobufs"
msgstr ""
msgid "enodev"
msgstr ""
msgid "enolck"
msgstr ""
msgid "enolink"
msgstr ""
msgid "enoent"
msgstr ""
msgid "enomem"
msgstr ""
msgid "enospc"
msgstr ""
msgid "enosr"
msgstr ""
msgid "enostr"
msgstr ""
msgid "enosys"
msgstr ""
msgid "enotblk"
msgstr ""
msgid "enotdir"
msgstr ""
msgid "enotsup"
msgstr ""
msgid "enxio"
msgstr ""
msgid "eopnotsupp"
msgstr ""
msgid "eoverflow"
msgstr ""
msgid "epipe"
msgstr ""
msgid "erange"
msgstr ""
msgid "erofs"
msgstr ""
msgid "espipe"
msgstr ""
msgid "esrch"
msgstr ""
msgid "estale"
msgstr ""
msgid "etxtbsy"
msgstr ""
msgid "exdev"
msgstr ""

File diff suppressed because it is too large Load diff

View file

@ -3,16 +3,16 @@ msgstr ""
"Project-Id-Version: PACKAGE VERSION\n" "Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2020-05-15 09:37+0000\n" "POT-Creation-Date: 2020-05-15 09:37+0000\n"
"PO-Revision-Date: 2020-06-02 07:36+0000\n" "PO-Revision-Date: 2022-08-07 10:46+0000\n"
"Last-Translator: Fristi <fristi@subcon.town>\n" "Last-Translator: Fristi <fristi@subcon.town>\n"
"Language-Team: Dutch <https://translate.pleroma.social/projects/pleroma/" "Language-Team: Dutch <http://translate.akkoma.dev/projects/akkoma/"
"pleroma/nl/>\n" "akkoma-backend-errors/nl/>\n"
"Language: nl\n" "Language: nl\n"
"MIME-Version: 1.0\n" "MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n" "Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n" "Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n" "Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.0.4\n" "X-Generator: Weblate 4.13.1\n"
## This file is a PO Template file. ## This file is a PO Template file.
## ##
@ -118,7 +118,7 @@ msgstr "Al gestemd"
#: lib/pleroma/web/oauth/oauth_controller.ex:360 #: lib/pleroma/web/oauth/oauth_controller.ex:360
#, elixir-format #, elixir-format
msgid "Bad request" msgid "Bad request"
msgstr "Bad request" msgstr "Ongeldig request"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
#, elixir-format #, elixir-format
@ -155,7 +155,7 @@ msgstr "Object kan niet geliked worden"
#: lib/pleroma/web/common_api/utils.ex:556 #: lib/pleroma/web/common_api/utils.ex:556
#, elixir-format #, elixir-format
msgid "Cannot post an empty status without attachments" msgid "Cannot post an empty status without attachments"
msgstr "Status kan niet geplaatst worden zonder tekst of bijlagen" msgstr "Bericht kan niet geplaatst worden zonder tekst of bijlagen"
#: lib/pleroma/web/common_api/utils.ex:504 #: lib/pleroma/web/common_api/utils.ex:504
#, elixir-format #, elixir-format
@ -165,122 +165,122 @@ msgstr "Opmerking dient maximaal %{max_size} karakters te bevatten"
#: lib/pleroma/config/config_db.ex:222 #: lib/pleroma/config/config_db.ex:222
#, elixir-format #, elixir-format
msgid "Config with params %{params} not found" msgid "Config with params %{params} not found"
msgstr "" msgstr "Instelling met parameters %{params} kon niet gevonden worden"
#: lib/pleroma/web/common_api/common_api.ex:95 #: lib/pleroma/web/common_api/common_api.ex:95
#, elixir-format #, elixir-format
msgid "Could not delete" msgid "Could not delete"
msgstr "" msgstr "Verwijderen mislukt"
#: lib/pleroma/web/common_api/common_api.ex:141 #: lib/pleroma/web/common_api/common_api.ex:141
#, elixir-format #, elixir-format
msgid "Could not favorite" msgid "Could not favorite"
msgstr "" msgstr "Favoriet maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:370 #: lib/pleroma/web/common_api/common_api.ex:370
#, elixir-format #, elixir-format
msgid "Could not pin" msgid "Could not pin"
msgstr "" msgstr "Vastmaken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:112 #: lib/pleroma/web/common_api/common_api.ex:112
#, elixir-format #, elixir-format
msgid "Could not repeat" msgid "Could not repeat"
msgstr "" msgstr "Herhalen mislukt"
#: lib/pleroma/web/common_api/common_api.ex:188 #: lib/pleroma/web/common_api/common_api.ex:188
#, elixir-format #, elixir-format
msgid "Could not unfavorite" msgid "Could not unfavorite"
msgstr "" msgstr "Favoriet ongedaan maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:380 #: lib/pleroma/web/common_api/common_api.ex:380
#, elixir-format #, elixir-format
msgid "Could not unpin" msgid "Could not unpin"
msgstr "" msgstr "Vastmaken ongedaan maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:126 #: lib/pleroma/web/common_api/common_api.ex:126
#, elixir-format #, elixir-format
msgid "Could not unrepeat" msgid "Could not unrepeat"
msgstr "" msgstr "Herhalen ongedaan maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:428 #: lib/pleroma/web/common_api/common_api.ex:428
#: lib/pleroma/web/common_api/common_api.ex:437 #: lib/pleroma/web/common_api/common_api.ex:437
#, elixir-format #, elixir-format
msgid "Could not update state" msgid "Could not update state"
msgstr "" msgstr "Status bijwerken mislukt"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
#, elixir-format #, elixir-format
msgid "Error." msgid "Error."
msgstr "" msgstr "Fout."
#: lib/pleroma/web/twitter_api/twitter_api.ex:106 #: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format #, elixir-format
msgid "Invalid CAPTCHA" msgid "Invalid CAPTCHA"
msgstr "" msgstr "Ongeldige CAPTCHA"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
#: lib/pleroma/web/oauth/oauth_controller.ex:569 #: lib/pleroma/web/oauth/oauth_controller.ex:569
#, elixir-format #, elixir-format
msgid "Invalid credentials" msgid "Invalid credentials"
msgstr "" msgstr "Ongeldige inloggegevens"
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format #, elixir-format
msgid "Invalid credentials." msgid "Invalid credentials."
msgstr "" msgstr "Ongeldige inloggegevens."
#: lib/pleroma/web/common_api/common_api.ex:265 #: lib/pleroma/web/common_api/common_api.ex:265
#, elixir-format #, elixir-format
msgid "Invalid indices" msgid "Invalid indices"
msgstr "" msgstr "Ongeldige indexen"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147 #: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
#, elixir-format #, elixir-format
msgid "Invalid parameters" msgid "Invalid parameters"
msgstr "" msgstr "Ongeldige parameters"
#: lib/pleroma/web/common_api/utils.ex:411 #: lib/pleroma/web/common_api/utils.ex:411
#, elixir-format #, elixir-format
msgid "Invalid password." msgid "Invalid password."
msgstr "" msgstr "Ongeldig wachtwoord."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
#, elixir-format #, elixir-format
msgid "Invalid request" msgid "Invalid request"
msgstr "" msgstr "Ongeldig request"
#: lib/pleroma/web/twitter_api/twitter_api.ex:109 #: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format #, elixir-format
msgid "Kocaptcha service unavailable" msgid "Kocaptcha service unavailable"
msgstr "" msgstr "Kocaptcha service niet beschikbaar"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
#, elixir-format #, elixir-format
msgid "Missing parameters" msgid "Missing parameters"
msgstr "" msgstr "Ontbrekende parameters"
#: lib/pleroma/web/common_api/utils.ex:540 #: lib/pleroma/web/common_api/utils.ex:540
#, elixir-format #, elixir-format
msgid "No such conversation" msgid "No such conversation"
msgstr "" msgstr "Gesprek niet gevonden"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:439 #: lib/pleroma/web/admin_api/admin_api_controller.ex:439
#: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507 #: lib/pleroma/web/admin_api/admin_api_controller.ex:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
#, elixir-format #, elixir-format
msgid "No such permission_group" msgid "No such permission_group"
msgstr "" msgstr "Permission_group niet gevonden"
#: lib/pleroma/plugs/uploaded_media.ex:74 #: lib/pleroma/plugs/uploaded_media.ex:74
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:485 lib/pleroma/web/admin_api/admin_api_controller.ex:1135
#: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143 #: lib/pleroma/web/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format #, elixir-format
msgid "Not found" msgid "Not found"
msgstr "" msgstr "Niet gevonden"
#: lib/pleroma/web/common_api/common_api.ex:241 #: lib/pleroma/web/common_api/common_api.ex:241
#, elixir-format #, elixir-format
msgid "Poll's author can't vote" msgid "Poll's author can't vote"
msgstr "" msgstr "De peiling-auteur kan niet stemmen"
#: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20 #: lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:20
#: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49 #: lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:37 lib/pleroma/web/mastodon_api/controllers/poll_controller.ex:49
@ -288,215 +288,215 @@ msgstr ""
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:71
#, elixir-format #, elixir-format
msgid "Record not found" msgid "Record not found"
msgstr "" msgstr "Record niet gevonden"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1153 #: lib/pleroma/web/admin_api/admin_api_controller.ex:1153
#: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32 #: lib/pleroma/web/feed/user_controller.ex:79 lib/pleroma/web/mastodon_api/controllers/fallback_controller.ex:32
#: lib/pleroma/web/ostatus/ostatus_controller.ex:149 #: lib/pleroma/web/ostatus/ostatus_controller.ex:149
#, elixir-format #, elixir-format
msgid "Something went wrong" msgid "Something went wrong"
msgstr "" msgstr "Er is iets misgegaan"
#: lib/pleroma/web/common_api/activity_draft.ex:107 #: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format #, elixir-format
msgid "The message visibility must be direct" msgid "The message visibility must be direct"
msgstr "" msgstr "De zichtbaarheid van het bericht dient privé te zijn"
#: lib/pleroma/web/common_api/utils.ex:566 #: lib/pleroma/web/common_api/utils.ex:566
#, elixir-format #, elixir-format
msgid "The status is over the character limit" msgid "The status is over the character limit"
msgstr "" msgstr "Het bericht is langer dan het karakter-limiet"
#: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31 #: lib/pleroma/plugs/ensure_public_or_authenticated_plug.ex:31
#, elixir-format #, elixir-format
msgid "This resource requires authentication." msgid "This resource requires authentication."
msgstr "" msgstr "Deze gegevens vereisen authenticatie."
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206 #: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format #, elixir-format
msgid "Throttled" msgid "Throttled"
msgstr "" msgstr "Geremd"
#: lib/pleroma/web/common_api/common_api.ex:266 #: lib/pleroma/web/common_api/common_api.ex:266
#, elixir-format #, elixir-format
msgid "Too many choices" msgid "Too many choices"
msgstr "" msgstr "Teveel keuzes"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
#, elixir-format #, elixir-format
msgid "Unhandled activity type" msgid "Unhandled activity type"
msgstr "" msgstr "Niet-ondersteund activiteits-type"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536 #: lib/pleroma/web/admin_api/admin_api_controller.ex:536
#, elixir-format #, elixir-format
msgid "You can't revoke your own admin status." msgid "You can't revoke your own admin status."
msgstr "" msgstr "Je kan je eigen beheerdersrechten niet intrekken."
#: lib/pleroma/web/oauth/oauth_controller.ex:218 #: lib/pleroma/web/oauth/oauth_controller.ex:218
#: lib/pleroma/web/oauth/oauth_controller.ex:309 #: lib/pleroma/web/oauth/oauth_controller.ex:309
#, elixir-format #, elixir-format
msgid "Your account is currently disabled" msgid "Your account is currently disabled"
msgstr "" msgstr "Je account is momenteel uitgeschakeld"
#: lib/pleroma/web/oauth/oauth_controller.ex:180 #: lib/pleroma/web/oauth/oauth_controller.ex:180
#: lib/pleroma/web/oauth/oauth_controller.ex:332 #: lib/pleroma/web/oauth/oauth_controller.ex:332
#, elixir-format #, elixir-format
msgid "Your login is missing a confirmed e-mail address" msgid "Your login is missing a confirmed e-mail address"
msgstr "" msgstr "Je login bevat geen bevestigd e-mailadres"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:389
#, elixir-format #, elixir-format
msgid "can't read inbox of %{nickname} as %{as_nickname}" msgid "can't read inbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "kan de inbox van %{nickname} niet lezen als %{as_nickname}"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:472
#, elixir-format #, elixir-format
msgid "can't update outbox of %{nickname} as %{as_nickname}" msgid "can't update outbox of %{nickname} as %{as_nickname}"
msgstr "" msgstr "kan de outbox van %{nickname} niet bijwerken als %{as_nickname}"
#: lib/pleroma/web/common_api/common_api.ex:388 #: lib/pleroma/web/common_api/common_api.ex:388
#, elixir-format #, elixir-format
msgid "conversation is already muted" msgid "conversation is already muted"
msgstr "" msgstr "gesprek is al genegeerd"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:316
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:491
#, elixir-format #, elixir-format
msgid "error" msgid "error"
msgstr "" msgstr "fout"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29 #: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
#, elixir-format #, elixir-format
msgid "mascots can only be images" msgid "mascots can only be images"
msgstr "" msgstr "mascottes kunnen alleen afbeeldingen zijn"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
#, elixir-format #, elixir-format
msgid "not found" msgid "not found"
msgstr "" msgstr "niet gevonden"
#: lib/pleroma/web/oauth/oauth_controller.ex:395 #: lib/pleroma/web/oauth/oauth_controller.ex:395
#, elixir-format #, elixir-format
msgid "Bad OAuth request." msgid "Bad OAuth request."
msgstr "" msgstr "Ongeldig OAuth request."
#: lib/pleroma/web/twitter_api/twitter_api.ex:115 #: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format #, elixir-format
msgid "CAPTCHA already used" msgid "CAPTCHA already used"
msgstr "" msgstr "CAPTCHA is al gebruikt"
#: lib/pleroma/web/twitter_api/twitter_api.ex:112 #: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format #, elixir-format
msgid "CAPTCHA expired" msgid "CAPTCHA expired"
msgstr "" msgstr "CAPTCHA is verlopen"
#: lib/pleroma/plugs/uploaded_media.ex:55 #: lib/pleroma/plugs/uploaded_media.ex:55
#, elixir-format #, elixir-format
msgid "Failed" msgid "Failed"
msgstr "" msgstr "Mislukt"
#: lib/pleroma/web/oauth/oauth_controller.ex:411 #: lib/pleroma/web/oauth/oauth_controller.ex:411
#, elixir-format #, elixir-format
msgid "Failed to authenticate: %{message}." msgid "Failed to authenticate: %{message}."
msgstr "" msgstr "Authenticatie mislukt: %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:442 #: lib/pleroma/web/oauth/oauth_controller.ex:442
#, elixir-format #, elixir-format
msgid "Failed to set up user account." msgid "Failed to set up user account."
msgstr "" msgstr "Aanmaken van gebruikersaccount is mislukt."
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38 #: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format #, elixir-format
msgid "Insufficient permissions: %{permissions}." msgid "Insufficient permissions: %{permissions}."
msgstr "" msgstr "Niet voldoende rechten: %{permissions}."
#: lib/pleroma/plugs/uploaded_media.ex:94 #: lib/pleroma/plugs/uploaded_media.ex:94
#, elixir-format #, elixir-format
msgid "Internal Error" msgid "Internal Error"
msgstr "" msgstr "Interne Fout"
#: lib/pleroma/web/oauth/fallback_controller.ex:22 #: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29 #: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format #, elixir-format
msgid "Invalid Username/Password" msgid "Invalid Username/Password"
msgstr "" msgstr "Ongeldige Gebruikersnaam/Wachtwoord"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118 #: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format #, elixir-format
msgid "Invalid answer data" msgid "Invalid answer data"
msgstr "" msgstr "Ongeldig antwoord"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128 #: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
#, elixir-format #, elixir-format
msgid "Nodeinfo schema version not handled" msgid "Nodeinfo schema version not handled"
msgstr "" msgstr "Nodeinfo schema wordt niet ondersteund"
#: lib/pleroma/web/oauth/oauth_controller.ex:169 #: lib/pleroma/web/oauth/oauth_controller.ex:169
#, elixir-format #, elixir-format
msgid "This action is outside the authorized scopes" msgid "This action is outside the authorized scopes"
msgstr "" msgstr "Deze actie bevindt zich buiten de gemachtigde scopes"
#: lib/pleroma/web/oauth/fallback_controller.ex:14 #: lib/pleroma/web/oauth/fallback_controller.ex:14
#, elixir-format #, elixir-format
msgid "Unknown error, please check the details and try again." msgid "Unknown error, please check the details and try again."
msgstr "" msgstr "Onbekende fout, controleer a.u.b. de details en probeer het opnieuw."
#: lib/pleroma/web/oauth/oauth_controller.ex:116 #: lib/pleroma/web/oauth/oauth_controller.ex:116
#: lib/pleroma/web/oauth/oauth_controller.ex:155 #: lib/pleroma/web/oauth/oauth_controller.ex:155
#, elixir-format #, elixir-format
msgid "Unlisted redirect_uri." msgid "Unlisted redirect_uri."
msgstr "" msgstr "Niet-vermelde redirect_uri."
#: lib/pleroma/web/oauth/oauth_controller.ex:391 #: lib/pleroma/web/oauth/oauth_controller.ex:391
#, elixir-format #, elixir-format
msgid "Unsupported OAuth provider: %{provider}." msgid "Unsupported OAuth provider: %{provider}."
msgstr "" msgstr "Niet ondersteunde OAuth provider: %{provider}."
#: lib/pleroma/uploaders/uploader.ex:72 #: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format #, elixir-format
msgid "Uploader callback timeout" msgid "Uploader callback timeout"
msgstr "" msgstr "Uploader terugkoppeling timeout"
#: lib/pleroma/web/uploader_controller.ex:23 #: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format #, elixir-format
msgid "bad request" msgid "bad request"
msgstr "" msgstr "ongeldig request"
#: lib/pleroma/web/twitter_api/twitter_api.ex:103 #: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format #, elixir-format
msgid "CAPTCHA Error" msgid "CAPTCHA Error"
msgstr "" msgstr "CAPTCHA Fout"
#: lib/pleroma/web/common_api/common_api.ex:200 #: lib/pleroma/web/common_api/common_api.ex:200
#, elixir-format #, elixir-format
msgid "Could not add reaction emoji" msgid "Could not add reaction emoji"
msgstr "" msgstr "Reactie-emoji toevoegen mislukt"
#: lib/pleroma/web/common_api/common_api.ex:211 #: lib/pleroma/web/common_api/common_api.ex:211
#, elixir-format #, elixir-format
msgid "Could not remove reaction emoji" msgid "Could not remove reaction emoji"
msgstr "" msgstr "Reactie-emoji verwijderen mislukt"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129 #: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format #, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})" msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr "" msgstr "Ongeldige CAPTCHA (Ontbrekende parameter: %{name})"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92 #: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format #, elixir-format
msgid "List not found" msgid "List not found"
msgstr "" msgstr "Lijst niet gevonden"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124 #: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
#, elixir-format #, elixir-format
msgid "Missing parameter: %{name}" msgid "Missing parameter: %{name}"
msgstr "" msgstr "Ontbrekende parameter: %{name}"
#: lib/pleroma/web/oauth/oauth_controller.ex:207 #: lib/pleroma/web/oauth/oauth_controller.ex:207
#: lib/pleroma/web/oauth/oauth_controller.ex:322 #: lib/pleroma/web/oauth/oauth_controller.ex:322
#, elixir-format #, elixir-format
msgid "Password reset is required" msgid "Password reset is required"
msgstr "" msgstr "Wachtwoordherstel is vereist"
#: lib/pleroma/tests/auth_test_controller.ex:9 #: lib/pleroma/tests/auth_test_controller.ex:9
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6 #: lib/pleroma/web/activity_pub/activity_pub_controller.ex:6 lib/pleroma/web/admin_api/admin_api_controller.ex:6
@ -528,53 +528,63 @@ msgstr ""
#, elixir-format #, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped." msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr "" msgstr ""
"Schending van beveiliging: OAuth scope-controle is niet uitgevoerd en niet "
"expliciet overgeslagen."
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28 #: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format #, elixir-format
msgid "Two-factor authentication enabled, you must use a access token." msgid "Two-factor authentication enabled, you must use a access token."
msgstr "" msgstr ""
"Tweefactor authenticatie is ingeschakeld, een toegangssleutel is verplicht."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while adding file to pack." msgid "Unexpected error occurred while adding file to pack."
msgstr "" msgstr ""
"Er is een onverwachte fout opgetreden tijdens het toevoegen van het bestand."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while creating pack." msgid "Unexpected error occurred while creating pack."
msgstr "" msgstr ""
"Er is een onverwachte fout opgetreden tijdens het aanmaken van het pakket."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while removing file from pack." msgid "Unexpected error occurred while removing file from pack."
msgstr "" msgstr ""
"Er is een onverwachte fout opgetreden tijdens het verwijderen van het "
"bestand."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while updating file in pack." msgid "Unexpected error occurred while updating file in pack."
msgstr "" msgstr ""
"Er is een onverwachte fout opgetreden tijdens het bijwerken van het bestand."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179 #: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
#, elixir-format #, elixir-format
msgid "Unexpected error occurred while updating pack metadata." msgid "Unexpected error occurred while updating pack metadata."
msgstr "" msgstr ""
"Er is een onverwachte fout opgetreden tijdens het bijwerken van de pakket-"
"metadata."
#: lib/pleroma/plugs/user_is_admin_plug.ex:21 #: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format #, elixir-format
msgid "User is not an admin." msgid "User is not an admin."
msgstr "" msgstr "Gebruiker is niet een beheerder."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61 #: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format #, elixir-format
msgid "Web push subscription is disabled on this Pleroma instance" msgid "Web push subscription is disabled on this Pleroma instance"
msgstr "" msgstr "Web push abbonement is uitgeschakeld op deze Pleroma instantie"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:502 #: lib/pleroma/web/admin_api/admin_api_controller.ex:502
#, elixir-format #, elixir-format
msgid "You can't revoke your own admin/moderator status." msgid "You can't revoke your own admin/moderator status."
msgstr "" msgstr "Je kan je eigen beheerders- of moderatorrechten niet intrekken."
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105 #: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:105
#, elixir-format #, elixir-format
msgid "authorization required for timeline view" msgid "authorization required for timeline view"
msgstr "" msgstr "machtiging is vereist voor de tijdlijn weergave"

View file

@ -0,0 +1,567 @@
msgid ""
msgstr ""
"Project-Id-Version: PACKAGE VERSION\n"
"Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2022-08-07 10:48+0000\n"
"PO-Revision-Date: 2022-08-07 19:52+0000\n"
"Last-Translator: Fristi <fristi@subcon.town>\n"
"Language-Team: Dutch <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-static-pages/nl/>\n"
"Language: nl\n"
"MIME-Version: 1.0\n"
"Content-Type: text/plain; charset=UTF-8\n"
"Content-Transfer-Encoding: 8bit\n"
"Plural-Forms: nplurals=2; plural=n != 1;\n"
"X-Generator: Weblate 4.13.1\n"
## This file is a PO Template file.
##
## "msgid"s here are often extracted from source code.
## Add new translations manually only if they're dynamic
## translations that can't be statically extracted.
##
## Run "mix gettext.extract" to bring this file up to
## date. Leave "msgstr"s empty as changing them here as no
## effect: edit them in PO (.po) files instead.
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:9
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button"
msgid "Authorize"
msgstr "Machtigen"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error fetching user"
msgstr "Fout bij ophalen gebruiker"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header"
msgid "Remote follow"
msgstr "Extern volgen"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "placeholder text for auth code entry"
msgid "Authentication code"
msgstr "Authenticatiecode"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:10
#, elixir-autogen, elixir-format
msgctxt "placeholder text for password entry"
msgid "Password"
msgstr "Wachtwoord"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "placeholder text for username entry"
msgid "Username"
msgstr "Gebruikersnaam"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:13
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button for login"
msgid "Authorize"
msgstr "Machtigen"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button for mfa"
msgid "Authorize"
msgstr "Machtigen"
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error following account"
msgstr "Fout bij volgen van account"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_login.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header, need login"
msgid "Log in to follow"
msgstr "Log in om te volgen"
#: lib/pleroma/web/templates/twitter_api/remote_follow/follow_mfa.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow mfa header"
msgid "Two-factor authentication"
msgstr "Tweefactor authenticatie"
#: lib/pleroma/web/templates/twitter_api/remote_follow/followed.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow success"
msgid "Account followed!"
msgstr "Account gevolgd!"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:7
#, elixir-autogen, elixir-format
msgctxt "placeholder text for account id"
msgid "Your account ID, e.g. lain@quitter.se"
msgstr "Je account ID, b.v. gebruiker@instantie.net"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "remote follow authorization button for following with a remote account"
msgid "Follow"
msgstr "Volgen"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "remote follow error"
msgid "Error: %{error}"
msgstr "Fout: %{error}"
#: lib/pleroma/web/templates/twitter_api/util/subscribe.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "remote follow header"
msgid "Remotely follow %{nickname}"
msgstr "%{nickname} extern volgen"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "password reset button"
msgid "Reset"
msgstr "Herstellen"
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "password reset failed homepage link"
msgid "Homepage"
msgstr "Homepagina"
#: lib/pleroma/web/templates/twitter_api/password/reset_failed.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "password reset failed message"
msgid "Password reset failed"
msgstr "Wachtwoordherstel mislukt"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "password reset form confirm password prompt"
msgid "Confirmation"
msgstr "Bevestiging"
#: lib/pleroma/web/templates/twitter_api/password/reset.html.eex:4
#, elixir-autogen, elixir-format
msgctxt "password reset form password prompt"
msgid "Password"
msgstr "Wachtwoord"
#: lib/pleroma/web/templates/twitter_api/password/invalid_token.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "password reset invalid token message"
msgid "Invalid Token"
msgstr "Ongeldige Token"
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "password reset successful homepage link"
msgid "Homepage"
msgstr "Homepagina"
#: lib/pleroma/web/templates/twitter_api/password/reset_success.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "password reset successful message"
msgid "Password changed!"
msgstr "Wachtwoord gewijzigd!"
#: lib/pleroma/web/templates/feed/feed/tag.atom.eex:15
#: lib/pleroma/web/templates/feed/feed/tag.rss.eex:7
#, elixir-autogen, elixir-format
msgctxt "tag feed description"
msgid "These are public toots tagged with #%{tag}. You can interact with them if you have an account anywhere in the fediverse."
msgstr ""
"Dit zijn openbare berichten die getagd zijn met #%{tag}. Je kunt op deze "
"reageren indien je een account hebt in de fediverse."
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "oauth authorization exists page title"
msgid "Authorization exists"
msgstr "Machtiging bestaat"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:32
#, elixir-autogen, elixir-format
msgctxt "oauth authorize approve button"
msgid "Approve"
msgstr "Goedkeuren"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:30
#, elixir-autogen, elixir-format
msgctxt "oauth authorize cancel button"
msgid "Cancel"
msgstr "Annuleren"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:23
#, elixir-autogen, elixir-format
msgctxt "oauth authorize message"
msgid "Application <strong>%{client_name}</strong> is requesting access to your account."
msgstr ""
"Applicatie <strong>%{client_name}</strong> vraagt om toegang tot je account."
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "oauth authorized page title"
msgid "Successfully authorized"
msgstr "Machtiging is geslaagd"
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "oauth external provider page title"
msgid "Sign in with external provider"
msgstr "Inloggen bij externe provider"
#: lib/pleroma/web/templates/o_auth/o_auth/consumer.html.eex:13
#, elixir-autogen, elixir-format
msgctxt "oauth external provider sign in button"
msgid "Sign in with %{strategy}"
msgstr "Inloggen met %{strategy}"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:54
#, elixir-autogen, elixir-format
msgctxt "oauth login button"
msgid "Log In"
msgstr "Inloggen"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:51
#, elixir-autogen, elixir-format
msgctxt "oauth login password prompt"
msgid "Password"
msgstr "Wachtwoord"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:47
#, elixir-autogen, elixir-format
msgctxt "oauth login username prompt"
msgid "Username"
msgstr "Gebruikersnaam"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:39
#, elixir-autogen, elixir-format
msgctxt "oauth register nickname prompt"
msgid "Pleroma Handle"
msgstr "Pleroma Gebruiker"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:37
#, elixir-autogen, elixir-format
msgctxt "oauth register nickname unchangeable warning"
msgid "Choose carefully! You won't be able to change this later. You will be able to change your display name, though."
msgstr ""
"Let op! Je kunt je accountnaam hierna niet meer wijzigen. Je kunt echter wel "
"nog je weergavenaam wijzigen."
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:18
#, elixir-autogen, elixir-format
msgctxt "oauth register page email prompt"
msgid "Email"
msgstr "E-mail"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:10
#, elixir-autogen, elixir-format
msgctxt "oauth register page fill form prompt"
msgid "If you'd like to register a new account, please provide the details below."
msgstr ""
"Indien je graag een nieuw account wilt registreren, vul dan a.u.b de "
"onderstaande details in."
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:35
#, elixir-autogen, elixir-format
msgctxt "oauth register page login button"
msgid "Proceed as existing user"
msgstr "Doorgaan als bestaande gebruiker"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:31
#, elixir-autogen, elixir-format
msgctxt "oauth register page login password prompt"
msgid "Password"
msgstr "Wachtwoord"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:24
#, elixir-autogen, elixir-format
msgctxt "oauth register page login prompt"
msgid "Alternatively, sign in to connect to existing account."
msgstr "Alternatief, log in om te verbinden met een bestaand account."
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:27
#, elixir-autogen, elixir-format
msgctxt "oauth register page login username prompt"
msgid "Name or email"
msgstr "Naam of e-mail"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:14
#, elixir-autogen, elixir-format
msgctxt "oauth register page nickname prompt"
msgid "Nickname"
msgstr "Weergavenaam"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:22
#, elixir-autogen, elixir-format
msgctxt "oauth register page register button"
msgid "Proceed as new user"
msgstr "Doorgaan als nieuwe gebruiker"
#: lib/pleroma/web/templates/o_auth/o_auth/register.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "Registration Details"
msgstr "Registratiegegevens"
#: lib/pleroma/web/templates/o_auth/o_auth/show.html.eex:36
#, elixir-autogen, elixir-format
msgctxt "oauth register page title"
msgid "This is the first time you visit! Please enter your Pleroma handle."
msgstr "Dit is je eerste bezoek! Vul a.u.b. je Pleroma gebruikersnaam in."
#: lib/pleroma/web/templates/o_auth/o_auth/_scopes.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "oauth scopes message"
msgid "The following permissions will be granted"
msgstr "De volgende rechten zullen worden toegekend"
#: lib/pleroma/web/templates/o_auth/o_auth/oob_authorization_created.html.eex:2
#: lib/pleroma/web/templates/o_auth/o_auth/oob_token_exists.html.eex:2
#, elixir-autogen, elixir-format
msgctxt "oauth token code message"
msgid "Token code is <br>%{token}"
msgstr "Token code is <br>%{token}"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "mfa auth code prompt"
msgid "Authentication code"
msgstr "Authenticatiecode"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "mfa auth page title"
msgid "Two-factor authentication"
msgstr "Tweefactor authenticatie"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:23
#, elixir-autogen, elixir-format
msgctxt "mfa auth page use recovery code link"
msgid "Enter a two-factor recovery code"
msgstr "Voer een tweefactor herstelcode in"
#: lib/pleroma/web/templates/o_auth/mfa/totp.html.eex:20
#, elixir-autogen, elixir-format
msgctxt "mfa auth verify code button"
msgid "Verify"
msgstr "Controleren"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "mfa recover page title"
msgid "Two-factor recovery"
msgstr "Tweefactor herstel"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:12
#, elixir-autogen, elixir-format
msgctxt "mfa recover recovery code prompt"
msgid "Recovery code"
msgstr "Herstelcode"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:23
#, elixir-autogen, elixir-format
msgctxt "mfa recover use 2fa code link"
msgid "Enter a two-factor code"
msgstr "Voer een tweefactor code in"
#: lib/pleroma/web/templates/o_auth/mfa/recovery.html.eex:20
#, elixir-autogen, elixir-format
msgctxt "mfa recover verify recovery code button"
msgid "Verify"
msgstr "Controleren"
#: lib/pleroma/web/templates/static_fe/static_fe/profile.html.eex:8
#, elixir-autogen, elixir-format
msgctxt "static fe profile page remote follow button"
msgid "Remote follow"
msgstr "Extern volgen"
#: lib/pleroma/web/templates/email/digest.html.eex:163
#, elixir-autogen, elixir-format
msgctxt "digest email header line"
msgid "Hey %{nickname}, here is what you've missed!"
msgstr "Hoi %{nickname}, dit is wat je hebt gemist!"
#: lib/pleroma/web/templates/email/digest.html.eex:544
#, elixir-autogen, elixir-format
msgctxt "digest email receiver address"
msgid "The email address you are subscribed as is <a href='mailto:%{@user.email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
msgstr ""
"Het e-mailadres waarmee je bent ingeschreven is <a href='mailto:%{@user."
"email}' style='color: %{color};text-decoration: none;'>%{email}</a>. "
#: lib/pleroma/web/templates/email/digest.html.eex:538
#, elixir-autogen, elixir-format
msgctxt "digest email sending reason"
msgid "You have received this email because you have signed up to receive digest emails from <b>%{instance}</b> Pleroma instance."
msgstr ""
"Je ontvangt deze e-mail omdat je bent ingeschreven voor overzichts-mails te "
"ontvangen van <b>%{instance}</b> Pleroma instantie."
#: lib/pleroma/web/templates/email/digest.html.eex:547
#, elixir-autogen, elixir-format
msgctxt "digest email unsubscribe action"
msgid "To unsubscribe, please go %{here}."
msgstr "Je kunt je %{here} uitschrijven voor deze e-mails."
#: lib/pleroma/web/templates/email/digest.html.eex:547
#, elixir-autogen, elixir-format
msgctxt "digest email unsubscribe action link text"
msgid "here"
msgstr "hier"
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_failure.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe failed message"
msgid "UNSUBSCRIBE FAILURE"
msgstr "UITSCHRIJVEN MISLUKT"
#: lib/pleroma/web/templates/mailer/subscription/unsubscribe_success.html.eex:1
#, elixir-autogen, elixir-format
msgctxt "mailer unsubscribe successful message"
msgid "UNSUBSCRIBE SUCCESSFUL"
msgstr "UITSCHRIJVEN GESLAAGD"
#: lib/pleroma/web/templates/email/digest.html.eex:385
#, elixir-format
msgctxt "new followers count header"
msgid "%{count} New Follower"
msgid_plural "%{count} New Followers"
msgstr[0] "%{count} Nieuwe Volger"
msgstr[1] "%{count} Nieuwe Volgers"
#: lib/pleroma/emails/user_email.ex:356
#, elixir-autogen, elixir-format
msgctxt "account archive email body - self-requested"
msgid "<p>You requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>Je hebt een verzoek ingediend voor een volledige back-up van je Pleroma "
"account. Deze is gereed om te downloaden:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
#: lib/pleroma/emails/user_email.ex:384
#, elixir-autogen, elixir-format
msgctxt "account archive email subject"
msgid "Your account archive is ready"
msgstr "Je account archief is gereed"
#: lib/pleroma/emails/user_email.ex:188
#, elixir-autogen, elixir-format
msgctxt "approval pending email body"
msgid "<h3>Awaiting Approval</h3>\n<p>Your account at %{instance_name} is being reviewed by staff. You will receive another email once your account is approved.</p>\n"
msgstr ""
"<h3>Goedkeuring in afwachting</h3>\n"
"<p>Je account bij %{instance_name} zal worden beoordeeld door de beheerders. "
"Je zult een opvolgende e-mail ontvangen wanneer je account goed gekeurd "
"is.</p>\n"
#: lib/pleroma/emails/user_email.ex:202
#, elixir-autogen, elixir-format
msgctxt "approval pending email subject"
msgid "Your account is awaiting approval"
msgstr "Je account is in afwachting van goedkeuring"
#: lib/pleroma/emails/user_email.ex:158
#, elixir-autogen, elixir-format
msgctxt "confirmation email body"
msgid "<h3>Thank you for registering on %{instance_name}</h3>\n<p>Email confirmation is required to activate the account.</p>\n<p>Please click the following link to <a href=\"%{confirmation_url}\">activate your account</a>.</p>\n"
msgstr ""
"<h3>Bedankt voor het registreren bij %{instance_name}</h3>\n"
"<p>Bevestiging via e-mail is vereist om je account te activeren.</p>\n"
"<p>Je kunt je account activeren door op <a href=\"%{confirmation_url}\">deze "
"link te klikken</a>.</p>\n"
#: lib/pleroma/emails/user_email.ex:174
#, elixir-autogen, elixir-format
msgctxt "confirmation email subject"
msgid "%{instance_name} account confirmation"
msgstr "%{instance_name} account bevestiging"
#: lib/pleroma/emails/user_email.ex:310
#, elixir-autogen, elixir-format
msgctxt "digest email subject"
msgid "Your digest from %{instance_name}"
msgstr "Je overzicht van %{instance_name}"
#: lib/pleroma/emails/user_email.ex:81
#, elixir-autogen, elixir-format
msgctxt "password reset email body"
msgid "<h3>Reset your password at %{instance_name}</h3>\n<p>Someone has requested password change for your account at %{instance_name}.</p>\n<p>If it was you, visit the following link to proceed: <a href=\"%{password_reset_url}\">reset password</a>.</p>\n<p>If it was someone else, nothing to worry about: your data is secure and your password has not been changed.</p>\n"
msgstr ""
"<h3>Herstel je wachtwoord bij %{instance_name}</h3>\n"
"<p>Iemand heeft een verzoek ingediend om het wachtwoord van je account bij "
"%{instance_name} te herstellen.</p>\n"
"<p>Als je dit zelf geweest bent, volg dan de volgende link om door te gaan: "
"<a href=\"%{password_reset_url}\">wachtwoord herstellen</a>.</p>\n"
"<p>Indien je dit niet geweest bent, hoef je geen verdere acties te "
"ondernemen: je gegevens zijn veilig en je wachtwoord is niet gewijzigd.</p>\n"
#: lib/pleroma/emails/user_email.ex:98
#, elixir-autogen, elixir-format
msgctxt "password reset email subject"
msgid "Password reset"
msgstr "Wachtwoord herstellen"
#: lib/pleroma/emails/user_email.ex:215
#, elixir-autogen, elixir-format
msgctxt "successful registration email body"
msgid "<h3>Hello @%{nickname},</h3>\n<p>Your account at %{instance_name} has been registered successfully.</p>\n<p>No further action is required to activate your account.</p>\n"
msgstr ""
"<h3>Hoi @%{nickname},</h3>\n"
"<p>Het registreren van je account bij %{instance_name} is gelukt.</p>\n"
"<p>Er zijn geen verdere stappen vereist om je account te activeren.</p>\n"
#: lib/pleroma/emails/user_email.ex:231
#, elixir-autogen, elixir-format
msgctxt "successful registration email subject"
msgid "Account registered on %{instance_name}"
msgstr "Account registratie bij %{instance_name}"
#: lib/pleroma/emails/user_email.ex:119
#, elixir-autogen, elixir-format
msgctxt "user invitation email body"
msgid "<h3>You are invited to %{instance_name}</h3>\n<p>%{inviter_name} invites you to join %{instance_name}, an instance of Pleroma federated social networking platform.</p>\n<p>Click the following link to register: <a href=\"%{registration_url}\">accept invitation</a>.</p>\n"
msgstr ""
"<h3>Je bent uitgenodigd bij %{instance_name}</h3>\n"
"<p>%{inviter_name} nodigt je uit om je te registreren bij %{instance_name}, "
"een instantie van het Pleroma gefedereerde sociale netwerk.</p>\n"
"<p>Om je te registreren, klink op de volgende link: <a href=\""
"%{registration_url}\">uitnodiging accepteren</a>.</p>\n"
#: lib/pleroma/emails/user_email.ex:136
#, elixir-autogen, elixir-format
msgctxt "user invitation email subject"
msgid "Invitation to %{instance_name}"
msgstr "Uitnodiging van %{instance_name}"
#: lib/pleroma/emails/user_email.ex:53
#, elixir-autogen, elixir-format
msgctxt "welcome email html body"
msgid "Welcome to %{instance_name}!"
msgstr "Welkom bij %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:41
#, elixir-autogen, elixir-format
msgctxt "welcome email subject"
msgid "Welcome to %{instance_name}!"
msgstr "Welkom bij %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:65
#, elixir-autogen, elixir-format
msgctxt "welcome email text body"
msgid "Welcome to %{instance_name}!"
msgstr "Welkom bij %{instance_name}!"
#: lib/pleroma/emails/user_email.ex:368
#, elixir-autogen, elixir-format
msgctxt "account archive email body - admin requested"
msgid "<p>Admin @%{admin_nickname} requested a full backup of your Pleroma account. It's ready for download:</p>\n<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"
msgstr ""
"<p>Beheerder @%{admin_nickname} heeft een verzoek ingediend voor een "
"volledige back-up van je Pleroma account. Deze is gereed om te "
"downloaden:</p>\n"
"<p><a href=\"%{download_url}\">%{download_url}</a></p>\n"

View file

@ -56,8 +56,36 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:u, []) Meta.allow_tag_with_these_attributes(:u, [])
Meta.allow_tag_with_these_attributes(:ul, []) Meta.allow_tag_with_these_attributes(:ul, [])
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "quote-inline"]) Meta.allow_tags_with_style_attributes([:span])
Meta.allow_tag_with_these_attributes(:span, [])
Meta.allow_tag_with_this_attribute_values(:span, "class", [
"h-card",
"quote-inline",
"mfm",
"mfm _mfm_tada_",
"mfm _mfm_jelly_",
"mfm _mfm_twitch_",
"mfm _mfm_shake_",
"mfm _mfm_spin_",
"mfm _mfm_jump_",
"mfm _mfm_bounce_",
"mfm _mfm_flip_",
"mfm _mfm_x2_",
"mfm _mfm_x3_",
"mfm _mfm_x4_",
"mfm _mfm_blur_",
"mfm _mfm_rainbow_",
"mfm _mfm_rotate_"
])
Meta.allow_tag_with_these_attributes(:span, [
"data-x",
"data-y",
"data-h",
"data-v",
"data-left",
"data-right"
])
Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"]) Meta.allow_tag_with_this_attribute_values(:code, "class", ["inline"])
@ -101,4 +129,6 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:small, []) Meta.allow_tag_with_these_attributes(:small, [])
Meta.strip_everything_not_covered() Meta.strip_everything_not_covered()
defp scrub_css(value), do: value
end end

View file

@ -2,7 +2,6 @@
# XXX: This should be removed when elixir's releases get custom command support # XXX: This should be removed when elixir's releases get custom command support
detect_flavour() { detect_flavour() {
echo "Trying to autodetect flavour, you may want to override this with --flavour"
arch="$(uname -m)" arch="$(uname -m)"
if [ "$arch" = "x86_64" ]; then if [ "$arch" = "x86_64" ]; then
arch="amd64" arch="amd64"

View file

@ -1,27 +1,27 @@
[Unit] [Unit]
Description=Pleroma social network Description=Akkoma social network
After=network.target postgresql.service nginx.service After=network.target postgresql.service nginx.service
[Service] [Service]
KillMode=process KillMode=process
Restart=on-failure Restart=on-failure
; Name of the user that runs the Pleroma service. ; Name of the user that runs the Akkoma service.
User=pleroma User=akkoma
; Make sure that all paths fit your installation. ; Make sure that all paths fit your installation.
; Path to the home directory of the user running the Pleroma service. ; Path to the home directory of the user running the Akkoma service.
Environment="HOME=/opt/pleroma" Environment="HOME=/opt/akkoma"
; Path to the folder containing the Pleroma installation. ; Path to the folder containing the Akkoma installation.
WorkingDirectory=/opt/pleroma WorkingDirectory=/opt/akkoma
; Path to the Pleroma binary. ; Path to the Mix binary.
ExecStart=/opt/pleroma/bin/pleroma start ExecStart=/opt/akkoma/bin/pleroma start
ExecStop=/opt/pleroma/bin/pleroma stop ExecStop=/opt/akkoma/bin/pleroma stop
; Some security directives. ; Some security directives.
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops. ; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
PrivateTmp=true PrivateTmp=true
; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Pleroma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false. ; The /home, /root, and /run/user folders can not be accessed by this service anymore. If your Akkoma user has its home folder in one of the restricted places, or use one of these folders as its working directory, you have to set this to false.
ProtectHome=true ProtectHome=true
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service. ; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
ProtectSystem=full ProtectSystem=full

View file

@ -3,17 +3,17 @@
supervisor=supervise-daemon supervisor=supervise-daemon
# Requires OpenRC >= 0.35 # Requires OpenRC >= 0.35
directory=/opt/pleroma directory=/opt/akkoma
command=/opt/pleroma/bin/pleroma command=/opt/akkoma/bin/pleroma
command_args="start" command_args="start"
command_user=pleroma command_user=akkoma
command_background=1 command_background=1
# Ask process to terminate within 30 seconds, otherwise kill it # Ask process to terminate within 30 seconds, otherwise kill it
retry="SIGTERM/30/SIGKILL/5" retry="SIGTERM/30/SIGKILL/5"
pidfile="/var/run/pleroma.pid" pidfile="/var/run/akkoma.pid"
depend() { depend() {
want nginx want nginx

View file

@ -1 +1,5 @@
external_emoji, https://example.com/emoji.png external_emoji, https://example.com/emoji.png
firefox, /emoji/Firefox.gif, Gif,Fun
blank, /emoji/blank.png, Fun
dinosaur, /emoji/dino walking.gif, Gif
100a, /emoji/100a.png, Fun

View file

@ -3,7 +3,7 @@
"type": "Note", "type": "Note",
"attributedTo": "https://misskey.local.live/users/92hzkskwgy", "attributedTo": "https://misskey.local.live/users/92hzkskwgy",
"summary": null, "summary": null,
"content": "this gets replaced", "content": "this does not get replaced",
"source": { "source": {
"content": "@akkoma_user @remote_user @full_tag_remote_user@misskey.local.live @oops_not_a_mention linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa", "content": "@akkoma_user @remote_user @full_tag_remote_user@misskey.local.live @oops_not_a_mention linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa",
"mediaType": "text/x.misskeymarkdown" "mediaType": "text/x.misskeymarkdown"

View file

@ -30,7 +30,7 @@ test "it should extract items from an embedded array in a Collection" do
} }
end) end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id) {:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
end end
@ -53,7 +53,7 @@ test "it should extract items from an embedded array in an OrderedCollection" do
} }
end) end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id) {:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
end end
@ -106,7 +106,7 @@ test "it should extract items from an referenced first page in a Collection" do
} }
end) end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id) {:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
end end
@ -161,7 +161,58 @@ test "it should stop fetching when we hit :max_collection_objects" do
} }
end) end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id) {:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}] = objects
end
test "it should stop fetching when we hit a 404" do
clear_config([:activitypub, :max_collection_objects], 1)
unordered_collection =
"test/fixtures/collections/unordered_page_reference.json"
|> File.read!()
first_page =
"test/fixtures/collections/unordered_page_first.json"
|> File.read!()
ap_id = "https://example.com/collection/unordered_page_reference"
first_page_id = "https://example.com/collection/unordered_page_reference?page=1"
second_page_id = "https://example.com/collection/unordered_page_reference?page=2"
Tesla.Mock.mock(fn
%{
method: :get,
url: ^ap_id
} ->
%Tesla.Env{
status: 200,
body: unordered_collection,
headers: [{"content-type", "application/activity+json"}]
}
%{
method: :get,
url: ^first_page_id
} ->
%Tesla.Env{
status: 200,
body: first_page,
headers: [{"content-type", "application/activity+json"}]
}
%{
method: :get,
url: ^second_page_id
} ->
%Tesla.Env{
status: 404,
body: nil,
headers: [{"content-type", "application/activity+json"}]
}
end)
{:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}] = objects assert [%{"type" => "Create"}] = objects
end end
end end

View file

@ -1,77 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.GunTest do
use ExUnit.Case
use Pleroma.Tests.Helpers
import Mox
alias Pleroma.HTTP.AdapterHelper.Gun
setup :verify_on_exit!
describe "options/1" do
setup do: clear_config([:http, :adapter], a: 1, b: 2)
test "https url with default port" do
uri = URI.parse("https://example.com")
opts = Gun.options([receive_conn: false], uri)
assert opts[:certificates_verification]
end
test "https ipv4 with default port" do
uri = URI.parse("https://127.0.0.1")
opts = Gun.options([receive_conn: false], uri)
assert opts[:certificates_verification]
end
test "https ipv6 with default port" do
uri = URI.parse("https://[2a03:2880:f10c:83:face:b00c:0:25de]")
opts = Gun.options([receive_conn: false], uri)
assert opts[:certificates_verification]
end
test "https url with non standart port" do
uri = URI.parse("https://example.com:115")
opts = Gun.options([receive_conn: false], uri)
assert opts[:certificates_verification]
end
test "merges with defaul http adapter config" do
defaults = Gun.options([receive_conn: false], URI.parse("https://example.com"))
assert Keyword.has_key?(defaults, :a)
assert Keyword.has_key?(defaults, :b)
end
test "parses string proxy host & port" do
clear_config([:http, :proxy_url], "localhost:8123")
uri = URI.parse("https://some-domain.com")
opts = Gun.options([receive_conn: false], uri)
assert opts[:proxy] == {'localhost', 8123}
end
test "parses tuple proxy scheme host and port" do
clear_config([:http, :proxy_url], {:socks, 'localhost', 1234})
uri = URI.parse("https://some-domain.com")
opts = Gun.options([receive_conn: false], uri)
assert opts[:proxy] == {:socks, 'localhost', 1234}
end
test "passed opts have more weight than defaults" do
clear_config([:http, :proxy_url], {:socks5, 'localhost', 1234})
uri = URI.parse("https://some-domain.com")
opts = Gun.options([receive_conn: false, proxy: {'example.com', 4321}], uri)
assert opts[:proxy] == {'example.com', 4321}
end
end
end

View file

@ -1,35 +0,0 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2021 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.HTTP.AdapterHelper.HackneyTest do
use ExUnit.Case, async: true
use Pleroma.Tests.Helpers
alias Pleroma.HTTP.AdapterHelper.Hackney
setup_all do
uri = URI.parse("http://domain.com")
{:ok, uri: uri}
end
describe "options/2" do
setup do: clear_config([:http, :adapter], a: 1, b: 2)
test "add proxy and opts from config", %{uri: uri} do
opts = Hackney.options([proxy: "localhost:8123"], uri)
assert opts[:a] == 1
assert opts[:b] == 2
assert opts[:proxy] == "localhost:8123"
end
test "respect connection opts and no proxy", %{uri: uri} do
opts = Hackney.options([a: 2, b: 1], uri)
assert opts[:a] == 2
assert opts[:b] == 1
refute Keyword.has_key?(opts, :proxy)
end
end
end

View file

@ -13,16 +13,38 @@ test "with nil" do
end end
test "with string" do test "with string" do
assert AdapterHelper.format_proxy("127.0.0.1:8123") == {{127, 0, 0, 1}, 8123} assert AdapterHelper.format_proxy("http://127.0.0.1:8123") == {:http, "127.0.0.1", 8123, []}
end end
test "localhost with port" do test "localhost with port" do
assert AdapterHelper.format_proxy("localhost:8123") == {'localhost', 8123} assert AdapterHelper.format_proxy("https://localhost:8123") ==
{:https, "localhost", 8123, []}
end end
test "tuple" do test "tuple" do
assert AdapterHelper.format_proxy({:socks4, :localhost, 9050}) == assert AdapterHelper.format_proxy({:http, "localhost", 9050}) ==
{:socks4, 'localhost', 9050} {:http, "localhost", 9050, []}
end
end
describe "maybe_add_proxy_pool/1" do
test "should do nothing with nil" do
assert AdapterHelper.maybe_add_proxy_pool([], nil) == []
end
test "should create pools" do
assert AdapterHelper.maybe_add_proxy_pool([], "proxy") == [
pools: %{default: [conn_opts: [proxy: "proxy"]]}
]
end
test "should not override conn_opts if set" do
assert AdapterHelper.maybe_add_proxy_pool(
[pools: %{default: [conn_opts: [already: "set"]]}],
"proxy"
) == [
pools: %{default: [conn_opts: [proxy: "proxy", already: "set"]]}
]
end end
end end
end end

View file

@ -31,18 +31,23 @@ def start_socket(qs \\ nil, headers \\ []) do
WebsocketClient.start_link(self(), path, headers) WebsocketClient.start_link(self(), path, headers)
end end
test "refuses invalid requests" do test "allows multi-streams" do
capture_log(fn -> capture_log(fn ->
assert {:error, {404, _}} = start_socket() assert {:ok, _} = start_socket()
assert {:error, {404, _}} = start_socket("?stream=ncjdk")
assert {:error, %WebSockex.RequestError{code: 404, message: "Not Found"}} =
start_socket("?stream=ncjdk")
Process.sleep(30) Process.sleep(30)
end) end)
end end
test "requires authentication and a valid token for protected streams" do test "requires authentication and a valid token for protected streams" do
capture_log(fn -> capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa") assert {:error, %WebSockex.RequestError{code: 401}} =
assert {:error, {401, _}} = start_socket("?stream=user") start_socket("?stream=user&access_token=aaaaaaaaaaaa")
assert {:error, %WebSockex.RequestError{code: 401}} = start_socket("?stream=user")
Process.sleep(30) Process.sleep(30)
end) end)
end end
@ -91,7 +96,7 @@ test "receives well formatted events" do
{:ok, token} = OAuth.Token.exchange_token(app, auth) {:ok, token} = OAuth.Token.exchange_token(app, auth)
%{user: user, token: token} %{app: app, user: user, token: token}
end end
test "accepts valid tokens", state do test "accepts valid tokens", state do
@ -102,7 +107,7 @@ test "accepts the 'user' stream", %{token: token} = _state do
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}") assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
capture_log(fn -> capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user") assert {:error, %WebSockex.RequestError{code: 401}} = start_socket("?stream=user")
Process.sleep(30) Process.sleep(30)
end) end)
end end
@ -111,7 +116,9 @@ test "accepts the 'user:notification' stream", %{token: token} = _state do
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}") assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
capture_log(fn -> capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user:notification") assert {:error, %WebSockex.RequestError{code: 401}} =
start_socket("?stream=user:notification")
Process.sleep(30) Process.sleep(30)
end) end)
end end
@ -120,11 +127,27 @@ test "accepts valid token on Sec-WebSocket-Protocol header", %{token: token} do
assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}]) assert {:ok, _} = start_socket("?stream=user", [{"Sec-WebSocket-Protocol", token.token}])
capture_log(fn -> capture_log(fn ->
assert {:error, {401, _}} = assert {:error, %WebSockex.RequestError{code: 401}} =
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}]) start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
Process.sleep(30) Process.sleep(30)
end) end)
end end
test "disconnect when token is revoked", %{app: app, user: user, token: token} do
assert {:ok, _} = start_socket("?stream=user:notification&access_token=#{token.token}")
assert {:ok, _} = start_socket("?stream=user&access_token=#{token.token}")
{:ok, auth} = OAuth.Authorization.create_authorization(app, user)
{:ok, token2} = OAuth.Token.exchange_token(app, auth)
assert {:ok, _} = start_socket("?stream=user&access_token=#{token2.token}")
OAuth.Token.Strategy.Revoke.revoke(token)
assert_receive {:close, _}
assert_receive {:close, _}
refute_receive {:close, _}
end
end end
end end

View file

@ -224,7 +224,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
task = task =
Task.async(fn -> Task.async(fn ->
{:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token) {:ok, _topic} = Streamer.get_topic_and_add_socket("user", user, oauth_token)
assert_receive {:render_with_user, _, _, _}, 4_000 assert_receive {:render_with_user, _, _, _, "user"}, 4_000
end) end)
task_user_notification = task_user_notification =
@ -232,7 +232,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
{:ok, _topic} = {:ok, _topic} =
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
assert_receive {:render_with_user, _, _, _}, 4_000 assert_receive {:render_with_user, _, _, _, "user:notification"}, 4_000
end) end)
activity = insert(:note_activity) activity = insert(:note_activity)

View file

@ -620,13 +620,14 @@ test "it blocks blacklisted email domains" do
assert changeset.valid? assert changeset.valid?
end end
test "it sets the password_hash and ap_id" do test "it sets the password_hash, ap_id and PEM key" do
changeset = User.register_changeset(%User{}, @full_user_data) changeset = User.register_changeset(%User{}, @full_user_data)
assert changeset.valid? assert changeset.valid?
assert is_binary(changeset.changes[:password_hash]) assert is_binary(changeset.changes[:password_hash])
assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname}) assert changeset.changes[:ap_id] == User.ap_id(%User{nickname: @full_user_data.nickname})
assert is_binary(changeset.changes[:keys])
assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers" assert changeset.changes.follower_address == "#{changeset.changes.ap_id}/followers"
end end

View file

@ -782,6 +782,7 @@ test "mastodon pin/unpin", %{conn: conn} do
|> String.replace("{{status_id}}", status_id) |> String.replace("{{status_id}}", status_id)
status_url = "https://example.com/users/lain/statuses/#{status_id}" status_url = "https://example.com/users/lain/statuses/#{status_id}"
replies_url = status_url <> "/replies?only_other_accounts=true&page=true"
user = user =
File.read!("test/fixtures/users_mock/user.json") File.read!("test/fixtures/users_mock/user.json")
@ -820,6 +821,16 @@ test "mastodon pin/unpin", %{conn: conn} do
|> String.replace("{{nickname}}", "lain"), |> String.replace("{{nickname}}", "lain"),
headers: [{"content-type", "application/activity+json"}] headers: [{"content-type", "application/activity+json"}]
} }
%{
method: :get,
url: ^replies_url
} ->
%Tesla.Env{
status: 404,
body: "",
headers: [{"content-type", "application/activity+json"}]
}
end) end)
data = %{ data = %{

View file

@ -216,6 +216,43 @@ test "has a matching host but only as:Public in to" do
end end
end end
describe "describe/1" do
test "returns a description of the policy" do
clear_config([:mrf_simple, :reject], [
{"remote.instance", "did not give my catboy a burg"}
])
assert {:ok, %{mrf_simple: %{reject: ["remote.instance"]}}} = SimplePolicy.describe()
end
test "excludes domains listed in :transparency_exclusions" do
clear_config([:mrf, :transparency_exclusions], [{"remote.instance", ":("}])
clear_config([:mrf_simple, :reject], [
{"remote.instance", "did not give my catboy a burg"}
])
{:ok, description} = SimplePolicy.describe()
assert %{mrf_simple: %{reject: []}} = description
assert description[:mrf_simple_info][:reject] == nil
end
test "obfuscates domains listed in :transparency_obfuscate_domains" do
clear_config([:mrf, :transparency_obfuscate_domains], ["remote.instance", "a.b"])
clear_config([:mrf_simple, :reject], [
{"remote.instance", "did not give my catboy a burg"},
{"a.b", "spam-poked me on facebook in 2006"}
])
assert {:ok,
%{
mrf_simple: %{reject: ["rem***.*****nce", "a.b"]},
mrf_simple_info: %{reject: %{"rem***.*****nce" => %{}}}
}} = SimplePolicy.describe()
end
end
defp build_ftl_actor_and_message do defp build_ftl_actor_and_message do
actor = insert(:user) actor = insert(:user)

View file

@ -98,8 +98,6 @@ test "a misskey MFM status with a content field should work and be linked", _ do
changes: %{ changes: %{
content: content, content: content,
source: %{ source: %{
"content" =>
"@akkoma_user @remote_user @full_tag_remote_user@misskey.local.live @oops_not_a_mention linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa",
"mediaType" => "text/x.misskeymarkdown" "mediaType" => "text/x.misskeymarkdown"
} }
} }
@ -115,7 +113,9 @@ test "a misskey MFM status with a content field should work and be linked", _ do
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{full_tag_remote_user.id}\" href=\"#{full_tag_remote_user.ap_id}\" rel=\"ugc\">@<span>full_tag_remote_user</span></a></span>" "<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{full_tag_remote_user.id}\" href=\"#{full_tag_remote_user.ap_id}\" rel=\"ugc\">@<span>full_tag_remote_user</span></a></span>"
assert content =~ "@oops_not_a_mention" assert content =~ "@oops_not_a_mention"
assert content =~ "$[jelly mfm goes here] <br><br>## aaa"
assert content =~
"<span class=\"mfm _mfm_jelly_\" style=\"display: inline-block; animation: 1s linear 0s infinite normal both running mfm-rubberBand;\">mfm goes here</span> </p>aaa"
end end
test "a misskey MFM status with a _misskey_content field should work and be linked", _ do test "a misskey MFM status with a _misskey_content field should work and be linked", _ do
@ -129,22 +129,34 @@ test "a misskey MFM status with a _misskey_content field should work and be link
|> File.read!() |> File.read!()
|> Jason.decode!() |> Jason.decode!()
expected_content =
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{local_user.id}\" href=\"#{local_user.ap_id}\" rel=\"ugc\">@<span>akkoma_user</span></a></span> linkifylink <a class=\"hashtag\" data-tag=\"dancedance\" href=\"http://localhost:4001/tag/dancedance\">#dancedance</a> $[jelly mfm goes here] <br><br>## aaa"
changes = ArticleNotePageValidator.cast_and_validate(note) changes = ArticleNotePageValidator.cast_and_validate(note)
%{ %{
valid?: true, valid?: true,
changes: %{ changes: %{
content: content,
source: %{ source: %{
"content" => "@akkoma_user linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa", "mediaType" => "text/x.misskeymarkdown",
"mediaType" => "text/x.misskeymarkdown" "content" => "@akkoma_user linkifylink #dancedance $[jelly mfm goes here] \n\n## aaa"
} }
} }
} = changes } = changes
assert changes.changes[:content] == expected_content assert content =~
"<span class=\"h-card\"><a class=\"u-url mention\" data-user=\"#{local_user.id}\" href=\"#{local_user.ap_id}\" rel=\"ugc\">@<span>akkoma_user</span></a></span>"
end end
end end
test "a Note without replies/first/items validates" do
insert(:user, ap_id: "https://mastodon.social/users/emelie")
note =
"test/fixtures/tesla_mock/status.emelie.json"
|> File.read!()
|> Jason.decode!()
|> pop_in(["replies", "first", "items"])
|> elem(1)
%{valid?: true} = ArticleNotePageValidator.cast_and_validate(note)
end
end end

View file

@ -380,7 +380,6 @@ test "schedules background fetching of `replies` items if max thread depth limit
clear_config([:instance, :federation_incoming_replies_max_depth], 10) clear_config([:instance, :federation_incoming_replies_max_depth], 10)
{:ok, activity} = Transmogrifier.handle_incoming(data) {:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"]) object = Object.normalize(activity.data["object"])
assert object.data["replies"] == items assert object.data["replies"] == items

View file

@ -153,7 +153,7 @@ test "rejects incoming AP docs with incorrect origin" do
} }
assert {:ok, job} = Federator.incoming_ap_doc(params) assert {:ok, job} = Federator.incoming_ap_doc(params)
assert {:error, :origin_containment_failed} = ObanHelpers.perform(job) assert {:discard, :origin_containment_failed} = ObanHelpers.perform(job)
end end
test "it does not crash if MRF rejects the post" do test "it does not crash if MRF rejects the post" do
@ -169,7 +169,7 @@ test "it does not crash if MRF rejects the post" do
|> Jason.decode!() |> Jason.decode!()
assert {:ok, job} = Federator.incoming_ap_doc(params) assert {:ok, job} = Federator.incoming_ap_doc(params)
assert {:error, _} = ObanHelpers.perform(job) assert {:discard, _} = ObanHelpers.perform(job)
end end
end end
end end

View file

@ -0,0 +1,38 @@
defmodule Pleroma.Web.MastoFEControllerTest do
use Pleroma.Web.ConnCase, async: true
alias Pleroma.Web.MastodonAPI.AuthController
describe "index/2 (main page)" do
test "GET /web/ (glitch-soc)" do
clear_config([:frontends, :mastodon], %{"name" => "mastodon-fe"})
{:ok, masto_app} = AuthController.local_mastofe_app()
user = Pleroma.Factory.insert(:user)
token = Pleroma.Factory.insert(:oauth_token, app: masto_app, user: user)
%{conn: conn} = oauth_access(["read", "write"], oauth_token: token, user: user)
resp =
conn
|> get("/web/getting-started")
|> html_response(200)
assert resp =~ "glitch"
end
test "GET /web/ (fedibird)" do
clear_config([:frontends, :mastodon], %{"name" => "fedibird-fe"})
{:ok, masto_app} = AuthController.local_mastofe_app()
user = Pleroma.Factory.insert(:user)
token = Pleroma.Factory.insert(:oauth_token, app: masto_app, user: user)
%{conn: conn} = oauth_access(["read", "write"], oauth_token: token, user: user)
resp =
conn
|> get("/web/getting-started")
|> html_response(200)
refute resp =~ "glitch"
end
end
end

View file

@ -10,10 +10,11 @@ defmodule Pleroma.Web.MastodonAPI.InstanceControllerTest do
import Pleroma.Factory import Pleroma.Factory
test "get instance information", %{conn: conn} do test "get instance information", %{conn: conn} do
clear_config([:instance, :languages], ["en", "ja"])
conn = get(conn, "/api/v1/instance") conn = get(conn, "/api/v1/instance")
assert result = json_response_and_validate_schema(conn, 200) assert result = json_response_and_validate_schema(conn, 200)
email = Pleroma.Config.get([:instance, :email]) email = Pleroma.Config.get([:instance, :email])
thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail]) thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image]) background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image])
@ -29,7 +30,7 @@ test "get instance information", %{conn: conn} do
}, },
"stats" => _, "stats" => _,
"thumbnail" => from_config_thumbnail, "thumbnail" => from_config_thumbnail,
"languages" => _, "languages" => ["en", "ja"],
"registrations" => _, "registrations" => _,
"approval_required" => _, "approval_required" => _,
"poll_limits" => _, "poll_limits" => _,

View file

@ -264,6 +264,7 @@ test "posting a fake status", %{conn: conn} do
|> Map.put("url", nil) |> Map.put("url", nil)
|> Map.put("uri", nil) |> Map.put("uri", nil)
|> Map.put("created_at", nil) |> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "context"], nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil) |> Kernel.put_in(["pleroma", "conversation_id"], nil)
fake_conn = fake_conn =
@ -287,6 +288,7 @@ test "posting a fake status", %{conn: conn} do
|> Map.put("url", nil) |> Map.put("url", nil)
|> Map.put("uri", nil) |> Map.put("uri", nil)
|> Map.put("created_at", nil) |> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "context"], nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil) |> Kernel.put_in(["pleroma", "conversation_id"], nil)
assert real_status == fake_status assert real_status == fake_status

View file

@ -44,14 +44,15 @@ test "has an emoji reaction list" do
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 2, me: false, url: nil}, %{name: "", count: 2, me: false, url: nil, account_ids: [other_user.id, user.id]},
%{ %{
count: 2, count: 2,
me: false, me: false,
name: "dinosaur", name: "dinosaur",
url: "http://localhost:4001/emoji/dino walking.gif" url: "http://localhost:4001/emoji/dino walking.gif",
account_ids: [other_user.id, user.id]
}, },
%{name: "🍵", count: 1, me: false, url: nil} %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
] ]
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
@ -59,14 +60,15 @@ test "has an emoji reaction list" do
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec()) assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 2, me: true, url: nil}, %{name: "", count: 2, me: true, url: nil, account_ids: [other_user.id, user.id]},
%{ %{
count: 2, count: 2,
me: true, me: true,
name: "dinosaur", name: "dinosaur",
url: "http://localhost:4001/emoji/dino walking.gif" url: "http://localhost:4001/emoji/dino walking.gif",
account_ids: [other_user.id, user.id]
}, },
%{name: "🍵", count: 1, me: false, url: nil} %{name: "🍵", count: 1, me: false, url: nil, account_ids: [third_user.id]}
] ]
end end
@ -82,7 +84,7 @@ test "works correctly with badly formatted emojis" do
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: true, url: nil} %{name: "", count: 1, me: true, url: nil, account_ids: [user.id]}
] ]
end end
@ -102,7 +104,7 @@ test "doesn't show reactions from muted and blocked users" do
status = StatusView.render("show.json", activity: activity) status = StatusView.render("show.json", activity: activity)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: false, url: nil} %{name: "", count: 1, me: false, url: nil, account_ids: [other_user.id]}
] ]
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
@ -114,19 +116,25 @@ test "doesn't show reactions from muted and blocked users" do
status = StatusView.render("show.json", activity: activity) status = StatusView.render("show.json", activity: activity)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 2, me: false, url: nil} %{
name: "",
count: 2,
me: false,
url: nil,
account_ids: [third_user.id, other_user.id]
}
] ]
status = StatusView.render("show.json", activity: activity, for: user) status = StatusView.render("show.json", activity: activity, for: user)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: false, url: nil} %{name: "", count: 1, me: false, url: nil, account_ids: [third_user.id]}
] ]
status = StatusView.render("show.json", activity: activity, for: other_user) status = StatusView.render("show.json", activity: activity, for: other_user)
assert status[:pleroma][:emoji_reactions] == [ assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: true, url: nil} %{name: "", count: 1, me: true, url: nil, account_ids: [other_user.id]}
] ]
end end
@ -239,7 +247,7 @@ test "a note activity" do
object_data = Object.normalize(note, fetch: false).data object_data = Object.normalize(note, fetch: false).data
user = User.get_cached_by_ap_id(note.data["actor"]) user = User.get_cached_by_ap_id(note.data["actor"])
convo_id = :erlang.crc32(object_data["context"]) convo_id = :erlang.crc32(object_data["context"]) |> Bitwise.band(Bitwise.bnot(0x8000_0000))
status = StatusView.render("show.json", %{activity: note}) status = StatusView.render("show.json", %{activity: note})
@ -272,6 +280,7 @@ test "a note activity" do
spoiler_text: HTML.filter_tags(object_data["summary"]), spoiler_text: HTML.filter_tags(object_data["summary"]),
visibility: "public", visibility: "public",
media_attachments: [], media_attachments: [],
emoji_reactions: [],
mentions: [], mentions: [],
tags: [ tags: [
%{ %{
@ -292,6 +301,7 @@ test "a note activity" do
pleroma: %{ pleroma: %{
local: true, local: true,
conversation_id: convo_id, conversation_id: convo_id,
context: object_data["context"],
in_reply_to_account_acct: nil, in_reply_to_account_acct: nil,
content: %{"text/plain" => HTML.strip_tags(object_data["content"])}, content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])}, spoiler_text: %{"text/plain" => HTML.strip_tags(object_data["summary"])},
@ -418,6 +428,34 @@ test "a quote that we can't resolve" do
assert is_nil(status.quote) assert is_nil(status.quote)
end end
test "a quote from a user we block" do
user = insert(:user)
other_user = insert(:user)
blocked_user = insert(:user)
{:ok, _relationship} = User.block(user, blocked_user)
{:ok, activity} = CommonAPI.post(blocked_user, %{status: ":< i am ANGERY"})
{:ok, quote_activity} = CommonAPI.post(other_user, %{status: "hehe", quote_id: activity.id})
status = StatusView.render("show.json", %{activity: quote_activity, for: user})
assert is_nil(status.quote)
end
test "a quote from a user we mute" do
user = insert(:user)
other_user = insert(:user)
blocked_user = insert(:user)
{:ok, _relationship} = User.mute(user, blocked_user)
{:ok, activity} = CommonAPI.post(blocked_user, %{status: ":< i am ANGERY"})
{:ok, quote_activity} = CommonAPI.post(other_user, %{status: "hehe", quote_id: activity.id})
status = StatusView.render("show.json", %{activity: quote_activity, for: user})
assert is_nil(status.quote)
end
test "contains mentions" do test "contains mentions" do
user = insert(:user) user = insert(:user)
mentioned = insert(:user) mentioned = insert(:user)

View file

@ -494,6 +494,129 @@ test "renders authentication page if user is already authenticated but user requ
assert html_response(conn, 200) =~ ~s(type="submit") assert html_response(conn, 200) =~ ~s(type="submit")
end end
test "allows access if the user has a prior authorization but is authenticated with another client",
%{
app: app,
conn: conn
} do
user = insert(:user)
token = insert(:oauth_token, app: app, user: user)
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
authorization = insert(:oauth_authorization, user: user, app: other_app)
_reusable_token = insert(:oauth_token, app: other_app, user: user)
conn =
conn
|> AuthHelper.put_session_token(token.token)
|> AuthHelper.put_session_user(user.id)
|> get(
"/oauth/authorize",
%{
"response_type" => "code",
"client_id" => other_app.client_id,
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
"scope" => "read"
}
)
assert URI.decode(redirected_to(conn)) ==
"https://other_redirect.url?code=#{authorization.token}"
end
test "renders login page if the user has an authorization but no token",
%{
app: app,
conn: conn
} do
user = insert(:user)
token = insert(:oauth_token, app: app, user: user)
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
_authorization = insert(:oauth_authorization, user: user, app: other_app)
conn =
conn
|> AuthHelper.put_session_token(token.token)
|> AuthHelper.put_session_user(user.id)
|> get(
"/oauth/authorize",
%{
"response_type" => "code",
"client_id" => other_app.client_id,
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
"scope" => "read"
}
)
assert html_response(conn, 200) =~ ~s(type="submit")
end
test "does not reuse other people's tokens",
%{
app: app,
conn: conn
} do
user = insert(:user)
other_user = insert(:user)
token = insert(:oauth_token, app: app, user: user)
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
_authorization = insert(:oauth_authorization, user: other_user, app: other_app)
_reusable_token = insert(:oauth_token, app: other_app, user: other_user)
conn =
conn
|> AuthHelper.put_session_token(token.token)
|> AuthHelper.put_session_user(user.id)
|> get(
"/oauth/authorize",
%{
"response_type" => "code",
"client_id" => other_app.client_id,
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
"scope" => "read"
}
)
assert html_response(conn, 200) =~ ~s(type="submit")
end
test "does not reuse expired tokens",
%{
app: app,
conn: conn
} do
user = insert(:user)
token = insert(:oauth_token, app: app, user: user)
other_app = insert(:oauth_app, redirect_uris: "https://other_redirect.url")
_authorization = insert(:oauth_authorization, user: user, app: other_app)
_reusable_token =
insert(:oauth_token,
app: other_app,
user: user,
valid_until: NaiveDateTime.add(NaiveDateTime.utc_now(), -100)
)
conn =
conn
|> AuthHelper.put_session_token(token.token)
|> AuthHelper.put_session_user(user.id)
|> get(
"/oauth/authorize",
%{
"response_type" => "code",
"client_id" => other_app.client_id,
"redirect_uri" => OAuthController.default_redirect_uri(other_app),
"scope" => "read"
}
)
assert html_response(conn, 200) =~ ~s(type="submit")
end
test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params", test "with existing authentication and non-OOB `redirect_uri`, redirects to app with `token` and `state` params",
%{ %{
app: app, app: app,

View file

@ -31,7 +31,13 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
assert to_string(activity.id) == id assert to_string(activity.id) == id
assert result["pleroma"]["emoji_reactions"] == [ assert result["pleroma"]["emoji_reactions"] == [
%{"name" => "", "count" => 1, "me" => true, "url" => nil} %{
"name" => "",
"count" => 1,
"me" => true,
"url" => nil,
"account_ids" => [other_user.id]
}
] ]
{:ok, activity} = CommonAPI.post(user, %{status: "#cofe"}) {:ok, activity} = CommonAPI.post(user, %{status: "#cofe"})
@ -54,7 +60,8 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
"name" => "dinosaur", "name" => "dinosaur",
"count" => 1, "count" => 1,
"me" => true, "me" => true,
"url" => "http://localhost:4001/emoji/dino walking.gif" "url" => "http://localhost:4001/emoji/dino walking.gif",
"account_ids" => [other_user.id]
} }
] ]

View file

@ -86,10 +86,12 @@ test "halts the connection when `signature` header is not present", %{conn: conn
test "aliases redirected /object endpoints", _ do test "aliases redirected /object endpoints", _ do
obj = insert(:note) obj = insert(:note)
act = insert(:note_activity, note: obj) act = insert(:note_activity, note: obj)
params = %{"actor" => "http://mastodon.example.org/users/admin"} params = %{"actor" => "someparam"}
path = URI.parse(obj.data["id"]).path path = URI.parse(obj.data["id"]).path
conn = build_conn(:get, path, params) conn = build_conn(:get, path, params)
assert ["/notice/#{act.id}"] == HTTPSignaturePlug.route_aliases(conn)
assert ["/notice/#{act.id}", "/notice/#{act.id}?actor=someparam"] ==
HTTPSignaturePlug.route_aliases(conn)
end end
end end
end end

View file

@ -157,7 +157,8 @@ test "it streams the user's post in the 'user' stream", %{user: user, token: oau
Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.get_topic_and_add_socket("user", user, oauth_token)
{:ok, activity} = CommonAPI.post(user, %{status: "hey"}) {:ok, activity} = CommonAPI.post(user, %{status: "hey"})
assert_receive {:render_with_user, _, _, ^activity} stream_name = "user:#{user.id}"
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
refute Streamer.filtered_by_user?(user, activity) refute Streamer.filtered_by_user?(user, activity)
end end
@ -168,7 +169,11 @@ test "it streams boosts of the user in the 'user' stream", %{user: user, token:
{:ok, activity} = CommonAPI.post(other_user, %{status: "hey"}) {:ok, activity} = CommonAPI.post(other_user, %{status: "hey"})
{:ok, announce} = CommonAPI.repeat(activity.id, user) {:ok, announce} = CommonAPI.repeat(activity.id, user)
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} stream_name = "user:#{user.id}"
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce,
^stream_name}
refute Streamer.filtered_by_user?(user, announce) refute Streamer.filtered_by_user?(user, announce)
end end
@ -221,7 +226,11 @@ test "it streams boosts of mastodon user in the 'user' stream", %{
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} = {:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data) Pleroma.Web.ActivityPub.Transmogrifier.handle_incoming(data)
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce} stream_name = "user:#{user.id}"
assert_receive {:render_with_user, Pleroma.Web.StreamerView, "update.json", ^announce,
^stream_name}
refute Streamer.filtered_by_user?(user, announce) refute Streamer.filtered_by_user?(user, announce)
end end
@ -233,7 +242,7 @@ test "it sends notify to in the 'user' stream", %{
Streamer.get_topic_and_add_socket("user", user, oauth_token) Streamer.get_topic_and_add_socket("user", user, oauth_token)
Streamer.stream("user", notify) Streamer.stream("user", notify)
assert_receive {:render_with_user, _, _, ^notify} assert_receive {:render_with_user, _, _, ^notify, "user"}
refute Streamer.filtered_by_user?(user, notify) refute Streamer.filtered_by_user?(user, notify)
end end
@ -245,7 +254,7 @@ test "it sends notify to in the 'user:notification' stream", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
Streamer.stream("user:notification", notify) Streamer.stream("user:notification", notify)
assert_receive {:render_with_user, _, _, ^notify} assert_receive {:render_with_user, _, _, ^notify, "user:notification"}
refute Streamer.filtered_by_user?(user, notify) refute Streamer.filtered_by_user?(user, notify)
end end
@ -291,7 +300,7 @@ test "it sends favorite to 'user:notification' stream'", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
{:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id) {:ok, favorite_activity} = CommonAPI.favorite(user2, activity.id)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user:notification"}
assert notif.activity.id == favorite_activity.id assert notif.activity.id == favorite_activity.id
refute Streamer.filtered_by_user?(user, notif) refute Streamer.filtered_by_user?(user, notif)
end end
@ -320,7 +329,7 @@ test "it sends follow activities to the 'user:notification' stream", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token) Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
{:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user) {:ok, _follower, _followed, follow_activity} = CommonAPI.follow(user2, user)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user:notification"}
assert notif.activity.id == follow_activity.id assert notif.activity.id == follow_activity.id
refute Streamer.filtered_by_user?(user, notif) refute Streamer.filtered_by_user?(user, notif)
end end
@ -384,7 +393,7 @@ test "it sends to public (authenticated)" do
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
{:ok, activity} = CommonAPI.post(other_user, %{status: "Test"}) {:ok, activity} = CommonAPI.post(other_user, %{status: "Test"})
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(other_user, activity) refute Streamer.filtered_by_user?(other_user, activity)
end end
@ -436,7 +445,7 @@ test "it filters to user if recipients invalid and thread containment is enabled
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
Streamer.stream("public", activity) Streamer.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
assert Streamer.filtered_by_user?(user, activity) assert Streamer.filtered_by_user?(user, activity)
end end
@ -458,7 +467,7 @@ test "it sends message if recipients invalid and thread containment is disabled"
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
Streamer.stream("public", activity) Streamer.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(user, activity) refute Streamer.filtered_by_user?(user, activity)
end end
@ -481,7 +490,7 @@ test "it sends message if recipients invalid and thread containment is enabled b
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
Streamer.stream("public", activity) Streamer.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(user, activity) refute Streamer.filtered_by_user?(user, activity)
end end
end end
@ -495,7 +504,7 @@ test "it filters messages involving blocked users", %{user: user, token: oauth_t
Streamer.get_topic_and_add_socket("public", user, oauth_token) Streamer.get_topic_and_add_socket("public", user, oauth_token)
{:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"}) {:ok, activity} = CommonAPI.post(blocked_user, %{status: "Test"})
assert_receive {:render_with_user, _, _, ^activity} assert_receive {:render_with_user, _, _, ^activity, "public"}
assert Streamer.filtered_by_user?(user, activity) assert Streamer.filtered_by_user?(user, activity)
end end
@ -512,17 +521,17 @@ test "it filters messages transitively involving blocked users", %{
{:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"}) {:ok, activity_one} = CommonAPI.post(friend, %{status: "hey! @#{blockee.nickname}"})
assert_receive {:render_with_user, _, _, ^activity_one} assert_receive {:render_with_user, _, _, ^activity_one, "public"}
assert Streamer.filtered_by_user?(blocker, activity_one) assert Streamer.filtered_by_user?(blocker, activity_one)
{:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"}) {:ok, activity_two} = CommonAPI.post(blockee, %{status: "hey! @#{friend.nickname}"})
assert_receive {:render_with_user, _, _, ^activity_two} assert_receive {:render_with_user, _, _, ^activity_two, "public"}
assert Streamer.filtered_by_user?(blocker, activity_two) assert Streamer.filtered_by_user?(blocker, activity_two)
{:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"}) {:ok, activity_three} = CommonAPI.post(blockee, %{status: "hey! @#{blocker.nickname}"})
assert_receive {:render_with_user, _, _, ^activity_three} assert_receive {:render_with_user, _, _, ^activity_three, "public"}
assert Streamer.filtered_by_user?(blocker, activity_three) assert Streamer.filtered_by_user?(blocker, activity_three)
end end
end end
@ -583,7 +592,8 @@ test "it sends wanted private posts to list", %{user: user_a, token: user_a_toke
visibility: "private" visibility: "private"
}) })
assert_receive {:render_with_user, _, _, ^activity} stream_name = "list:#{list.id}"
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
refute Streamer.filtered_by_user?(user_a, activity) refute Streamer.filtered_by_user?(user_a, activity)
end end
end end
@ -601,7 +611,8 @@ test "it filters muted reblogs", %{user: user1, token: user1_token} do
Streamer.get_topic_and_add_socket("user", user1, user1_token) Streamer.get_topic_and_add_socket("user", user1, user1_token)
{:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2) {:ok, announce_activity} = CommonAPI.repeat(create_activity.id, user2)
assert_receive {:render_with_user, _, _, ^announce_activity} stream_name = "user:#{user1.id}"
assert_receive {:render_with_user, _, _, ^announce_activity, ^stream_name}
assert Streamer.filtered_by_user?(user1, announce_activity) assert Streamer.filtered_by_user?(user1, announce_activity)
end end
@ -617,7 +628,7 @@ test "it filters reblog notification for reblog-muted actors", %{
Streamer.get_topic_and_add_socket("user", user1, user1_token) Streamer.get_topic_and_add_socket("user", user1, user1_token)
{:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2) {:ok, _announce_activity} = CommonAPI.repeat(create_activity.id, user2)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user"}
assert Streamer.filtered_by_user?(user1, notif) assert Streamer.filtered_by_user?(user1, notif)
end end
@ -633,7 +644,7 @@ test "it send non-reblog notification for reblog-muted actors", %{
Streamer.get_topic_and_add_socket("user", user1, user1_token) Streamer.get_topic_and_add_socket("user", user1, user1_token)
{:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id) {:ok, _favorite_activity} = CommonAPI.favorite(user2, create_activity.id)
assert_receive {:render_with_user, _, "notification.json", notif} assert_receive {:render_with_user, _, "notification.json", notif, "user"}
refute Streamer.filtered_by_user?(user1, notif) refute Streamer.filtered_by_user?(user1, notif)
end end
end end
@ -648,7 +659,8 @@ test "it filters posts from muted threads" do
{:ok, activity} = CommonAPI.post(user, %{status: "super hot take"}) {:ok, activity} = CommonAPI.post(user, %{status: "super hot take"})
{:ok, _} = CommonAPI.add_mute(user2, activity) {:ok, _} = CommonAPI.add_mute(user2, activity)
assert_receive {:render_with_user, _, _, ^activity} stream_name = "user:#{user2.id}"
assert_receive {:render_with_user, _, _, ^activity, ^stream_name}
assert Streamer.filtered_by_user?(user2, activity) assert Streamer.filtered_by_user?(user2, activity)
end end
end end
@ -690,7 +702,8 @@ test "it doesn't send conversation update to the 'direct' stream when the last m
}) })
create_activity_id = create_activity.id create_activity_id = create_activity.id
assert_receive {:render_with_user, _, _, ^create_activity} stream_name = "direct:#{user.id}"
assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
assert_receive {:text, received_conversation1} assert_receive {:text, received_conversation1}
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
@ -725,8 +738,9 @@ test "it sends conversation update to the 'direct' stream when a message is dele
visibility: "direct" visibility: "direct"
}) })
assert_receive {:render_with_user, _, _, ^create_activity} stream_name = "direct:#{user.id}"
assert_receive {:render_with_user, _, _, ^create_activity2} assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
assert_receive {:render_with_user, _, _, ^create_activity2, ^stream_name}
assert_receive {:text, received_conversation1} assert_receive {:text, received_conversation1}
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1) assert %{"event" => "conversation", "payload" => _} = Jason.decode!(received_conversation1)
assert_receive {:text, received_conversation1} assert_receive {:text, received_conversation1}
@ -746,4 +760,105 @@ test "it sends conversation update to the 'direct' stream when a message is dele
assert last_status["id"] == to_string(create_activity.id) assert last_status["id"] == to_string(create_activity.id)
end end
end end
describe "stop streaming if token got revoked" do
setup do
child_proc = fn start, finalize ->
fn ->
start.()
receive do
{StreamerTest, :ready} ->
assert_receive {:render_with_user, _, "update.json", _, _}
receive do
{StreamerTest, :revoked} -> finalize.()
end
end
end
end
starter = fn user, token ->
fn -> Streamer.get_topic_and_add_socket("user", user, token) end
end
hit = fn -> assert_receive :close end
miss = fn -> refute_receive :close end
send_all = fn tasks, thing -> Enum.each(tasks, &send(&1.pid, thing)) end
%{
child_proc: child_proc,
starter: starter,
hit: hit,
miss: miss,
send_all: send_all
}
end
test "do not revoke other tokens", %{
child_proc: child_proc,
starter: starter,
hit: hit,
miss: miss,
send_all: send_all
} do
%{user: user, token: token} = oauth_access(["read"])
%{token: token2} = oauth_access(["read"], user: user)
%{user: user2, token: user2_token} = oauth_access(["read"])
post_user = insert(:user)
CommonAPI.follow(user, post_user)
CommonAPI.follow(user2, post_user)
tasks = [
Task.async(child_proc.(starter.(user, token), hit)),
Task.async(child_proc.(starter.(user, token2), miss)),
Task.async(child_proc.(starter.(user2, user2_token), miss))
]
{:ok, _} =
CommonAPI.post(post_user, %{
status: "hi"
})
send_all.(tasks, {StreamerTest, :ready})
Pleroma.Web.OAuth.Token.Strategy.Revoke.revoke(token)
send_all.(tasks, {StreamerTest, :revoked})
Enum.each(tasks, &Task.await/1)
end
test "revoke all streams for this token", %{
child_proc: child_proc,
starter: starter,
hit: hit,
send_all: send_all
} do
%{user: user, token: token} = oauth_access(["read"])
post_user = insert(:user)
CommonAPI.follow(user, post_user)
tasks = [
Task.async(child_proc.(starter.(user, token), hit)),
Task.async(child_proc.(starter.(user, token), hit))
]
{:ok, _} =
CommonAPI.post(post_user, %{
status: "hi"
})
send_all.(tasks, {StreamerTest, :ready})
Pleroma.Web.OAuth.Token.Strategy.Revoke.revoke(token)
send_all.(tasks, {StreamerTest, :revoked})
Enum.each(tasks, &Task.await/1)
end
end
end end

View file

@ -0,0 +1,25 @@
# Pleroma: A lightweight social networking server
# Copyright © 2017-2022 Pleroma Authors <https://pleroma.social/>
# SPDX-License-Identifier: AGPL-3.0-only
defmodule Pleroma.Workers.ReceiverWorkerTest do
use Pleroma.DataCase, async: true
use Oban.Testing, repo: Pleroma.Repo
import Mock
import Pleroma.Factory
alias Pleroma.Workers.ReceiverWorker
test "it ignores MRF reject" do
params = insert(:note).data
with_mock Pleroma.Web.ActivityPub.Transmogrifier,
handle_incoming: fn _ -> {:reject, "MRF"} end do
assert {:discard, "MRF"} =
ReceiverWorker.perform(%Oban.Job{
args: %{"op" => "incoming_ap_doc", "params" => params}
})
end
end
end

View file

@ -407,6 +407,15 @@ def get("http://mastodon.example.org/users/admin", _, _, _) do
}} }}
end end
def get(
"http://mastodon.example.org/users/admin/statuses/99512778738411822/replies?min_id=99512778738411824&page=true",
_,
_,
_
) do
{:ok, %Tesla.Env{status: 404, body: ""}}
end
def get("http://mastodon.example.org/users/relay", _, _, [ def get("http://mastodon.example.org/users/relay", _, _, [
{"accept", "application/activity+json"} {"accept", "application/activity+json"}
]) do ]) do

View file

@ -5,18 +5,17 @@
defmodule Pleroma.Integration.WebsocketClient do defmodule Pleroma.Integration.WebsocketClient do
# https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs # https://github.com/phoenixframework/phoenix/blob/master/test/support/websocket_client.exs
use WebSockex
@doc """ @doc """
Starts the WebSocket server for given ws URL. Received Socket.Message's Starts the WebSocket server for given ws URL. Received Socket.Message's
are forwarded to the sender pid are forwarded to the sender pid
""" """
def start_link(sender, url, headers \\ []) do def start_link(sender, url, headers \\ []) do
:crypto.start() WebSockex.start_link(
:ssl.start() url,
:websocket_client.start_link(
String.to_charlist(url),
__MODULE__, __MODULE__,
[sender], %{sender: sender},
extra_headers: headers extra_headers: headers
) )
end end
@ -36,27 +35,32 @@ def send_text(server_pid, msg) do
end end
@doc false @doc false
def init([sender], _conn_state) do @impl true
{:ok, %{sender: sender}} def handle_frame(frame, state) do
end
@doc false
def websocket_handle(frame, _conn_state, state) do
send(state.sender, frame) send(state.sender, frame)
{:ok, state} {:ok, state}
end end
@impl true
def handle_disconnect(conn_status, state) do
send(state.sender, {:close, conn_status})
{:ok, state}
end
@doc false @doc false
def websocket_info({:text, msg}, _conn_state, state) do @impl true
def handle_info({:text, msg}, state) do
{:reply, {:text, msg}, state} {:reply, {:text, msg}, state}
end end
def websocket_info(:close, _conn_state, _state) do @impl true
def handle_info(:close, _state) do
{:close, <<>>, "done"} {:close, <<>>, "done"}
end end
@doc false @doc false
def websocket_terminate(_reason, _conn_state, _state) do @impl true
def terminate(_reason, _state) do
:ok :ok
end end
end end