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
DEBIAN_FRONTEND: noninteractive
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
- *clean
- 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]
### 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
- extended runtime module support, see config cheatsheet
- quote posting; quotes are limited to public posts

View file

@ -2,6 +2,8 @@
*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
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,
background_upload_limit: 4_000_000,
banner_upload_limit: 4_000_000,
languages: ["en"],
poll_limits: %{
max_options: 20,
max_option_chars: 200,
@ -734,6 +735,14 @@
"build_dir" => "distribution",
"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" => %{
"name" => "admin-fe",
"git" => "https://akkoma.dev/AkkomaGang/admin-fe",
@ -746,7 +755,7 @@
"git" => "https://gitlab.com/soapbox-pub/soapbox-fe",
"build_url" =>
"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"
},
# For developers - enables a swagger frontend to view the openapi spec
@ -785,7 +794,8 @@
config :pleroma, :mrf,
policies: [Pleroma.Web.ActivityPub.MRF.ObjectAgePolicy, Pleroma.Web.ActivityPub.MRF.TagPolicy],
transparency: true,
transparency_exclusions: []
transparency_exclusions: [],
transparency_obfuscate_domains: []
config :ex_aws, http_client: Pleroma.HTTP.ExAws

View file

@ -509,6 +509,16 @@
"Pleroma"
]
},
%{
key: :languages,
type: {:list, :string},
description: "Languages the instance uses",
suggestions: [
"en",
"ja",
"fr"
]
},
%{
key: :email,
label: "Admin Email Address",
@ -1169,7 +1179,6 @@
hideFilteredStatuses: false,
hideMutedPosts: false,
hidePostStats: false,
hideSitename: false,
hideUserStats: false,
loginMethod: "password",
logo: "/static/logo.svg",
@ -1235,12 +1244,6 @@
type: :boolean,
description: "Hide notices statistics (repeats, favorites, ...)"
},
%{
key: :hideSitename,
label: "Hide Sitename",
type: :boolean,
description: "Hides instance name from PleromaFE banner"
},
%{
key: :hideUserStats,
label: "Hide user stats",
@ -1350,6 +1353,42 @@
type: :string,
description: "Which theme to use. Available themes are defined in styles.json",
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,
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"]
},
%{
key: :background_color,
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"]
}
]
@ -2597,9 +2637,10 @@
%{
key: :proxy_url,
label: "Proxy URL",
type: [:string, :tuple],
description: "Proxy URL",
suggestions: ["localhost:9020", {:socks5, :localhost, 3090}]
type: :string,
description:
"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,

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)).
* `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_obfuscate_domains`: Show domains with `*` in the middle, to censor them if needed. For example, `ridingho.me` will show as `rid*****.me`
## Federation
### MRF policies
@ -283,14 +284,19 @@ config :pleroma, :frontends,
"name" => "swagger-ui",
"ref" => "stable",
"enabled" => true
},
mastodon: %{
"name" => "mastodon-fe",
"ref" => "akkoma"
}
```
* `:primary` - The frontend that will be served at `/`
* `: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.
* `: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.
@ -516,7 +522,7 @@ Available caches:
### :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`)
* `user_agent`: what user agent should we use? (default: `:default`), must be string or `:default`
* `adapter`: array of adapter options

View file

@ -19,6 +19,10 @@ config :pleroma, :frontends,
admin: %{
"name" => "admin-fe",
"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
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.
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
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.Application.limiters_setup()
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
Logger.remove_backend(:console)
@ -45,6 +53,7 @@ def start_pleroma do
Pleroma.Emoji,
{Pleroma.Config.TransferTask, false},
Pleroma.Web.Endpoint,
{Finch, finch_config},
{Oban, oban_config},
{Majic.Pool,
[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 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
start_pleroma()
@ -27,7 +27,7 @@ def run(["index"]) do
end
{:ok, _} =
meili_post(
meili_put(
"/indexes/objects/settings/ranking-rules",
[
"published:desc",
@ -41,7 +41,7 @@ def run(["index"]) do
)
{:ok, _} =
meili_post(
meili_put(
"/indexes/objects/settings/searchable-attributes",
[
"content"
@ -91,7 +91,7 @@ def run(["index"]) 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)}")
end
else

View file

@ -258,6 +258,25 @@ def run(["untag", nickname | tags]) do
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
{options, [], []} =
OptionParser.parse(rest,
@ -519,6 +538,26 @@ def run(["fix_follow_state", local_user, remote_user]) do
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
{:ok, user} =
user

View file

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

View file

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

View file

@ -9,6 +9,7 @@ defmodule Pleroma.Helpers.AuthHelper do
import Plug.Conn
@oauth_token_session_key :oauth_token
@oauth_user_session_key :oauth_user
@doc """
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
delete_session(conn, @oauth_token_session_key)
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

View file

@ -6,7 +6,7 @@ defmodule Pleroma.HTTP.AdapterHelper do
@moduledoc """
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 host() :: charlist() | :inet.ip_address()
@ -25,15 +25,58 @@ def format_proxy(nil), do: nil
def format_proxy(proxy_url) do
case parse_proxy(proxy_url) do
{:ok, host, port} -> {host, port}
{:ok, type, host, port} -> {type, host, port}
{:ok, host, port} -> {:http, host, port, []}
{:ok, type, host, port} -> {type, host, port, []}
_ -> nil
end
end
@spec maybe_add_proxy(keyword(), proxy() | nil) :: keyword()
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 """
Merge default connection & adapter options with received ones.
@ -46,36 +89,31 @@ def options(%URI{} = uri, opts \\ []) do
|> AdapterHelper.Default.options(uri)
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) ::
{:ok, host(), pos_integer()}
| {:ok, proxy_type(), host(), pos_integer()}
| {:error, atom()}
| nil
def parse_proxy(nil), do: nil
def parse_proxy(proxy) when is_binary(proxy) do
with [host, port] <- String.split(proxy, ":"),
{port, ""} <- Integer.parse(port) do
{:ok, parse_host(host), port}
with %URI{} = uri <- URI.parse(proxy),
{:ok, type} <- proxy_type(uri.scheme) do
{:ok, type, uri.host, uri.port}
else
{_, _} ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
:error ->
Logger.warn("Parsing port failed #{inspect(proxy)}")
{:error, :invalid_proxy_port}
_ ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}")
e ->
Logger.warn("Parsing proxy failed #{inspect(proxy)}, #{inspect(e)}")
{:error, :invalid_proxy}
end
end
def parse_proxy(proxy) when is_tuple(proxy) do
with {type, host, port} <- proxy do
{:ok, type, parse_host(host), port}
{:ok, type, host, port}
else
_ ->
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()
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))
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)
match?(["Mix", "Tasks", "Pleroma" | _], module) and
String.downcase(List.last(module)) == task
task_match?(module, task)
end)
if module do
@ -35,6 +35,13 @@ defp mix_task(task, args) do
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
Mix.Tasks.Pleroma.Ecto.Migrate.run(args)
end

View file

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

View file

@ -42,7 +42,6 @@ def search(:activities, q) do
results
|> Enum.map(fn result -> result["_id"] end)
|> Pleroma.Activity.all_by_ids_with_object()
|> Enum.sort(&(&1.inserted_at >= &2.inserted_at))
else
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_format(:nickname, local_nickname_regex())
|> put_ap_id()
|> put_keys()
|> unique_constraint(:ap_id)
|> put_following_and_follower_and_featured_address()
end
@ -740,6 +741,7 @@ def register_changeset(struct, params \\ %{}, opts \\ []) do
|> validate_length(:registration_reason, max: reason_limit)
|> maybe_validate_required_email(opts[:external])
|> put_password_hash
|> put_keys()
|> put_ap_id()
|> unique_constraint(:ap_id)
|> put_following_and_follower_and_featured_address()
@ -755,6 +757,11 @@ def maybe_validate_required_email(changeset, _) do
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
ap_id = ap_id(%User{nickname: get_field(changeset, :nickname)})
put_change(changeset, :ap_id, ap_id)

View file

@ -41,6 +41,16 @@ defmodule Pleroma.Web.ActivityPub.MRF do
suggestions: [
"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}
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
def describe do
exclusions = Config.get([:mrf, :transparency_exclusions]) |> MRF.instance_list_from_tuples()
obfuscations =
Config.get([:mrf, :transparency_obfuscate_domains], []) |> MRF.subdomains_regex()
mrf_simple_excluded =
Config.get(:mrf_simple)
|> Enum.map(fn {rule, instances} ->
@ -269,7 +294,7 @@ def describe do
mrf_simple =
mrf_simple_excluded
|> 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)
|> Map.new()
@ -286,7 +311,9 @@ def describe do
|> Enum.map(fn {rule, 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()
{rule, instances}

View file

@ -6,7 +6,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.ArticleNotePageValidator do
use Ecto.Schema
alias Pleroma.User
alias Pleroma.EctoType.ActivityPub.ObjectValidators
alias Pleroma.Object.Fetcher
alias Pleroma.Web.CommonAPI.Utils
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
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(data), do: Map.drop(data, ["tag"])
defp fix_replies(%{"replies" => %{"first" => %{"items" => replies}}} = 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" => replies} = data) when is_list(replies), do: data
defp fix_replies(%{"replies" => %{"first" => first}} = data) do
with {:ok, %{"orderedItems" => replies}} <-
Fetcher.fetch_and_contain_remote_object_from_id(first) do
with {:ok, replies} <- Akkoma.Collections.Fetcher.fetch_collection(first) do
Map.put(data, "replies", replies)
else
{:error, _} ->
@ -79,7 +69,10 @@ defp fix_replies(%{"replies" => %{"first" => first}} = data) do
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(
%{"id" => ap_id, "tag" => tags},
@ -108,6 +101,8 @@ defp remote_mention_resolver(
end
# 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(
%{"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.Containment
alias Pleroma.User
alias Pleroma.Web.ActivityPub.Transmogrifier
alias Pleroma.Web.ActivityPub.Utils
require Pleroma.Constants
def cast_and_filter_recipients(message, field, follower_collection, field_fallback \\ []) do
{: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("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|> fix_implicit_addressing(follower_collection)
end
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("bto", follower_collection)
|> cast_and_filter_recipients("bcc", follower_collection)
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|> fix_implicit_addressing(follower_collection)
end
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)
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

View file

@ -13,7 +13,6 @@ defmodule Pleroma.Web.ActivityPub.ObjectValidators.CreateGenericValidator do
alias Pleroma.User
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonValidations
alias Pleroma.Web.ActivityPub.Transmogrifier
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("bto", follower_collection, object["bto"])
|> CommonFixes.cast_and_filter_recipients("bcc", follower_collection, object["bcc"])
|> Transmogrifier.fix_implicit_addressing(follower_collection)
|> CommonFixes.fix_implicit_addressing(follower_collection)
end
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.Utils
alias Pleroma.Web.ActivityPub.Visibility
alias Pleroma.Web.ActivityPub.ObjectValidators.CommonFixes
alias Pleroma.Web.Federator
alias Pleroma.Workers.TransmogrifierWorker
@ -95,29 +96,6 @@ def fix_explicit_addressing(%{"to" => to, "cc" => cc} = object, follower_collect
|> Map.put("cc", final_cc)
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
{:ok, %User{follower_address: follower_collection}} =
object
@ -130,7 +108,7 @@ def fix_addressing(object) do
|> fix_addressing_list("bto")
|> fix_addressing_list("bcc")
|> fix_explicit_addressing(follower_collection)
|> fix_implicit_addressing(follower_collection)
|> CommonFixes.fix_implicit_addressing(follower_collection)
end
def fix_actor(%{"attributedTo" => actor} = object) do

View file

@ -152,9 +152,15 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
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`"
},
context: %Schema{
type: :string,
description: "The thread identifier the status is associated with"
},
conversation_id: %Schema{
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{
type: :integer,
@ -356,6 +362,7 @@ defmodule Pleroma.Web.ApiSpec.Schemas.Status do
"pinned" => false,
"pleroma" => %{
"content" => %{"text/plain" => "foobar"},
"context" => "http://localhost:4001/objects/8b4c0c80-6a37-4d2a-b1b9-05a19e3875aa",
"conversation_id" => 345_972,
"direct_conversation_id" => nil,
"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
text
|> Formatter.markdown_to_html()
|> MfmParser.Parser.parse()
|> MfmParser.Encoder.to_html()
|> Formatter.linkify(options)
|> Formatter.html_escape("text/x.misskeymarkdown")
|> (fn {text, mentions, tags} ->
{String.replace(text, ~r/\r?\n/, "<br>"), mentions, tags}
end).()
|> Formatter.html_escape("text/html")
end
def format_input(text, "text/markdown", options) do

View file

@ -27,9 +27,21 @@ defmodule Pleroma.Web.MastoFEController do
def index(conn, _params) do
with %{assigns: %{user: %User{} = user, token: %Token{app_id: token_app_id} = token}} <- conn,
{: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
|> put_layout(false)
|> render("index.html",
|> render(index,
token: token.token,
user: user,
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
with {:ok, app} <- local_mastofe_app(),
{: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 =
conn
|> local_mastodon_post_login_path()

View file

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

View file

@ -57,8 +57,19 @@ defp get_replied_to_activities(activities) do
end)
end
defp get_context_id(%{data: %{"context" => context}}) when is_binary(context),
do: :erlang.crc32(context)
# DEPRECATED 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.)
# 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
@ -364,9 +375,11 @@ def render("show.json", %{activity: %{data: %{"object" => _object}} = activity}
emojis: build_emojis(object.data["emoji"]),
quote_id: if(quote, do: quote.id, else: nil),
quote: maybe_render_quote(quote, opts),
emoji_reactions: emoji_reactions,
pleroma: %{
local: activity.local,
conversation_id: get_context_id(activity),
context: object.data["context"],
in_reply_to_account_acct: reply_to_user && reply_to_user.nickname,
content: %{"text/plain" => content_plaintext},
spoiler_text: %{"text/plain" => summary},
@ -577,7 +590,8 @@ defp build_emoji_map(emoji, users, url, current_user) do
name: emoji,
count: length(users),
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
@ -609,15 +623,19 @@ defp build_image_url(_, _), do: nil
defp maybe_render_quote(nil, _), do: nil
defp maybe_render_quote(quote, opts) do
if opts[:do_not_recurse] || !visible_for_user?(quote, opts[:for]) do
nil
else
with %User{} = quoted_user <- User.get_cached_by_ap_id(quote.actor),
false <- Map.get(opts, :do_not_recurse, false),
true <- visible_for_user?(quote, opts[:for]),
false <- User.blocks?(opts[:for], quoted_user),
false <- User.mutes?(opts[:for], quoted_user) do
opts =
opts
|> Map.put(:activity, quote)
|> Map.put(:do_not_recurse, true)
render("show.json", opts)
else
_ -> nil
end
end
end

View file

@ -32,8 +32,15 @@ def init(%{qs: qs} = req, state) do
req
end
{:cowboy_websocket, req, %{user: user, topic: topic, count: 0, timer: nil},
%{idle_timeout: @timeout}}
{:cowboy_websocket, req,
%{
user: user,
topic: topic,
count: 0,
timer: nil,
subscriptions: [],
oauth_token: oauth_token
}, %{idle_timeout: @timeout}}
else
{:error, :bad_topic} ->
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}"
)
Streamer.add_socket(state.topic, state.user)
Streamer.add_socket(state.topic, state.oauth_token)
{:ok, %{state | timer: timer()}}
end
@ -65,21 +72,50 @@ def websocket_handle(:pong, state) do
# We only receive pings for now
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)
{:reply, {:text, "pong"}, %{state | timer: timer()}}
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
Logger.error("#{__MODULE__} received frame: #{inspect(frame)}")
{:ok, state}
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)
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
{:ok, state}
end
@ -103,6 +139,10 @@ def websocket_info(:tick, state) do
{:reply, :ping, %{state | timer: nil, count: 0}, :hibernate}
end
def websocket_info(:close, state) do
{:stop, state}
end
# 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
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)
|> Repo.find_resource()
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

View file

@ -59,18 +59,39 @@ def authorize(%Plug.Conn{assigns: %{token: %Token{}}} = conn, %{"force_login" =>
# after user already authorized to MastodonFE.
# So we have to check client and token.
def authorize(
%Plug.Conn{assigns: %{token: %Token{} = token}} = conn,
%Plug.Conn{assigns: %{token: %Token{} = token, user: %User{} = user}} = conn,
%{"client_id" => client_id} = params
) do
with %Token{} = t <- Repo.get_by(Token, token: token.token) |> Repo.preload(:app),
^client_id <- t.app.client_id do
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
_ -> do_authorize(conn, params)
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
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
with {:ok, auth, user} <- do_create_authorization(conn, params, opts[:user]),
{: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
error ->
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"]),
{:ok, auth} <- Authorization.get_by_token(app, fixed_token),
%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})
else
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
conn
|> AuthHelper.put_session_token(token.token)
|> AuthHelper.put_session_user(token.user_id)
|> json(OAuthView.render("token.json", view_params))
end

View file

@ -70,6 +70,16 @@ def exchange_token(app, auth) do
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
changeset
|> change(%{token: Token.Utils.generate_token()})
@ -86,6 +96,14 @@ defp put_refresh_token(changeset, attrs) do
|> unique_constraint(:refresh_token)
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
valid_until =
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)
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
def preload(query \\ Token, assoc_preload \\ [])

View file

@ -21,6 +21,18 @@ def revoke(%App{} = app, %{"token" => token} = _attrs) do
@doc "Revokes access token"
@spec revoke(Token.t()) :: {:ok, Token.t()} | {:error, Ecto.Changeset.t()}
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

View file

@ -74,6 +74,8 @@ defp filter(reactions, %{emoji: emoji}) when is_binary(emoji) do
defp filter(reactions, _), do: reactions
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
activity = Activity.get_by_id(activity_id)

View file

@ -27,11 +27,11 @@ def call(conn, _opts) do
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)
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
_ -> []
end
@ -64,7 +64,9 @@ defp maybe_assign_valid_signature(conn) do
if has_signature_header?(conn) do
# set (request-target) header to the appropriate value
# 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)
else
Logger.debug("No signature header!")

View file

@ -457,6 +457,11 @@ defmodule Pleroma.Web.Router do
get("/federation_status", InstancesController, :show)
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
pipe_through(:authenticated_api)

View file

@ -36,7 +36,7 @@ def registry, do: @registry
{:ok, topic :: String.t()} | {:error, :bad_topic} | {:error, :unauthorized}
def get_topic_and_add_socket(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
@ -114,15 +114,20 @@ def get_topic("list", _user, _oauth_token, _params) do
{:error, :unauthorized}
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
{:error, :bad_topic}
end
@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
auth? = if user, do: true
Registry.register(@registry, topic, auth?)
oauth_token_id = if oauth_token, do: oauth_token.id, else: false
Registry.register(@registry, topic, oauth_token_id)
end
{:ok, topic}
@ -186,8 +191,8 @@ defp do_stream("direct", item) do
end
defp do_stream("follow_relationship", item) do
text = StreamerView.render("follow_relationships_update.json", item)
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")
@ -235,7 +240,7 @@ defp do_stream(topic, %Notification{} = item)
when topic in ["user", "user:notification"] do
Registry.dispatch(@registry, "#{topic}:#{item.user_id}", fn list ->
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
@ -259,7 +264,7 @@ defp do_stream(topic, item) do
end
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 ->
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, item) do
anon_render = StreamerView.render("update.json", item)
anon_render = StreamerView.render("update.json", item, topic)
Registry.dispatch(@registry, topic, fn list ->
Enum.each(list, fn {pid, auth?} ->
if auth? do
send(pid, {:render_with_user, StreamerView, "update.json", item})
send(pid, {:render_with_user, StreamerView, "update.json", item, topic})
else
send(pid, {:text, anon_render})
end
@ -306,6 +311,22 @@ defp thread_containment(activity, user) do
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 benchmark environment, returns false.
# 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: %{
title: Config.get([:instance, :name]),
streaming_api_base_url: Pleroma.Web.Endpoint.websocket_url(),
access_token: token,
locale: "en",
@ -27,7 +28,11 @@ def initial_state(token, user, custom_emojis) do
display_sensitive_media: false,
reduce_motion: false,
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]),
rights: %{
@ -56,6 +61,7 @@ def initial_state(token, user, custom_emojis) do
"video\/mp4"
]
},
lists: [],
settings: user.mastofe_settings || %{},
push_subscription: nil,
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.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",
payload:
Pleroma.Web.MastodonAPI.StatusView.render(
@ -25,8 +26,9 @@ def render("update.json", %Activity{} = activity, %User{} = user) do
|> Jason.encode!()
end
def render("notification.json", %Notification{} = notify, %User{} = user) do
def render("notification.json", %Notification{} = notify, %User{} = user, topic) do
%{
stream: [topic],
event: "notification",
payload:
NotificationView.render(
@ -38,8 +40,9 @@ def render("notification.json", %Notification{} = notify, %User{} = user) do
|> Jason.encode!()
end
def render("update.json", %Activity{} = activity) do
def render("update.json", %Activity{} = activity, topic) do
%{
stream: [topic],
event: "update",
payload:
Pleroma.Web.MastodonAPI.StatusView.render(
@ -51,8 +54,9 @@ def render("update.json", %Activity{} = activity) do
|> Jason.encode!()
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",
payload:
%{
@ -73,8 +77,9 @@ def render("follow_relationships_update.json", item) do
|> Jason.encode!()
end
def render("conversation.json", %Participation{} = participation) do
def render("conversation.json", %Participation{} = participation, topic) do
%{
stream: [topic],
event: "conversation",
payload:
Pleroma.Web.MastodonAPI.ConversationView.render("participation.json", %{

View file

@ -9,6 +9,12 @@ defmodule Pleroma.Workers.ReceiverWorker do
@impl Oban.Worker
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

11
mix.exs
View file

@ -4,8 +4,8 @@ defmodule Pleroma.Mixfile do
def project do
[
app: :pleroma,
version: version("3.0.1"),
elixir: "~> 1.9",
version: version("3.1.0"),
elixir: "~> 1.12",
elixirc_paths: elixirc_paths(Mix.env()),
compilers: [:phoenix, :gettext] ++ Mix.compilers(),
elixirc_options: [warnings_as_errors: warnings_as_errors()],
@ -129,7 +129,7 @@ defp deps do
override: true},
{:bcrypt_elixir, "~> 2.2"},
{:trailing_format_plug, "~> 0.0.7"},
{:fast_sanitize, "~> 0.2.0"},
{:fast_sanitize, "~> 0.2.3"},
{:html_entities, "~> 0.5", override: true},
{:phoenix_html, "~> 3.1", override: true},
{:calendar, "~> 1.0"},
@ -191,6 +191,9 @@ defp deps do
{:ecto_psql_extras, "~> 0.6"},
{:elasticsearch,
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
{: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
{:excoveralls, "0.12.3", 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()
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"},
"meck": {:hex, :meck, "0.9.2", "85ccbab053f1db86c7ca240e9fc718170ee5bda03810a6292b5306bf31bae5f5", [:rebar3], [], "hexpm", "81344f561357dc40a8344afa53767c32669153355b626ea9fcbc8da6b3045826"},
"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"},
"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"},
@ -109,6 +110,7 @@
"table_rex": {:hex, :table_rex, "3.1.1", "0c67164d1714b5e806d5067c1e96ff098ba7ae79413cc075973e17c38a587caa", [:mix], [], "hexpm", "678a23aba4d670419c23c17790f9dcd635a4a89022040df7d5d772cb21012490"},
"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"},
"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"},
"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"},
@ -118,5 +120,5 @@
"unsafe": {:hex, :unsafe, "1.0.1", "a27e1874f72ee49312e0a9ec2e0b27924214a05e3ddac90e91727bc76f8613d8", [:mix], [], "hexpm", "6c7729a2d214806450d29766abc2afaa7a2cbecf415be64f36a6691afebb50e5"},
"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"},
"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"
"Report-Msgid-Bugs-To: \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"
"Language-Team: Dutch <https://translate.pleroma.social/projects/pleroma/"
"pleroma/nl/>\n"
"Language-Team: Dutch <http://translate.akkoma.dev/projects/akkoma/"
"akkoma-backend-errors/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.0.4\n"
"X-Generator: Weblate 4.13.1\n"
## This file is a PO Template file.
##
@ -118,7 +118,7 @@ msgstr "Al gestemd"
#: lib/pleroma/web/oauth/oauth_controller.ex:360
#, elixir-format
msgid "Bad request"
msgstr "Bad request"
msgstr "Ongeldig request"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:425
#, elixir-format
@ -155,7 +155,7 @@ msgstr "Object kan niet geliked worden"
#: lib/pleroma/web/common_api/utils.ex:556
#, elixir-format
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
#, elixir-format
@ -165,122 +165,122 @@ msgstr "Opmerking dient maximaal %{max_size} karakters te bevatten"
#: lib/pleroma/config/config_db.ex:222
#, elixir-format
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
#, elixir-format
msgid "Could not delete"
msgstr ""
msgstr "Verwijderen mislukt"
#: lib/pleroma/web/common_api/common_api.ex:141
#, elixir-format
msgid "Could not favorite"
msgstr ""
msgstr "Favoriet maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:370
#, elixir-format
msgid "Could not pin"
msgstr ""
msgstr "Vastmaken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:112
#, elixir-format
msgid "Could not repeat"
msgstr ""
msgstr "Herhalen mislukt"
#: lib/pleroma/web/common_api/common_api.ex:188
#, elixir-format
msgid "Could not unfavorite"
msgstr ""
msgstr "Favoriet ongedaan maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:380
#, elixir-format
msgid "Could not unpin"
msgstr ""
msgstr "Vastmaken ongedaan maken mislukt"
#: lib/pleroma/web/common_api/common_api.ex:126
#, elixir-format
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:437
#, elixir-format
msgid "Could not update state"
msgstr ""
msgstr "Status bijwerken mislukt"
#: lib/pleroma/web/mastodon_api/controllers/timeline_controller.ex:202
#, elixir-format
msgid "Error."
msgstr ""
msgstr "Fout."
#: lib/pleroma/web/twitter_api/twitter_api.ex:106
#, elixir-format
msgid "Invalid CAPTCHA"
msgstr ""
msgstr "Ongeldige CAPTCHA"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:117
#: lib/pleroma/web/oauth/oauth_controller.ex:569
#, elixir-format
msgid "Invalid credentials"
msgstr ""
msgstr "Ongeldige inloggegevens"
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:38
#, elixir-format
msgid "Invalid credentials."
msgstr ""
msgstr "Ongeldige inloggegevens."
#: lib/pleroma/web/common_api/common_api.ex:265
#, elixir-format
msgid "Invalid indices"
msgstr ""
msgstr "Ongeldige indexen"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:1147
#, elixir-format
msgid "Invalid parameters"
msgstr ""
msgstr "Ongeldige parameters"
#: lib/pleroma/web/common_api/utils.ex:411
#, elixir-format
msgid "Invalid password."
msgstr ""
msgstr "Ongeldig wachtwoord."
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:187
#, elixir-format
msgid "Invalid request"
msgstr ""
msgstr "Ongeldig request"
#: lib/pleroma/web/twitter_api/twitter_api.ex:109
#, elixir-format
msgid "Kocaptcha service unavailable"
msgstr ""
msgstr "Kocaptcha service niet beschikbaar"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:113
#, elixir-format
msgid "Missing parameters"
msgstr ""
msgstr "Ontbrekende parameters"
#: lib/pleroma/web/common_api/utils.ex:540
#, elixir-format
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:465 lib/pleroma/web/admin_api/admin_api_controller.ex:507
#, elixir-format
msgid "No such permission_group"
msgstr ""
msgstr "Permission_group niet gevonden"
#: 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/feed/user_controller.ex:73 lib/pleroma/web/ostatus/ostatus_controller.ex:143
#, elixir-format
msgid "Not found"
msgstr ""
msgstr "Niet gevonden"
#: lib/pleroma/web/common_api/common_api.ex:241
#, elixir-format
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/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
#, elixir-format
msgid "Record not found"
msgstr ""
msgstr "Record niet gevonden"
#: 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/ostatus/ostatus_controller.ex:149
#, elixir-format
msgid "Something went wrong"
msgstr ""
msgstr "Er is iets misgegaan"
#: lib/pleroma/web/common_api/activity_draft.ex:107
#, elixir-format
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
#, elixir-format
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
#, elixir-format
msgid "This resource requires authentication."
msgstr ""
msgstr "Deze gegevens vereisen authenticatie."
#: lib/pleroma/plugs/rate_limiter/rate_limiter.ex:206
#, elixir-format
msgid "Throttled"
msgstr ""
msgstr "Geremd"
#: lib/pleroma/web/common_api/common_api.ex:266
#, elixir-format
msgid "Too many choices"
msgstr ""
msgstr "Teveel keuzes"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:442
#, elixir-format
msgid "Unhandled activity type"
msgstr ""
msgstr "Niet-ondersteund activiteits-type"
#: lib/pleroma/web/admin_api/admin_api_controller.ex:536
#, elixir-format
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:309
#, elixir-format
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:332
#, elixir-format
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
#, elixir-format
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
#, elixir-format
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
#, elixir-format
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:491
#, elixir-format
msgid "error"
msgstr ""
msgstr "fout"
#: lib/pleroma/web/pleroma_api/controllers/mascot_controller.ex:29
#, elixir-format
msgid "mascots can only be images"
msgstr ""
msgstr "mascottes kunnen alleen afbeeldingen zijn"
#: lib/pleroma/web/activity_pub/activity_pub_controller.ex:60
#, elixir-format
msgid "not found"
msgstr ""
msgstr "niet gevonden"
#: lib/pleroma/web/oauth/oauth_controller.ex:395
#, elixir-format
msgid "Bad OAuth request."
msgstr ""
msgstr "Ongeldig OAuth request."
#: lib/pleroma/web/twitter_api/twitter_api.ex:115
#, elixir-format
msgid "CAPTCHA already used"
msgstr ""
msgstr "CAPTCHA is al gebruikt"
#: lib/pleroma/web/twitter_api/twitter_api.ex:112
#, elixir-format
msgid "CAPTCHA expired"
msgstr ""
msgstr "CAPTCHA is verlopen"
#: lib/pleroma/plugs/uploaded_media.ex:55
#, elixir-format
msgid "Failed"
msgstr ""
msgstr "Mislukt"
#: lib/pleroma/web/oauth/oauth_controller.ex:411
#, elixir-format
msgid "Failed to authenticate: %{message}."
msgstr ""
msgstr "Authenticatie mislukt: %{message}."
#: lib/pleroma/web/oauth/oauth_controller.ex:442
#, elixir-format
msgid "Failed to set up user account."
msgstr ""
msgstr "Aanmaken van gebruikersaccount is mislukt."
#: lib/pleroma/plugs/oauth_scopes_plug.ex:38
#, elixir-format
msgid "Insufficient permissions: %{permissions}."
msgstr ""
msgstr "Niet voldoende rechten: %{permissions}."
#: lib/pleroma/plugs/uploaded_media.ex:94
#, elixir-format
msgid "Internal Error"
msgstr ""
msgstr "Interne Fout"
#: lib/pleroma/web/oauth/fallback_controller.ex:22
#: lib/pleroma/web/oauth/fallback_controller.ex:29
#, elixir-format
msgid "Invalid Username/Password"
msgstr ""
msgstr "Ongeldige Gebruikersnaam/Wachtwoord"
#: lib/pleroma/web/twitter_api/twitter_api.ex:118
#, elixir-format
msgid "Invalid answer data"
msgstr ""
msgstr "Ongeldig antwoord"
#: lib/pleroma/web/nodeinfo/nodeinfo_controller.ex:128
#, elixir-format
msgid "Nodeinfo schema version not handled"
msgstr ""
msgstr "Nodeinfo schema wordt niet ondersteund"
#: lib/pleroma/web/oauth/oauth_controller.ex:169
#, elixir-format
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
#, elixir-format
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:155
#, elixir-format
msgid "Unlisted redirect_uri."
msgstr ""
msgstr "Niet-vermelde redirect_uri."
#: lib/pleroma/web/oauth/oauth_controller.ex:391
#, elixir-format
msgid "Unsupported OAuth provider: %{provider}."
msgstr ""
msgstr "Niet ondersteunde OAuth provider: %{provider}."
#: lib/pleroma/uploaders/uploader.ex:72
#, elixir-format
msgid "Uploader callback timeout"
msgstr ""
msgstr "Uploader terugkoppeling timeout"
#: lib/pleroma/web/uploader_controller.ex:23
#, elixir-format
msgid "bad request"
msgstr ""
msgstr "ongeldig request"
#: lib/pleroma/web/twitter_api/twitter_api.ex:103
#, elixir-format
msgid "CAPTCHA Error"
msgstr ""
msgstr "CAPTCHA Fout"
#: lib/pleroma/web/common_api/common_api.ex:200
#, elixir-format
msgid "Could not add reaction emoji"
msgstr ""
msgstr "Reactie-emoji toevoegen mislukt"
#: lib/pleroma/web/common_api/common_api.ex:211
#, elixir-format
msgid "Could not remove reaction emoji"
msgstr ""
msgstr "Reactie-emoji verwijderen mislukt"
#: lib/pleroma/web/twitter_api/twitter_api.ex:129
#, elixir-format
msgid "Invalid CAPTCHA (Missing parameter: %{name})"
msgstr ""
msgstr "Ongeldige CAPTCHA (Ontbrekende parameter: %{name})"
#: lib/pleroma/web/mastodon_api/controllers/list_controller.ex:92
#, elixir-format
msgid "List not found"
msgstr ""
msgstr "Lijst niet gevonden"
#: lib/pleroma/web/mastodon_api/controllers/account_controller.ex:124
#, elixir-format
msgid "Missing parameter: %{name}"
msgstr ""
msgstr "Ontbrekende parameter: %{name}"
#: lib/pleroma/web/oauth/oauth_controller.ex:207
#: lib/pleroma/web/oauth/oauth_controller.ex:322
#, elixir-format
msgid "Password reset is required"
msgstr ""
msgstr "Wachtwoordherstel is vereist"
#: 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
@ -528,53 +528,63 @@ msgstr ""
#, elixir-format
msgid "Security violation: OAuth scopes check was neither handled nor explicitly skipped."
msgstr ""
"Schending van beveiliging: OAuth scope-controle is niet uitgevoerd en niet "
"expliciet overgeslagen."
#: lib/pleroma/plugs/ensure_authenticated_plug.ex:28
#, elixir-format
msgid "Two-factor authentication enabled, you must use a access token."
msgstr ""
"Tweefactor authenticatie is ingeschakeld, een toegangssleutel is verplicht."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:210
#, elixir-format
msgid "Unexpected error occurred while adding file to pack."
msgstr ""
"Er is een onverwachte fout opgetreden tijdens het toevoegen van het bestand."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:138
#, elixir-format
msgid "Unexpected error occurred while creating pack."
msgstr ""
"Er is een onverwachte fout opgetreden tijdens het aanmaken van het pakket."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:278
#, elixir-format
msgid "Unexpected error occurred while removing file from pack."
msgstr ""
"Er is een onverwachte fout opgetreden tijdens het verwijderen van het "
"bestand."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:250
#, elixir-format
msgid "Unexpected error occurred while updating file in pack."
msgstr ""
"Er is een onverwachte fout opgetreden tijdens het bijwerken van het bestand."
#: lib/pleroma/web/pleroma_api/controllers/emoji_api_controller.ex:179
#, elixir-format
msgid "Unexpected error occurred while updating pack metadata."
msgstr ""
"Er is een onverwachte fout opgetreden tijdens het bijwerken van de pakket-"
"metadata."
#: lib/pleroma/plugs/user_is_admin_plug.ex:21
#, elixir-format
msgid "User is not an admin."
msgstr ""
msgstr "Gebruiker is niet een beheerder."
#: lib/pleroma/web/mastodon_api/controllers/subscription_controller.ex:61
#, elixir-format
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
#, elixir-format
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
#, elixir-format
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(:ul, [])
Meta.allow_tag_with_this_attribute_values(:span, "class", ["h-card", "quote-inline"])
Meta.allow_tag_with_these_attributes(:span, [])
Meta.allow_tags_with_style_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"])
@ -101,4 +129,6 @@ defmodule Pleroma.HTML.Scrubber.Default do
Meta.allow_tag_with_these_attributes(:small, [])
Meta.strip_everything_not_covered()
defp scrub_css(value), do: value
end

View file

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

View file

@ -1,27 +1,27 @@
[Unit]
Description=Pleroma social network
Description=Akkoma social network
After=network.target postgresql.service nginx.service
[Service]
KillMode=process
Restart=on-failure
; Name of the user that runs the Pleroma service.
User=pleroma
; Name of the user that runs the Akkoma service.
User=akkoma
; Make sure that all paths fit your installation.
; Path to the home directory of the user running the Pleroma service.
Environment="HOME=/opt/pleroma"
; Path to the folder containing the Pleroma installation.
WorkingDirectory=/opt/pleroma
; Path to the Pleroma binary.
ExecStart=/opt/pleroma/bin/pleroma start
ExecStop=/opt/pleroma/bin/pleroma stop
; Path to the home directory of the user running the Akkoma service.
Environment="HOME=/opt/akkoma"
; Path to the folder containing the Akkoma installation.
WorkingDirectory=/opt/akkoma
; Path to the Mix binary.
ExecStart=/opt/akkoma/bin/pleroma start
ExecStop=/opt/akkoma/bin/pleroma stop
; Some security directives.
; Use private /tmp and /var/tmp folders inside a new file system namespace, which are discarded after the process stops.
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
; Mount /usr, /boot, and /etc as read-only for processes invoked by this service.
ProtectSystem=full

View file

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

View file

@ -1 +1,5 @@
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",
"attributedTo": "https://misskey.local.live/users/92hzkskwgy",
"summary": null,
"content": "this gets replaced",
"content": "this does not get replaced",
"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"

View file

@ -30,7 +30,7 @@ test "it should extract items from an embedded array in a Collection" do
}
end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
{:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
end
@ -53,7 +53,7 @@ test "it should extract items from an embedded array in an OrderedCollection" do
}
end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
{:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
end
@ -106,7 +106,7 @@ test "it should extract items from an referenced first page in a Collection" do
}
end)
{:ok, objects} = Fetcher.fetch_collection_by_ap_id(ap_id)
{:ok, objects} = Fetcher.fetch_collection(ap_id)
assert [%{"type" => "Create"}, %{"type" => "Like"}] = objects
end
@ -161,7 +161,58 @@ test "it should stop fetching when we hit :max_collection_objects" do
}
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
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
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
test "localhost with port" do
assert AdapterHelper.format_proxy("localhost:8123") == {'localhost', 8123}
assert AdapterHelper.format_proxy("https://localhost:8123") ==
{:https, "localhost", 8123, []}
end
test "tuple" do
assert AdapterHelper.format_proxy({:socks4, :localhost, 9050}) ==
{:socks4, 'localhost', 9050}
assert AdapterHelper.format_proxy({:http, "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

View file

@ -31,18 +31,23 @@ def start_socket(qs \\ nil, headers \\ []) do
WebsocketClient.start_link(self(), path, headers)
end
test "refuses invalid requests" do
test "allows multi-streams" do
capture_log(fn ->
assert {:error, {404, _}} = start_socket()
assert {:error, {404, _}} = start_socket("?stream=ncjdk")
assert {:ok, _} = start_socket()
assert {:error, %WebSockex.RequestError{code: 404, message: "Not Found"}} =
start_socket("?stream=ncjdk")
Process.sleep(30)
end)
end
test "requires authentication and a valid token for protected streams" do
capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user&access_token=aaaaaaaaaaaa")
assert {:error, {401, _}} = start_socket("?stream=user")
assert {:error, %WebSockex.RequestError{code: 401}} =
start_socket("?stream=user&access_token=aaaaaaaaaaaa")
assert {:error, %WebSockex.RequestError{code: 401}} = start_socket("?stream=user")
Process.sleep(30)
end)
end
@ -91,7 +96,7 @@ test "receives well formatted events" do
{:ok, token} = OAuth.Token.exchange_token(app, auth)
%{user: user, token: token}
%{app: app, user: user, token: token}
end
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}")
capture_log(fn ->
assert {:error, {401, _}} = start_socket("?stream=user")
assert {:error, %WebSockex.RequestError{code: 401}} = start_socket("?stream=user")
Process.sleep(30)
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}")
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)
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}])
capture_log(fn ->
assert {:error, {401, _}} =
assert {:error, %WebSockex.RequestError{code: 401}} =
start_socket("?stream=user", [{"Sec-WebSocket-Protocol", "I am a friend"}])
Process.sleep(30)
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

View file

@ -224,7 +224,7 @@ test "it creates a notification for user and send to the 'user' and the 'user:no
task =
Task.async(fn ->
{: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)
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} =
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)
activity = insert(:note_activity)

View file

@ -620,13 +620,14 @@ test "it blocks blacklisted email domains" do
assert changeset.valid?
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)
assert changeset.valid?
assert is_binary(changeset.changes[:password_hash])
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"
end

View file

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

View file

@ -216,6 +216,43 @@ test "has a matching host but only as:Public in to" do
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
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: %{
content: content,
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"
}
}
@ -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>"
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
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!()
|> 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)
%{
valid?: true,
changes: %{
content: content,
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
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
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

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)
{:ok, activity} = Transmogrifier.handle_incoming(data)
object = Object.normalize(activity.data["object"])
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 {:error, :origin_containment_failed} = ObanHelpers.perform(job)
assert {:discard, :origin_containment_failed} = ObanHelpers.perform(job)
end
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!()
assert {:ok, job} = Federator.incoming_ap_doc(params)
assert {:error, _} = ObanHelpers.perform(job)
assert {:discard, _} = ObanHelpers.perform(job)
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
test "get instance information", %{conn: conn} do
clear_config([:instance, :languages], ["en", "ja"])
conn = get(conn, "/api/v1/instance")
assert result = json_response_and_validate_schema(conn, 200)
email = Pleroma.Config.get([:instance, :email])
thumbnail = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :instance_thumbnail])
background = Pleroma.Web.Endpoint.url() <> Pleroma.Config.get([:instance, :background_image])
@ -29,7 +30,7 @@ test "get instance information", %{conn: conn} do
},
"stats" => _,
"thumbnail" => from_config_thumbnail,
"languages" => _,
"languages" => ["en", "ja"],
"registrations" => _,
"approval_required" => _,
"poll_limits" => _,

View file

@ -264,6 +264,7 @@ test "posting a fake status", %{conn: conn} do
|> Map.put("url", nil)
|> Map.put("uri", nil)
|> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "context"], nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
fake_conn =
@ -287,6 +288,7 @@ test "posting a fake status", %{conn: conn} do
|> Map.put("url", nil)
|> Map.put("uri", nil)
|> Map.put("created_at", nil)
|> Kernel.put_in(["pleroma", "context"], nil)
|> Kernel.put_in(["pleroma", "conversation_id"], nil)
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 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,
me: false,
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)
@ -59,14 +60,15 @@ test "has an emoji reaction list" do
assert_schema(status, "Status", Pleroma.Web.ApiSpec.spec())
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,
me: true,
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
@ -82,7 +84,7 @@ test "works correctly with badly formatted emojis" do
status = StatusView.render("show.json", activity: activity, for: user)
assert status[:pleroma][:emoji_reactions] == [
%{name: "", count: 1, me: true, url: nil}
%{name: "", count: 1, me: true, url: nil, account_ids: [user.id]}
]
end
@ -102,7 +104,7 @@ test "doesn't show reactions from muted and blocked users" do
status = StatusView.render("show.json", activity: activity)
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)
@ -114,19 +116,25 @@ test "doesn't show reactions from muted and blocked users" do
status = StatusView.render("show.json", activity: activity)
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)
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)
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
@ -239,7 +247,7 @@ test "a note activity" do
object_data = Object.normalize(note, fetch: false).data
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})
@ -272,6 +280,7 @@ test "a note activity" do
spoiler_text: HTML.filter_tags(object_data["summary"]),
visibility: "public",
media_attachments: [],
emoji_reactions: [],
mentions: [],
tags: [
%{
@ -292,6 +301,7 @@ test "a note activity" do
pleroma: %{
local: true,
conversation_id: convo_id,
context: object_data["context"],
in_reply_to_account_acct: nil,
content: %{"text/plain" => HTML.strip_tags(object_data["content"])},
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)
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
user = 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")
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",
%{
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 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"})
@ -54,7 +60,8 @@ test "PUT /api/v1/pleroma/statuses/:id/reactions/:emoji", %{conn: conn} do
"name" => "dinosaur",
"count" => 1,
"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
obj = insert(:note)
act = insert(:note_activity, note: obj)
params = %{"actor" => "http://mastodon.example.org/users/admin"}
params = %{"actor" => "someparam"}
path = URI.parse(obj.data["id"]).path
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

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)
{: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)
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, 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)
end
@ -221,7 +226,11 @@ test "it streams boosts of mastodon user in the 'user' stream", %{
{:ok, %Pleroma.Activity{data: _data, local: false} = announce} =
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)
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.stream("user", notify)
assert_receive {:render_with_user, _, _, ^notify}
assert_receive {:render_with_user, _, _, ^notify, "user"}
refute Streamer.filtered_by_user?(user, notify)
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.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)
end
@ -291,7 +300,7 @@ test "it sends favorite to 'user:notification' stream'", %{
Streamer.get_topic_and_add_socket("user:notification", user, oauth_token)
{: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
refute Streamer.filtered_by_user?(user, notif)
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)
{: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
refute Streamer.filtered_by_user?(user, notif)
end
@ -384,7 +393,7 @@ test "it sends to public (authenticated)" do
Streamer.get_topic_and_add_socket("public", user, oauth_token)
{: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)
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.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity}
assert_receive {:render_with_user, _, _, ^activity, "public"}
assert Streamer.filtered_by_user?(user, activity)
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.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity}
assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(user, activity)
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.stream("public", activity)
assert_receive {:render_with_user, _, _, ^activity}
assert_receive {:render_with_user, _, _, ^activity, "public"}
refute Streamer.filtered_by_user?(user, activity)
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)
{: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)
end
@ -512,17 +521,17 @@ test "it filters messages transitively involving blocked users", %{
{: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)
{: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)
{: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)
end
end
@ -583,7 +592,8 @@ test "it sends wanted private posts to list", %{user: user_a, token: user_a_toke
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)
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)
{: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)
end
@ -617,7 +628,7 @@ test "it filters reblog notification for reblog-muted actors", %{
Streamer.get_topic_and_add_socket("user", user1, user1_token)
{: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)
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)
{: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)
end
end
@ -648,7 +659,8 @@ test "it filters posts from muted threads" do
{:ok, activity} = CommonAPI.post(user, %{status: "super hot take"})
{: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)
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
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 %{"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"
})
assert_receive {:render_with_user, _, _, ^create_activity}
assert_receive {:render_with_user, _, _, ^create_activity2}
stream_name = "direct:#{user.id}"
assert_receive {:render_with_user, _, _, ^create_activity, ^stream_name}
assert_receive {:render_with_user, _, _, ^create_activity2, ^stream_name}
assert_receive {:text, received_conversation1}
assert %{"event" => "conversation", "payload" => _} = Jason.decode!(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)
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

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
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", _, _, [
{"accept", "application/activity+json"}
]) do

View file

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